+2

Đơn giản hóa nested fields Rails với gem Cocoon

Trong bài này chúng ta sẽ cùng thảo luận về vấn đề làm thế nào để xây dựng forms sử dụng đặc tính Rails nested attributes. Tôi sẽ trình bày với bạn làm thế nào vận dụng nhiều các bản ghi quan hệ từ một single form và thiết lập đụng các models và controller để kich hoạt những tính năng này. Thật vậy, chúng ta sẽ thảo luận về các lỗi hay găp phải và chúng ta sẽ sử dụng gem Cocoon để làm cho form của chúng ta trở lên linh hoạt. Giải pháp này cho phép thêm và xóa bỏ nested fiedlds không đồng bằng việc cung cấp nhiều option và callbacks.

Building a Simple Form

Đầu tiên chúng ta sẽ tạo một ứng dụng mà không đi kèm test mặc định.

$ rails new NestedForms -T

Giả sử rằng, với app này, chúng ta muốn giữ lưu các places yêu thích và address của chúng. Ví dụ, một plasce có nhiều address, vì thế chúng ta sẽ mô tả nó sử dụng quan hệ:

$ rails g model Place title:string
$ rails g model Address city:string street:string place:belongs_to
$ rake db:migrate

Cần đảm bảo rằng các quan hệ này được cài đặt chính xác:

models/place.rb

[...]
has_many :addresses, dependent: :destroy
[...]
models/address.rb

[...]
belongs_to :place
[...]

Giờ tới controller: app/controllers/places_controller.rb

class PlacesController < ApplicationController
  def index
    @places = Place.all
  end

  def new
    @place = Place.new
  end

  def create
    @place = Place.new(place_params)
    if @place.save
      redirect_to root_path
    else
      render :new
    end
  end

  private

  def place_params
    params.require(:place).permit(:title)
  end
end

Thêm routes:

config/routes.rb

[...]
resources :places, only: [:new, :create, :edit, :update]

root to: 'places#index'
[...]

View root page:

views/places/index.html.erb

<h1>Places</h1>

<p><%= link_to 'Add place', new_place_path %></p>

<ul><%= render @places %></ul>

Chúng ta có render @places, chúng ta cần tạo partial tương ứng: views/places/_place.html.erb

<li>
  <strong><%= place.title %></strong>

  <% if place.addresses.any? %>
    Addresses:
    <ul>
      <% place.addresses.each do |addr| %>
        <li>
          <%= addr.city %>, <%= addr.street %>
        </li>
      <% end %>
    </ul>
  <% end %>
</li>

tạo view places:

views/places/new.html.erb

<h1>Add place</h1>

<%= render 'form' %>

tạo partial form:

views/places/_form.html.erb

<%= render 'shared/errors', object: @place %>

<%= form_for @place do |f| %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>

  <%= f.submit %>
<% end %>

Tuy vậy, về phía người dùng thì tôi thích thêm places của address ngay trên cùng một form thay vì tạo ra hai form riêng. Điều này cũng giúp chúng ta chỉ phải thao tác trên một controller. Đây là nơi nested attribute được sử dụng.

Adding Nested Attributes

Ý tưởng đằng sau nested attribute là khá đơn giản. Bạn có signle form nơi mà bạn có thể tạo một object với nhiều các bản ghi quan hệ. Tính năng này có thể được bổ sung thực sự nhanh chóng. Vì nó đòi hỏi sự thay đổi rất nhỏ ở controller và model, cũng như một số đánh dấu. Tất cả bắt đầu với việc bổ sung từ khóa dài: accepts_nested_attributes_for.

models/places.rb

[...]
accepts_nested_attributes_for :addresses
[...]

Khi bạn submit một form với nested field, params[:place] sẽ chứa 1 mảng dưới một key là :addresses_attributes, Mảng này sẽ mô tả mỗi địa chỉ được thêm vào database. Miễn là chúng ta sử dụng strong_params, những thuộc tính mới sẽ được permit. Giờ chung ta sẽ thêm nested form vào view: views/places/_form.html.erb

