Last week I came across a blog post featured in Ruby Weekly called Better Single-Table Inheritance, by Nathan Long. On it Nathan described his experiences coming up with a sane way to do STI on Ruby on Rails.
For those of you not familiar with the technique: STI consists of reusing the same table to express a variety of models which share similar attributes. So instead of having a database like this one:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
It look something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Looks better, right?
I’ve always liked the concept of STI and felt that it was a fairly elegant way to have models that are persisted on a database inherit from each other, but what has always bothered me is how the database ends up being really ugly. What happens is that for all your, say,
Scientists stored in your
People table the
recipes field will hold a NULL value. The database ends up being a mess, and you can’t quite make sense of the data structure just by looking at the schema anymore.
To avoid this Nathan’s post proposed the following solution:
Put common attributes in a single table, non-shared attributes in separate tables with foreign key references, and use object delegation so that each model transparently pulls what it needs from both.
Which I initially liked, the problem came up when I scrolled down and saw this implementation of that idea for ActiveRecord
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
The horror. I don’t know about everybody else, but I dread finding this kind of stuff on an application I have to work on. So yes, while I liked the idea I found the implementation to be extremely unintuitive and I’d be willing to bet that this is because of how ActiveRecord does things.
You have to also do some weird stuff to add a foreign key in your models and even have to keep writing code to make something like
Scientist.where(university: 'Stanford') work. Not good.
Even with those problems I still liked the idea and wanted to see if I could find a cleaner way to implement it, I decided to use Sequel instead of ActiveRecord, because… well: I like Sequel and this is a simple thought exercise. What I found during my research to do this made me like it even more.
The solution: already a thing
I didn’t know about it before, and I’m super happy I took the time to read Nathan’s post and found out about this: The approach he came up on his own is a known technique called Class Table Inheritance, what’s even better is that Sequel already has a plugin that implements it.
By doing something as simple adding a plugin configuration line and adding foreign keys to each class that needs to inherit from another I have a perfect integration of multi-level inheritance in the ORM that allows me to query the database without any further problem. This code works:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
And the schema
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
1 2 3 4 5 6 7 8 9 10 11 12 13 14
Works perfectly, without doing any magic and exactly as you would expect it to.
I don’t want this to look like I am bashing Nathan’s post, I think the idea is great, the fact that it is a known technique does not take any merit from he coming up with it particularly in the Rails world where STI is so often joked about. Thanks to his post I was able to learn something fairly cool and play around with Sequel. This post is merely a recolection of the research he prompted me to do.
In the end: Class Table Inheritance is pretty awesome, and I look forward to be able to implement this on a real application, using Sequel, obviously.