accepted_nested_attributes_for find_or_create?
我使用Rails的方法accept_nested_attributes_for取得了巨大的成功,但是如果logging已经存在,我怎么能不创build新的logging呢?
举例来说:
假设我有三个模型:团队,会员和玩家,每个团队都有许多玩家通过会员,玩家可以属于很多团队。 团队模型可能会接受玩家的嵌套属性,但这意味着通过组合的团队+玩家forms提交的每个玩家将被创build为新的玩家logging。
如果我只想创build一个新的玩家logging,如果还没有一个同名的玩家,我应该怎么做? 如果有同名球员,则不应创build新的球员logging,而应find正确的球员并将其与新的球队logging相关联。
当您为自动保存关联定义一个钩子时,正常的代码path将被跳过,您的方法被调用。 因此,你可以这样做:
class Post < ActiveRecord::Base belongs_to :author, :autosave => true accepts_nested_attributes_for :author # If you need to validate the associated record, you can add a method like this: # validate_associated_record_for_author def autosave_associated_records_for_author # Find or create the author by name if new_author = Author.find_by_name(author.name) self.author = new_author else self.author.save! end end end
这段代码没有经过testing,但应该是你所需要的。
不要把它看作是给球队添加球员,而是把球员join球队。 表格不直接与玩家合作。 成员资格模型可以有一个player_name
虚拟属性。 在幕后,这可以查找玩家或创build一个。
class Membership < ActiveRecord::Base def player_name player && player.name end def player_name=(name) self.player = Player.find_or_create_by_name(name) unless name.blank? end end
然后,只需将一个player_name文本字段添加到任何成员资格表单构build器。
<%= f.text_field :player_name %>
这种方式不是特定于accept_nested_attributes_for,可以用于任何会员forms。
注意:使用这种技术,玩家模型是在validation发生之前创build的。 如果你不想要这个效果,那么将播放器存储在一个实例variables中,然后将其保存在before_savecallback函数中。
使用:accepts_nested_attributes_for
,提交现有logging的id
将导致ActiveRecord 更新现有logging,而不是创build新logging。 我不确定你的标记是什么样的,但是尝试一下这样的东西:
<%= text_field_tag "team[player][name]", current_player.name %> <%= hidden_field_tag "team[player][id]", current_player.id if current_player %>
如果提供了id
,Player name将被更新,但是另外创build。
定义autosave_associated_record_for_
方法的方法非常有趣。 我一定会用的! 但是,也要考虑这个更简单的解决scheme。
只是在问题的讨论中(指find_or_create),弗朗索瓦答案中的if块可以被重新表述为:
self.author = Author.find_or_create_by_name(author.name) unless author.name.blank? self.author.save!
如果你有has_one或belongs_to的关系,这个效果很好。 但是,有一个has_many或has_many通过。
我有一个利用has_many:through关系的标记系统。 这两种解决scheme都不是在我需要的地方find我的,所以我想出了一个可以帮助他人的解决scheme。 这已经在Rails 3.2上testing过了。
build立
这里是我的模型的基本版本:
位置对象:
class Location < ActiveRecord::Base has_many :city_taggables, :as => :city_taggable, :dependent => :destroy has_many :city_tags, :through => :city_taggables accepts_nested_attributes_for :city_tags, :reject_if => :all_blank, allow_destroy: true end
标记对象
class CityTaggable < ActiveRecord::Base belongs_to :city_tag belongs_to :city_taggable, :polymorphic => true end class CityTag < ActiveRecord::Base has_many :city_taggables, :dependent => :destroy has_many :ads, :through => :city_taggables end
解
我确实覆盖了autosave_associated_recored_for方法,如下所示:
class Location < ActiveRecord::Base private def autosave_associated_records_for_city_tags tags =[] #For Each Tag city_tags.each do |tag| #Destroy Tag if set to _destroy if tag._destroy #remove tag from object don't destroy the tag self.city_tags.delete(tag) next end #Check if the tag we are saving is new (no ID passed) if tag.new_record? #Find existing tag or use new tag if not found tag = CityTag.find_by_label(tag.label) || CityTag.create(label: tag.label) else #If tag being saved has an ID then it exists we want to see if the label has changed #We find the record and compare explicitly, this saves us when we are removing tags. existing = CityTag.find_by_id(tag.id) if existing #Tag labels are different so we want to find or create a new tag (rather than updating the exiting tag label) if tag.label != existing.label self.city_tags.delete(tag) tag = CityTag.find_by_label(tag.label) || CityTag.create(label: tag.label) end else #Looks like we are removing the tag and need to delete it from this object self.city_tags.delete(tag) next end end tags << tag end #Iterate through tags and add to my Location unless they are already associated. tags.each do |tag| unless tag.in? self.city_tags self.city_tags << tag end end end
上面的实现保存,删除和更改以嵌套forms使用fields_for时所需的方式标记。 如果有方法可以简化我可以反馈。 需要指出的是,当标签更改时,我正在明确地更改标签,而不是更新标签标签。
before_validation
钩子是一个不错的select:这是一个标准的机制,导致代码更简单,而不是覆盖更晦涩的autosave_associated_records_for_*
。
class Quux < ActiveRecord::Base has_and_belongs_to_many :foos accepts_nested_attributes_for :foos, reject_if: ->(object){ object[:value].blank? } before_validation :find_foos def find_foos self.foos = self.foos.map do |object| Foo.where(value: object.value).first_or_initialize end end end
@ dustin-m的回答对我有帮助 – 我正在用has_many:through关系做一些自定义的事情。 我有一个主题,有一个趋势,有很多孩子(recursion)。
ActiveRecord不喜欢它时,我把它configuration为一个标准的has_many :searches, through: trend, source: :children
关系。 它检索topic.trend和topic.searches,但不会做topic.searches.create(name:foo)。
所以我使用上面的构造一个自定义的自定义,并正在与accepts_nested_attributes_for :searches, allow_destroy: true
实现正确的结果accepts_nested_attributes_for :searches, allow_destroy: true
def autosave_associated_records_for_searches searches.each do | s | if s._destroy self.trend.children.delete(s) elsif s.new_record? self.trend.children << s else s.save end end end
accepts_nested_attributes_for :searches, allow_destroy: true
def autosave_associated_records_for_searches searches.each do | s | if s._destroy self.trend.children.delete(s) elsif s.new_record? self.trend.children << s else s.save end end end
def autosave_associated_records_for_searches searches.each do | s | if s._destroy self.trend.children.delete(s) elsif s.new_record? self.trend.children << s else s.save end end end
通过回答@FrançoisBeausoleil是真棒,并解决了一个大问题。 很好的了解autosave_associated_record_for
的概念。
不过,我在这个实现中发现了一个angular落案例。 如果update
现有职位的作者( A1
),如果新的作者姓名( A2
)通过,则最终将更改原始( A1
)作者的姓名。
p = Post.first p.author #<Author id: 1, name: 'JK Rowling'> # now edit is triggered, and new author(non existing) is passed(eg: Cal Newport). p.author #<Author id: 1, name: 'Cal Newport'>
欧代码:
class Post < ActiveRecord::Base belongs_to :author, :autosave => true accepts_nested_attributes_for :author # If you need to validate the associated record, you can add a method like this: # validate_associated_record_for_author def autosave_associated_records_for_author # Find or create the author by name if new_author = Author.find_by_name(author.name) self.author = new_author else self.author.save! end end end
这是因为,在编辑的情况下, self.author
作者已经是一个ID为1的作者,它将进入其他的,阻止并将更新author
而不是创build一个新的。
我改变了代码( elsif
条件)来减轻这个问题:
class Post < ActiveRecord::Base belongs_to :author, :autosave => true accepts_nested_attributes_for :author # If you need to validate the associated record, you can add a method like this: # validate_associated_record_for_author def autosave_associated_records_for_author # Find or create the author by name if new_author = Author.find_by_name(author.name) self.author = new_author elsif author && author.persisted? && author.changed? # New condition: if author is already allocated to post, but is changed, create a new author. self.author = Author.new(name: author.name) else # else create a new author self.author.save! end end end