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.
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.