4. Introduction
In the beginning everything always works. Things get complicated with
time when you start adding new features.
Some weeks ago I had to refactor some parts of a project and also add
some new features to it.
After some hours of reviewing the actual code I run into some problems:
1. Same code in multiple places
2. Same data stored in 2 or more tables
create_table "users", :force => true do |t|
...
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
...
4 / 22
6. What is STI?
STI is basically the idea of using a single table to reflect multiple models
that inherit from a base model, which itself inherits from
ActiveRecord::Base.
In the database schema, sub-models are indicated by a single "type"
column.
In other words we have:
a base class
descendant classes
6 / 22
7. When should we use STI?
Should be considered when dealing with model classes that share much of
the same functionality and data fields (granular control over extending or
adding to each class individually)
When you have duplicate code over and over for multiple tables (and not
being DRY)
STI permits you to use keep your data in a single table while writing
specialized functionality.
7 / 22
8. When should we use STI?
Suppose you have three classes in your application which model similar
things. To make this easier to think about I’ll be referring to some hypothetical
classes by name: Business and Employee. Let’s consider three choices for
modeling this situation:
1. Polymorphic Associations (separate classes, multiple tables)
2. Single Table Inheritance (separate classes, one table)
3. Single Class with conditionals (one class, one table)
8 / 22
9. When should we use STI?
Polymorphic Associations (separate classes, multiple tables)
With Polymorphic Associations we use modules to share code among
classes.
We have the following active record models for a “Business” and an
“Employee”.
class Business < ActiveRecord::Base
# Methods, variables and constants
end
class Employee < ActiveRecord::Base
# Methods, variables and constants
end
9 / 22
10. When should we use STI?
Polymorphic Associations (separate classes, multiple tables)
For the contact information, we have the following active record models:
class PhoneNumber < ActiveRecord::Base
# Methods, variables and constants
end
class Website < ActiveRecord::Base
# Methods, variables and constants
end
10 / 22
11. When should we use STI?
Polymorphic Associations (separate classes, multiple tables)
Now, for the associations, Business and Employee models can have many
phone numbers and websites. And conversely, Phone Number/Website can
belong to either a Business model or an Employee model.
class Business < ActiveRecord::Base
has_many :phone_numbers, :as => phonable
has_many :websites, :as => webable
end
class Employee < ActiveRecord::Base
has_many :phone_numbers, :as => phonable
has_many :websites, :as => webable
end
11 / 22
12. When should we use STI?
Polymorphic Associations (separate classes, multiple tables)
class PhoneNumber < ActiveRecord::Base
belongs_to :phonable, :polymorphic => true
end
class Website < ActiveRecord::Base
belongs_to :webable, :polymorphic => true
end
Having the above setup, you can get a collection of phone numbers for a
business instance via the call “@business.phone_numbers.
Similarly, you can get a collection of phone numbers for an employee instance
via “@employee.phone_numbers”.
12 / 22
13. When should we use STI?
Single Class with conditionals (one class, one table)
A Single Class is not exactly a design pattern, or anything particularly
interesting.
I’m thinking of a model with a type-like attribute (maybe called kind) and
some if statements in methods where you need different behavior for
different kinds of objects.
13 / 22
14. When should we use STI?
Questions to ask yourself
When deciding how to design your data models, here are some questions to
ask yourself:
1. Are the objects, conceptually, children of a single parent?
2. Do you need to do database queries on all objects together?
3. Do the objects have similar data but different behavior?
14 / 22
15. How do we implement STI?
Create the base class
Model: Employee
Attributes:
Name - String
Age - Integer
Department - String
$ rails generate model Employee name:string age:integer department:string
# /app/models/employee.rb
class Employee < ActiveRecord::Base
# Methods, variables and constants
end
15 / 22
16. How do we implement STI?
Add a :type attribute as a string to the base class
$ rails generate migration add_type_to_employee type:string
16 / 22
17. How do we implement STI?
Create any necessary descendant classes
# /app/models/developer.rb
class Developer < Employee
# Methods, variables and constants
end
# /app/models/tester.rb
class Tester < Employee
# Methods, variables and constants
end
17 / 22
18. Reasons why you shouldn't use STI
It creates a cluttered data model
Why don’t we just have one table called objects and store everything as
STI?
STI tables have a tendency to grow and expand as an application develops,
and become intimidating and unweildy as it isn’t clear which columns
belong to which models.
18 / 22
19. Reasons why you shouldn't use STI
It forces you to use nullable columns
If sub-classes that you intend to use for STI have many different data
fields, then including them all in the same table would result in a lot of
null values and make it difficult to scale over time. In this case, you may
end up with so much code in your model sub-classes that the shared
functionality between sub-classes is minimal and warrants separate
tables.
A comic book must have an illustrator, but regular books don’t have an
illustrator.
Subclassing Book with Comic using STI forces you to allow illustrator to be
null at the database level (for books that aren’t comics), and pushes your
data integrity up into the application layer, which is not ideal.
19 / 22
20. Reasons why you shouldn't use STI
It prevents you from efficiently indexing your data
Every index has to reference the type column, and you end up with
indexes that are only relevant for a certain type.
20 / 22
21. Reasons why you shouldn't use STI
Two objects types have similar attributes
Both airplanes and bicycles have wheels, but it probalby doesn’t make
sense to group them into the same table, given that intuitively, they’re
different objects that will have vastly different functionality and data
fields in an application.
Demo
21 / 22