Fork me on GitHub

Back Home

Single Table Inheritance with Rails

by Phil McClure

What is it?

With single table inheritance you have a base model which inherits from ActiveRecord::Base, then one or more sub-classes, which inherit from the base model.

Single table inheritance is a software pattern described by Martin Fowler. Since (most) databases don't support inheritance, there is an issue when trying to map objects to database tables. This is known as the Object-relational impedance mismatch.

Rails uses the Single Table Inheritance pattern to solve this problem. The basic idea is that another field in the base database table is used to store the type of the object.

Why use it?

If you want to acurately model a domain then inheritance is going to be necessary at some point or another. So single table inheritance gives you this flexibility.

Be Careful!

Just be careful when using single table inheritance. Since all the data from all the sub-types is include in one table, you can end up with a lot of "null"s scattered throughout the table. These ultimately increase the size of the table and you could end up with a scaling problem on your hands.

For instance, say you have an abstract object called "Employee" and several sub-classes called "FullTimeEmployee", "TempEmployee" and "StudentEmployee". This is shown in the class diagram below:

Employee Hierarchy

As you can see, the super-class (Employee) has two instance variables "Name" and "Salary". Further to this, each of the sub-types have an instance variable relating, specifically to them. These instance variables in the sub-classes are what cause the problems. If you have a look at the table below, all will become clear:

Type Name Salary Hours Duration University FullTimeEmployee Jim 10,000 37 null null TempEmployee John 15,000 null 5 null StudentEmployee Joe 20,000 null null Queens University, Belfast

As you can see, each type has nulls in the fields which don't apply to it. This problem is only compounded when extra objects are added. So, as I already mentioned, the scaling problems can be serious, so keep this in mind when modeling your application.

Coding it

Using the "Employee" model above we first need to generate the Employee model:

ruby script/generate model employee name:string salary:string 
hours:string duration:string university:string type:string

Notice we have included a "type" field. This is used to store the type of object that the record applies to. Now migrate this into the database:

rake db:migrate

Now, in the app/models folder create a model file for each sub-class:

full_time_employee.rb

We can now test this out using the console:

>> FullTimeEmployee.create!(:name => "Jim", :salary => "10,000", :hours => "37")
>> TempEmployee.create!(:name => "John", :salary => "15,000", :duration => "5")
>> StudentEmployee.create!(:name => "Joe", :salary => "20,000", 
   :university => "Queens University, Belfast")

Now, print the records, we just created, to screen:

>> y Employee.all

!ruby/object:FullTimeEmployee attributes:

name: Jim
updated_at: 2009-06-12 19:16:42
university:
salary: "10,000"
type: FullTimeEmployee
id: "1"
hours: "37"
duration:
created_at: 2009-06-12 19:16:42

attributes_cache: {}

!ruby/object:TempEmployee attributes:

name: John
updated_at: 2009-06-12 19:19:50
university:
salary: "15,000"
type: TempEmployee
id: "2"
hours:
duration: "5"
created_at: 2009-06-12 19:19:50

attributes_cache: {}

!ruby/object:StudentEmployee attributes:

name: Joe
updated_at: 2009-06-12 19:19:59
university: Queens University, Belfast
salary: "20,000"
type: StudentEmployee
id: "3"
hours:
duration:
created_at: 2009-06-12 19:19:59

attributes_cache: {}

See how the type field has been updated for you?

Now, when we retrieve the employees, we want them to be of the appropriate type. Let's see if this works by getting all employees and calling the "class" method for each.

>> employees = Employee.all

>> employees.each do |employee|
?>    puts employee.class
>> end

Outputs:

FullTimeEmployee
TempEmployee
StudentEmployee

So it works! Yah!

Summary

Single table inheritance is extremely useful for modeling more complex domains. However, it should be used with caution as it can lead to scaling problems.


blog comments powered by Disqus
© 2009 Phil McClure This site was developed using Compass, Blueprint and Ruby on Rails - Much thanks to these guys!