【问题标题】:Controlling routes loading order from Engines从引擎控制路线加载顺序
【发布时间】:2011-08-02 16:25:56
【问题描述】:

所以在 Rails3 中,引擎带有自己的模型/控制器/视图,当然还有路由。现在的问题是:如何确保引擎路由将在应用程序路由和所有其他存在的引擎之前(或之后)加载?

这是我的 Rails 应用程序路由示例:

match '*(path)', :to => 'foo_controller#bar_action'

还有我的引擎:

match '/news', :to => 'bar_controller#foo_action'

因此默认情况下,引擎路由将在应用程序路由之后加载。这意味着由于我的应用程序中的包罗万象的路线,无法访问引擎路线。如何强制引擎路由首先(或最后)加载?

【问题讨论】:

标签: ruby-on-rails-3


【解决方案1】:

你想做的事情有点困难。如前所述,引擎路由是在应用程序路由之后加载的,覆盖此行为可能会出现问题。我能想到几件事,你可以试试。

在路由路径初始化器之后使用初始化器

在 rails 源代码中的 engine.rb 中有一个初始化程序,实现您所追求的一种方法是尝试挂钩它处理的功能。默认情况下,初始化程序如下所示:

initializer :add_routing_paths do |app|
  paths.config.routes.to_a.each do |route|
    app.routes_reloader.paths.unshift(route) if File.exists?(route)
  end
end

本质上,这应该获取 Rails 知道的所有路由文件的路径,并尝试将它们添加到路由重新加载器(如果更改了路由文件,它会自动为您重新加载路由文件)。您可以定义另一个初始化程序在此初始化程序之后立即执行,然后您将检查存储在路由重新加载器中的路径,拉出属于您的引擎的路径,将其从路径数组中删除并将其重新插入,但在最后的路径数组。所以,在你的config/application.rb

class Application < Rails::Application
  initializer :munge_routing_paths, :after => :add_routing_paths do |app|
    engine_routes_path = app.routes_reloader.paths.select{|path| path =~ /<regex that matches path to my engine>/}.first
    app.routes_reloader.paths.delete(engine_routes_path)
    app.routes_reloader.paths << engine_routes_path
  end
end

这可能有效,也可能无效,无论哪种方式我都不太推荐它,它不是特别优雅(即丑陋的 hack 玩 Rails 的胆量)。

使用 Rails 3.1

这可能不是一个选项,但如果是,我可能会选择这个。在 Rails 3.1 中,您可以拥有 2 种不同类型的引擎,完整的和可安装的(这里是 an SO question talking about some of the differences)。但从本质上讲,您会将您的引擎更改为可挂载引擎,可挂载引擎中的路由是命名空间的,您可以将它们显式包含在主应用程序的路由文件中,例如:

Rails.application.routes.draw do
  mount MyEngine::Engine => "/news"
end

您还可以确定安装的引擎路线并执行各种其他花哨的操作(更多信息here)。长话短说,如果您可以转到 3.1,那么这就是使用的方法。

将路由从您的引擎动态插入到您的主应用中

目前最知名的 Rails 引擎之一是 Devise。现在,devise 是一个引擎,它可能会为您的应用程序添加相当多的路由,但如果您查看 devise 源代码,您会发现它实际上根本没有 config/routes.rb 文件!这是因为设计动态地将其路由优势添加到您的主应用程序的routes.rb 文件中。

当您运行 devise 附带的模型生成器时,生成器会做的一件事是在您的 routes.rb 文件顶部添加一行,例如 devise_for :model,紧跟在 Rails.application.routes.draw do 行之后。因此,在您执行生成器以创建用户模型后,您的 route.rb 看起来与此类似:

Rails.application.routes.draw do
  devise_for :users
  ...
end

现在,devise_for 是一种神奇的方法,它是 devise 的一部分(在 lib/devise/rails/routes.rb 中),但本质上它会根据您生成的模型创建一堆我们都知道的常规路线。

我们需要知道的是,如何设计将这一行插入到应用程序routes.rb 文件中,然后我们可以在我们的引擎中编写一个生成器,它将我们的任何路由插入主应用程序的顶部routes.rb文件。为此,我们查看lib/generators/devise/devise_generator.rb。在add_devise_routes 方法中,最后一行是route devise_routeRoute 是一个 Thor 操作,它将传递给它的字符串插入到主应用程序的 routes.rb 文件中。所以我们可以编写自己的生成器并做类似的事情,例如:

class MyCrazyGenerator < Rails::Generators::NamedBase
  ...
  def add_my_crazy_routes
    my_route  = "match '/news', :to => 'bar_controller#foo_action'"
    route my_route
  end
end

当然,您需要确保所有生成器基础设施都到位,但这就是它的本质。 Devise 是由一些非常聪明的 Rails 家伙编写的,并且被很多人使用,模仿他们的工作可能是一个很好的方法。在我建议的三件事中,这件事是我处理你的问题的方式(考虑到迁移到 rails 3.1 可能不是一种选择)。

【讨论】:

  • 这很棒。感谢您努力回答这个问题。
  • 我放弃了我的梦想并使用了设计方法而不是猴子修补实际的路由代码,最终它更加灵活,尤其是当您将东西包装在自定义范围中时。
【解决方案2】:

现在有同样的问题。我想出的另一个不是特别优雅但安全的解决方案是在你的 gem 文件的末尾添加另一个引擎 gem,只包含 catch all 路线,没有别的。

编辑:实际上,与直觉相反,路由 gem 需要所有其他引擎 gem 之前列出,才能最后加载其路由。

【讨论】:

  • 是的。它基本上是随机播放宝石,直到它起作用为止。对于 gems 来说,提供一个在你的 routes.rb 中显式调用的方法会更好。这样你就可以控制你想要这些路线的位置(如果有的话)。
【解决方案3】:

我不是专家,但我昨天在玩sferik/rails_admin 时遇到了他们的路由解决方案。通过他们的rails_admin:install rake 任务,他们通过在文件开头添加mount RailsAdmin::Engine =&gt; '/admin', :as =&gt; 'rails_admin' 来直接修改您的config/routes.rb 文件,因此他们确定他们的路线始终具有最高优先级。那个mount方法是指自己的routes.rb:

RailsAdmin::Engine.routes.draw do
  # Prefix route urls with "admin" and route names with "rails_admin_"
  scope "history", :as => "history" do
    controller "history" do
      [...]
    end
  end
end

这是你的解决方案吗?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-10-07
    • 2012-03-25
    • 1970-01-01
    • 2021-04-25
    • 1970-01-01
    • 2012-10-30
    • 2021-10-08
    相关资源
    最近更新 更多