views:

35

answers:

3

Let's say I have a FireNinja < Ninja object in my database, stored using single table inheritance. Later, I realize he's really a WaterNinja < Ninja. What's the cleanest way to change him to the different subclass? Even better, I'd love to create a new WaterNinja object and just replace the old FireNinja in the DB, preserving the ID.

Edit I know how to create the new WaterNinja object from my existing FireNinja, and I also know I can delete the old one and save the new one. What I'd like to do is mutate the class of the existing item. Whether I do that by creating a new object and doing some ActiveRecord magic to replace the row, or by doing some sort of crazy thing to the object itself, or even by deleting it and reinserting with the same ID is part of the question though.

A: 

You will have to define a WaterNinja#to_fireninja method. Ruby has no way to change the class of an object while keeping it the same object.

class WaterNinja < Ninja
  def to_fireninja
    FireNinja.new :name => name
  end
end
Adrian
That will create the new object, but when I call save, it'll create a different row in the database. I want to replace my persisted instance of FireNinja with my new WaterNinja object.
Isaac Cambron
@Isaac: I don't think that is possible, unless you can delete the WaterNinja and force ActiveRecord to save the new FireNinja with the same ID.
Adrian
@Adrian, one way the question could be answered is if you could tell me how to save the new ninja with an existing ID. I'd be happy to delete and reinsert.
Isaac Cambron
+2  A: 

You need to do two things:

  • Create WaterNinja < Ninja
  • In ninjas table run something like UPDATE ninjas SET (type = 'WaterNinja') WHERE (type = 'FireNinja')

That's about it.

For doing runtime conversion this will do it, but I don't recommend.

class Ninja
  def become_another_ninja(new_ninja_type)
    update_attribute(:type, new_ninja_type)
    self.class.find(id)
  end
end

@water_ninja = @fire_ninja.become_another_ninja('WaterNinja')

The problem with this is that @fire_ninja will now be a throwaway object.

hakunin
It sounds like you're answering, "how do I convert all `FireNinjas` to `WaterNinjas`?" The answer still works though if I change the condition to `ID=myid`. Yeah, I'd thought about that one. I'd also have to pull the object out after the update to do some WaterNinja-specific field setting, but that's fine. I was hoping for something a bit cleaner, though.
Isaac Cambron
I see what you mean. I don't recommend doing this, but this will do the trick: `ninja_id = @ninja.id; @ninja.update_attribute(:type, 'WaterNinja'); @ninja = WaterNinja.find(ninja_id)`
hakunin
Edited to show the method I described.
hakunin
Makes sense. I'm going to leave this open for just a bit longer in case there are any other bright ideas, but this looks pretty clean.
Isaac Cambron
A: 

Favor composition over inheritance - you should be using the strategy pattern to change this type of behavior at runtime.

http://en.wikipedia.org/wiki/Strategy_pattern

Paul
Not necessarily. Google for "replace type with polymorphism" and "replace type with strategy" refactoring patterns. Using strategy covers some cases, but in others one would prefer inheritance. These patterns are also described well in Refactoring (http://www.amazon.com/Refactoring-Ruby-Jay-Fields/dp/0321603508) book.
samuil
I have good reasons for using inheritance independent of the problem I'm presenting above. I'm not building changetypeofninja.com.
Isaac Cambron