2010-11-04 33 views
1

比方说,我有一个类,如:动态实例化Rails嵌套STI子类?

class Basket < ActiveRecord::Base 
    has_many :fruits 

其中“水果”是具有像“苹果”,“桔子”等子类的STI基类...

我想能够有一个setter方法在篮状:

def fruits=(params) 
    unless params.nil? 
    params.each_pair do |fruit_type, fruit_data| 
     fruit_type.build(fruit_data) 
    end 
    end 
end 

但是,很明显,我得到这样一个例外:

NoMethodError (undefined method `build' for "apples":String) 

我认为作品像这样的解决方法:

def fruits=(params) 
    unless params.nil? 
    params.each_pair do |fruit_type, fruit_data| 
     "#{fruit_type}".create(fruit_data.merge({:basket_id => self.id})) 
    end 
    end 
end 

但是,这导致水果STI对象篮下上课前被实例化,所以basket_id密钥永远不会保存在水果子类(因为basket_id没有按” t存在)。

我完全难住。有人有主意吗?

回答

1

反而在篮加入setter方法,它加入水果:

class Fruit < ActiveRecord::Base 
    def type_setter=(type_name) 
    self[:type]=type_name 
    end 
end 

现在,你可以通过当你通过关联建立的对象类型:

b = Basket.new 
b.fruits.build(:type_setter=>"Apple") 

注意您不能以这种方式分配:type,因为它受到大规模分配的保护。

编辑

哦,你想这取决于子类来运行不同的回调?对。

你可以这样做:

fruit_type = "apples" 
b = Basket.new 
new_fruit = b.fruits << fruit_type.titleize.singularize.constantize.new 
new_fruit.class # Apple 

或定义每个类型has_many协会:

require_dependency 'fruit' # assuming Apple is defined in app/models/fruit.rb 

class Basket 
    has_many :apples 
end 

然后

fruit_type = "apples" 
b = Basket.new 
new_fruit = b.send(fruit_type).build 
new_fruit.class # Apple 
+0

这适用于我的目的。但是,如果我正确理解这一点,这意味着我可能对我的STI子类有任何回调将无法运行,对吗? – 2010-11-04 23:36:59

+0

不幸的是,是的。虽然看到我编辑的答案。 – zetetic 2010-11-05 00:09:41

+0

太棒了!非常感谢! – 2010-11-05 01:20:39

-1

在Ruby术语中,"#{x}"简单等价于x.to_s,它对于字符串值与字符串本身完全相同。在PHP等其他语言中,您可以取消引用一个字符串并将其视为一个类,但在这里不是这种情况。你大概的意思是这样的:

fruit_class = fruit_type.titleize.singularize.constantize 
fruit_class.create(...) 

constantize方法从一个字符串等价类转换,但它是区分大小写。

请记住,您正在将自己暴露给有可能创建fruit_type设置为"users"的可能性,然后继续创建管理员帐户。可能更负责任的是做一个额外的检查,你正在做什么实际上是正确的类。

fruit_class = fruit_type.titleize.singularize.constantize 
if (fruit_class.superclass == Fruit) 
    fruit_class.create(...) 
else 
    render(:text => "What you're doing is fruitless.") 
end 

有一点要注意加载类时这种方式是constantize不会自动加载类,比如让他们阐述了在你的应用程序一样。在开发模式下,您可能无法创建未明确引用的子类。您可以使用它解决了潜在的安全问题和预加载一次全部映射表避免这种情况:

fruit_class = Fruit::SUBCLASS_FOR[fruit_type] 

您可以定义此常数是这样的:

class Fruit < ActiveRecord::Base 
    SUBCLASS_FOR = { 
    'apples' => Apple, 
    'bananas' => Banana, 
    # ... 
    'zuchini' => Zuchini 
    } 
end 

使用文字类常量在你的模型中会有立即加载它们的效果。

+0

OK,这是一个好主意,但我仍然卡住。篮子似乎没有在寻找一个班级,因为我们通过篮子has_many:fruits association创建了某种水果。所以这个工程:Basket.fruits.create(params)或Basket.apples.create(params)但不是Basket.fruit_class.create(params)。 – 2010-11-04 23:25:36

+0

由于关联本身是键入的,因此您不会通过关联创建它。你可以做的是创建合并了“basket”属性的子对象,就像你在你的问题中一样。 – tadman 2010-11-05 03:32:32

+0

但是你不能那样做,因为Basket.id还不知道 - 篮子还没有被保存。 – 2010-11-05 21:06:29