103

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?

Arcolye
  • 6,863
  • 4
  • 29
  • 26
  • 3
    I'm trying to do the same thing you are, only in a new/create action... I'm wondering if you could share your controller actions. I want to create a `User` through an `Account` using a `Relationship` as a `linker`... but I can't figure out what the new and create actions are meant to look like... would you mind? – Mohamad Mar 05 '12 at 21:24
  • https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through – zx1986 Jul 13 '17 at 13:48

3 Answers3

91

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...
Arcolye
  • 6,863
  • 4
  • 29
  • 26
6

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
Makoto
  • 96,408
  • 24
  • 164
  • 210
Daniel Doezema
  • 1,542
  • 10
  • 13
  • I'm not sure, but I think that has been assumed all along with `accepts_nested_attributes_for` – Arcolye May 17 '13 at 17:10
  • 2
    @Arcolye - Finding this information on the internet for an association like this was such a pain at the time -- maybe my google-fu was off that day. I wanted to at least document it here as I as my co-worker and I just assumed rails converted linked_attributes to an array, instead of a zero indexed hash. Hopefully this tidbit helps someone in the future :) – Daniel Doezema May 17 '13 at 17:25
3

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.

Community
  • 1
  • 1
8bithero
  • 1,265
  • 1
  • 16
  • 21