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