Хорошо, у меня было множество разочарований в этой области Rails, и я пришел к следующему подходу, возможно, это поможет другим.
Во-первых, имейте в виду, что ряд решений выше и вокруг сети предлагают использовать constantize для предоставленных клиентом параметров. Это известный вектор атак DoS, так как Ruby не собирает символы мусора, что позволяет злоумышленнику создавать произвольные символы и использовать доступную память.
Я реализовал следующий подход, который поддерживает создание экземпляров подклассов модели и является БЕЗОПАСНЫМ из вышеописанной проблемы. Это очень похоже на то, что делает rails 4, но также допускает более одного уровня подклассов (в отличие от Rails 4) и работает в Rails 3.
# initializers/acts_as_castable.rb
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)
Попробовав различные подходы к «загрузке подкласса в проблеме devlopment», многие из которых были похожи на предложенные выше, я обнаружил, что единственное, что надежно работает, - это использование require_dependency в моих модельных классах. Это гарантирует, что загрузка классов работает должным образом в разработке и не вызывает проблем в производстве. В процессе разработки без 'require_dependency' AR не будет знать обо всех подклассах, что влияет на SQL, выдаваемый для сопоставления в столбце типа. Кроме того, без 'require_dependency' вы также можете оказаться в ситуации с несколькими версиями классов модели одновременно! (например, это может произойти, когда вы изменяете базовый или промежуточный класс, кажется, что подклассы не всегда перезагружаются и остаются подклассами старого класса)
# contact.rb
class Contact < ActiveRecord::Base
acts_as_castable
end
require_dependency 'person'
require_dependency 'organisation'
Я также не переопределяю имя_модели, как предложено выше, потому что я использую I18n и мне нужны разные строки для атрибутов разных подклассов, например: tax_identifier становится 'ABN' для Организации и 'TFN' для Person (в Австралии).
Я также использую отображение маршрута, как предложено выше, устанавливая тип:
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 }
И чтобы это работало, в моем базовом ResourceContoller я предоставляю имя_ресурса_ресурса InheritedResource как вспомогательный метод для представления:
helper_method :resource_request_name
Если вы не используете InheritedResources, используйте что-то вроде следующего в вашем 'ResourceController':
# controllers/resource_controller.rb
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
Всегда рад услышать от других опыт и улучшения.