好的,我在Rails这个领域遇到了很多挫折,最后找到了以下方法,希望能对其他人有所帮助。
首先要注意,许多解决方案在Internet上建议使用客户端提供的参数进行constantize,这是已知的DoS攻击向量,因为Ruby不会垃圾回收符号,从而允许攻击者创建任意符号并消耗可用内存。
我采用了下面的方法来支持模型子类的实例化,并且是安全的,能够避免上述的contantize问题。 这与Rails 4非常相似,但允许多个级别的子类(不像Rails 4)并且适用于Rails 3。
module ActsAsCastable
extend ActiveSupport::Concern
module ClassMethods
def new_with_cast(*args, &block)
if (attrs = args.first).is_a?(Hash)
if klass = descendant_class_from_attrs(attrs)
return klass.new(*args, &block)
end
end
new_without_cast(*args, &block)
end
def descendant_class_from_attrs(attrs)
subclass_name = attrs.with_indifferent_access[inheritance_column]
return nil if subclass_name.blank? || subclass_name == self.name
unless subclass = descendants.detect { |sub| sub.name == subclass_name }
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
end
subclass
end
def acts_as_castable
class << self
alias_method_chain :new, :cast
end
end
end
end
ActiveRecord::Base.send(:include, ActsAsCastable)
尝试了多种方法解决“开发中的子类加载问题”,类似于上面建议的方法,但我发现唯一可靠的方法是在我的模型类中使用“require_dependency”。这可以确保类加载在开发中正常工作,并且不会在生产中出现任何问题。在开发中,如果没有“require_dependency”,AR将无法了解所有子类,这会影响用于匹配类型列的SQL。此外,如果没有“require_dependency”,您还可能陷入同时具有多个模型类版本的情况!(例如,当您更改基类或中间类时,子类并不总是重新加载,并且仍然从旧类继承)
class Contact < ActiveRecord::Base
acts_as_castable
end
require_dependency 'person'
require_dependency 'organisation'
我也不像上面建议的那样覆盖model_name,因为我使用I18n并需要针对不同的子类属性使用不同的字符串,例如:tax_identifier在组织中变为“ABN”,在个人中变为“TFN”(在澳大利亚)。
我也使用路由映射,如上所述,设置类型:
resources :person, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Person.sti_name } }
resources :organisation, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Organisation.sti_name } }
除了路由映射之外,我还使用了InheritedResources和SimpleForm,并且我为新操作使用以下通用表单包装器:
simple_form_for resource, as: resource_request_name, url: collection_url,
html: { class: controller_name, multipart: true }
对于编辑操作:
simple_form_for resource, as: resource_request_name, url: resource_url,
html: { class: controller_name, multipart: true }
为了使这个工作正常,在我的基本ResourceController中,我将InheritedResource的resource_request_name作为视图的辅助方法公开:
helper_method :resource_request_name
如果您没有使用InheritedResources,那么请在“ResourceController”中使用以下类似的内容:
class ResourceController < ApplicationController
protected
helper_method :resource
helper_method :resource_url
helper_method :collection_url
helper_method :resource_request_name
def resource
@model
end
def resource_url
polymorphic_path(@model)
end
def collection_url
polymorphic_path(Model)
end
def resource_request_name
ActiveModel::Naming.param_key(Model)
end
end
很高兴听到他人的经验和改进意见。