Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

STI in Rails: How do I change from a superclass to a subclass without accessing the "type" attribute directly?

So, I have the following:

class Product < ActiveRecord::Base
  # Has a bunch of common stuff about assembly hierarchy, etc
end

class SpecializedProduct < Product
  # Has some special stuff that a "Product" can not do!
end

There's a manufacturing and assembly process in which data is captured about Products. At the time of capture the eventual product type is not known. after the Product record has been created in the database (perhaps days later) it may be necessary to turn that product into a specialized product and fill in the additional information. Not all products will become specialized, however.

I've been trying to use the following:

object_to_change = Product.find(params[:id])
object_to_change.becomes SpecializedProduct
object_to_change.save

Then, when I do a SpecializedProduct.all the resulting set does not include object_to_change. Instead object_to_change is still listed in the database as Product

UPDATE "products" SET "type" = ?, "updated_at" = ? WHERE "products"."type" IN ('SpecializedProduct') AND "products"."id" = 30  [["type", "Product"], ["updated_at", Fri, 17 May 2013 10:28:06 UTC +00:00]]

So, after the call to .becomes SpecializedProduct the .save method is now using the right type, but it's not able to update the record because the WHERE clause of the update is too specific.

Do I really need to access the type attribute of the model directly? I'd really rather not.

like image 651
Jamie Avatar asked May 20 '13 18:05

Jamie


2 Answers

Looking at the source of becomes and becomes!, it doesn't mutate the original object. You need to assign it to a new variable:

some_product = Product.find(params[:id])
specialized_product = some_product.becomes SpecializedProduct
specialized_product.save

Not sure how this will handle the primary key of the record, though, so you may need to do some additional finagling to make sure your relations don't get mangled.

like image 51
lobati Avatar answered Nov 10 '22 20:11

lobati


You just need the bang version of the becomes method with (!) and save.

The difference between the two methods: becomes creates a new instance of the new class with all the same attribute values of the original object. becomes! also updates the type column.

object_to_change = Product.find(params[:id])
object_to_change.becomes! SpecializedProduct
object_to_change.save
like image 35
Mark Swardstrom Avatar answered Nov 10 '22 18:11

Mark Swardstrom