Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use accepts_nested_attributes_for?

Editing my question for conciseness and to update what I've done:

How do I model having multiple Addresses for a Company and assign a single Address to a Contact, and be able to assign them when creating or editing a Contact?

I want to use nested attributes to be able to add an address at the time of creating a new contact. That address exists as its own model because I may want the option to drop-down from existing addresses rather than entering from scratch.

I can't seem to get it to work. I get a undefined method `build' for nil:NilClass error

Here is my model for Contacts:

class Contact < ActiveRecord::Base
  attr_accessible :first_name, :last_name, :title, :phone, :fax, :email, :company, 
                  :date_entered, :campaign_id, :company_name, :address_id, :address_attributes

  belongs_to :company
  belongs_to :address
  accepts_nested_attributes_for :address
end

Here is my model for Address:

class Address < ActiveRecord::Base
  attr_accessible :street1, :street2, :city, :state, :zip

  has_many :contacts
end

I would like, when creating an new contact, access all the Addresses that belong to the other Contacts that belong to the Company. So here is how I represent Company:

class Company < ActiveRecord::Base
  attr_accessible :name, :phone, :addresses

  has_many :contacts

  has_many :addresses, :through => :contacts

end

Here is how I am trying to create a field in the View for _form for Contact so that, when someone creates a new Contact, they pass the address to the Address model and associate that address to the Contact:

  <% f.fields_for :address, @contact.address do |builder| %>
  <p>
    <%= builder.label :street1, "Street 1" %> </br> 
    <%= builder.text_field :street1 %>
  <p>
  <% end %>

When I try to Edit, the field for Street 1 is blank. And I don't know how to display the value from show.html.erb.

At the bottom is my error console -- can't seem to create values in the address table:

My Contacts controller is as follows:

  def new
    @contact = Contact.new
    @contact.address.build # Iundefined method `build' for nil:NilClass

    @contact.date_entered = Date.today
    @campaigns = Campaign.find(:all, :order => "name")
    if params[:campaign_id].blank? 

    else
      @campaign = Campaign.find(params[:campaign_id])
      @contact.campaign_id = @campaign.id
    end

    if params[:company_id].blank?

    else
      @company = Company.find(params[:company_id])
      @contact.company_name = @company.name
    end

  end

  def create
    @contact = Contact.new(params[:contact])
    if @contact.save
      flash[:notice] = "Successfully created contact."
      redirect_to @contact
    else
      render :action => 'new'
    end
  end

  def edit
    @contact = Contact.find(params[:id])
    @campaigns = Campaign.find(:all, :order => "name")
  end

Here is a snippet of my error console: I am POSTING the attribute, but it is not CREATING in the Address table....

Processing ContactsController#create (for 127.0.0.1 at 2010-05-12 21:16:17)

[POST] Parameters: {"commit"=>"Submit", "authenticity_token"=>"d8/gx0zy0Vgg6ghfcbAYL0YtGjYIUC2b1aG+dDKjuSs=", "contact"=>{"company_name"=>"Allyforce", "title"=>"", "campaign_id"=>"2", "address_attributes"=>{"street1"=>"abc"}, "fax"=>"", "phone"=>"", "last_name"=>"", "date_entered"=>"2010-05-12", "email"=>"", "first_name"=>"abc"}}

