When single-table inheritance attacks

Broken Ping-Pong Table
Photo by DarlingSnail

Rails provides a very quick and easy way to implement single-table inheritance (STI). The implementation of STI in Rails is so trivial as to make gotchas nearly unavoidable. I’ve spent a couple weeks ignoring, and later dealing with, one of these issues.

As described previously, I’ve been monkeying around with has_many_polymorphs (HMP). Finally I had HMP configured just right and I started seeing strange errors when hitting pages referencing the parents side of my HMP relationship:

LoadError (Expected script/../config/../app/models/ticker.rb to define Ticker)

Hmmm…let me see. Do I have a app/models/ticker.rb file? Check. Does that file have a class Ticker defined? Check. Am I free of typos? Check.

One more strange behavior is that, upon server startup, the first load of said page works beautifully. The above error only occurs on subsequent loads.

Oh, and another thing. If you cache your classes in development.rb, your pages should load fine.

config.cache_classes = true

What the hell!

A model LoadError for every controller

Surfing around, I found the LoadError to happen in each controller action referencing either of my STI subclasses. This looked to be an STI problem, not a HMP problem. Time to expose my basic STI modeling:

# app/models/announcer.rb
class Announcer < ActiveRecord::Base
  # Various useful methods defined
end

# app/models/ticker.rb
class Ticker < Announcer
  # Various additional useful methods defined
end

# app/models/news_module.rb
class NewsModule < Announcer
  # Various additional useful methods defined
end

Pretty straightforward, I think.

Resolving the STI LoadError problem

Through some Google searching, blog reading and experimentation, I found that I must do something special to make a controller aware of these STI subclasses. I don’t know if this can rightly be called a bug, or if it is more of a non-ideal situation in a framework that is frequently ideal, but I can say the solution suffers from near-non-existent documentation.

In all controllers that reference the STI subclasses, I had to do the following:

class TickerController < ApplicationController
  # Rails does not pull out single-table inheritance subclasses properly on its own.
  # Must require the STI superclass explicitly in controllers.
  require_dependency ‘announcer’
 
  # More handy methods
end

This gets to be a little smelly, as I now have require_dependency lines in several controllers. While the quantity of controllers is still small relative to my total number of controllers, I’m considering moving the require_dependency line up to ApplicationController so developers on this app don’t run into the same problem in the future.

The problem of limited STI visibility in controllers is described much more succinctly at the bottom of the Rails wiki STI page:

Gang, this’ll save you some hours of frustration. Your controllers may not see your STI subclasses unless you include the following in your controller files:

require_dependency 'model'

…where ‘model’ is the name of the parent class.

Technorati Tags: , , ,

Share if you like:
  • Digg
  • StumbleUpon
  • del.icio.us
  • Technorati
  • Ma.gnolia
  • Reddit
  • description

Comments (7)

  1. Chris wrote:

    What about when using the console? Do you just need to type “require_dependency ‘model’” before accessing STI models?

    Friday, October 26, 2007 at 11:41 pm #
  2. Barry wrote:

    Nope.

    Friday, October 26, 2007 at 11:48 pm #
  3. Chris wrote:

    Actually, I was playing around with it last night. I have an STI with 3 submodels. If you’re working from the console there is no controller to be run, so trying to do

    > t = SubClass.new

    results in the LoadError. Specifying the require_dependency at the beginning of your console session fixes this. This is pretty inconvenient IMO - Perhaps specifying the dependency could be moved to the model or the application.rb file to make console work more efficient.

    (I’m using a frozen 1.2.3 instance, perhaps 1.2.4,5 or 2.0 has changed this up?)

    Saturday, October 27, 2007 at 9:46 am #
  4. Barry wrote:

    Hmm…I just ran your command and had no problems in console. The app is currently at Rails 1.2.4, though I don’t know if that has anything to do with it.

    Sunday, October 28, 2007 at 3:16 pm #
  5. Chris wrote:

    *In a moment of clarity, our hero realizes the error of his ways…*

    A-ha! I may have been going against convention here. I was including the subclasses in the same file as the parent class:

    # app/models/vehicle.rb
    class Vehicle

    Thursday, November 8, 2007 at 5:19 pm #
  6. Chris wrote:

    Err… that didn’t work right. I don’t think your form likes gt signs. Lemme try this again:

    *In a moment of clarity, our hero realizes the error of his ways…*

    A-ha! I may have been going against convention here. I was including the subclasses in the same file as the parent class:

    # app/models/vehicle.rb
    class Vehicle < ActiveRecord::Base; end

    class Car < Vehicle; end
    class Quad < Vehicle; end
    # end app/models/vehicle.rb

    Firing up my console, I keep getting the LoadError messages when trying to work on a subclass model until I specify `require_dependency ‘Vehicle’`.

    However, if I split each subclass into its own model file (i.e. app/models/quad.rb) then they are available straight away with no need to explicitly require the dependent file.

    Thursday, November 8, 2007 at 5:22 pm #
  7. Barry wrote:

    Oh Rails and its opinions!

    Thursday, November 8, 2007 at 7:20 pm #