1

假设我有一个像这样的课程:

class Basket < ActiveRecord::Base
  has_many :fruits

其中“fruits”是 STI 基类,具有“apples”、“oranges”等子类......

我希望能够在 Basket 中有一个 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

但这会导致 Fruit STI 对象在 Basket 类之前被实例化,因此 basket_id 键永远不会保存在 Fruit 子类中(因为 basket_id 尚不存在)。

我完全被难住了。有人有想法么?

4

2 回答 2

1

不要在 Basket 中添加 setter 方法,而是在 Fruit 中添加:

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
于 2010-11-04T19:18:32.083 回答
-1

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

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

constantize方法从字符串转换为等效类,但区分大小写。

请记住,您将自己暴露在有人可能会使用fruit_typeset to创建某些内容"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

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

于 2010-11-04T19:05:10.337 回答