STI where inheritance column values are not the same as the name of the class #16125

Closed
ericroberts opened this Issue Jul 10, 2014 · 2 comments

Projects

None yet

3 participants

@ericroberts

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?

@pixeltrix
Ruby on Rails member

@ericroberts this is more of a support request - I don't see a bug here.

I suggest you ask the regular support channels like the mailing list, IRC, Stack Overflow, etc.

@pixeltrix pixeltrix closed this Jul 10, 2014
@apeiros
apeiros commented Jun 23, 2016 edited

I've stumbled across the very same issue. @pixeltrix - this is IMO quite clearly a bug. Either that, or please explain why find_sti_class and sti_name exist and are used by rails, except when creating from the base-class (and yes, I did go to the irc channel and ask about it - the only explanation I got was that maybe rails does not allow to override the class derivation mechanism - but then the existence of find_sti_class and sti_name make no sense).
The consequence of this bug is also, that associated models with a custom sti-class mechanism can't be created. E.g. Product.new(currency_attributes: {units: '$'}) will fail with the exact same exception for the same reason.
@ericroberts was so nice as to provide a patch: master...ericroberts:sti-class-name

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