许多插件在 plugin.rb 中包含大量类定义,或者使用 require_relative 来加载 Ruby 文件。这可行,但存在一些缺点:
- 开发中无法自动重载更改。任何更改都需要完全重启服务器
- 正确设置
require调用顺序可能很麻烦 - 如果它们在
after_initialize块之外被require,那么其他自动加载的类/模块可能不可用
有一个解决方案!插件可以利用标准的 Rails 自动加载系统。对于新插件,您所需的一切都在 plugin-skeleton 中定义。本主题介绍如何改编现有插件并扩展配置。
1. 为您的插件定义一个模块和 Rails::Engine
在 plugin.rb 中,为您的插件定义一个具有唯一 PLUGIN_NAME 的模块,并添加一个 require_relative 行来加载我们将要创建的引擎文件。
# name: my-plugin-name
# ...
module ::MyPluginModule
PLUGIN_NAME = "my-plugin-name"
end
require_relative "lib/my_plugin_module/engine"
现在创建 {plugin}/lib/my_plugin_module/engine.rb:
module ::MyPluginModule
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
isolate_namespace MyPluginModule
end
end
需要注意的重要事项:
- 在
plugin.rb中,您必须在模块名称前加上::,以将其定义在根命名空间下(否则,它将被定义在Plugin::Instance下) require_relative "lib/.../engine"必须位于plugin.rb文件的根目录中,而不是在after_initialize块内- 将引擎放在
lib/下的单独文件中很重要。直接在plugin.rb文件中定义它将不起作用。(Rails 使用lib/目录的存在来确定引擎的根目录) - 文件路径应包含模块名称,根据 Zeitwerk 规则
engine_name用作引擎定义的 rake 任务和任何路由的前缀(
Rails 文档)isolate_namespace有助于防止核心和插件之间发生泄漏(
Rails 文档)
2. 在正确的目录结构中定义 Ruby 文件
引擎现在将自动加载 {plugin}/app/{type}/* 中的所有文件。例如,我们可以定义一个控制器
{plugin}/app/controllers/my_plugin_module/examples_controller.rb
module ::MyPluginModule
class ExamplesController < ::ApplicationController
requires_plugin PLUGIN_NAME
def index
render json: { hello: "world" }
end
end
end
现在,当 Rails 中的任何内容尝试访问 ::MyPluginModule::MyController 时,它都会被自动加载。要进行测试,请尝试从 rails 控制台访问该类。
为了使自动加载正常工作,文件路径必须根据 Zeitwerk 定义的规则 匹配完整的模块/类层次结构。
3. 在插件引擎上定义路由
创建 {plugin}/config/routes.rb 文件
MyPluginModule::Engine.routes.draw do
get "/examples" => "examples#index"
# define routes here
end
Discourse::Application.routes.draw do
mount ::MyPluginModule::Engine, at: "my-plugin"
end
此文件将由引擎自动加载,并且更改将在不重启服务器的情况下生效。在这种情况下,控制器操作将在 /my-plugin/examples.json 处可用。
4. 添加更多自动加载路径
有时您可能希望引入其他可自动加载的 Ruby 文件目录。最常见的例子是插件中的 lib/ 目录。
修改您的引擎定义,将 lib/ 追加到引擎的自动加载路径:
class Engine < ::Rails::Engine
engine_name PLUGIN_NAME
isolate_namespace MyPluginModule
config.autoload_paths << File.join(config.root, "lib")
end
现在您可以定义一个 lib 模块,例如
{plugin}/lib/my_plugin_module/some_lib_module.rb
module ::MyPluginModule::SomeLibModule
end
现在,任何对 ::MyPluginModule::SomeLibModule 的引用都将自动从该文件加载模块。
5. 盈利!
所有这些文件现在都将被自动加载,无需任何显式的 require 调用。Rails 将自动检测更改,并在不重启服务器的情况下进行就地重新加载。
本文档受版本控制 - 在 github 上建议更改。