Getting fields_for and accepts_nested_attributes_for to work with a belongs_to relationship

I cannot seem to get a nested form to generate in a rails view for a belongs_to relationship using the new accepts_nested_attributes_for facility of Rails 2.3. I did check out many of the resources available and it looks like my code should be working, but fields_for explodes on me, and I suspect that it has something to do with how I have the nested models configured.

The error I hit is a common one that can have many causes:

'@account[owner]' is not allowed as an instance variable name

Here are the two models involved:

class Account < ActiveRecord::Base
  # Relationships
  belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id'
  accepts_nested_attributes_for :owner
  has_many :users
end

class User < ActiveRecord::Base
  belongs_to :account
end

Perhaps this is where I am doing it 'rong', as an Account can have an 'owner', and may 'users', but a user only has one 'account', based on the user model account_id key.

This is the view code in new.html.haml that blows up on me:

- form_for :account, :url => account_path do |account|
  = account.text_field :name
  - account.fields_for :owner do |owner|
    = owner.text_field :name

And this is the controller code for the new action:

class AccountsController < ApplicationController
  # GET /account/new
  def new
    @account  = Account.new
  end
end

When I try to load /account/new I get the following exception:

NameError in Accounts#new
Showing app/views/accounts/new.html.haml where line #63 raised:
@account[owner] is not allowed as an instance variable name

If I try to use the mysterious 'build' method, it just bombs out in the controller, perhaps because build is just for multi-record relationships:

class AccountsController < ApplicationController
  # GET /account/new
  def new
    @account  = Account.new
    @account.owner.build
  end
end

You have a nil object when you didn't expect it!
The error occurred while evaluating nil.build

If I try to set this up using @account.owner_attributes = {} in the controller, or @account.owner = User.new, I'm back to the original error, "@account[owner] is not allowed as an instance variable name".

Does anybody else have the new accepts_nested_attributes_for method working with a belongs_to relationship? Is there something special or different you have to do? All the official examples and sample code (like the great stuff over at Ryans Scraps) is concerned with multi-record associations.


Solution 1:

I'm a few months too late, but I was looking to solve this error and my situation was that I could not change the relationship to 'face the other way'.

The answer really is quite simple, you have to do this in your new action:

@account.build_owner

The reason why the form did not display using fields_for was because it did not have a valid object. You had the right idea up there with:

@account.owner.build

However, this is not the way belongs_to work. This method is only generated with has_many and has_and_belongs_to_many.

Reference: http://guides.rubyonrails.org/association_basics.html#belongs-to-association-reference

Solution 2:

I think your accepts_nested_attributes is on the wrong side of the relationship. Maybe something like this would work?

class Account < ActiveRecord::Base
  belongs_to :owner, :class_name => 'User', :foreign_key => 'owner_id'
  has_many :users
end

class User < ActiveRecord::Base
  belongs_to :account
  has_one :account, :foreign_key => :owner_id
  accepts_nested_attributes_for :account
end

For building the account you want to use build_account.

You can see more examples in the docs.

Solution 3:

I'm using Rails 2.3.5 and I noticed the same thing. Checking out the source for active_record's nested_attributes.rb, it looks like belongs_to should work fine. So it appears it might be a "nested forms" bug.

I have a nested form exactly like yours, with User belongs_to :address, and Address is independent of the user.

Then in the form, I just do <% f.fields_for :address_attributes do |address_form| %> instead of <% f.fields_for :address do |address_form| %>. Temporary hack until there's a better way, but this works. The method accepts_nested_attributes_for is expecting the params to include something like:

{user=>{address_attributes=>{attr1=>'one',attr2=>'two'}, name=>'myname'}

...but fields_for is producing:

{user=>{address=>{attr1=>'one',attr2=>'two'}, name=>'myname'}

This way you don't have to add that has_one :account to your code, which doesn't work in my case.

Update: Found a better answer:

Here is the gist of the code I'm using to make this work right:

Rails Nested Forms with belongs_to Gist

Hope that helps.