Search code examples
rubyruby-on-rails-3single-table-inheritanceduck-typing

Rails -- Single Table Inheritance -- flawed casting approach?


So I have been looking into Single Table Inheritance lately, and have found this common question/answer:

question: how do you change the class of an object obj from Alpha to Beta, assuming Beta < Alpha, in STI?

answer: ruby is a duck-typed language, so you don't use casting. But all you need to do is set the "type" variable to "Beta" and save the object, and the next time you load the Alpha object it will be of type Beta:

obj = Alpha.new
obj.save #now obj is of type Alpha

obj.type = "Beta"
obj.save #now obj is of type Beta

However, this approach does not seem to work for me. While obj does correctly save, it doesn't seem to function as a Beta object at all. It saves without running Beta validations, and when I checked obj.respond_to?(:beta_method) #beta_method being a method in the beta class, it returned false. Does this approach not work? Is there a correct approach? Or am I simply doing something wrong?

EDIT

I found that when I did Alpha.last.respond_to(:beta_method) it returned false, while Beta.last.respond_to(:beta_method) returned true (both Alpha.last and Beta.last returned the same object however). Interesting development? Still if someone could explain this in detail (with respect to how ruby handles inheritance) that would be awesome.


Solution

  • As you said, Ruby does not allow typecasting. Rather, what is happening here is that ActiveRecord is instantiating an instance of a different class depending on what the value of the type field is. So when you are changing it to "Beta", you aren't really doing anything from Ruby's point of view.

    But when ActiveRecord looks in the database to retrieve the record, it sees the new type value and instantiates an instance of Beta rather than Alpha. This process has less to do with Ruby than it does with ActiveRecord in particular.

    There are sometimes more elegant approaches depending on your situation. If you need to "convert" a model from one type to another (e.g. an Employee becomes a Manager, which has additional methods), this is a reasonable way to it. In other cases, your goal might be better achieved by using mixins or some other tool.

    You can also create a temporary instance of Beta from Alpha's attributes if you need to, like this:

    # obj is an instance of Alpha
    obj = Beta.new(obj.attributes)
    

    But this is similarly a weird thing to need to do in most cases.