许多插件在 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 docs) -
isolate_namespace有助于防止核心和插件之间发生泄漏(
Rails docs)
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"
# 在此处定义路由
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 上建议更改。