Feed

Blog :: Polymorphic forms

A brief tutorial on setting up polymorphic associations in Rails.

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.


Published: Tags: Ruby on Rails, tutorial

Add a comment

Twitter feed

Twitter_bird

RT @ruby_news: Fashion Quest: a Ruby-based, cross-platform interactive fiction framework http://bit.ly/asadE5
Tweeted: 1 day ago

Mobile-phone pocket watch - http://www.celsius-x-vi-ii.com/ Amazing design and technology.
Tweeted: 7 days ago

RT @dhh: Rails 3.0: It's ready http://bit.ly/bqyTHh - after two years of work & thousands of commits. What an amazing community achievement!
Tweeted: 9 days ago