6

我对一个新的 Rails 应用程序有一个有点奇怪的要求。我需要构建一个应用程序,其中所有路由都在多个命名空间中定义(让我解释一下)。我想要一个应用程序,其中学校科目(数学、英语等)是命名空间:

%w[math english].each do |subject|
  namespace subject.to_sym do
    resources :students
  end
end

这很好,而且有效,但它需要我StudentsController为每个主题创建一个命名空间,这意味着如果我添加一个新主题,那么我需要创建一个新控制器。

我想要的是创建一个Base::StudentsController,如果,假设Math::StudentsController存在,那么它将被使用,如果它不存在,那么我们可以动态创建这个控制器并从Base::StudentsController.

这是可能的吗?如果是这样,那么我将如何实施呢?

4

6 回答 6

3

我相信这会做到:

  %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

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

于 2013-05-12T19:11:20.857 回答
3

这听起来像是用于const_missing. 如果你想做的是

创建一个 Base::StudentsController

如果,假设 Math::StudentsController 存在

那么它将被使用

如果它不存在,那么我们可以动态创建这个控制器并从 Base::StudentsController 继承

您可以通过添加动态常量查找 ( const_missing) 和带有继承的动态常量定义() 来实现这一点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

这会将动态常量查找添加到ActionDispatch::Routing::RouteSet,从中Dynamicroutes::Application.routes继承,因此未定义Dynamicroutes::Application.routes.draw的常量将生成从Base::StudentsController.

于 2013-05-15T21:56:31.197 回答
3

以这种方式定义路由:

%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。缺席。

于 2013-05-09T19:30:03.317 回答
3

重申您的要求:

  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
于 2013-05-12T21:19:03.620 回答
1

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不存在的控制器,我宁愿路由解析失败并出现异常,而不是尝试处理它。

编辑:函数参数中的错字

编辑 2:忘记&block函数参数

编辑 3:将“_controller”添加到target_controller变量中

于 2013-05-16T20:56:43.223 回答
1

我最终将一些自定义逻辑写入ActionDispatch::Routing::RouteSet::Dispatcher.controller_reference. 我尝试查找给定控制器所需的所有常量,并在它们丢失时创建它们。此代码远非完美,因此请随时进行编辑以进行改进。

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
于 2013-05-17T13:10:23.450 回答