Nested form with has_many :through -- how to modify the "through" attributes

This question occurred to me as I was answering a different question. Given a has_many :through relationship, where there are additional attributes in the :through model, how would you construct a form so you could update those attributes?

#foo.rb class Foo < ActiveRecord::Base   has_many :bars   has_many :bazzes, :through => :bars   accepts_nested_attributes_for :bazzes end

#bar.rb class Bar < ActiveRecord::Base   belongs_to :foo   belongs_to :baz   # also defines the 'blarg' attribute end

#baz.rb class Baz < ActiveRecord::Base   has_many :bars   has_many :foos, :through => :bars end

So in a form_for @foo, in fields_for :bazzes, how would I define a checkbox to set the 'blarg' attribute to true or false? Does the nested @foo.baz just have a magical #blarg attribute, even though that attribute is defined in the linking model Bar?

Thanks,

Walter

I've gone so far as to scaffold this out, because I really would like to know the answer! Aside from having the plural of Baz wrong, my code above stands, and works as far as having multiple baz objects attached to a foo through a bar, and being able to create and modify these in a nested form. This part of Rails has only gotten easier to use in the latest version. What I cannot seem to do is in any way change the Bar when saving the association of Baz from a nested Foo form.

Here's my view:

<%= form_for(@foo) do |f| %>   <% if @foo.errors.any? %>     <div id="error_explanation">       <h2><%= pluralize(@foo.errors.count, "error") %> prohibited this foo from being saved:</h2>

      <ul>       <% @foo.errors.full_messages.each do |msg| %>         <li><%= msg %></li>       <% end %>       </ul>     </div>   <% end %>

  <div class="field">     <%= f.label :name %><br />     <%= f.text_field :name %>   </div>

<!-- this all works swimmingly -->   <%= f.fields_for :bazs do |b| %>     <div class="field">       <%= b.label :title, "Baz" %><br />       <%= b.text_field :title %>

<!-- here be the error -->       <%= b.check_box :blarged %> <!-- what do I do to set bar#blarged= in the context of h/m/t? -->

    </div>   <% end %>   <div class="actions">     <%= f.submit %>   </div> <% end %>

Trying this in console, I can get the list of bazs associated with a foo, and if I inspect one of those, I can see that it has a foo, but I can't seem to do anything to get the bar that stands between them and get or set any of its properties.

Loading development environment (Rails 3.2.7) 1.9.2p320 :001 > f = Foo.last   Foo Load (0.1ms) SELECT "foos".* FROM "foos" ORDER BY "foos"."id" DESC LIMIT 1 => #<Foo id: 3, name: "I'm a Foo", created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38"> 1.9.2p320 :002 > f.bars   Bar Load (0.3ms) SELECT "bars".* FROM "bars" WHERE "bars"."foo_id" = 3 => [#<Bar id: 1, blarged: nil, foo_id: 3, baz_id: 1, created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38">, #<Bar id: 2, blarged: nil, foo_id: 3, baz_id: 2, created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38">] 1.9.2p320 :004 > f.bazs   Baz Load (0.2ms) SELECT "bazs".* FROM "bazs" INNER JOIN "bars" ON "bazs"."id" = "bars"."baz_id" WHERE "bars"."foo_id" = 3 => [#<Baz id: 1, title: "Here's a baz", created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38">, #<Baz id: 2, title: "Here's another", created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38">] 1.9.2p320 :005 > f.bazs.first.bar NoMethodError: undefined method `bar' for #<Baz:0x007ff89a9ba7b8>   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/activemodel-3.2.7/lib/active_model/attribute_methods.rb:407:in `method_missing'   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/activerecord-3.2.7/lib/active_record/attribute_methods.rb:149:in `method_missing'   from (irb):5   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/railties-3.2.7/lib/rails/commands/console.rb:47:in `start'   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/railties-3.2.7/lib/rails/commands/console.rb:8:in `start'   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/railties-3.2.7/lib/rails/commands.rb:41:in `<top (required)>'   from script/rails:6:in `require'   from script/rails:6:in `<main>' 1.9.2p320 :006 >

It seems to me as though if I use the magical h/m/t and nested form behavior, I don't get access to the specific bar being used to bridge one foo to one baz. If I manually inspect a foo's bars, I can see the extra attribute (but I would have to rewrite my form and controller and not use the magic). What's the trick I need?

Thanks in advance,

Walter

SOLVED: Note to future self:

This question occurred to me as I was answering a different question. Given a has_many :through relationship, where there are additional attributes in the :through model, how would you construct a form so you could update those attributes?

#foo.rb class Foo < ActiveRecord::Base has_many :bars has_many :bazzes, :through => :bars accepts_nested_attributes_for :bazzes end

class Foo < ActiveRecord::Base   attr_accessible :name, :bars_attributes   has_many :bars   has_many :bazs, :through => :bars, :dependent => :destroy   accepts_nested_attributes_for :bars end

#bar.rb class Bar < ActiveRecord::Base belongs_to :foo belongs_to :baz # also defines the 'blarg' attribute end

class Bar < ActiveRecord::Base   attr_accessible :baz_id, :blarged, :foo_id, :baz_attributes, :baz   belongs_to :baz   belongs_to :foo   accepts_nested_attributes_for :baz end

#baz.rb class Baz < ActiveRecord::Base has_many :bars has_many :foos, :through => :bars end

