accepts_nested_attributes_for with find_or_create?
When you define a hook for autosave associations, the normal code path is skipped and your method is called instead. Thus, you can do this:
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 endend
This code is untested, but it should be pretty much what you need.
Don't think of it as adding players to teams, think of it as adding memberships to teams. The form doesn't work with the players directly. The Membership model can have a player_name
virtual attribute. Behind the scenes this can either look up a player or create one.
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? endend
And then just add a player_name text field to any Membership form builder.
<%= f.text_field :player_name %>
This way it is not specific to accepts_nested_attributes_for and can be used in any membership form.
Note: With this technique the Player model is created before validation happens. If you don't want this effect then store the player in an instance variable and then save it in a before_save callback.
A before_validation
hook is a good choice: it's a standard mechanism resulting in simpler code than overriding the more obscure 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 endend