Skip to content

Instantly share code, notes, and snippets.

@ericroberts
Last active April 7, 2020 13:49
Show Gist options
  • Save ericroberts/e3f9d2d376b39d6ece02 to your computer and use it in GitHub Desktop.
Save ericroberts/e3f9d2d376b39d6ece02 to your computer and use it in GitHub Desktop.
STI where inheritance column values are not the same as the name of the class

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.

  1. I had a class 'Code' and a database table 'codes'.
  2. 'Code' had an attribute 'units', which could be either '$' or '%'
  3. 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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment