The blog of Rahoul Baruah from 3hv Ltd

What's going on?

My name is Rahoul Baruah (aka Baz) and I'm a software developer in Leeds (England).

This is a log of things I've discovered while writing software in Ruby on Rails. In other words, geek stuff.

However, I've decided to put this blog on ice - I would ask you to check out my business blog here (or subscribe here).

02 October, 2007

Single Table Inheritance in Ruby on Rails

I remember years ago, when Object-Orientated Programming became fashionable, every single text on it (at least those that I could be bothered to read) repeated the mantra "OO is about inheritance". Of course, that's rubbish, but when you've been dealing with structs in C or Cobol it's probably an easy way of thinking of things - objects are these data structures with these extra bits.

Nowadays, I rarely use much inheritance, beyond extending what my framework gives me. And I find myself using it much more in statically-typed (compile-time type checked) languages. I think splitting my functionality into a web of tiny objects gives me much more flexibility (as opposed to ending up with a couple of huge objects with layers of functionality added through generations of inheritance). Think of a chain of people - you ask a question of A, who in turn asks her friend B, who in turn asks his boss C, who in turn asks his wife D. In a different situation you may ask the question of B who gets the answer off A. Each of A, B, C and D is small and simple with a tiny public interface. Compare that to asking every question of an ABCD amalgamation - large and unwieldy with a large public interface.

Of course, Ruby does allow inheritance. It's not always needed for reuse as you have mixins, but there are times when it is useful. And Rails lets you use inheritance in your models - it's not always needed as, for joining disparate classes together you can use polymorphic associations, but there are times when it is useful.

So now I've tried to warn you off, how do you do it?

Imagine an address book application. You have basic AddressBookEntries which fall into two categories - People and Companies. This is an excellent candidate for inheritance as there is a fair amount of stuff shared between People and Companies (name, address, telephone number) but also some different stuff (People belong to Companies, Companies have a list of People and a VAT (tax) number).

To start with you create a model - AddressBookEntry.

Your migration may look like:

create_table :address_book_entries do | t |
# system fields
t.column :created_on, :datetime
t.column :updated_on, :datetime
t.column :lock_version, :integer, :default => 0
t.column :type, :string
# common fields
t.column :name, :string
t.column :address, :text
t.column :telephone, :string
# person specific fields
t.column :company_id, :integer
# company specific fields
t.column :vat_number, :string

Note that we have split the fields into different sections - system fields, common fields, person fields and company fields. Not strictly necessary but a nice to have.

Our model looks like this:

class AddressBookEntry < ActiveRecord::Base


But where are our people and companies?

Two new model files - person.rb and company.rb are needed.

# person.rb
class Person < AddressBookEntry
belongs_to :company
# company.rb
class Company < AddressBookEntry
has_many :people

There you go. All done (apart from the unit tests).

So how do you use this?

Simple - create a company and a person.

tiny_co = Company.create :name => 'Tiny Co', :address => 'Tiny Towers', :telephone => '4321', :vat_number => '987654321'

dave = tiny_co.people.create :name => 'Dave', :address => '22 Acacia Avenue', :telephone => '1234'

If we now look at our table we will see the following (some fields omitted because I'm lazy):

id type name company_id
1 Company Tiny Co null
2 Person Dave 1

The key is the "type" field we added. Rails treats this as one of its special "magic" fields and when you create an instance of Person or Company, Rails fills it out for you. Likewise, if you call AddressBookEntry.find(1) it will return you an instance of Company (not AddressBookEntry) - it uses the type to govern what it instantiates.

This means that Rails hides most of the mechanics of inheritance from you - you just write your models and they automagically do the right thing. There are some issues - your fixtures have to contain all descendants in a single file (in this case address_book_entries.yml) and it is perfectly legal to write dave.vat_number = '54321' (as the vat_number field, which technically belongs to a Company, not a Person is still accessible to all AddressBookEntries).

But Rails has made inheritance in a database about as easy as I can imagine. There is a little bit more to inheritance - with ActiveRecord abstract classes, but that's a story for another day.

Photo by spektator

No comments:

eXTReMe Tracker