Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

accepts_nested_attributes_for with find_or_create?

I'm using Rails' accepts_nested_attributes_for method with great success, but how can I have it not create new records if a record already exists?

By way of example:

Say I've got three models, Team, Membership, and Player, and each team has_many players through memberships, and players can belong to many teams. The Team model might then accept nested attributes for players, but that means that each player submitted through the combined team+player(s) form will be created as a new player record.

How should I go about doing things if I want to only create a new player record this way if there isn't already a player with the same name? If there is a player with the same name, no new player records should be created, but instead the correct player should be found and associated with the new team record.

like image 426
trisignia Avatar asked Aug 26 '10 22:08

trisignia


2 Answers

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
  end
end

This code is untested, but it should be pretty much what you need.

like image 102
François Beausoleil Avatar answered Nov 09 '22 12:11

François Beausoleil


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?
  end
end

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.

like image 32
ryanb Avatar answered Nov 09 '22 13:11

ryanb