Các phương pháp hiển thị cảnh báo trong rails

Xin chào các bạn, dưới đây là bài viết cho bạn nào mới bắt đầu tìm hiểu về rails và hôm nay mình sẽ giới thiệu các bạn một vài cách hiển thị errors message trong rails nhằm giúp các bạn có thể control view của mình một cách OK nhất có thể

đầu tiên mình sẽ tạo ra một App để làm mẫu

rails new app_test
cd app_test

Sau đó, chúng ta sẽ tạo một model với view để sử dụng

rails g scaffold Product name:string price:float description:text

và giờ là tạo database và kết nối server

rake db:create
rake db:migrate
rails s

OK. và giờ mình giới thiệu qua, hiện mình biết có 2 cách hiển thị lỗi trong rails

  1. load trực tiếp lại trang
  2. load thông qua JS và trong mỗi loại đều có thể 2 loại lỗi trả về
  3. lỗi do validates của model sẽ trả ra cho errors messages
  4. lỗi cảnh báo flash

và giờ chúng ta sẽ thêm một số validates vào file app/models/product.rb

class Product < ActiveRecord::Base
  validates :name, presence: true, length: {maximum: 30}
  validates :price, presence: true
end

OK, và giờ chúng ta sẽ đưa đến lỗi đầu tiên là 1.1 load trực tiếp lại trang và hiển thị lỗi do validates

Sau khi chạy lệnh scaffold chúng ta sẽ load link

http://localhost:3000/products/new

product_new.png

Cách 1: hiển thị lỗi thông qua load lại trang

chúng ta sẽ xem kết quả trước tiên

product_new_error_1.png

đây là lỗi được hiển thị sau khi reload lại chính trang new đó cơ chế của nó là khi create ở new, dữ liệu sẽ được chuyển đến action create

  def create
    @product = Product.new(product_params)

    respond_to do |format|
      if @product.save
        format.html { redirect_to @product, notice: 'Product was successfully created.' }
        format.json { render :show, status: :created, location: @product }
      else
        format.html { render :new }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end

và khi dữ liệu chạy qua lệnh @product.save, model sẽ kiểm tra dữ liệu và khi dữ liệu lỗi nó sẽ được lưu dưới dạng dưới đây(PS: kiểm tra byebug sau khi sử dụng lệnh @product.save bằng lệnh @product.errors)

<ActiveModel::Errors:0x007f79fd2cb888 @base=#<Product id: nil, name: "", price: nil, description: "", created_at: nil, updated_at: nil>, @messages={:name=>["can't be blank"], :price=>["can't be blank"]}>

Như vậy chúng ta đã load được ra dữ liệu lỗi và được lưu trong @product.errors.messages

và theo format thì khi có lỗi chương trình sẽ chạy lệnh

format.html { render :new }

do đó trang new sẽ được load lại cùng với biến toàn cục @product có mang theo errors messages
và tại trang new chúng sẽ được hiển thị thông qua đoạn code dưới

  <% if @product.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>
      <ul>
      <% @product.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

Đối với flash cũng gần tương tự như message, chỉ khác nhau một chút

Ví dụ, tôi muốn hiển thị flash cảnh báo khi cài đặt lỗi và flash thông báo khi tạo mới thành công chúng ta sẽ sửa action create của controller app/controllers/products_controller.rb

  def create
    @product = Product.new(product_params)

    respond_to do |format|
      if @product.save
        flash[:success] = "Create Product Success! ^_^"
        format.html { redirect_to @product, notice: 'Product was successfully created.' }
        format.json { render :show, status: :created, location: @product }
      else
        flash[:success] = "Create product Fail! >.<"
        format.html { render :new }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end

và hiển thị ra ở ngoài view, add đoạn dưới vào vị trí trong view hiển thị, ví dụ hiển thị lỗi trong khi create product
ta add phía trên cảnh báo messages trong app/views/products/_form.html.erb

<% flash.each do |key, value| %>
    <div id="error_explanation">
      <div class="alert alert-#{key}">
        <%= value unless value.blank? %>
      </div>
    </div>
  <% end %>

và chúng ta có kết quả:
errors_2.png

Cách thứ 2: hiển thị lỗi thông qua JS

và tiếp theo, chúng ta sẽ hiển thị thông qua JS, giờ chúng ta sẽ chuyển đoạn code hiển thị message vào flash vào hai file app/view/shared/_flash.html.erb

  <% flash.each do |key, value| %>
    <div id="error_explanation">
      <div class="alert alert-#{key}">
        <%= value unless value.blank? %>
      </div>
    </div>
  <% end %>

và file app/view/shared/_message_errors.html.erb

  <% if @product.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@product.errors.count, "error") %> prohibited this product from being saved:</h2>

      <ul>
      <% @product.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

Đồng thời chúng ta cũng xóa đi trong app/views/products/_form.html.erb
và thêm remote: true vào trong form_for của _form

<%= form_for(@product), remote: true do |f| %>
  <div class="field">
    <%= f.label :name %><br>
    <%= f.text_field :name %>
  </div>
  <div class="field">
    <%= f.label :price %><br>
    <%= f.text_field :price %>
  </div>
  <div class="field">
    <%= f.label :description %><br>
    <%= f.text_area :description %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>

tiếp theo chúng ta sẽ sửa một chút trong controller để nhận JS app/controllers/products/products_controller.rb

  def create
    @product = Product.new(product_params)

    respond_to do |format|
      if @product.save
        flash[:success] = "Create Product Success! ^_^"
        format.js {}
        format.html { redirect_to @product, notice: 'Product was successfully created.' }
        format.json { render :show, status: :created, location: @product }
      else
        flash[:success] = "Create product Fail! >.<"
        format.js {}
        format.html { render :new }
        format.json { render json: @product.errors, status: :unprocessable_entity }
      end
    end
  end

và sau đó, chúng ta sẽ tạo ra một file app/views/products/create.js.erb

<% if @product.errors.any? %>
  $(".alert").remove()
  $("#error_explanation").remove()
  $("form").prepend(
    "<%= j render "shared/message_errors" %>")
  $("form").prepend(
    "<%= j render "shared/flash" %>")
<% end %>

và chúng ta cũng nhận được kết quả
errors_3.png

Kết luận

Trên đây là 2 cách giúp các bạn hiển thị lỗi errors tùy vào trường hợp bạn sử dụng, còn đối với ý kiến của tôi thì nếu OK nhất bạn nên dùng JS, vì sẽ rất thuận tiện trong trường hợp bạn sử dụng dialog hay modal và muốn hiển thị lỗi nhưng không muốn bị load lại trang

Rất cám ơn các bạn đã quan tâm

Thanks for reading!