Active Record Nested Attributes
嵌套屬性讓您可以透過父類別儲存關聯記錄中的屬性。預設關閉嵌套屬性更新,您可以使用 accepts_nested_attributes_for
類別方法來啟用。當你啟用嵌套屬性時,會在模型中定義屬性寫入器。
屬性寫入器依據關聯命名,這表示在下列範例中,新增兩個新方法到您的模型中
author_attributes=(attributes)
和 pages_attributes=(attributes)
。
class Book < ActiveRecord::Base
has_one :author
has_many :pages
accepts_nested_attributes_for :author, :pages
end
請注意,使用 accepts_nested_attributes_for
的所有關聯都會自動啟用 :autosave
選項。
一對一
考慮一個有頭像的 Member 模型
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar
end
在一個一對一關聯中啟用嵌套屬性讓您一次就能建立成員和頭像
params = { member: { name: 'Jack', avatar_attributes: { icon: 'smiling' } } }
member = Member.create(params[:member])
member.avatar.id # => 2
member.avatar.icon # => 'smiling'
它也讓您可以透過成員來更新頭像
params = { member: { avatar_attributes: { id: '2', icon: 'sad' } } }
member.update params[:member]
member.avatar.icon # => 'sad'
如果您想在沒有提供 id 的情況下更新目前的頭像,必須新增 :update_only
選項。
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar, update_only: true
end
params = { member: { avatar_attributes: { icon: 'sad' } } }
member.update params[:member]
member.avatar.id # => 2
member.avatar.icon # => 'sad'
預設情況下,您只能設定和更新關聯模型中的屬性。如果您想透過屬性雜湊來毀掉關聯模型,您必須先使用 :allow_destroy
選項來啟用。
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar, allow_destroy: true
end
現在,當你將 _destroy
金鑰新增到屬性雜湊中,值若評估為 true
,您將毀掉關聯模型
member.avatar_attributes = { id: '2', _destroy: '1' }
member.avatar.marked_for_destruction? # => true
member.save
member.reload.avatar # => nil
請注意,模型要到父類別儲存後才不會被毀掉。
此外,請注意,模型不會被毀掉,除非您也在更新的雜湊中指定它的 id。
一對多
考慮一個有多個文章的成員
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts
end
現在,您可以透過一個成員的屬性雜湊來設定或更新關聯文章的屬性:以文章屬性雜湊陣列作為值,包含金鑰 :posts_attributes
。
對於 不 含 id
金鑰的每個雜湊,將會實例化一個新記錄,除非雜湊還包含 _destroy
金鑰並評估為 true
。
params = { member: {
name: 'joe', posts_attributes: [
{ title: 'Kari, the awesome Ruby documentation browser!' },
{ title: 'The egalitarian assumption of the modern citizen' },
{ title: '', _destroy: '1' } # this will be ignored
]
}}
member = Member.create(params[:member])
member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
您也可以設定一個 :reject_if
程序,來不聲不響地忽視任何新的記錄雜湊,前提是它們未通過您的判斷標準。例如,前一個範例可以改寫為
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: proc { |attributes| attributes['title'].blank? }
end
params = { member: {
name: 'joe', posts_attributes: [
{ title: 'Kari, the awesome Ruby documentation browser!' },
{ title: 'The egalitarian assumption of the modern citizen' },
{ title: '' } # this will be ignored because of the :reject_if proc
]
}}
member = Member.create(params[:member])
member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation browser!'
member.posts.second.title # => 'The egalitarian assumption of the modern citizen'
或者,:reject_if
也接受符號,以便使用這些方法
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: :new_record?
end
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, reject_if: :reject_posts
def reject_posts(attributes)
attributes['title'].blank?
end
end
如果雜湊包含與已經關聯的記錄相符的 id
金鑰,就會修改相符的記錄
member.attributes = {
name: 'Joe',
posts_attributes: [
{ id: 1, title: '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!' },
{ id: 2, title: '[UPDATED] other post' }
]
}
member.posts.first.title # => '[UPDATED] An, as of yet, undisclosed awesome Ruby documentation browser!'
member.posts.second.title # => '[UPDATED] other post'
但是,上述情況會在父模型也進行更新時才會套用。例如,如果您想建立一個名為 joe 的 member
並同時更新 posts
,這會產生 ActiveRecord::RecordNotFound
錯誤。
預設情況下,關聯記錄會受到毀掉的保護。如果您想透過屬性雜湊毀掉任何關聯記錄,您必須先使用 :allow_destroy
選項來啟用。這將允許您使用 _destroy
金鑰來毀掉現有的記錄
class Member < ActiveRecord::Base
has_many :posts
accepts_nested_attributes_for :posts, allow_destroy: true
end
params = { member: {
posts_attributes: [{ id: '2', _destroy: '1' }]
}}
member.attributes = params[:member]
member.posts.detect { |p| p.id == 2 }.marked_for_destruction? # => true
member.posts.length # => 2
member.save
member.reload.posts.length # => 1
連結集合的巢狀屬性也可以作為雜湊的雜湊格式傳遞,而不是雜湊的陣列。
Member.create(
name: 'joe',
posts_attributes: {
first: { title: 'Foo' },
second: { title: 'Bar' }
}
)
它與下列程式碼的效果相同:
Member.create(
name: 'joe',
posts_attributes: [
{ title: 'Foo' },
{ title: 'Bar' }
]
)
這種情況下,作為 :posts_attributes
雜湊值的金鑰會被忽略。不過,不允許使用 'id'
或 :id
做為其中一個金鑰,否則雜湊值會封裝成一個陣列,並視為單一貼文的屬性雜湊值加以詮釋。
以雜湊的雜湊格式傳遞連結集合的屬性可用於從 HTTP/HTML 參數產生的雜湊,其中可能沒有自然的方式提交雜湊陣列。
儲存
對模型所做的所有變更,包括標示要毀棄的模型毀棄,都會在儲存父模型時自動與原子化地儲存與毀棄。這發生在父模型的儲存方法所引發的事務中。請參閱 ActiveRecord::AutosaveAssociation
。
驗證是否存在父模型
預設情況下,belongs_to
關聯會驗證父模型是否存在。您可以指定 optional: true
來停用此行為。例如,用在有條件地驗證是否存在父模型時
class Veterinarian < ActiveRecord::Base
has_many :patients, inverse_of: :veterinarian
accepts_nested_attributes_for :patients
end
class Patient < ActiveRecord::Base
belongs_to :veterinarian, inverse_of: :patients, optional: true
validates :veterinarian, presence: true, unless: -> { awaiting_intake }
end
請注意,如果您未指定 :inverse_of
選項,Active Record 會根據經驗法則嘗試自動判斷反向關聯。
對於一對一巢狀關聯,如果您在指派之前自己建構新的(記憶體內)子物件,這個模組就不會覆寫它,例如:
class Member < ActiveRecord::Base
has_one :avatar
accepts_nested_attributes_for :avatar
def avatar
super || build_avatar(width: 200)
end
end
member = Member.new
member.avatar_attributes = {icon: 'sad'}
member.avatar.width # => 200
使用巢狀屬性建立表單
使用 ActionView::Helpers::FormHelper#fields_for
建立巢狀屬性的表單元素。
Integration
測試參數應反映表單的結構。例如
post members_path, params: {
member: {
name: 'joe',
posts_attributes: {
'0' => { title: 'Foo' },
'1' => { title: 'Bar' }
}
}
}
常數
REJECT_ALL_BLANK_PROC | = | proc { |attributes| attributes.all? { |key, value| key == "_destroy" || value.blank? } } |
執行個體公開的方法
accepts_nested_attributes_for(*attr_names) 連結
定義指定關聯的屬性 writer。
支援的選項
- :allow_destroy
-
如果是 true,就毀棄屬性雜湊中具有
_destroy
金鑰,且評估後等於true
(例如 1、‘1’、true 或 ‘true’)的值的任何成員。這個選項預設為 false。 - :reject_if
-
讓您可以指定一個 Proc 或一個指向檢查某個屬性雜湊是否應建置為某個記錄的方法的
Symbol
。將雜湊傳遞到提供的 Proc 或方法,它應傳回true
或false
。如果未指定:reject_if
,將為所有未具有評估後等於 true_destroy
值的屬性雜湊建置記錄。傳遞:all_blank
(而不是 Proc)會建立一個 Proc,拒絕所有屬性為空白的記錄(排除任何_destroy
值)。 - :limit
-
讓您可以指定使用巢狀屬性處理的關聯記錄的最大數目。限制也可以指定為 Proc 或一個指向應傳回數字的方法的
Symbol
。如果巢狀屬性陣列的大小超過指定的限制,則引發NestedAttributes::TooManyRecords
例外。如果省略,則可以處理任何數量的關聯。請注意,:limit
選項僅適用於一對多關聯。 - :update_only
-
對於一對一關聯,此選項允許您在關聯記錄已存在時,指定嵌套屬性將如何使用。一般來說,現有的記錄可能會使用新的屬性值集進行更新,或者由包含這些值的新記錄完全替換。預設情況下,
:update_only
選項為 false,且嵌套的屬性僅在包含記錄的:id
值時,才會用於更新現有記錄。否則,將實例化一個新的記錄,並用於替換現有的記錄。但是,如果:update_only
選項為 true,則嵌套的屬性將用於更新記錄的屬性,而不論:id
是否存在。選項會略過集合關聯。
範例
# creates avatar_attributes=
accepts_nested_attributes_for :avatar, reject_if: proc { |attributes| attributes['name'].blank? }
# creates avatar_attributes=
accepts_nested_attributes_for :avatar, reject_if: :all_blank
# creates avatar_attributes= and posts_attributes=
accepts_nested_attributes_for :avatar, :posts, allow_destroy: true
來源: 顯示 | 在 GitHub 上
# File activerecord/lib/active_record/nested_attributes.rb, line 351 def accepts_nested_attributes_for(*attr_names) options = { allow_destroy: false, update_only: false } options.update(attr_names.extract_options!) options.assert_valid_keys(:allow_destroy, :reject_if, :limit, :update_only) options[:reject_if] = REJECT_ALL_BLANK_PROC if options[:reject_if] == :all_blank attr_names.each do |association_name| if reflection = _reflect_on_association(association_name) reflection.autosave = true define_autosave_validation_callbacks(reflection) nested_attributes_options = self.nested_attributes_options.dup nested_attributes_options[association_name.to_sym] = options self.nested_attributes_options = nested_attributes_options type = (reflection.collection? ? :collection : :one_to_one) generate_association_writer(association_name, type) else raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?" end end end