动态命名空间控制器在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。 缺席。

重申您的要求:

  1. 每个主题/资源对的最小声明
  2. 如果存在,使用专用控制器( 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.routesinheritance的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/studentsMath::StudentsController/english/students/English::StudentsController ,所有其他科目(例如/physics/students/cs/students )去Base::StudentsController

我认为这正是您想要的,只需在原始解决方案中添加一行代码。

所有路由助手(如resourcesscope等)都只是应用程序路由中的函数。 您可以按如下方式定义自定义函数:

 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