Reputation: 3229
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
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
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