2
votes

I'm working on porting a very large Rails project from DataMapper to ActiveRecord.  Among the models that has to be ported is a set of User models that used Single Table Inheritance (STI) to distinguish one type from another.  Here is a simplified version of what it looks like:

class User < ActiveRecord::Base
  ...
end

class AdminUser < User
  ...
end

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').  And it works fine in development, but when I try User.create in the test environment, I get:

ActiveRecord::StatementInvalid: Mysql::Error: Column 'type' cannot be null

ActiveRecord tries to insert a new row, setting the type column to NULL... what could cause this to be happening in the test environment, but not in development?

1

1 Answers

1
votes

Turns out it was a slight difference in the database table itself that was causing a change in behavior for ActiveRecord. The test database had no default value for the type column, whereas in development, the default value was 'User'. Apparently ActiveRecord uses the default value when inserting data for an object of the primary class type (the class that inherits from ActiveRecord::Base - in this case, User). Why it doesn't just use the class name is beyond my understanding!

My real confusion came when I updated my dev database to have a default for the type column, which I actually knew it needed, because somehow the production database already had one, so clearly my dev database was just out of sync. So I did this:

mysql> ALTER TABLE users MODIFY COLUMN type varchar(50) NOT NULL DEFAULT 'User';
...[ok]
mysql> exit
Bye
$> bundle exec rake db:test:prepare # <-- My Mistake
...[ok]

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 then User.create worked in development, but broke in testing :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 used 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.