Rails Sweetness

Posted by Curtis Miller Curtis Miller

I was recently adding addresses to my site data. For example, a user may have a home address and/or work address, whereas a business may have a physical address and/or mailing address. I wanted to have a single database table that held the information about an address since that seems pretty standard. Not surprisingly, it was called addresses.

How then do you hook up your users and business with this common table? Enter Rails single-table inheritance implementation. Martin Fowler has a nice diagram explaining the idea behind single table inheritance. You basically take your modeled inheritance hierarchy and smash it into a single database table.

I added my address table, ensuring that it contained the magic field ‘type'. The type column in the address table will automatically insert the class associated with the object being saved to the database. This is key.

Next, I generated the Address model. Along with the Address model, I also created some inheritance models:

class Address < ActiveRecord::Base ; end

class BusinessAddress < Address ; end

class UserAddress < Address ; end

class UserHomeAddress < UserAddress ; end

class UserWorkAddress < UserAddress ; end

The inheritance hierarchy allows you to do things like finds within a particular branch of a hierarchy. For instance, if we were to do a

UserAddress.find(:all)

The results would be all UserAddress, UserHomeAddress, and UserWorkAddress entries in the database.

I then added a join table called users_addresses and connected a user to an address through a has_and_belongs_to_many relationship:

has_and_belongs_to_many :useraddresses,
                        :class_name => 'UserAddress',
                        :join_table => 'users_addresses',
                        :foreign_key => "user_id",
                        :association_foreign_key => "address_id"
                        

If you wanted easier access to the addresses of the user, then just add a simple method to the User model. For example, if you wanted the ability to get the home and work address like so

user = User.find(:first)
user.homeaddress
user.workaddress

This could be accomplished by a few simple methods in the User model class:

def homeaddress
  self.useraddresses.find(:first, :conditions => "type = 'UserHomeAddress'")
end

def workaddress
  self.useraddresses.find(:first, :conditions => "type = 'UserWorkAddress'")
end

The self.useraddresses portion gets all of the UserAddress objects associated with this User id. This means UserAddress, UserHomeAddress and UserWorkAddress objects.

There may be another way to do this, but this seems pretty freakin sweet to me. this is also the tip of the iceberg with inheritance and I realize my prototype does not use it nearly enough. Oh well, guess it can wait for refactoring. Here are a few other resources about STI:

Let me know what you think.



Velocity Labs

Need web application development, maintenance for your existing app, or a third party code review?

Velocity Labs can help.

Hire us!