动态实例化 Rails 嵌套 STI 子类?
假设我有一个这样的类:
class Basket < ActiveRecord::Base
has_many :fruits
其中“fruits”是一个 STI 基类,具有“苹果”、“橙子”等子类...
我希望能够在 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 类之前实例化,因此篮子 id 键永远不会保存在 Fruit 子类中(因为篮子 id 不保存)还存在)。
我完全被难住了。有人有什么想法吗?
Let's say I have a class like:
class Basket < ActiveRecord::Base
has_many :fruits
Where "fruits" is an STI base class having subclasses like "apples", "oranges", etc...
I'd like to be able to have a setter method in Basket like:
def fruits=(params)
unless params.nil?
params.each_pair do |fruit_type, fruit_data|
fruit_type.build(fruit_data)
end
end
end
But, obviously, I get an exception like:
NoMethodError (undefined method `build' for "apples":String)
A workaround I thought of works like this:
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
But that causes the Fruit STI object to be instantiated before the Basket class, and so the basket_id key is never saved in the Fruit subclass (because basket_id doesn't exist yet).
I'm totally stumped. Anyone have any ideas?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
不要在 Basket 中添加 setter 方法,而是在 Fruit 中添加它:
现在,您可以在通过关联构建对象时传递类型:
请注意,您不能以这种方式分配
:type
,因为它受到保护,免受批量分配。编辑
哦,您想根据子类运行不同的回调吗?正确的。
您可以这样做:
或为每种类型定义一个
has_many
关联:然后
Instead of adding a setter method in Basket, add it in Fruit:
Now you can pass the type in when you build the object through an association:
Note that you can't assign
:type
this way, since it is protected from mass assignment.EDIT
Oh, you wanted to run different callbacks depending on the subclass? Right.
You could do this:
or define a
has_many
association for each type:then
在 Ruby 术语中,
"#{x}"
简单地等同于x.to_s
,对于字符串值来说,它与字符串本身完全相同。在其他语言(例如 PHP)中,您可以取消引用字符串并将其视为类,但这里的情况并非如此。您的意思可能是这样的:constantize
方法从字符串转换为等效的类,但它区分大小写。请记住,您将面临这样的可能性:有人可能会创建一些将
fruit_type
设置为“users”
的内容,然后继续创建管理员帐户。也许更负责任的是进行额外的检查,确保您正在制作的产品实际上属于正确的类别。以这种方式加载类时需要注意的一件事是,
constantize
不会像在应用程序中拼写出来那样自动加载类。在开发模式下,您可能无法创建未显式引用的子类。您可以通过使用映射表来避免这种情况,该映射表解决了潜在的安全问题并一次性预加载:您可以像这样定义这个常量:
在模型中使用文字类常量将具有立即加载它们的效果。
In Ruby terms,
"#{x}"
is simply equivalent tox.to_s
which for String values is exactly the same as the string itself. In other languages, like PHP, you can de-reference a string and treat it as a class, but that's not the case here. What you probably mean is this:The
constantize
method converts from a string to the equivalent class, but it is case sensitive.Keep in mind that you're exposing yourself to the possibility someone might create something with
fruit_type
set to"users"
and then go ahead and make an administrator account. What's perhaps more responsible is to do an additional check that what you're making is actually of the right class.One thing to watch out for when loading classes this way is that
constantize
will not auto-load classes like having them spelled out in your application does. In development mode you may be unable to create subclasses that have not been explicitly referenced. You can avoid this by using a mapping table which solves the potential security problem and pre-loading all at once:You can define this constant like this:
Using the literal class constant in your model will have the effect of loading them immediately.