ActiveRecord's confusing Single Table Inheritance behavior with NULL type

Published on
3,363 Points
Last Modified:
Recently I spent hours debugging an issue in a Rails project where ActiveRecord was causing MySQL errors trying to create a User object of a class at the top level of a Single Table Inheritance model structure.  It turns out `.create` behaves differently based on the default value of the type field!
I recently had the pleasure of participating in porting a very large and complex Rails project from DataMapper to ActiveRecord. Among the models that had to be ported was a set of User models that used Single Table Inheritance (STI) to distinguish one type from another. Here is a simplified version of what that looks like:
class User < ActiveRecord::Base

class AdminUser < User

Open in new window

In this case, the same table is used to store data about both Users and AdminUsers. Generally, the 'type' field is used to tell the difference between Users and AdminUsers, by storing the class name of the object being saved (i.e. 'AdminUser'). This all works out fine 99% of the time. When you create a new model that uses STI, ActiveRecord presumably takes care of setting up a default value for the column and marking it as non nullable, etc.

I say presumably, because I haven't actually done it - my database was already setup by DataMapper years prior. And this is what happens when you try to create a Single Table Inheritance model (User.create) on a table that doesn't have a default value for the 'type' column:
ActiveRecord::StatementInvalid: Mysql::Error: Column 'type' cannot be null

Open in new window

ActiveRecord tries to insert a new row, setting the type column to NULL, which is a horrible way to store data for an STI model! Fortunately, in my case, type is a non-nullable column, so I get an error.

My real confusion came when I updated my dev database to have a default for the type column (because somehow the production database already had a default, so clearly my dev database was just out of sync):
mysql> ALTER TABLE users MODIFY COLUMN type varchar(50) NOT NULL DEFAULT 'User';
mysql> exit
$> bundle exec rake db:test:prepare # <-- My Mistake

Open in new window

I thought this was all I had to do, but it turns out running db:test:prepare just matches your test database to your schema.rb file, and my schema.rb file hadn't been updated, so now User.create is working fine in development, but broken in testing, and THAT kind of failure is the most fun you can possibly have trying to debug a test: the kind that only happens in the test environment! :D

Eventually, I came to understand all of the above, in addition to the fact that I needed to run db:migrate in order to update my schema.rb file BEFORE running db:test:prepare. Once I did that: voila! User.create actually uses the default value of the type column to insert new User objects.

Moral of the story:
  • NEVER let your development database get out of sync with production. If it is: blow it away with a db:schema:load and start over with new dev data! (Or get a production dump, or something)
  • Choose your ORM wisely. R.I.P. DataMapper - I'll miss your elegant abstractions... but not your bugs.

Featured Post

Free Tool: Port Scanner

Check which ports are open to the outside world. Helps make sure that your firewall rules are working as intended.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

Join & Write a Comment

Suggested Articles

The purpose of this video is to demonstrate how to set up the WordPress backend so that each page automatically generates a Mailchimp signup form in the sidebar. This will be demonstrated using a Windows 8 PC. Tools Used are Photoshop, Awesome…
Watch the video to learn how one can deal with PST file corruption issue with an outstanding Kernel for Outlook PST Repair Tool easily. Using this tool, non-technical users can swiftly perform the repair process to restore their essential data witho…
Suggested Courses

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month