I recently tried to retrofit STI on a database table that had already existed for a while. Here's a basic outline of the scenario.
- I had a class 'Code' and a database table 'codes'.
- 'Code' had an attribute 'units', which could be either '$' or '%'
- I wanted the STI classes to be Code::Dollar or Code::Percent
I successfully implemented this with the following:
class Code
self.inheritance_column = 'units'
class << self
def find_sti_class(units)
unit_class_for[units]
end
def sti_name
unit_class_for.invert[self]
end
def unit_class_for
{
'$' => Code::Dollar,
'%' => Code::Percent
}
end
end
end
This works perfectly if I use it in the following way:
Code::Dollar.new(initialization_hash)
Code::Percent.new(initialization_hash)
However, if I do just Code.new(units: '$')
or something.build(units: '$')
, I get an error like the following:
ActiveRecord::SubclassNotFound: Invalid single-table inheritance type: $ is not a subclass of Code
What I really want is for Code.new(units: '$')
to return me a Code::Dollar
object.
I was able to trace the lookup of the class name into ActiveRecord::Inheritance::ClassMethods#subclass_from_attrs
. From there I could see that it was trying to build the class name from the units value in the database, which obviously doesn't work as there isn't a class named $ or %.
What I'm really trying to do is setup STI to work correctly when the value of the database column doesn't correspond to a class name. As there is already another method called find_sti_class
, it seems curious that we couldn't use it inside of subclass_from_attrs
in order to make it work in this way. I did try it and was successful, but as find_sti_class
is a private method, I did not submit a patch using this.
So, after all of that, I guess what I'm after is finding out if doing such a thing is possible in Rails as is. If not, would a patch to make it possible be desired by people other than myself? And if that patch made find_sti_class part of the public interface, would that be likely to be accepted?