Tìm hiểu về Nested Attributes trong Rails
Bài đăng này đã không được cập nhật trong 3 năm
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à Account và UserInfo 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_only là false 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_only là true 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