Implement STI (single-table inheritance) in your Rails Models

Rohit Sharma 2010-11-29
Implement STI (single-table inheritance) in your Rails Models

According to Wikipedia STI is

Single table inheritance is a way to emulate object-oriented inheritance in a relational database. When mapping from a database table to an object in an object-oriented language, a field in the database identifies what class in the hierarchy the object belongs to. In Ruby on Rails the field in the table called ‘type’ identifies the name of the class.

I hope that explains you what STI is, so lets see how can we implement this in our Rails Application. Let’s consider a scenario where we have a user, the user can be multiple types depending on the access rights, like “Owner”, “Admin”, “Guest”.

First Step : Create a class with the name of a specific type(For instance : Owner)

Create a new Ruby class file in your rails app/models directory and give it the name of one of the type/s e.g. Owner.rb

And similarly create classes for Admin and Guest

Second Step : Let these classes extend the User class

class Owner < User

Third Step : Add a type field to the User Model

This type field will store the type of user. ie. Owner, Admin or Guest. You can add the field as follows:

class CreateUsers < ActiveRecord::Migration
  def self.up
    create_table :users do |t|
      t.string :name
      t.string :email
      t.string :password
      t.string :type

    def self.down
      drop_table :user

Fourth Step : Add the following code in the User Model

class << self
  def new_with_cast(*a,&b)
    if (h = a.first).is_a? Hash and (type = h[:type] || h['type']) and (klass = type.constantize) != self
      raise 'Error!!' unless klass < self # klass should be a descendant of User
      return, &b)
    new_without_cast(*a, &b)
  alias_method_chain :new, :cast

What we do here basically is, we are checking

  1. Are we getting some value in the type field from the user submitted values
  2. If we are getting some value we constantize the string and then we check whether the recieved constant is one of the descendant of User class. If not then we raise an Exception.
  3. If the constant is one of the descendant of User class [‘Owner’,’Admin’,’Guest’], then the User becomes of that type.

Fifth Step : Changes in new.html.erb and edit.html.erb
In new and edit page of User add a select field which will determine the type of User

  <%= f.label :type %><br />
  <%= :type, ['Owner', 'Admin', 'Guest'] %>

Last Step : We have done it. Time to test.
That is it. We have successfully implemented the functionality of Single Table Inheritance. Lets take a look how things go.

User.create(:name => 'Rohit', :email => '', :password => 'test123456' ,:type => 'Owner')
#This line will create a record in User model with type Owner.
@user = User.find_by_name('Rohit')
#This will give an object of type Owner, because we have set the type as Owner.

Everything seems to be working fine.

Thanks for reading the post. any suggestions are most welcome.