class Baz < ActiveRecord::Base   attr_accessible :title   has_many :bars   has_many :foos, :through => :bars, :dependent => :destroy end

So in a form_for @foo, in fields_for :bazzes, how would I define a checkbox to set the 'blarg' attribute to true or false? Does the nested @foo.baz just have a magical #blarg attribute, even though that attribute is defined in the linking model Bar?

I've gone so far as to scaffold this out, because I really would like to know the answer! Aside from having the plural of Baz wrong, my code above stands, and works as far as having multiple baz objects attached to a foo through a bar, and being able to create and modify these in a nested form. This part of Rails has only gotten easier to use in the latest version. What I cannot seem to do is in any way change the Bar when saving the association of Baz from a nested Foo form.

Here's my view:

<%= form_for(@foo) do |f| %> <% if @foo.errors.any? %>    <div id="error_explanation">      <h2><%= pluralize(@foo.errors.count, "error") %> prohibited this foo from being saved:</h2>

     <ul>      <% @foo.errors.full_messages.each do |msg| %>        <li><%= msg %></li>      <% end %>      </ul>    </div> <% end %>

<div class="field">    <%= f.label :name %><br />    <%= f.text_field :name %> </div>

<!-- this all works swimmingly --> <%= f.fields_for :bazs do |b| %>    <div class="field">      <%= b.label :title, "Baz" %><br />      <%= b.text_field :title %>

<!-- here be the error -->      <%= b.check_box :blarged %> <!-- what do I do to set bar#blarged= in the context of h/m/t? -->

   </div> <% end %> <div class="actions">    <%= f.submit %> </div> <% end %>

<%= form_for(@foo) do |f| %> <% if @foo.errors.any? %> <div id="error_explanation">   <h2><%= pluralize(@foo.errors.count, "error") %> prohibited this foo from being saved:</h2>

  <ul>     <% @foo.errors.full_messages.each do |msg| %>     <li><%= msg %></li>     <% end %>   </ul> </div> <% end %>

<div class="field">   <%= f.label :name %><br />   <%= f.text_field :name %> </div> <%= f.fields_for :bars do |b| %> <div class="field">   <%= b.fields_for :baz do |z| %>   <%= z.label :title, "Baz" %><br />   <%= z.text_field :title %>   <% end %>   <%= b.check_box :blarged %> </div> <% end %> <div class="actions">   <%= f.submit %> </div> <% end %>

Trying this in console, I can get the list of bazs associated with a foo, and if I inspect one of those, I can see that it has a foo, but I can't seem to do anything to get the bar that stands between them and get or set any of its properties.

Loading development environment (Rails 3.2.7) 1.9.2p320 :001 > f = Foo.last Foo Load (0.1ms) SELECT "foos".* FROM "foos" ORDER BY "foos"."id" DESC LIMIT 1 => #<Foo id: 3, name: "I'm a Foo", created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38"> 1.9.2p320 :002 > f.bars Bar Load (0.3ms) SELECT "bars".* FROM "bars" WHERE "bars"."foo_id" = 3 => [#<Bar id: 1, blarged: nil, foo_id: 3, baz_id: 1, created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38">, #<Bar id: 2, blarged: nil, foo_id: 3, baz_id: 2, created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38">] 1.9.2p320 :004 > f.bazs Baz Load (0.2ms) SELECT "bazs".* FROM "bazs" INNER JOIN "bars" ON "bazs"."id" = "bars"."baz_id" WHERE "bars"."foo_id" = 3 => [#<Baz id: 1, title: "Here's a baz", created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38">, #<Baz id: 2, title: "Here's another", created_at: "2012-08-09 16:37:38", updated_at: "2012-08-09 16:37:38">] 1.9.2p320 :005 > f.bazs.first.bar NoMethodError: undefined method `bar' for #<Baz:0x007ff89a9ba7b8>   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/activemodel-3.2.7/lib/active_model/attribute_methods.rb:407:in `method_missing'   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/activerecord-3.2.7/lib/active_record/attribute_methods.rb:149:in `method_missing'   from (irb):5   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/railties-3.2.7/lib/rails/commands/console.rb:47:in `start'   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/railties-3.2.7/lib/rails/commands/console.rb:8:in `start'   from /Users/waltd/.rvm/gems/ruby-1.9.2-p320/gems/railties-3.2.7/lib/rails/commands.rb:41:in `<top (required)>'   from script/rails:6:in `require'   from script/rails:6:in `<main>' 1.9.2p320 :006 >

It seems to me as though if I use the magical h/m/t and nested form behavior, I don't get access to the specific bar being used to bridge one foo to one baz. If I manually inspect a foo's bars, I can see the extra attribute (but I would have to rewrite my form and controller and not use the magic). What's the trick I need?

The trick was to not try to build a bridge in mid-air. The foo could find a baz, but that found baz did not seem to have any notion of which bar was standing between the two. Nesting my forms one layer at a time did the trick. fields_for bars then contained fields_for baz.

Walter

Walter Davis wrote in post #1071859:

SOLVED: Note to future self:

The trick was to not try to build a bridge in mid-air. The foo could find a baz, but that found baz did not seem to have any notion of which bar was standing between the two. Nesting my forms one layer at a time did the trick. fields_for bars then contained fields_for baz.

Walter

Thank you very much i appreciate it!!!