【发布时间】:2016-12-24 11:24:14
【问题描述】:
在使用 Rails STI(单表继承)时,我定义了一个名为 Poi(兴趣点)的模型。
我们的应用程序要求 Poi 的子类(如餐厅、俱乐部等)必须在 Admin::Categories 视图中创建(其中有一个class_name 字符串输入字段),以便管理员应该能够随时创建一个新的子类,而不需要程序员打开一个带有空(无用)子类的新 ruby 文件并重新部署应用程序。
同时,如果将来我们想为 Poi 的子类指定不同的行为(实例/类方法),我们可以只创建该 ruby 文件,但这应该是一个选项而不是强制性的。对于该子类具有不同字段的不同表单也是如此:我们只需要在该子类中设置一个partial_name_for_form 实例方法,该方法返回一个带有部分名称的字符串,并且视图将相应地呈现它。如果没有找到,则呈现默认的 Poi 视图。
如果您尝试实例化具有与 Poi 的子类不匹配的 'type' 属性的新 Poi 对象(因此必须事先定义子类),Rails 会引发错误,因此我们提出了以下动态解决方案根据 class_name 创建 Poi 子类:
模型类别中的
after_create钩子立即使用以下代码定义新类:Object.const_set(category.class_name, Class.new(Poi))-
模型 Poi 文件中的
require_dependency调用(因为它在自动加载路径中)要求我们最终创建硬编码子类的子类的 ruby 文件(仅当文件存在时):
Category.all.each do |category|
require_dependency category.class_name.underscore if File.exist (File.join("app","models","pois","#{category.class_name.underscore}.rb"))
end
- 使用#1 中相同的代码定义所有剩余类的初始化程序(“剩余”是指其他没有自己定义它们的 ruby 文件的子类),但首先检查
if Object.const_defined? category.class_name(因为那些由 require_dependency 定义的不需要重新定义)。
即使是所有这些复杂性都让我们几乎后悔当初使用 STI,它运行良好 - 在开发中。
但在生产环境中,在创建提供class_name 的新类别后,没有定义该类,因为尝试使用该子类创建新 Poi 时会引发错误uninitialized constant。
我在生产环境的 Rails 控制台中确认 after_create 钩子正在工作,因为在那里定义了类。我的猜测是因为我们使用了 Unicorn,所以这个 bug 可能与应用程序代码的分叉有关,但我不知道如何进行。
【问题讨论】:
标签: ruby-on-rails ruby