views:

833

answers:

3

Banging my head against this one for a long time. On Rails 2.3.2, Ruby 1.9.1.

Trying to use one form to create three objects that have these relations:

class Person
  has_one :goat
end

class Goat
  belongs_to :person
  has_many :kids
end

class Goat::Kid
  belongs_to :goat
end

Here's a summary of the schema:

Person
  first_name
  last_name
Goat
  name
  color
Goat::Kid
  nickname
  age

I'd like my #create action to instantiate new instances of all three models with the specified associations. However, while it appears that my params hash is being passed to the controller as it should (based on the backtrace logs in the browser when it blows up), the Goat::Kid object is not collecting the params.

irb (irb session is just a psuedo-representation of what I'm trying to accomplish so if it doesn't call #save! or any other necessities it's not really meant to be correct. I'm trying to do this all through the browser/web form.)

a = Person.new :first_name => 'Leopold', :last_name => 'Bloom'
b = Goat.new :name => 'Billy', :color => 'white'
c = Goat::Kid.new :nickname => 'Jr.', :age => 2

a.goat.kids

>> []

Now, I cannot figure out how to get the view to pass the params to each object and to get the controller to save these params to the db.

My questions: A) is this a good place to use nested_attributes_for and if so how do I declare that with a namespace? B) is there a much simpler, easier to understand way to do this?

Passing params to three models has just been very challenging to me and no matter how much documentation I read I can't wrap my head around it (#form_for and #fields_for). The namespace further complexifies this. Thanks for any help!


Addendum: if I end up declaring

accepts_nested_attributes_for

what's the proper way to use the symbol argument for a namespaced model?

accepts_nested_attributes_for :kids, :through => :goats

or

accepts_nested_attributes_for :goats_kids, :through => :goats

or

accepts_nested_attributes_for :goats::kids, :through => :goats

I'm not sure how namespaced models translate to their symbol identifiers. Thanks!

+1  A: 

Well, this is my first time playing with accepts_nested_attributes_for, but with a little playing around I was able to get something to work.

First the model setup:

class Person < ActiveRecord::Base
  has_one :goat
  accepts_nested_attributes_for :goat
end

class Goat < ActiveRecord::Base
  belongs_to :person
  has_many :kids

  accepts_nested_attributes_for :kids
end

class Goat::Kid < ActiveRecord::Base
  belongs_to :goat
end

With a simple restful controller:

ActionController::Routing::Routes.draw do |map|
  map.resources :farm
end

class FarmController < ApplicationController
  def new
  end

  def create
    person = Person.new params[:person]
    person.save
    render :text => person.inspect
  end
end

Then comes the semi-complex form:

Next, the form setup:

<% form_for :person, :url => farm_index_path do |p| %>
  <%= p.label :first_name %>: <%= p.text_field :first_name %><br />
  <%= p.label :last_name %>: <%= p.text_field :last_name %><br />
  <% p.fields_for :goat_attributes do |g| %>
    <%= g.label :name %>: <%= g.text_field :name %><br />
    <%= g.label :color %>: <%= g.text_field :color %><br />
    <% g.fields_for 'kids_attributes[]', Goat::Kid.new do |k| %>
      <%= k.label :nickname %>: <%= k.text_field :nickname %><br />
      <%= k.label :age %>: <%= k.text_field :age %><br />
    <% end %>
  <% end %>
  <%= p.submit %>
<% end %>

From looking at the source for accepts_nested_attributes_for, it looks like it will create a method for you called #{attr_name}_attributes=, so I needed to setup my fields_for to reflect that (Rails 2.3.3). Next, getting the has_many :kids working with accepts_nested_attributes_for. The kids_attributes= method was looking for an array of objects, so I needed to specify the array association in the form manually and tell fields_for what type of model to use.

Hope this helps.

Michael Sepcot
Heya, Michael, I appreciate your offering but the implementation threw me a NoMethodError that I don't fully understand--any ideas? I really appreciate it. (I left a comment below because I had trouble logging in to this account)
A: 

Hi, Michael,

I tried your attempt but it threw me this error:

NoMethodError in Farm#new

Showing app/views/farm/new.html.erb where line #1 raised:

undefined method `^' for "5":String
Extracted source (around line #1):

1: <% form_for :person, :url => farm_index_path do |p| %>
2:   <%= p.label :first_name %>: <%= p.text_field :first_name %><br />
3:   <%= p.label :last_name %>: <%= p.text_field :last_name %><br />
4:   <% p.fields_for :goat_attributes do |g| %>

Is this because the accepts_nested_attributes_for is calling a method on the wrong data type above?

Here's the full trace:

/usr/local/lib/ruby/gems/1.9.1/gems/activesupport-2.3.4/lib/active_support/message_verifier.rb:46:in `block in secure_compare'
/usr/local/lib/ruby/gems/1.9.1/gems/activesupport-2.3.4/lib/active_support/message_verifier.rb:45:in `each'
/usr/local/lib/ruby/gems/1.9.1/gems/activesupport-2.3.4/lib/active_support/message_verifier.rb:45:in `secure_compare'
/usr/local/lib/ruby/gems/1.9.1/gems/activesupport-2.3.4/lib/active_support/message_verifier.rb:28:in `verify'
/usr/local/lib/ruby/gems/1.9.1/gems/actionpack-2.3.4/lib/action_controller/session/cookie_store.rb:156:in `unmarshal'
/usr/local/lib/ruby/gems/1.9.1/gems/actionpack-2.3.4/lib/action_controller/session/cookie_store.rb:145:in `load_session'
/usr/local/lib/ruby/gems/1.9.1/gems/actionpack-2.3.4/lib/action_controller/session/abstract_store.rb:62:in `block in load!'
/usr/local/lib/ruby/gems/1.9.1/gems/actionpack-2.3.4/lib/action_controller/session/abstract_store.rb:70:in `stale_session_check!'
/usr/local/lib/ruby/gems/1.9.1/gems/actionpack-2.3.4/lib/action_controller/session/abstract_store.rb:61:in `load!'
/usr/local/lib/ruby/gems/1.9.1/gems/actionpack-2.3.4/lib/action_controller/session/abstract_store.rb:28:in `[]'
/usr/local/lib/ruby/gems/1.9.1/gems/actionpack-2.3.4/lib/action_controller/request_forgery_protection.rb:102:in `form_authenticity_token'
(eval):2:in `form_authenticity_token'

Can't seem to track this down in the source, any ideas?

Looks to be a problem with Rails 2.3.4 and Ruby 1.9.1, more details here: https://rails.lighthouseapp.com/projects/8994/tickets/3144
Michael Sepcot
Michael, you're awesome, thanks for the follow up!
A: 

I'm having the same problem as you, and my project is due tomorrow, so before I implement it in this way, does this work properly?

Cindy
Sorry, Cindy, just saw this now. I could _not_ get it to work. :/If you solve it, let me know!