+2

Tìm hiểu về Nested Attributes trong Rails

Mở đầu

Nested Attributes là kỹ thuật cho phép chúng ta có thể dễ dàng khởi tạo và lưu thuộc tính của đối tượng thông qua đối tượng cha của nó (associated records).

Lấy ví dụ, chúng ta có 2 model là AccountUserInfo liên kết với nhau qua mối quan hệ 1-1. Giờ muốn tạo và lưu bản ghi account có thuộc tính user_info theo cách thông thường ta phải làm theo các bước sau: Tạo đối tượng account, lưu account, tạo đối tượng user_info tương ứng bằng câu lệnh build, lưu user_info. Nhưng với Nested Attributes, ta chỉ cần tạo account, truyền params bao gồm cả param của account và user_info và lưu lại là xong.

Áp dụng

Model

app/models/account.rb

class Account < ActiveRecord::Base
  has_one :user_info
  accepts_nested_attributes_for :user_info
end

app/models/user_info.rb

class UserInfo < ActiveRecord::Base
  belong_to :account
end

Sử dụng accepts_nested_attributes_for trong model Account thì khi create/update cho đối tượng account có thể create/update luôn cho user_info bằng cách truyền thêm thuộc tính của user_info vào params:

params = { account: { username: 'duongvu', password: '123456',
                      user_info_attributes: { name: 'vu binh duong', age: '21' } } }
account = Account.create!(params[:account])

Params trong trường hợp quan hệ 1-n:

params = { account: { username: 'duongvu', password: '123456',
                      posts_attributes: {
                            { title: 'title 1' },
                            { title: 'title 2' },
                            { title: 'title 3' }
                      } } }

View

Tạo form có Nested Attributes : Ở đây ta sẽ sử dụng fields_for để tạo ra một scope xung quanh một đối tượng cụ thể nhưng không tạo ra form_tags chính nó. Vì thế fields_for thích hợp cho việc xác định các model object bổ sung trong cùng form đó.

app/views/account/new.html.erb

<%= form_for @account do |f| %>
  <%= f.label :username %>
  <%= f.text_field :username %>
  <%= f.label :password %>
  <%= f.text_field :password %>

  <%= f.fields_for :user_info do |ff| %>
    <%= ff.label :name %>
    <%= ff.text_field :name %>
    <%= ff.label :age %>
    <%= ff.text_field :age %>
  <% end %>

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

Controller

app/controllers/accounts_controller.rb

class AccountsController < ApplicationController
  def new
    @account = Account.new
    @account.build_user_info
  end

  def create
    @account = Account.new account_params
    if @account.save
      flash[:success] = "Created success!"
      redirect_to root_path
    else
      flash[:error] = "Created failed!"
      render :new
    end
  end

  private
  def account_params
    params.require(:account).permit :username, :password, user_info_attributes: [:name, :age]
  end
end

Một số Supported options

:update_only

Theo mặc định, tùy chọn :update_onlyfalse và các thuộc tính lồng nhau chỉ được sử dụng để cập nhật bản ghi hiện tại nếu chúng bao gồm giá trị :id của bản ghi. Nếu không, một bản ghi mới sẽ được khởi tạo và thay thế bản ghi hiện có. Khi :update_onlytrue chỉ cần truyền param muốn update, không cần truyền id.

class Account < ActiveRecord::Base
  has_one :user_info
  accepts_nested_attributes_for :user_info, update_only: true
end

params = { account: { user_info_attributes: { name: 'someone else', age: '21' } } }
account.update params[:account]

:allow_destroy

:allow_destroy cho phép xóa tất cả thuộc tính với param truyền vào có key là _destroy và value là true:

class Account < ActiveRecord::Base
  has_one :user_info
  accepts_nested_attributes_for :user_info, allow_destroy: true
end

account.user_info_attributes = { id: '1', _destroy: '1' }
account.save
account.user_info.name # => nil

:reject_if

:reject_if cho phép chỉ định một Proc hoặc một Symbol trỏ đến một method kiểm tra xem bản ghi có thỏa mãn điều kiện nhất định hay không.

class Account < ActiveRecord::Base
  has_one :user_info
  accepts_nested_attributes_for :user_info, reject_if: proc { |att| att['name'].blank? }
end

Có thể dùng :all_blank thay vì proc để kiểm tra bất cứ thuộc tính nào trống hay không.

Áp dụng :reject_if với Symbol:

class Account < ActiveRecord::Base
  has_one :user_info
  accepts_nested_attributes_for :user_info, reject_if: :reject_user
  
  def reject_account(attributes)
      attributes['name'].blank? || attributes['age'].blank?
  end
end

:limit

Option này chỉ áp dụng cho mối quan hệ 1-n. :limit cho phép chỉ định số lượng bản ghi con tối đa có thể được xử lý với nested attributes. Nếu kích thước của mảng thuộc tính lồng nhau vượt quá giới hạn đã chỉ định, thì sẽ raise exception NestedAttributes :: TooManyRecords.

class Account < ActiveRecord::Base
  has_many :posts
  accepts_nested_attributes_for :posts, limit: 10
end

Tài liệu tham khảo

https://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html


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í