Single Table Inheritance with Rails

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

class FullTimeEmployee < Employee
end

temp_employee.rb

class TempEmployee < Employee
end

student_employee.rb

class StudentEmployee < Employee
end

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.