Wednesday, January 9, 2008

Single Table Inheritance in Rails

I've been experimenting with Single Table Inheritance or STI.
STI documentation is rather sparse, distributed, confusing and lacking so I hope to make some sense out of it.

The Wiki describes Single Table Inheritance like this:

Single Table Inheritance represents an inheritance hierarchy of classes as a single table that has columns for all the fields of the various classes.
Martin Fowler explains:
Relational databases don't support inheritance, so when mapping from objects to databases we have to consider how to represent our nice inheritance structures in relational tables. ... Single Table Inheritance maps all fields of all classes of an inheritance structure into a single table.
What this means, is that at the DB table level all records (objects) from all levels of the type hierarchy (classes) have all the fields (members) of all the other classes (and direct access to them). In other words, the table fields are the union of all the members of all the classes in the hierarchy (plus one special field called :type).
Due to this fact, obviously, two classes in the hierarchy cannot have two differently typed members with the same name.

Interestingly enough, this kind of inheritance actually goes against some of OOP's main tennets such as, data decoupling, data encapsulation/hiding, access control and separation of concerns.

These issues notwithstanding, STI can still be useful.

To build your hierarchy, say by sub-classing from Person, generate a migration script:
>>ruby script/generate migration AddColumnsForPersonHierarchy
then in the resulting migration file add all the additional columns you need for any or all the classes of your hierarchy. Additionally, add the special string column, conventionally called :type. Rails will automatically fill :type with the actual class name.

Then, add your class models by giving each class a separate file, e.g. create a new file app/models/employee.rb:
class Employee < Person 
validates_presence_of :department
end
You could do it all in one model file, as shown in various example on the web, (e.g. in app/models/person.rb) but that creates lookup problems for Rails, so that Employee will not be found before Person is first used.

For improving class data consistency we can, inside our class models, validates_presence_of any fields that are relevant just for this class, as shown in the example above. Other classes can accept nil.

In the next post in this series I will explore the how to add controllers to our STI hierarchy. I will also investigate how far Rails can allow true polymorphism with STI.

If you found this post helpful, please leave a message in the comments. If you want to read more about my exploration of polymorphism and STI on Rails, subscribe to the blog feed and you won't miss a thing.

4 comments:

Andy said...

Thanks for your STI example and links. After reading a few posts now, I get STI, but it still doesn't seem "right." I guess the bigger issue of course is the whole idea of ORM.

A.S. said...

I agree, Andy. ORM makes STI rather weak on the data hiding side. Another issue is how the MVC pattern fits in with that. My post about which view gets dispatched (polymorphically) also raises the issue that the controllers should also have a corresponding hierarchy. That this stuff is not automatically supported goes to show that this is not a big issue for the RoR developers.

Unknown said...

Nice! Thanks for the write up.

Unknown said...

Thanks for the example. Unfortunately, I saw it *after* I spent two hours figuring out that I needed to refer to the parent class in order for the child classes to be recognized (had them all defined in the same file). Still trying to figure out why instances of the parent class have an empty type field.