Sunday, May 19, 2013

Rails Single Table Inheritance

Updated at Sep 21, 2014
Rails Version 4.1.1

Why single table inheritance?
In Rails,we usually have one database table for each of our model. That's very straightforward. But how do we deal with classes (or models) that inherit from other classes. Do we need to create database tables for each of them. The answer is it depends. If all the subclasses are very similar to each other or even identical (attribute wise), then creating individual tables for each of them might not be a good idea. Luckily, we have single table inheritance in Rails. Implied by its name, single table inheritance has only one table for the base class and all the subclasses. This table must meet the following requirement:

  • Contains the attributes of all the classes involved in single table inheritance
  • Contain a column called "type" to identify the class of each record in it
  • Allow null for each of the attribute that do not appear in all classes
What's the benefit of it?
The benefit of single table inheritance is very obvious: you got only one table. You give up data purity to gain more performance. Model wise, however, you still get the flexibility: each class can have their unique behaviors (methods).

In the following I'm going to show you a simple example of how to use single table inheritance. Let's say we have a base class Student, which is the super class of the class TA (teaching assistant) and class RA (research assistant). First of all, we need to find out all the attributes these three classes have. The base class Student has student_id, firstname, lastname, department, major. TA has an extra attribute called course_number, indicating the course TA assists in teaching. RA has an extra attribute called topic, indicating the topic s/he is researching on. As we've mentioned, all the six attributes have to appear in the table student, as well as the type attribute.

Create the migrate for Student model
class CreateStudent < ActiveRecord::Migration
  def change
    create_table :students do |t|
      t.integer :student_id, :null => false
      t.string :firstname, :null => false
      t.string :lastname, :null => false
      t.integer :department_id, :null => false
      t.string :major, :null => false
      t.string :type, :null => false
      t.integer :course_number
      t.string :topic
    end
  end
end

Now let's create the hierarchy of our single table inheritance

student.rb
class Student
end
ta.rb
class Ta < Student
  def grade 
    puts 'I can grade'
  end
end
ra.rb
class Ra < Student
  def research 
    puts 'I can do research'
  end
end

Now let's launch the console and do some experiments.
david = Ra.create(:student_id => 1, :firstname=>'david', :lastname=>'zheng', :major=>'cs', :department_id=>1, :topic=> 'Database')
mike = Ta.create(:student_id => '002', :firstname=>'mike', :lastname=>'lu', :major=>'cs', :department_id=>1, :course_number =>'CS511')
All the records we created using subclass RA and TA will go into the table students.
david.research
I can do research
mike.grade
I can grade
Objects of subclasses can only call their own methods.
dave = Student.find_by_first_name('david')
dave.class.name
=> "RA"
The object uses the "type" column to determine which class it belongs to.

6 comments: