msmith1114
msmith1114

Reputation: 3229

Rails has_many/accepts_nested_attributes_for Create confusion?

I'm a bit confused when it comes to one thing and that's creating MULTIPLE "belongs_to" model items in the controller.

Basically, I've seen it where say we have a Person model, and that Person has_many Addresses and also accepts_nested_attributes_for :addresses

(Let's pretend the Address is just a single text field, and let's say in the form we are going to end up having 3 addresses for a person)

In the "new" controller I will typically see something like:

@person = Person.new
3.times { @person.addresses.build }

Then in the "create" controller action it'll go through and do the normal create all while accepting all 3 fields (using fields_for form) of addresses. via something like:

def person_params
 params.require(:booking).permit(:name,:email,:age, :addresses_attributes => [:address])
end

And it'll create all 3 addresses just fine. I don't understand how it works. Were building 3 different instances and then somehow rails just "knows" how to associate them?

Can anyone explain what's going on?

Upvotes: 0

Views: 468

Answers (2)

Abhilash Reddy
Abhilash Reddy

Reputation: 1549

accept_nested_attributes_for :addresses in your Person model defines an attributes writer for the has_many :addresses association.

Now when you submit your form to the Rails application, The params might look something similar to this:

"user"=>{"name"=>'John', "email"=>'[email protected]',"age"=>"25"
addresses_attributes"=>{"0"=>{"address"=>"Street 1"}, "1"=>{"address"=>"Street 2"}, "2"=>{"address"=>"Street 2"}}}

If you take a close look at the addresses_attributes param, it looks similar to a hash and each key in that hash represents an address object. That is how Rails will exactly know that it has to create 3 address records.

Also if you would like to understand this better, go through this rails documentation

Updated:

It is not at all compulsory to instantiate the addresses in advance in the new action with 3.times { @person.addresses.build }

And hardcoding number of addresses might not be a right way to do it. What if someone wants to store 5 addresses and your application allows just 3?

So probably you can use Jquery and implement this in a much better way. Or even you could use cocoon gem.

Upvotes: 1

Mark Merritt
Mark Merritt

Reputation: 2695

When you declare that a Person model accepts_nested_attributes_for :addresses, all you are really saying is I want Rails to create getter and setter instance methods on the Person model for addresses (plural). This is similar to other relationships. For instance, if you declare a has_many :friends relationship on Person, you would be able to call @person.friends(the getter) or @person.friends.create(the setter). Heres a visual...

class Person < ActiveRecord::Base
  # this is the getter method
  def addresses
    # I can retrieve all of the associated addresses for a particular person
  end

  # this is the setter method
  def addresses_attributes=(attributes)
    # I can take in any number of addresses and assoicate them to a particular person
  end
end

When you permit addresses_attributes in the controller

params.require(:person).permit(:addresses_attributes => [:address])

Think of it like you are allowing the addresses_attribues setter method to be called when creating a Person...the setter method takes in the addresses (plural) submitted from the form and associates them with that particular person. But, when you call @person.addresses, you'll notice that it returns an ActiveRecord::Associations::CollectionProxy, which is important to understand.

In the controller, when you say

@person = Person.new
3.times { @person.addresses.build }

Think of it like creating an empty Person and 3 empty addresses associated with that person, which will only be populated with attributes once the create action is called by submitting the form.

EDIT:

The main benefit of nested attributes (and it's difference from a normal association) is that it allows you to use a single form to create/modify the parent class (in this case, the Person), and at the same time create/modify it's associated attributes (in this case, addresses) through the use of fields_for

"I guess the other confusing part is how rails knows that the Person has 3 empty addresses?"

Rails doesn't know right off the bat...which is why you have to explicitly instantiate X amount of empty addresses in the controller. So now when you call form.fields_for :addresses there is only X amount of memory to store X amount of addresses.

Upvotes: 1

Related Questions