accepts_nested_attributes_for with find_or_create? accepts_nested_attributes_for with find_or_create? ruby-on-rails ruby-on-rails

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