Rails::Engine
允许您封装一个特定的 Rails 应用程序或功能子集,并与其他应用程序共享,或在一个更大的打包应用程序中共享。每一个 Rails::Application
只是一个引擎,这允许简单的功能和应用程序共享。
任何 Rails::Engine
也是一个 Rails::Railtie
,因此在 railties 中可用的相同方法(例如 rake_tasks 和 generators)以及配置选项也可以在引擎中使用。
创建 Engine
如果您希望一个 gem 作为引擎运行,您必须在插件的 lib
文件夹中为它指定一个 Engine
(类似于我们指定 Railtie
的方式)。
# lib/my_engine.rb
module MyEngine
class Engine < Rails::Engine
end
end
然后确保这个文件在您的 config/application.rb
(或您的 Gemfile
)中顶部加载,它会自动加载 app
中的模型、控制器和辅助方法,加载 config/routes.rb
中的路由,加载 config/locales/*/
中的语言环境,并在 lib/tasks/*/
中加载任务。
配置
与 railties 类似,引擎可以访问一个包含所有 railties 和应用程序共享的配置的 config 对象。此外,每个引擎可以访问 autoload_paths
、eager_load_paths
和 autoload_once_paths
设置,这些设置是针对该引擎的。
class MyEngine < Rails::Engine
# Add a load path for this specific Engine
config.autoload_paths << File.expand_path("lib/some/path", __dir__)
initializer "my_engine.add_middleware" do |app|
app.middleware.use MyEngine::Middleware
end
end
生成器
您可以使用 config.generators
方法为引擎设置生成器。
class MyEngine < Rails::Engine
config.generators do |g|
g.orm :active_record
g.template_engine :erb
g.test_framework :test_unit
end
end
您也可以使用 config.app_generators
为应用程序设置生成器。
class MyEngine < Rails::Engine
# note that you can also pass block to app_generators in the same way you
# can pass it to generators method
config.app_generators.orm :datamapper
end
路径
应用程序和引擎具有灵活的路径配置,这意味着您不需要将控制器放在 app/controllers
中,而可以在任何您觉得方便的地方。
例如,假设您想将控制器放在 lib/controllers
中。您可以将其设置为选项。
class MyEngine < Rails::Engine
paths["app/controllers"] = "lib/controllers"
end
您还可以从 app/controllers
和 lib/controllers
加载控制器。
class MyEngine < Rails::Engine
paths["app/controllers"] << "lib/controllers"
end
引擎中可用的路径是
class MyEngine < Rails::Engine
paths["app"] # => ["app"]
paths["app/controllers"] # => ["app/controllers"]
paths["app/helpers"] # => ["app/helpers"]
paths["app/models"] # => ["app/models"]
paths["app/views"] # => ["app/views"]
paths["lib"] # => ["lib"]
paths["lib/tasks"] # => ["lib/tasks"]
paths["config"] # => ["config"]
paths["config/initializers"] # => ["config/initializers"]
paths["config/locales"] # => ["config/locales"]
paths["config/routes.rb"] # => ["config/routes.rb"]
end
Application
类在此集合中添加了几个路径。与您的 Application
一样,app
下的所有文件夹都会自动添加到加载路径中。例如,如果您有一个 app/services
文件夹,它将默认添加。
端点
引擎也可以是一个 Rack
应用程序。如果您有一个您想提供一些 Engine
功能的 Rack
应用程序,这将非常有用。
要做到这一点,请使用 ::endpoint
方法。
module MyEngine
class Engine < Rails::Engine
endpoint MyRackApplication
end
end
现在您可以在应用程序的路由中挂载引擎。
Rails.application.routes.draw do
mount MyEngine::Engine => "/engine"
end
中间件堆栈
由于引擎现在可以是一个 Rack
端点,它也可以有一个中间件堆栈。用法与 Application
中完全相同。
module MyEngine
class Engine < Rails::Engine
middleware.use SomeMiddleware
end
end
路由
如果您没有指定端点,路由将用作默认端点。您可以像使用应用程序的路由一样使用它们。
# ENGINE/config/routes.rb
MyEngine::Engine.routes.draw do
get "/" => "posts#index"
end
挂载优先级
请注意,现在您的应用程序中可能有多个路由器,最好避免通过多个路由器传递请求。考虑这种情况
Rails.application.routes.draw do
mount MyEngine::Engine => "/blog"
get "/blog/omg" => "main#omg"
end
MyEngine
挂载在 /blog
上,/blog/omg
指向应用程序的控制器。在这种情况下,对 /blog/omg
的请求将通过 MyEngine
,如果 Engine
的路由中没有这样的路由,它将被调度到 main#omg
。最好将它交换为
Rails.application.routes.draw do
get "/blog/omg" => "main#omg"
mount MyEngine::Engine => "/blog"
end
现在,Engine
只会收到没有被 Application
处理的请求。
Engine
名称
在某些地方会使用引擎的名称
-
路由:当您使用
mount(MyEngine::Engine => '/my_engine')
挂载Engine
时,它用作默认的:as
选项。 -
安装迁移的 rake 任务
my_engine:install:migrations
Engine
名称默认情况下根据类名设置。对于 MyEngine::Engine
,它将是 my_engine_engine
。您可以使用 engine_name
方法手动更改它。
module MyEngine
class Engine < Rails::Engine
engine_name "my_engine"
end
end
隔离的 Engine
通常,当您在引擎内部创建控制器、辅助方法和模型时,它们会被视为在应用程序本身内部创建的。这意味着应用程序的所有辅助方法和命名路由也将在您引擎的控制器中可用。
但是,有时您希望将引擎与应用程序隔离,尤其是当您的引擎有自己的路由器时。为此,您只需调用 ::isolate_namespace
。此方法要求您传递一个模块,所有控制器、辅助方法和模型都应该嵌套到该模块中。
module MyEngine
class Engine < Rails::Engine
isolate_namespace MyEngine
end
end
使用这样的引擎,MyEngine
模块中的所有内容都将与应用程序隔离。
考虑这个控制器
module MyEngine
class FooController < ActionController::Base
end
end
如果 MyEngine
引擎被标记为隔离的,FooController
只能访问 MyEngine
中的辅助方法,以及 MyEngine::Engine.routes
中的 url_helpers
。
隔离引擎中改变的另一件事是路由的行为。通常,当您对控制器进行命名空间化时,您还需要对相关的路由进行命名空间化。对于隔离的引擎,引擎的命名空间会自动应用,因此您不需要在路由中显式指定它。
MyEngine::Engine.routes.draw do
resources :articles
end
如果 MyEngine
被隔离,上面的路由将指向 MyEngine::ArticlesController
。您也不需要使用更长的 URL 辅助方法,例如 my_engine_articles_path
。相反,您应该只使用 articles_path
,就像您对主应用程序所做的那样。
为了使这种行为与框架的其他部分保持一致,隔离的引擎还会影响 ActiveModel::Naming
。在普通的 Rails 应用程序中,当您使用命名空间模型(例如 Namespace::Article
)时,ActiveModel::Naming
将使用前缀“namespace”生成名称。在隔离的引擎中,为了方便起见,前缀将被省略在 URL 辅助方法和表单字段中。
polymorphic_url(MyEngine::Article.new)
# => "articles_path" # not "my_engine_articles_path"
form_with(model: MyEngine::Article.new) do
text_field :title # => <input type="text" name="article[title]" id="article_title" />
end
此外,隔离的引擎将根据其命名空间设置自己的名称,因此 MyEngine::Engine.engine_name
将返回“my_engine”。它还会将 MyEngine.table_name_prefix
设置为“my_engine_”,这意味着例如 MyEngine::Article
将默认使用 my_engine_articles
数据库表。
在 Engine
外部使用引擎的路由
由于您现在可以在应用程序的路由中挂载引擎,因此您无法直接访问 Application
中的 Engine
的 url_helpers
。当您在应用程序的路由中挂载引擎时,会创建一个特殊的辅助方法,允许您这样做。考虑这样的场景
# config/routes.rb
Rails.application.routes.draw do
mount MyEngine::Engine => "/my_engine", as: "my_engine"
get "/foo" => "foo#index"
end
现在,您可以在应用程序内部使用 my_engine
辅助方法。
class FooController < ApplicationController
def index
my_engine.root_url # => /my_engine/
end
end
还有一个 main_app
辅助方法,它允许您在 Engine 中访问应用程序的路由。
module MyEngine
class BarController
def index
main_app.foo_path # => /foo
end
end
end
请注意,挂载给出的 :as
选项将 engine_name
作为默认值,因此大多数情况下您可以简单地省略它。
最后,如果您想使用 polymorphic_url
生成指向引擎路由的 URL,您还需要传递引擎辅助方法。假设您想创建一个指向引擎路由之一的表单。您需要做的就是将辅助方法作为第一个元素传递给具有 URL 属性的数组。
form_with(model: [my_engine, @user])
这段代码将使用 my_engine.user_path(@user)
来生成正确的路由。
隔离引擎的辅助方法
有时您可能想隔离一个引擎,但使用为它定义的辅助方法。如果您想共享一些特定的辅助方法,您可以将它们添加到 ApplicationController 中的应用程序辅助方法中。
class ApplicationController < ActionController::Base
helper MyEngine::SharedEngineHelper
end
如果您想包含引擎的所有辅助方法,您可以使用引擎实例上的 helper 方法。
class ApplicationController < ActionController::Base
helper MyEngine::Engine.helpers
end
它将包含引擎目录中的所有辅助方法。请注意,这不会包含在控制器中使用 helper_method 或其他类似解决方案定义的辅助方法,只有在辅助方法目录中定义的辅助方法才会被包含。
迁移和种子数据
引擎可以有自己的迁移。迁移的默认路径与应用程序中的完全相同:db/migrate
。
要在应用程序中使用引擎的迁移,您可以使用下面的 rake 任务,它将它们复制到应用程序的目录中。
$ rake ENGINE_NAME:install:migrations
请注意,如果应用程序中已存在相同名称的迁移,一些迁移可能会被跳过。在这种情况下,您必须决定是保留该迁移,还是重命名应用程序中的迁移并重新运行迁移复制。
如果您的引擎有迁移,您可能还希望在 db/seeds.rb
文件中为数据库准备数据。您可以使用 load_seed
方法加载这些数据,例如。
MyEngine::Engine.load_seed
加载优先级
为了改变引擎的优先级,您可以在主应用程序中使用 config.railties_order
。它将影响加载视图、辅助方法、资产以及与引擎或应用程序相关的其他所有文件的优先级。
# load Blog::Engine with highest priority, followed by application and other railties
config.railties_order = [Blog::Engine, :main_app, :all]
- 类 Rails::Engine::Configuration
- 类 Rails::Engine::LazyRouteSet
- 类 Rails::Engine::Railties
- 类 Rails::Engine::Updater
- A
- C
- E
- F
- H
- I
- L
- N
- R
属性
[RW] | 调用来源 | |
[RW] | 隔离 | |
[RW] | 隔离? |
类公共方法
endpoint(endpoint = nil) 链接
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 379 def endpoint(endpoint = nil) @endpoint ||= nil @endpoint = endpoint if endpoint @endpoint end
find(path) 链接
查找给定路径的引擎。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 424 def find(path) expanded_path = File.expand_path path Rails::Engine.subclasses.each do |klass| engine = klass.instance return engine if File.expand_path(engine.root) == expanded_path end nil end
find_root(from) 链接
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 375 def find_root(from) find_root_with_flag "lib", from end
inherited(base) 链接
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 361 def inherited(base) unless base.abstract_railtie? Rails::Railtie::Configuration.eager_load_namespaces << base base.called_from = begin call_stack = caller_locations.map { |l| l.absolute_path || l.path } File.dirname(call_stack.detect { |p| !p.match?(%r[railties[\w.-]*/lib/rails|rack[\w.-]*/lib/rack]) }) end end super end
isolate_namespace(mod) 链接
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 385 def isolate_namespace(mod) engine_name(generate_railtie_name(mod.name)) config.default_scope = { module: ActiveSupport::Inflector.underscore(mod.name) } self.isolated = true unless mod.respond_to?(:railtie_namespace) name, railtie = engine_name, self mod.singleton_class.instance_eval do define_method(:railtie_namespace) { railtie } unless mod.respond_to?(:table_name_prefix) define_method(:table_name_prefix) { "#{name}_" } ActiveSupport.on_load(:active_record) do mod.singleton_class.redefine_method(:table_name_prefix) do "#{ActiveRecord::Base.table_name_prefix}#{name}_" end end end unless mod.respond_to?(:use_relative_model_naming?) class_eval "def use_relative_model_naming?; true; end", __FILE__, __LINE__ end unless mod.respond_to?(:railtie_helpers_paths) define_method(:railtie_helpers_paths) { railtie.helpers_paths } end unless mod.respond_to?(:railtie_routes_url_helpers) define_method(:railtie_routes_url_helpers) { |include_path_helpers = true| railtie.routes.url_helpers(include_path_helpers) } end end end end
new() 链接
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 440 def initialize @_all_autoload_paths = nil @_all_load_paths = nil @app = nil @config = nil @env_config = nil @helpers = nil @routes = nil @app_build_lock = Mutex.new super end
实例公共方法
app() 链接
返回此引擎的底层 Rack
应用程序。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 516 def app @app || @app_build_lock.synchronize { @app ||= begin stack = default_middleware_stack config.middleware = build_middleware.merge_into(stack) config.middleware.build(endpoint) end } end
call(env) 链接
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 533 def call(env) req = build_request env app.call req.env end
config() 链接
定义引擎的配置对象。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 552 def config @config ||= Engine::Configuration.new(self.class.find_root(self.class.called_from)) end
eager_load!() 链接
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 490 def eager_load! # Already done by Zeitwerk::Loader.eager_load_all. By now, we leave the # method as a no-op for backwards compatibility. end
endpoint() 链接
返回此引擎的端点。如果没有注册端点,则默认使用 ActionDispatch::Routing::RouteSet
。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 528 def endpoint self.class.endpoint || routes end
env_config() 链接
定义每次调用时添加的额外 Rack
环境配置。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 539 def env_config @env_config ||= {} end
helpers() 链接
返回包含引擎中定义的所有帮助器的模块。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 500 def helpers @helpers ||= begin helpers = Module.new AbstractController::Helpers.helper_modules_from_paths(helpers_paths).each do |mod| helpers.include(mod) end helpers end end
helpers_paths() 链接
返回所有注册的帮助器路径。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 511 def helpers_paths paths["app/helpers"].existent end
load_console(app = self) 链接
加载控制台并调用注册的钩子。有关更多信息,请查看 Rails::Railtie.console
。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 454 def load_console(app = self) run_console_blocks(app) self end
load_generators(app = self) 链接
加载 Rails 生成器并调用注册的钩子。有关更多信息,请查看 Rails::Railtie.generators
。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 476 def load_generators(app = self) require "rails/generators" run_generators_blocks(app) Rails::Generators.configure!(app.config.generators) self end
load_runner(app = self) 链接
加载 Rails 运行器并调用注册的钩子。有关更多信息,请查看 Rails::Railtie.runner
。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 461 def load_runner(app = self) run_runner_blocks(app) self end
load_seed() 链接
从 db/seeds.rb 文件加载数据。它可用于加载引擎的种子数据,例如:
Blog::Engine.load_seed
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 560 def load_seed seed_file = paths["db/seeds.rb"].existent.first run_callbacks(:load_seed) { load(seed_file) } if seed_file end
load_server(app = self) 链接
调用注册的服务器钩子。有关更多信息,请查看 Rails::Railtie.server
。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 485 def load_server(app = self) run_server_blocks(app) self end
load_tasks(app = self) 链接
加载 Rake 和轨道任务,并调用注册的钩子。有关更多信息,请查看 Rails::Railtie.rake_tasks
。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 468 def load_tasks(app = self) require "rake" run_tasks_blocks(app) self end
railties() 链接
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 495 def railties @railties ||= Railties.new end
routes(&block) 链接
定义此引擎的路由。如果向 routes 提供代码块,则将其追加到引擎。
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 545 def routes(&block) @routes ||= config.route_set_class.new_with_config(config) @routes.append(&block) if block_given? @routes end
实例私有方法
load_config_initializer(initializer) 链接
源代码: 显示 | 在 GitHub 上查看
# File railties/lib/rails/engine.rb, line 691 def load_config_initializer(initializer) # :doc: ActiveSupport::Notifications.instrument("load_config_initializer.railties", initializer: initializer) do load(initializer) end end