Company Load (0.0ms)[0m [0mSELECT * FROM "companies" WHERE ("companies"."name" = 'Allyforce') LIMIT 1[0m

Address Create (16.0ms)[0m
[0;1mINSERT INTO "addresses" ("city", "zip", "created_at", "street1", "updated_at", "street2", "state") VALUES(NULL, NULL, '2010-05-13 04:16:18', NULL, '2010-05-13 04:16:18', NULL, NULL)[0m

Contact Create (0.0ms)[0m
[0mINSERT INTO "contacts" ("company", "created_at", "title", "updated_at", "campaign_id", "address_id", "last_name", "phone", "fax", "company_id", "date_entered", "first_name", "email") VALUES(NULL, '2010-05-13 04:16:18', '', '2010-05-13 04:16:18', 2, 2, '', '', '', 5, '2010-05-12', 'abc', '')[0m

like image 998
Satchel Avatar asked May 09 '10 03:05

Satchel


People also ask

What is Accepts_nested_attributes_for?

accepts_nested_attributes_for(*attr_names) public. Defines an attributes writer for the specified association(s). Supported options: :allow_destroy. If true, destroys any members from the attributes hash with a _destroy key and a value that evaluates to true (eg.

What are nested attributes?

Nested attributes are a way of applying sub-categories to your attributes. For instance, instead of having a single searchable attribute price , you may set up some sub-categories: price.net , price. gross , price. margin (note the use of 'dot notation' here to separate the parent attribute from its child).


2 Answers

If each Address belongs to only one Contact, (like a home address), you could do:

class Company < ActiveRecord::Base
  has_many :contacts
end

class Contact < ActiveRecord::Base
  belongs_to :company
  has_one :address
end

class Address < ActiveRecord::Base
  belongs_to :contact
end

Of course, you could just move the address columns into the Contact table and get rid of the Address model altogether.

If each Address has multiple contacts (ie, the addresses are company facilities where the contacts work):

class Company < ActiveRecord::Base
  has_many :contacts
end

class Contact < ActiveRecord::Base
  belongs_to :company
  belongs_to :address
end

class Address < ActiveRecord::Base
  has_many :contacts
end

You can add a has_many :through association to get company.addresses:

class Company < ActiveRecord::Base
  has_many :contacts
  has_many :addresses, :through => :contacts
end

In both situations the address components (street, city, state, etc) are columns in the addresses table.

The last part of your question really depends on the data distribution -- if you have only a few addresses, you could simply stick them in a select element with the main address as the default. With more addresses you might need an AJAXified form with autocomplete that search the addresses table.

To get the new contacts/addresses into your tables, you might want to look at nested forms. Railscast 196 is a good introduction.

EDIT -- more about nested attributes

accepts_nested_attributes_for should work with either a has_many or has_one assocation, and should work with multiple levels of nesting. It seems that you want to have a Contact belong to a Company, and an Address to belong to a Contact, in which case:

class Company < ActiveRecord::Base
  has_many :contacts
  accepts_nested_attributes_for :contact
end

class Contact < ActiveRecord::Base
  belongs_to :company
  has_one :address # or has_many, if you prefer
end

class Address < ActiveRecord::Base
  belongs_to :contact
end

Then in the view use form_for and fields_for as described in the Railscast to get the nested form elements. This can be a bit tricky to get right if your form is unusual. There are excellent examples from ryanb here that cover several typical situations.

like image 21
zetetic Avatar answered Oct 07 '22 23:10

zetetic


Just a banal question, if you using this in your contact form shouldn't be address in singular?

 <% f.fields_for :address, @contact.address do |builder| %>
   <p>
     <%= builder.label :street1, "Street 1" %> </br> 
     <%= builder.text_field :street1 %>
   <p>
 <% end %>

In your action you have also to do

@ycontact.build_address

If you look at the source code your input field should look like.

< input type="text" size="30" name="contact[address_attributes][street1]" id="contact_address_attributes_street1">

I think you also have problems with your associations, if you store address_id in contact then

class Contact < ActiveRecord::Base
  belongs_to :address
  accepts_nested_attributes_for :address
end

class Address < ActiveRecord::Base
  attr_accessible :street1
  has_many :contacts
end

And i think this is your case because you want to assign an address to multiple contacts.

You can also do in inverse, if you store your contact_id in your address model:

class Contact < ActiveRecord::Base
  has_one :address
  accepts_nested_attributes_for :address
end

class Address < ActiveRecord::Base
  attr_accessible :street1
  belongs_to :contact
end

In this case you cant have users with the same address.

Update

In your user show.html.eb you can use

<%= @contact.address.street1 %>

In your company's show.html.erb you can display the addresses like:

<% @company.addresses.each do |address| %>
  <%= address.street1 %>
  ...
<% end %>

On your user edit page the form fields should be the same as at new.html.erb just the form tag should look like:

<% form_for @contact do |form| %>
  <%=render :partial=>'fields' %>
<% end %>

The fields partial contains all your fieds togheter with the:

 <% f.fields_for :address, @contact.address do |builder| %>
   <p>
     <%= builder.label :street1, "Street 1" %> </br> 
     <%= builder.text_field :street1 %>
   <p>
 <% end %>

This should work.

Update 2

You can access all the addresses which belong to a company with:

@company.addresses

If you have

class Company<Activerecord::Base
    has_many :contacts
    has_many :addresses, :through=>:contacts
end

About the drafts, or default addresses. You can have different options depending on your needs. I you want the option that a contact could select one of the existing addresses from the database. You can do a select box in your contact form like:

 <%= form.select :address_id, options_from_collection_for_select(@company.addresses, 'id', 'street1')%>

Also you can use some fancy javascript to add or remove the address form, in that case if the user chooses one of the existing addresses. In this case you have to remove the address_attributes from the form. I suggest to create a link with javascript code that adds the address_attributes and removes (toggle style).

like image 58
dombesz Avatar answered Oct 08 '22 00:10

dombesz