我对新的Rails应用程序有一些奇怪的要求。我需要构建一个应用程序,其中所有路由都在多个名称空间中定义(让我解释一下)。我希望有一个应用程序,其中学校科目(数学,英语等)是名称空间:
%w[math english].each do |subject|
namespace subject.to_sym do
resources :students
end
end
这很好,它可以工作,但它需要我为每个主题创建一个命名空间StudentsController
,这意味着如果我添加一个新主题,那么我需要创建一个新的控制器。
我想要的是创建一个Base::StudentsController
如果,假设存在Math::StudentsController
那么它将被使用,如果它不存在,那么我们可以动态创建这个控制器并继承来自Base::StudentsController
。
这是可能的吗?如果是这样,那么我将如何实施呢?
答案 0 :(得分: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。没有。
答案 1 :(得分: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
和{{ 1}})转到/cs/students
。
我认为 完全 您想要什么,只需在原始解决方案中添加一行代码。
答案 2 :(得分:3)
重申您的要求:
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
答案 3 :(得分: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
子类化的相应类。
答案 4 :(得分: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
变量
答案 5 :(得分:1)
我最终在ActionDispatch::Routing::RouteSet::Dispatcher.controller_reference
中编写了一些自定义逻辑。我试图查找给定控制器所需的所有常量,如果它们丢失则创建它们。此代码 FAR 非常完美,因此请随时修改/改进。
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