Single Table Inheritance in Rails

December 09, 2009 — Code

Updated 18 May 2011

Because single table inheritance (STI) has been invaluable to me in some recent Rails projects, I thought I’d try to dispel a little of its negative reputation by writing about some reasons you might use it, when to avoid it, and some tips for working with it. If you gave up on Rails’s STI a while ago because it required too many workarounds, I’d recommend giving it another look, as some annoyances (eg, regarding validation and demodulization) have recently been fixed.

For this article I’m assuming you know what STI is and how to set it up (add a type column and inherit from a parent class). I’m also assuming that you’re familiar with polymorphic associations and using modules to share code among different classes. Just because you know how to use these techniques doesn’t mean deciding which one to use is easy.

When To Use It

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: Car, Truck, and Motorcycle. Let’s consider three choices for modeling this situation:

  • Polymorphic Associations (separate classes, multiple tables)
  • Single Table Inheritance (separate classes, one table)
  • Single Class with conditionals (one class, one table)

With Polymorphic Associations we use modules to share code among classes. 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. OOP purists hate this, but it works well in the real world when there are only a few slight differences between object types and separate classes are overkill.

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?

First and foremost, your design choice needs to be understandable. In the case of a car, a truck, and a motorcycle, it seems perfectly reasonable to think of them all as vehicles. If you add a bicycle and a wheelbarrow it may become confusing because, in our minds, these things are not as vehicle-like as a car. This is to say: don’t use single table inheritance just because your classes share some attributes (eg: num_wheels, color, length), make sure there is actually an OO inheritance relationship between each of them and an understandable parent class. And be sure to choose class names carefully.

2. Do you need to do database queries on all objects together?

If you want to list the objects together or run aggregate queries on all of the data, you’ll probably want everything in the same database table for speed and simplicity, especially if there is a lot of data. This points to a Single Table Inheritance or a Single Class design.

Remember that while SQL is optimized for doing joins and ActiveRecord provides tools to make them easier, data separation may not be worth the increase in complexity for database operations. In the real world, 100% normalized data is not always the best design.

3. Do the objects have similar data but different behavior?

How many database columns are shared by every model? If there are going to be many model-specific columns, you should consider Polymorphic Associations. On the other hand, if a Car, Truck, and Motorcycle all have the same attributes, eg:

  • color
  • engine_size
  • price

but different method implementations, eg:

  • drivetrain_weight # sums different components
  • value_after_depreciation(years) # uses different depreciation rates
  • drivers_license_certifications # references different certifications

then Single Table Inheritance is probably a good design choice. If there are only minor differences in a few methods, you may want to “cheat” and go with a Single Class.

Single Table Inheritance Tips

Here are some things that can make your STI experience more enjoyable.

Use a Single Controller

This may not always apply, but I have yet to see a case where STI works well with multiple controllers. If we are using STI, our objects share a set of IDs and attributes, and therefore should all be accessed in basically the same way (find by some attribute, sort by some attribute, restrict to administrators, etc). If presentation varies greatly we may want to render different model-specific views from our controller. But if object access varies so much that it suggests separate controllers, then STI may not have been the correct design choice.

Put All Fixtures in the Parent’s File

If you’re using Rails’s fixtures for testing your app, remember that there is a one-to-one relationship between fixture files and database tables. With STI, everything is in one table, so in our example we’d have a single test/fixtures/vehicles.yml file with all our fixtures in it (be sure to provide a type attribute value for every record).

Allow Children To Use Their Parent’s Routes

Updated Dec 14, 2010: Now more robust, courtesy of Nathan McWilliams.

If you’ve ever tried to add STI to an existing Rails application you probably know that many of your link_to and form_for methods throw errors when you add a parent class. This is because ActionPack looks at the class of an object to determine its path and URL, and you haven’t mapped routes for your new subclasses. You can add the routes like so, though I don’t recommend it:


# NOT recommended:
map.resources :cars,        :as => :vehicles, :controller => :vehicles
map.resources :trucks,      :as => :vehicles, :controller => :vehicles
map.resources :motorcycles, :as => :vehicles, :controller => :vehicles

This only alleviates a particular symptom. If we use form_for, our form fields will still not have the names we expect (eg: params[:car][:color] instead of params[:vehicle][:color]). Instead, we should attack the root of the problem by implementing the model_name method in our parent class. I haven’t seen any documentation for this technique, so this is very unofficial, but it makes sense and it works perfectly for me in Rails 2.3 and 3:


def self.inherited(child)
  child.instance_eval do
    def model_name
      Vehicle.model_name
    end
  end
  super
end

This probably looks confusing, so let me explain:

When you call a URL-generating method (eg: link_to("car", car)), ActionPack calls model_name on the class of the given object (here car). This returns a special type of string that determines what the object is called in URLs. All we’re doing here is overriding the model_name method for subclasses of Vehicle so ActionPack will see Car, Truck, and Motorcycle subclasses as belonging to the parent class (Vehicle), and thus use the parent class’s named routes (VehiclesController) wherever URLs are generated. This is all assuming you’re using Rails resource-style (RESTful) URLs. (If you’re not, please do.)

To investigate the model_name invocation yourself, see the Rails source code for the ActionController::RecordIdentifier#model_name_from_record_or_class method. In Rails 2.3 the special string is an instance of ActiveSupport::ModelName, in Rails 3 it’s an ActiveModel::Name

Make The Parent Class Aware of Its Children

Let’s say we want to provide a single form for creating a Vehicle object, so the first field is a select menu where the user chooses the object class (Car, Truck, or Motorcycle). How do we know what the options are without hard-coding the class names into the select field? We can use Ruby’s inherited hook method to keep a running list of children as they inherit from the parent. It turns out that ActiveRecord actually does this already, so we could implement a Vehicle.select_options method like this:


def self.select_options
  descendants.map{ |c| c.to_s }.sort
end

Internally, ActiveRecord uses ActiveSupport’s DescendantsTracker module (versions prior to Rails 3 use a class variable @@subclasses which is accessible only through the protected method subclasses). This is another trick I discovered while reading the Rails source code (“unofficial” as far as I know), so if you don’t like the feel of it we can re-implement the behavior directly in our parent class:


@child_classes = []

def self.inherited(child)
  @child_classes << child
  super # important!
end

def self.child_classes
  @child_classes
end

In either case, we still have one more problem to solve: child classes are not recognized by the parent until they are loaded. In typical Rails production and test environments this happens right away, but in development, where config.cache_classes == false, classes aren’t loaded until you call upon them. So, for this to work consistently in our development environment we need to manually require classes:


# add config/initializers/preload_sti_models.rb:

if Rails.env.development?
  %w[vehicle car truck motorcycle].each do |c|
    require_dependency File.join("app","models","#{c}.rb")
  end
end

It’s important to understand that classes are lazy loaded in the development environment so you avoid serious problems.

That’s all for now. Good luck.


comments powered by Disqus