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
end

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
    end

    def self.down
      drop_table :user
    end
end

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 klass.new(h, &b)
    end
    new_without_cast(*a, &b)
  end
  alias_method_chain :new, :cast
end

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

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

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 => "rohit0981989@gmail.com", :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.

About these ads

3 comments

  1. Rohit

    You appear to be quite experienced in this field. I’m having a problem whereby I can successfullly create the sub classes in new form. However whenever I try to edit. the edit code runs withour error, but the SQL UPDATE call is never made when i check in the logs. have you come across this before ?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s