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

On Aug 9, 2012, at 11:25 AM, Walter Lee D. wrote:

class Bar < ActiveRecord::Base

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? %>


<%= pluralize(@foo.errors.count, “error”) %> prohibited this
foo from being saved:

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

<% end %>

<%= f.label :name %>
<%= f.text_field :name %>

<%= f.fields_for :bazs do |b| %>


<%= b.label :title, “Baz” %>

<%= b.text_field :title %>
  <%= b.check_box :blarged %>
</div>

<% end %>

<%= f.submit %>
<% 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:inmethod_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:instart’
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
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:

On Aug 9, 2012, at 1:39 PM, Walter Lee D. wrote:

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

 <h2><%= pluralize(@foo.errors.count, "error") %> prohibited this foo from 

being saved:

<%= f.label :name %>

<%= b.check_box :blarged %>

<% end %>
<%= f.submit %>
<% end %>

<%= form_for(@foo) do |f| %>
<% if @foo.errors.any? %>

<%= pluralize(@foo.errors.count, "error") %> prohibited this foo from being saved:

    <% @foo.errors.full_messages.each do |msg| %>
  • <%= msg %>
  • <% end %>
<% end %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :bars do |b| %>
<%= b.fields_for :baz do |z| %> <%= z.label :title, "Baz" %>
<%= z.text_field :title %> <% end %> <%= b.check_box :blarged %>
<% end %>
<%= f.submit %>
<% end %>

1.9.2p320 :004 > f.bazs
from script/rails:6:in require' from script/rails:6:in
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 D. 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!!!