I had a small problem today when trying to do unit testing of single-table inheritance models in Rails, all my tests initially failed and I wasn't quite sure why. It's a pretty simple fix but isn't documented too well anywhere, so here's the skinny.

My model structure was pretty simple, I had a Content class that acted as a base class for Article, Page, Event, and a bunch of others. So I added a Content class using a model generator, ending up with:

CODE:
  1.  
  2. class Content <ActiveRecord::Base
  3. end
  4.  

Then I added Article and Page classes using the model generator, and changed the default class definitions so that each of those classes inherited from Content. That looks like this:

CODE:
  1.  
  2. class Article <Content
  3. end
  4.  
  5. class Page <Content
  6. end
  7.  

Then I ran my tests, expecting that the default "test_true" assertions were going to pass. Explosion!

The first problem was that I named my table "content", not "contents". Since this isn't included in the pluralization rules for Rails, my model was trying to find a table called "contents". This was easily fixed by changing the Content class like this:

CODE:
  1.  
  2. class Content <ActiveRecord::Base
  3. set_table_name "content"
  4. end
  5.  

However, tests were still failing. After quite a bit of googling, it turns out that it was the auto-generated test fixture names that were the problem. First off, the articles.yml and pages.yml file were useless - when testing STI classes, you set up your fixtures in the superclass fixture definition. That is, in my case I only needed content.yml, and if I wanted to set up an Article fixture in there, it would look like this:

CODE:
  1.  
  2. article_one:
  3. id: 1
  4. title: foo
  5. body: this is a body
  6. type: Article
  7.  

Note that I had to set the type property manually to the class name of the subclass object that the fixture is for. Note also the name of the fixtures file - because of my use of a table name for Content that's not in the default pluralization rules, the fixture file needed to be content.yml rather than the default contents.yml.

One more thing and it all fell into place. All of the generated unit tests had incorrect fixture file names in them - content_test.rb had fixtures :contents, article_test.rb had fixtures :articles, and page_test.rb had fixtures :pages. All of these needed to be changed to fixtures :content before any of the tests would work. Rails apparently looks for a database table name matching the name of the fixtures file, so until I did this my tests were failing when looking for nonexistent tables.

Hopefully that will save someone from the horror of having to trawl the web using the keywords rails sti test.


6 Responses to “Testing Single Table Inheritance Models in Ruby on Rails”  

  1. 1 Kamil

    Thanks, it helped :)

  2. 2 Brandon Zylstra

    Wouldn’t it also have worked (with less work manually changing tests) to add a line to your inflections.rb like so:
    inflect.uncountable %w( fish sheep content )

  3. 3 Dave

    inflect.uncountable would do the job instead of set_table_name, yeah, but all the fixtures work is always necessary (so articles.yml is still useless, and you need to put your articles fixtures into the content.yml fixture with a type property, etc).

  4. 4 Laurent

    I have a problem in my test .
    When launching the test on my model ‘Module’ inherited from a class ‘Application’ (the table applications exist in database)
    My test tries first to clean the table ‘modules’ , that does not exist.
    Did i miss something?

  5. 5 Dave Hrycyszyn

    One thing that could cause this is if you forgot to change the

    fixtures :modules

    line from your test into

    fixtures :applications # or whatever your table name is

  6. 6 Vanne

    @Laurent

    Got the same problem. Turns out fixtures were generated for all models.
    So for the table that is really there but also for the models which inherit from it, which obviously don’t have a table, as that is the whole point of STI.
    The scaffold generator does create fixtures even if you specify –skip-migration it seems.

Leave a Reply