How do you edit the attributes of a join model when using accepts_nested_attributes_for?
I have 3 models: Topics and Articles joined by Linkers
class Topic < ActiveRecord::Base
has_many :linkers
has_many :articles, :through => :linkers, :foreign_key => :article_id
accepts_nested_attributes_for :articles
end
class Article < ActiveRecord::Base
has_many :linkers
has_many :topics, :through => :linkers, :foreign_key => :topic_id
end
class Linker < ActiveRecord::Base
#this is the join model, has extra attributes like \"relevance\"
belongs_to :topic
belongs_to :article
end
So when I build the article in the \"new\" action of the topics controller...
@topic.articles.build
...and make the nested form in topics/new.html.erb...
<% form_for(@topic) do |topic_form| %>
...fields...
<% topic_form.fields_for :articles do |article_form| %>
...fields...
...Rails automatically creates the linker, which is great.
Now for my question: My Linker model also has attributes that I want to be able to change via the \"new topic\" form. But the linker that Rails automatically creates has nil values for all its attributes except topic_id and article_id. How can I put fields for those other linker attributes into the \"new topic\" form so they don\'t come out nil?
Figured out the answer. The trick was:
@topic.linkers.build.build_article
That builds the linkers, then builds the article for each linker. So, in the models:
topic.rb needs accepts_nested_attributes_for :linkers
linker.rb needs accepts_nested_attributes_for :article
Then in the form:
<%= form_for(@topic) do |topic_form| %>
...fields...
<%= topic_form.fields_for :linkers do |linker_form| %>
...linker fields...
<%= linker_form.fields_for :article do |article_form| %>
...article fields...
When the form generated by Rails is submitted to the Rails controller#action
, the params
will have a structure similar to this (some made up attributes added):
params = {
\"topic\" => {
\"name\" => \"Ruby on Rails\' Nested Attributes\",
\"linkers_attributes\" => {
\"0\" => {
\"is_active\" => false,
\"article_attributes\" => {
\"title\" => \"Deeply Nested Attributes\",
\"description\" => \"How Ruby on Rails implements nested attributes.\"
}
}
}
}
}
Notice how linkers_attributes
is actually a zero-indexed Hash
with String
keys, and not an Array
? Well, this is because the form field keys that are sent to the server look like this:
topic[name]
topic[linkers_attributes][0][is_active]
topic[linkers_attributes][0][article_attributes][title]
Creating the record is now as simple as:
TopicController < ApplicationController
def create
@topic = Topic.create!(params[:topic])
end
end
A quick GOTCHA for when using has_one in your solution.
I will just copy paste the answer given by user KandadaBoggu in this thread.
The build
method signature is different for has_one
and has_many
associations.
class User < ActiveRecord::Base
has_one :profile
has_many :messages
end
The build syntax for has_many
association:
user.messages.build
The build syntax for has_one
association:
user.build_profile # this will work
user.profile.build # this will throw error
Read the has_one
association documentation for more details.