16 Jan
Earlier this week I was writing an app that had models connected to a number of legacy databases. To do this, I followed PragDave’s example of subclassing from ‘gatekeeper’ models that establish separate database connections. Without going into too much detail, here’s an example:
class Finance < ActiveRecord::Base
self.abstract_class = true
establish_connection(
ActiveRecord::Base.configurations["finance_#{ENV['RAILS_ENV']}"]
)
end
class Receipt < Finance
end
class Income< Finance
end
Looks good, but I hit a snag – one of my legacy tables had a ‘type’ column defined. When Rails sees a column named ‘type’ for models that aren’t immediate children of ActiveRecord::Base, it assumes that column holds the class name associated with your model. This is how Rails implements single table inheritance, and if that’s not what you intended, you’ve got a little extra work ahead of you.
Earlier this week I was writing an app that had models connected to a number of legacy databases. To do this, I followed PragDave’s example of subclassing from ‘gatekeeper’ models that establish separate database connections. This minimizes the number of active database connections, and looks good too. Without going into too much detail, here’s an example:
class Finance < ActiveRecord::Base
self.abstract_class = true
establish_connection(
ActiveRecord::Base.configurations["finance_#{ENV['RAILS_ENV']}"]
)
end
class Receipt < Finance
end
class Income< Finance
end
Looks good, but I hit a snag – one of my legacy tables had a ‘type’ column defined. When Rails sees a column named ‘type’ for models that aren’t immediate children of ActiveRecord::Base, it assumes that column holds the class name associated with your model. This is how Rails implements single table inheritance, and if that’s not what you intended, you’ve got a little extra work ahead of you.
When you attempt to do a database operation on that model, your SQL queries will contain an additional clause on your type column. Here’s the output from my development log when I try to invoke Receipt.count:
SQL (0.000) SELECT count(*) as count_all FROM receipts where ( ( receipts.`type` = 'Receipt ) )
I think this implementation of inheritance is kind of cool, but unfortunately for me, my ‘type’ column is strictly for historical purposes. Maybe it holds an acronym, a single letter, who knows. Maybe there’s dozens of types, and I don’t want to write inherited class models for all of them.
Okay, so the solution? Disable single table inheritance by overriding ActiveRecord’s inheritance_column method. Let’s return to our Receipt class:
class Receipt < Finance
def self.inheritance_column
nil
end
end
Okay, that will fix up our queries, but we’re not done yet – the type column is just full of surprises. It turns out that type is a Ruby core method, otherwise known as Object#type. Allow me to demonstrate:
$ ruby script/console
Loading development environment.
>> Receipt.find(:first).type
=> Receipt
Great, all that work, and we still can’t use our type column. Our best plan of action from here is to wrap it with some read/write methods, using a different name like ‘category’ instead. It’s a bit of a cop out, I know, but right now I’m out of ideas until it the method becomes fully deprecated in the next version of Ruby.
class Receipt < Finance
...
def category
attr_reader :type
end
def category=(type)
attr_writer :type, type
end
end
Success! … and that’s why you should avoid using ‘type’ columns for anything but inheritance lookups.