7

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

Satchel
  • 16,414
  • 23
  • 106
  • 192
  • Hmm i think creates insers value in the address table, here is where 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 – dombesz May 13 '10 at 07:02
  • Updated my post, included the code in the views. – dombesz May 13 '10 at 07:30
  • You can see that under Address Create the values for street1 are blank...not "abc".... – Satchel May 14 '10 at 00:13
  • Updated my post with the responses to your question. – dombesz May 14 '10 at 07:34

4 Answers4

3

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).

dombesz
  • 7,890
  • 5
  • 38
  • 47
  • Well, I want to have users that have the same address. But there are multiple addresses possible...imagine a large, distributed company with different employees. So would this work....this is using :through => :contacts, right? – Satchel May 13 '10 at 01:08
  • also...does accepts_nested_attributes_for :address work when Contact belongs_to :address? Appears I get an error....I think it only works for has_many or has_one....I updated my question to simplify the question – Satchel May 13 '10 at 04:13
  • The accepts_nested_attributes_for works also with belongs_to, i'm using it right now. The has_many :through doesn't matter until you want to create the addresses through the company model. Also doesnt really has sense. – dombesz May 13 '10 at 07:03
  • ah, okay, so how do I access all the addresses that the contacts that belong to a company have? would I need to loop through all the contacts in a company and grab the contact.address (?) – Satchel May 13 '10 at 16:36
  • hmm, I'll need to go back through it again, but I did try @contact.address.street1 and it didn't recognize street1 as an attribute....let me go through it again and remove the :through => contacts as well. – Satchel May 13 '10 at 16:37
  • hmm...why was I having mass assignment errors...do you get those? – Satchel May 14 '10 at 00:12
  • I seem to have gotten it to work mostly based on what you had...not sure what flipped it over....hmmm, so what happens if I want a *default* address, does that need to be assigned to the Company (oh drats, that means I do need polymorphic) – Satchel May 14 '10 at 01:42
  • Yes, I've been thinking about the best approach. Because an address has multiple fields (street1, city, state, zip), I'm not sure how well a "Select" drop down will work. .... thanks... – Satchel May 14 '10 at 23:11
1

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.

zetetic
  • 47,184
  • 10
  • 111
  • 119
  • Hi there! How would this be different from using polymorphic, as described in Rails Recipes? Each address does in fact have multiple contacts potentially; and so each company has multiple addresses....thanks. – Satchel May 11 '10 at 02:59
  • Hi, it doesn't seem I can use an accepts+_nested_attribute_for: in the Contacts model as accepts_nested_attributes_for : addresses because it is a belongs_to association. I looked at the API's and am trying has_one ... but does that sound right? – Satchel May 11 '10 at 03:30
  • Polymorphic associations are useful when you want to share a model (like Address) with other models (like Company and Person, for example). A company can have an address, and a person can have an address, so it makes sense to reuse the model if the address format is the same. Without polymorphism you'd need separate CompanyAddress and PersonAddress models. I'll edit the answer to respond to your other comment. – zetetic May 11 '10 at 07:03
  • Hi, I *seeM8 to have parts of it working but the builder is getting an error -- thanks so much for your help, it is definitely getting me int he right direction. Let me update what I have done in the question so you can see how close we are...thanks zetitic! – Satchel May 12 '10 at 04:04
  • So I've been thinking that the way a Company has an address (in real life and in the model) is because a Contact, which belongs_to a Company, has_one address, so effectively addresses for a Company are :through => :contacts does your answer help me to model that? – Satchel May 13 '10 at 01:10
  • Yes, the models in the edited answer would apply in that situation. – zetetic May 13 '10 at 02:43
  • when Address belongs_to :contact, doesn't that mean that the address contactins the contact_id foreign key? Actually, it's the other way around, because an address can have multiple addresses...so I'm not sure...I looked at the Railscast, I don't think my form is unusual, can you see what I did to see if it's close? – Satchel May 13 '10 at 04:00
  • I think the way you set up the models in the section *before* the edit is right...but then I cannot nest addresses under contacts because it only works with :has_many or :has_one....help!?: – Satchel May 13 '10 at 04:12
  • Yes, the `belongs_to` goes in the model with the foreign key. To get multiple Addresses for each Contact, change the `has_one :address` to `has_many :addresses` in the Contact model. You can still use `has_many :addresses, :through => :contacts` in the Company model, and you can still use `accepts_nested_attributes_for :contacts` -- your form can update Company, Contact and Address all at once. When I'm unclear about how to make associations, I find the best approach is to design the views first -- that generally makes it clear which association types are needed. – zetetic May 13 '10 at 05:30
