Rails中的STI:如何在不直接访问“type”属性的情况下从超类更改为子类?

时间:2013-05-20 18:05:30

标签: ruby-on-rails sti

所以,我有以下内容:

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

有一个制造和装配过程,其中捕获有关产品的数据。在捕获时,最终的产品类型是未知的。在数据库中创建产品记录之后(可能几天之后),可能需要将该产品转换为专用产品并填写其他信息。然而,并非所有产品都会变得专业化。

我一直在尝试使用以下内容:

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

然后,当我执行SpecializedProduct.all时,结果集不包含object_to_change。相反,object_to_change仍然在数据库中列为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]]

因此,在调用.becomes SpecializedProduct后,.save方法现在使用正确的类型,但它无法更新记录,因为更新的WHERE子句太具体了

我是否真的需要直接访问模型的type属性?我真的不愿意。

4 个答案:

答案 0 :(得分:1)

查看becomesbecomes!的来源,它不会改变原始对象。您需要将其分配给新变量:

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

不确定这将如何处理记录的主键,因此您可能需要进行一些额外的处理以确保您的关系不会受到损害。

答案 1 :(得分:1)

你只需要使用(!)并使用保存的方法获得变成方法的爆炸版本。

两种方法之间的区别:becomes使用原始对象的所有相同属性值创建新类的新实例。 becomes!还会更新类型列。

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

答案 2 :(得分:0)

我想不!检查becomesbecomes!方法的内容! https://github.com/rails/rails/blob/master/activerecord/lib/active_record/persistence.rb#L199

看起来您需要使用becomes!,因为它是becomes的包装,它也会更改实例的sti列值。

UPD: https://github.com/rails/rails/blob/4-0-stable/activerecord/test/cases/persistence_test.rb#L279 这是代码的测试用例。

<强> UPD2: 我想你可以尝试创建另一个类DefaultProject,它是Project的子类,然后你可以将每个从DefaultProject更改为SpecializedProduct,反之亦然

答案 3 :(得分:0)

我有一个类似的问题,我想从一个子类转换到另一个子类。不幸的是,Rails不能优雅地执行此操作,因为它希望使用“where type ='NewSubclass'”来限制保存。即:

UPDATE parents SET type='NewSubclass' WHERE type IN 'NewSubclass' AND id=1234

进入rails,似乎ActiveRecord的lib / active_record / inheritance.rb中的方法名为“finder_needs_type_condition?”被调用并且调用者不够聪明,无法意识到您正在更改类型字段,因此它显然不是那个值。

我在一轮谈判中“解决了这个问题”。我使用ActiveRecord核心作为如何使用属性加载我想要的任何类的实例的基础,并保存它而不必通过完整的ActiveRecord查找堆栈。

old_instance = Parent.find(id) #returns OldSubclass instance
tmp = Parent.allocate.init_with('attributes' => old_instance.attributes)
tmp.type = 'NewSubclass'
tmp.save
Parent.find(id) #returns NewSubclass instance

这真的很难看,我讨厌它。希望有人会考虑在ActiveRecord中修复它。我相信对象随着时间的推移在STI中更改子类会很有用。我有一个包含5个子类的表,因为它可以清理模型。

这是我唯一需要忍受的丑陋。请务必编写正确的测试,以便在ActiveRecord中断此“解决方法”时您可以检测到它。