动态命名空间控制器在Rails中具有回退function
我对新的Rails应用程序有一些奇怪的要求。 我需要构建一个应用程序,其中所有路由都在多个名称空间中定义(让我解释一下)。 我希望有一个应用程序,其中学校科目(数学,英语等)是名称空间:
%w[math english].each do |subject| namespace subject.to_sym do resources :students end end
这很好,它可以工作,但它需要我为每个主题创建一个命名空间的StudentsController
,这意味着如果我添加一个新的主题,那么我需要创建一个新的控制器。
我想要的是创建一个Base::StudentsController
,如果,我想说Math::StudentsController
存在然后它将被使用,如果它不存在,那么我们可以动态创建这个控制器并inheritanceBase::StudentsController
。
这是可能的吗? 如果是这样,那么我将如何实施呢?
使用这种方式定义路由:
%w[math english].each do |subject| scope "/#{subject}" do begin "#{subject.camelcase}::StudentsController".constantize resources :students, controller: "#{subject}::students", only: :index rescue resources :students, controller: "base::students", only: :index end end end
rake routes
输出:
students GET /math/students(.:format) base::students#index GET /english/students(.:format) english::students#index
如果有english / students_controller.rb和math / students_controller。 缺席。
重申您的要求:
- 每个主题/资源对的最小声明
- 如果存在,使用专用控制器(
Math::StudentsController
),否则使用基本控制器(StudentsController
)
Rails希望每个路由都有一个专用的控制器,并没有真正的方法来支持第二个要求。 所以,我就是这样做的:
Dynamicroutes::Application.routes.draw do SUBJECTS = [ "math", "english", "chemistry" ] RESOURCES = [ "assignments", "students" ] class DedicatedSubjectResourceControllerConstraint def initialize(subject, resource) @subject = subject @resource = resource end def matches?(request) begin defined?("#{@subject.capitalize}::#{@resource.capitalize}") return true rescue NameError Rails.logger.debug "No such class: #{@subject.capitalize}::#{@resource.capitalize}" return false end end end class ValidSubjectConstraint def matches?(request) return SUBJECTS.include?(request.path_parameters[:subject]) end end SUBJECTS.each do |subject| RESOURCES.each do |resource| namespace subject, :constraints => DedicatedSubjectResourceControllerConstraint.new(subject, resource) do resources resource end end end RESOURCES.each do |resource| scope "/:subject", :constraints => ValidSubjectConstraint.new do resources resource end end end
这听起来像是const_missing
。 如果你想做的是
创建一个Base :: StudentsController
如果,假设Math :: StudentsController存在
然后它将被使用
如果它不存在,那么我们可以动态创建这个控制器并inheritanceBase :: StudentsController
您可以通过添加动态常量查找( const_missing
)和带inheritance的动态常量定义( Object.const_set
)来实现。
我想象这样的事情; 通过一些调整和更严格的检查,将工作:
# initializers/dynamic_controllers.rb class ActionDispatch::Routing::RouteSet SUBJECTS = [ "math", "english", "chemistry" ] def const_missing(name, *args, &block) if SUBJECTS.any?{ |subject| name.include? subject.uppercase } Object.const_set name, Class.new(Base::StudentsController) else super end end end
这将为Dynamicroutes::Application.routes
inheritance的ActionDispatch::Routing::RouteSet
添加动态常量查找,因此Dynamicroutes::Application.routes.draw
未定义常量将生成从Base::StudentsController
子类化的相应类。
我相信这样做会:
%w[math english].each do |subject| namespace subject.to_sym do resources :students end end match ':subject/students(/:action(/:id))' => 'base/students'
通过这些组合路线, /math/students
去Math::StudentsController
, /english/students/
去English::StudentsController
,所有其他科目(例如/physics/students
和/cs/students
)去Base::StudentsController
。
我认为这正是您想要的,只需在原始解决方案中添加一行代码。
所有路由助手(如resources
, scope
等)都只是应用程序路由中的函数。 您可以按如下方式定义自定义函数:
YourApplication.routes.draw do # Let's define a custom method that you're going to use for your specific needs def resources_with_fallback(*args, &block) target_module = @scope[:module].camelize.constantize target_controller = "#{args.first.to_s}_controller".camelize fallback_controller = args.last.delete(:fallback).to_s.camelize.constantize # Create the target controller class # using fallback_controller as the superclass # if it doesn't exist unless target_module.const_defined?(target_controller) target_module.const_set target_controller, Class.new(fallback_controller) end # Call original resources method resources *args, &block end # Now go ahead and define your routes! namespace "test" do namespace "new" do # Use our custom_resources function and pass a fallback parameter custom_resources :photos, :fallback => 'base/some_controller' end end end
我在Rails 3.2中对此进行了测试,但它在所有3.x版本中同样适用。
我在任何地方都没有包括空检查或begin/rescue
块。 由于您将仅在需要时使用此自定义函数,我假设您将传递正确且必要的参数。 如果说你传递了一个不存在的fallback
控制器,我宁愿路由解析因exception而失败,而不是试图处理它。
编辑:函数参数中的错字
编辑2:在函数参数中忘记&block
编辑3:将“_controller”添加到target_controller
变量
我最终在ActionDispatch::Routing::RouteSet::Dispatcher.controller_reference
编写了一些自定义逻辑。 我试图查找给定控制器所需的所有常量,如果它们丢失则创建它们。 这段代码是完美的FAR所以请随时编辑w /改进。
class ActionDispatch::Routing::RouteSet::Dispatcher private def controller_reference(controller_param) const_name = @controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller" obj = Object const_name.split('::').each do |cn| begin obj = obj.const_get(cn) rescue if obj == Object obj = obj.const_set(cn, Class.new(ApplicationController)) else puts "Creating #{obj}::#{cn} based on Generic::#{cn}" obj = obj.const_set(cn, Class.new("Generic::#{cn}".constantize)) end end end ActiveSupport::Dependencies.constantize(const_name) end end