KatPadi's Point

STI

As I dive into more Rails-y stuff, I was told about the concept of Single Table Inheritance. (s/o to MJ for introducing me to this!) This Rails design pattern is characterized by using a single table that stores data from multiple models that inherit from a base model (the parent class of STI) which inherits from ActiveRecord::Base.

(Whew!) OK, let me say that again…

STI is when you have a parent model class that has subclasses and they store data into a single table.

Anyway…

In the Rails console/database perspective, implementing STI is easier done than said [sic]. To generate models, you just have to do the normal “rails g” thingy.

[pastacode lang=”bash” message=”” highlight=”” provider=”manual”]

rails g model category name description type

[/pastacode]

After doing so, you can generate your subclass models by doing the following:

[pastacode lang=”bash” message=”” highlight=”” provider=”manual”]

rails g model sports_category --parent category

rails g model food_category --parent category

rails g model movie_category --parent category

[/pastacode]

I learned that putting a “type” column in your parent model’s table is VERY IMPORTANT. The “type” column is like real-life magic because by default, ActiveRecord will search for that column for the STI to be in action. If you don’t want to use type, you can choose not to by defining something like below in your model. All of the tutorials I’ve read say that it’s better to use the default though.

[pastacode lang=”bash” message=”” highlight=”” provider=”manual”]

class MyModel < ActiveRecord::Base
    self.inheritance_column = 'you_dont_like_type'
end

[/pastacode]

Now, after you do a rake db:migrate, you’ll notice these models in your app.

[pastacode lang=”bash” message=”” highlight=”” provider=”manual”]

# We did not generate this Post model. It's just here for context
class Post < ActiveRecord::Base
  has_many :categories
end

class Category < ActiveRecord::Base
  belongs_to :post
end

class SportsCategory < Category
end

class MovieCategory < Category
end

class FoodCategory < Category
end

[/pastacode]

In essence, that’s how you do implement STI in Rails. We can have a lot of submodels with only one table:

[pastacode lang=”bash” message=”” highlight=”” provider=”manual”]

mysql> desc categories;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | int(11)      | NO   | PRI | NULL    | auto_increment |
| name        | varchar(255) | YES  |     | NULL    |                |
| description | varchar(255) | YES  |     | NULL    |                |
| created_at  | datetime     | YES  |     | NULL    |                |
| updated_at  | datetime     | YES  |     | NULL    |                |
| type        | varchar(255) | YES  |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

[/pastacode]

So now.. the interesting part!

How do we create new records? Is there something different? Well, the only different thing is that you always have to create a record with “type”:

[pastacode lang=”ruby” message=”” highlight=”” provider=”manual”]

Category.create(type: 'SportsCategory', name: 'NBA', description: 'where Kobe is king')
Category.create(type: 'SportsCategory', name: 'Football', description: 'kicking the ball is fun')
Category.create(type: 'MovieCategory', name: 'Romantic Comedy', description: 'Boring...')
Category.create(type: 'MovieCategory', name: 'Action', description: 'Awesome!!!...')

[/pastacode]

It’s important that you know the type and the subclass exists because you’ll get an error if you input an incorrect type.

[pastacode lang=”bash” message=”” highlight=”” provider=”manual”]

Category.create(type: 'BloodTypeB', name: 'IDK', description: 'This wont work!')
ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: BloodTypeB is not a subclass of Category

[/pastacode]

You can also create records from the submodel and it will be inserted into the database with the correct type automatically.

[pastacode lang=”bash” message=”” highlight=”” provider=”manual”]

FoodCategory.create(name: 'Pinoy Dishes', description: 'very oily stuff')

INSERT INTO `categories` (`created_at`, `description`, `name`, `type`, `updated_at`) VALUES ('2015-02-12 12:25:44', 'very oily stuff', 'Pinoy Dishes', 'FoodCategory', '2015-02-12 12:25:44')

[/pastacode]

So there, it’s fun playing around with STI models. As you can see, it can be really useful in DRY-ing up your codes. However, there are some noticeable disadvantages as well.

Drawbacks (as I noticed)

  • You can’t have the same name with different attribute data types– say the model has a column name and the other needs to be integer… you can’t STI no more!
  • nil values when not used by subclass– you’ll have nil values all over your database if a lot of fields are not used by every subclass

The second thing that I was told about is combining STI with polymorphism. But that’s another story. I should blog about that next time!

1 comment for “STI

Leave a Reply

Your email address will not be published. Required fields are marked *