<%= form_for @place do |f| %>
  <%= render 'shared/errors', object: @place %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>

  <div>
    <p><strong>Addresses:</strong></p>

    <%= f.fields_for :addresses do |address| %>
      <div>
        <%= address.label :city %>
        <%= address.text_field :city %>

        <%= address.label :street %>
        <%= address.text_field :street %>
      </div>
    <% end %>
  </div>

  <%= f.submit %>
<% end %>

Phương thức field_for, nó khá giống với phương thức form_for nhưng nó không cung cấp form_tag. Chú ý bên trong block chúng ta sử dụng biến cục bộ address - không được gọi nó f bời vì nó đã bao gồm builder cho form cha nó. Có một vấn đề, tuy vây. Khi bạn ghé thăm "New Places" page sẽ không nhìn thấy nested field, bởi vì chắc chắn thể hiện mới của places class không chứa nested addresses. Việc sửa chữa đơn giản, sẽ tạo một số address trực tiếp ngay trong controller:

places_controller.rb

[...]
def new
  @place = Place.new
  3.times { @place.addresses.build}
end
[...]

Making It Dynamic

Giờ form cơ bản đã xong, tuy nhiên sử dụng nó không được tiện lợi. Ví dụ không có cách nào có thể thêm nhiền hơn 3 address. Để làm được tính năng này thì phải làm nhiều việc, vì rails không hỗ trợ thêm nhiều field. Chúng ta có một giải pháp ở đây, sử dụng gem Cocoon và nó thật tuyệt vời. Cocoon tác động nested với javascript. Cho phép thêm hoặc xoa field môt cách linh động.

Để bắt đầu với gem Cocoon thật đơn giản. thêm gem mới:

Gemfile

[...]
gem "cocoon"
[...]

Và cài đặt nó:

$ bundle install

thêm vào file javascript mới:

javascripts/application.js

[...]
//= require cocoon
[...]

Giờ ta tách từng nested field ra thành từng phần riêng biệt:

views/places/_address_fields.html.erb

<div class="nested-fields">
  <%= f.label :city %>
  <%= f.text_field :city %>

  <%= f.label :street %>
  <%= f.text_field :street %>

  <%= f.check_box :_destroy %>

  <%= link_to_remove_association "remove address", f %>
</div>

ở đây ta thấy link_to_remove_association, Đây là một helper của Cocoon, tạo ra một liên kết mới không đồng bộ để xóa các associated record. Method nay chấp nhận 3 tham số:

tiêu đề của link form object HTML options Bây giờ chúng ta sử dụng partial này dưới form: views/places/_form.html.erb

<%= form_for @place do |f| %>
  <%= render 'shared/errors', object: @place %>

  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>

  <div>
    <p><strong>Addresses:</strong></p>

    <div id="addresses">
      <%= f.fields_for :addresses do |address| %>
        <%= render 'address_fields', f: address %>
      <% end %>

      <div class="links">
        <%= link_to_add_association 'add address', f, :addresses %>
      </div>
    </div>
  </div>

  <%= f.submit %>
<% end %>

ở đây chúng ta sử dụng một helper khác của Cocoon link_to_add_association. Nó sẽ tạo ra một liên kết động để có thể thêm nested field. Method này nhận đầu vào 4 tham số:

nội dung của link(text) form builder (parent's form) tên của quan hệ HTML option Giờ thì lại hãy khởi động lại server và thử thao tác lại xem, chắc chán thuận tiện hơn. Conclusion Ở trên chúng ta vừa thử tạo ra form nested cơ bản và đã thử dùng gem Cocoon. Còn một vài phần nữa nhưng tôi nghĩ nên dể dành cho phần khác, giờ chúng ta hãy cứ thực hành với các phần ở trên đã. Cảm ơn mọi người đã đọc bài của tôi.

Link tham khảo: https://www.sitepoint.com/better-nested-attributes-in-rails-with-the-cocoon-gem/?utm_source=sitepoint&utm_medium=relatedsidebar&utm_term=ruby


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí