Single Table Inheritance (STI)
An Active Record Association that associates instances without a foreign key.
Rails Association — Part 1
What, Why, How & 3 Types of Associations — belongs to, has one & has one — through discussed.
Rails Association — Part 2
has_many, has_many-:through & HABTM associations discussed.
All the 6 basic associations that I covered in Part 1, Part 2, Polymorphic & Self-Join had one thing in common, all of them had at least 1 foreign key in any one table. However, foreign-key isn’t needed to implement STI because as the name suggests, Single Table Inheritance, requires Single Table.
A single table without a foreign key? How would we associate with anything without a foreign key? A better question, what is the purpose of an STI in the first place?
To answer the question let's discuss an example. Let's say we have a table
A that has x number of attributes in it. And we wanted to create another table
B with the same-to-same attributes as the table
A. And then we wanted to create another table with the same attributes and so on… You see where this is going. The only difference between these tables is its name, the rest of it such as its attributes’ name and its type are the same for all tables. The concept of repeating ourselves with the same code is not what Ruby is about. STI helps us to avoid the creation of multiple tables with similar attributes with a single table. By using STI, we follow Do-not Repeat Yourself (DRY) concept.
STI is a Rails technique to categorize multiple types in a single table by having multiple types model classes that inherit from single tables’ model class.
Let's understand the implementation of STI with an example.
“A list of entertainment shows, such as a movie, TV-series or a documentary.”
Movies, TV series, and documentaries have a lot of common features such as
language etc. So instead of creating 3 different tables of these, we create a single table that holds records of all.
But how would we know whether a particular record in a table is of a movie, TV series, or documentary? We explicitly create an attribute in the table named ‘
type’ where each record can either have a value of as a movie, TV series, or documentary.
It is this STI attribute,
type, that lets rails know that we are using STI association. It's a convention provided by Rails that we follow in order to enable a certain feature.
Note, a foreign-key name followed by
_typeis a polymorphic attribute used for polymorphic association.
Now, let's create a table with the example discussed above. Since the table will hold the values of different types, we would appropriately name the table that categorizes our types.
rails g model Show name:string streaming_at:string released_on:date type:string
As discussed above, to implement STI we create
Show Model class file:
class Show < ApplicationRecord
shows table is a representation of all 3 types in our example, hence, we create only their model files without a table for each.
rails g model Movie --parent=Showrails g model TvSeries --parent=Showrails g model Documentary --parent=Show
— parent option let rails know that we want to create model files without a table & a migration file and inherit model class directly from the
Show class instead of the
ApplicationRecord class. This means that all behavior added to
Show class is available for
Documentary model class too, such as associations, public methods, validations, etc.
A peek into the model files generated :
class TvSeries < Show
class Movie < Show
class Documentary < Show
Now to save records in the
Show table we would save it the same way as if we had 3 different tables for each type as:
# movie = Movie.create(name: "Moonlight", streaming_at: "Amazon Prime", released_on: "18/11/2016")# tv = TvSeries.create(name: "Tiny World", streaming_at: "Apple TV+", released_on: "02/10/2020")# doc = Documentary.create(name: "Life in Colour", streaming_at: "Netflix", released_on: "22/04/2021")
Rails automatically set the value of the
type for each record by the name of the model class we initiated while creating a record.
=> #<ActiveRecord::Relation [#<Movie id: 1, name: "Moonlight", streaming_at: "Amazon Prime", released_on: "2016-11-18", type: "Movie", created_at: "##", updated_at: "##">]$ Documentary.all
=> #<ActiveRecord::Relation [#<Documentary id: 3, name: "Life in Colour", streaming_at: "Netflix", released_on: nil, type: "Documentary", created_at: "##", updated_at: "##">]$ Show.all
=> #<ActiveRecord::Relation [#<Movie id: 1, name: "Moonlight", streaming_at: "Amazon Prime", released_on: "2016-11-18", type: "Movie", created_at: ##, updated_at: ##>, #<TvSeries id: 2, name: "Tiny World", streaming_at: "Apple TV+", released_on: "2020-10-02", type: "TvSeries", created_at: ##, updated_at: ##>, #<Documentary id: 3, name: "Life in Colour", streaming_at: "Netflix", released_on: "2021-04-22", type: "Documentary", created_at: ##, updated_at: ##>]
I hope I have achieved to make you understand the topic thoroughly. If you have any questions or other comments, feel free to leave them below. Also, if you found this article useful, you can share it so others can find it as well.