I’m currently working on a Ruby on Rails project where both the User + Company models both have addresses. It seemed fairly straight forward then that to keep my database nice and DRY I should create an address table that holds a reference to both the user and company table.
My first impression was that they were going to be quite difficult to implement as there seems to be a lot of tutorials that show and explain how to set them up in the model but nothing about how to use them in the controllers and views. I did a good bit of googling and didn’t find a lot of help.
I was pleasantly surprised however at how simple Rails makes it to set-up. Firstly you just set-up your models.
class Company< ActiveRecord::Base has_one :address, :as =>; :addressable, :dependent => :destroy end class User < ActiveRecord::Base has_one :address, :as => :addressable, :dependent => :destroy end class Address < ActiveRecord::Base belongs_to :addressable, :polymorphic => true end
Next create the Addresses table to hold your addresses.
class CreateAddresses < ActiveRecord::Migration
def self.up
create_table :addresses do |t|
t.string :street_address1, :null => false
t.string :street_address2
t.string :city, :null => false
t.string :region, :null => false
t.string :postcode, :null => false, :limit => 55
t.integer :addressable_id, :null => false
t.string :addressable_type, :null => false
t.timestamps
end
end
def self.down
drop_table :addresses
end
end
Next the controller. You only need to amend the ‘new’, ‘create’, ‘edit’ and ‘update’ actions in your controller to see the address fields and update the Addresses table where necessary.
class CompaniesController < ApplicationController
def new
@company = Company.new
@company.address = Address.new
end
def edit
@company = Company.find(params[:id])
@company.address = Address.new unless @company.address != nil
end
def create
@company = Company.new(params[:company])
@company.address = Address.new(params[:address])
if @company.save
@company.address.save
flash[:notice] = 'Company was successfully created.'
redirect_to(@company)
else
render :action => 'new'
end
end
def update
@company = Company.find(params[:id])
if @company.update_attributes(params[:company])
@company.address.update_attributes(params[:address])
flash[:notice] = 'Company was successfully updated.'
redirect_to(@company)
else
render :action => 'edit'
end
end
end
Finally the last thing to get the address working is the form. For this I user the fields_for method. I’m using a custom form builder (Recipe 30 - Advance Rails Recipes) to build the labels and <li> tags into my form. On a side note I also only found out doing this that the form builder works nicely with the fields_for tag and not just the form_for.
<% form_for(@company, :builder => ErrorHandlingFormBuilder) do |f| %> <%= f.error_messages %> <dl> <%= f.text_field :name %> <%= f.text_field :telephone %> <%= f.text_field :fax %> <%= f.text_field :website_url %> </dl> <% fields_for(@company.address, :builder => ErrorHandlingFormBuilder) do |address_fields| %> <%= address_fields.hidden_field :addressable_id %> <%= address_fields.hidden_field :addressable_type %> <dl> <%= address_fields.text_field :street_address1 %> <%= address_fields.text_field :street_address2 %> <%= address_fields.text_field :city %> <%= address_fields.text_field :region %> <%= address_fields.text_field :postcode %> </dl> <% end %> <% end %>
Once thats done everything should work, it’s fairly straight forward to get working. Next thing to do might be to extend the functionality and allow companies to have multiple addresses, which would certainly complicate matters in the form, but I might leave that for a while.