1

Since you want an address to belong both to a company and to a contact, you have two choices. If you aren't going to ever need multiple addresses (which I might recommend against assuming), then you can put a "belongs_to :address" in both Company and Contact. Address would then have both a "has_many :companies" and "has_many :contacts".

The better option that makes more sense is to use polymorphic associations, where the address table would have :addressable_type and :addressable_id columns to link back to either model.

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

  has_many :contacts  
  accepts_nested_attributes_for :contacts

  has_many :addresses, :as => :addressable
  accepts_nested_attributes_for :addresses

end

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

  belongs_to :company
  has_many :addresses, :as => :addressable
  accepts_nested_attributes_for :addresses
end

class Address < ActiveRecord::Base
  attr_accessible :street1

  belongs_to :addressable, :polymorphic => true
end

Now your trouble comes with wanting to have a single association "company.addresses" to get both it's address and those of its contacts. I might recommend staying away from this and using a different approach.

You're going to want to map those addresses in the view to the correct record, so it might be best to separate them:

<% form_for @company do |company_form| %>
  <% company_form.fields_for :addresses do |address_form| %>
    <%= address_form.text_field :street1 %>
  <% end %>
  <% company_form.fields_for :contacts do |contact_form| %>
    <% contact_form.fields_for :addresses do |contact_address_form| %>
      <%= contact_address_form.text_field :street1 %>
    <% end %>
  <% end %>
<% end %>

Your address table has the polymorphic columns like this:

class CreateAddresses < ActiveRecord::Migration
  def self.up
    create_table :addresses do |t|
      t.string :street1
      t.string :addressable_type
      t.integer :addressable_id
      t.timestamps
    end
  end

  def self.down
    drop_table :addresses
  end
end

I'm not sure how you could keep all the linking straight if you don't nest the records individually like this.

If you do need to get all the addresses for a company for display purposes, you could use the standard ruby enumerable manipulations like collect to gather them together.

Good luck!

aceofspades
  • 7,568
  • 1
  • 35
  • 48
  • Hi, I'm still trying to decide whether to use polymorphic ... conceptually, the company has addresses :through contacts...in other words, the only reason I would have an address associated with a company is because a contact belongs_to it. What do you think of that? – Satchel May 13 '10 at 01:07
  • yeah, I do need to see all the company.addresses ... it will allow the User to select a potential address when adding a New Contact .... so would that mean to not use polymorphism? – Satchel May 13 '10 at 01:11
  • Polymorphism makes sense since if as your app grows you might want to associate addresses with other objects. If you don't foresee this, it's not necessary. If you don't use polymorphism, you can use the has_many :through, but only for display purposes. You'll still need to use the nested forms to access a specific contact to add/modify addresses for a company's contacts. P.S. Was this helpful enough for a vote up? – aceofspades May 13 '10 at 18:40
  • In any event, you can always loop over a company's contacts to get addresses, then link to that address id for a new contact. If you want to link that same address to multiple records (i.e. another contact), you would need a join table rather than using belongs_to. Hope this makes sense. – aceofspades May 13 '10 at 18:41
  • yes voted up, but still trying out implementation....at this point, I don't *think* I will associate addresses with other objects...but maybe it will be easier regardless...however, the real challenge is, when I create a new Contact, to create the address and assign contact the foreign key to that address.... – Satchel May 14 '10 at 00:52
  • Hmm...okay, seeing that I want an address assigned to the Company as a default...and the reste goes to the Contacts. Does that mean I need polymoprhism? – Satchel May 14 '10 at 01:42
  • hmmm...shoot, if that's the case, I think your answer might be what I need to do.... – Satchel May 14 '10 at 01:43
  • As long as you use the belongs_to on Address, you can upgrade to polymorphism easily later. When you create a new contact, use the build method on the association to create a provisional address for it: Contact.new(:name => 'Name').address.build. Then render the form with the nested address, and take a look at how rails names the form fields. It's pretty slick. – aceofspades May 14 '10 at 17:00
0

I'd recommend to let the Contact have many addresses. It happens anyways in the wild. If decided for only one, that would be has_one :address

Oh and you must downcase them: has_many :Contacts must be has_many :contacts

In the Address model you reference with (database needs a addressable_id and addressable_type field)

belongs_to :addressable, :polymorphic => true
Thomas R. Koll
  • 3,131
  • 1
  • 19
  • 26
  • How would the Address added to a new Contact then be available to new future contacts part of that Company? – Satchel May 09 '10 at 17:44