插件内部¶
本页将用作存储Redmine插件开发信息的中心位置。
覆盖Redmine核心¶
您可以在Redmine中覆盖视图,但不能覆盖控制器或模型。以下是在尝试覆盖一个虚构的插件MyPlugin
的控制器(或模型)和视图时,Redmine/Rails的工作方式。
控制器(或模型)¶
- Rails引导并加载所有其框架
- Rails开始加载插件中的代码
- Rails在MyPlugin中找到
IssueController
,并看到它定义了一个show
操作 - Rails加载所有其他插件
- Rails然后从../app加载应用程序
- Rails再次找到
IssueController
,并看到它也定义了一个show
操作 - Rails(或Ruby)用../app中的操作覆盖了插件中的
show
操作 - Rails完成加载并提供服务请求
视图¶
视图加载非常相似,但有一个小的区别(因为Redmine对Engines的补丁)
- Rails引导并加载所有其框架
- Rails开始加载插件中的代码
- Rails在../vendor/plugins/my_plugin/app/views中找到一个视图目录,并将其预附加到视图路径上
- Rails加载所有其他插件
- Rails然后从../app加载应用程序
- Rails完成加载并提供服务请求
- 请求进来,需要渲染一个视图
- Rails查找匹配的模板,并由于它被预附加到视图路径上,所以加载插件的模板
- Rails渲染插件的视图
由于通过包括模块的方式很容易扩展模型和控制器,Redmine不应该(实际上也没有)维护覆盖核心模型和/或控制器的API。另一方面,视图很棘手(因为Rails魔法),因此覆盖它们的API更有用(因此已在Redmine中实现)。
要覆盖现有的Redmine核心视图,只需创建一个与../app/views/中的名称完全相同的视图文件即可。例如,要覆盖项目索引页,请向../vendor/plugins/my_plugin/app/views/projects/index.html.erb中添加一个文件。
扩展Redmine核心¶
如上所述:您很少想覆盖模型/控制器。相反,您应该- 向模型/控制器添加新方法或
- 包装现有方法。
添加新方法¶
在Eric Davis的预算插件中可以找到一个添加新方法的快速示例。在这里,他向Issue添加了一个名为deliverable_subject
的新方法,并声明了一个关系。
module IssuePatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
end
module InstanceMethods
# Wraps the association to get the Deliverable subject. Needed for the
# Query and filtering
def deliverable_subject
unless self.deliverable.nil?
return self.deliverable.subject
end
end
end
end
包装现有方法¶
注意!
在 Rails 5 中已弃用 alias_method_chain 模式,因此此技术仅适用于低于 4.0.0 的 Redmine 版本。
以下是一个 包装现有方法 的快速示例,可以在 Eric Davis 的 Rate 插件 中找到。在这里,他使用 alias_method_chain
将钩子插入 UsersHelper 并包装 user_settings_tabs
方法。因此,当 Redmine 核心调用 user_settings_tabs
时,代码路径如下:
- Redmine 核心调用
UsersHelper#user_settings_tabs
UsersHelper#user_settings_tabs
运行(实际上是UsersHelper#user_settings_tabs_with_rate_tab
)UsersHelper#user_settings_tabs_with_rate_tab
调用原始的UsersHelper#user_settings_tabs
(重命名为UsersHelper#user_settings_tabs_without_rate_tab
)- 结果中添加了一个新的 Hash
UsersHelper#user_settings_tabs_with_rate_tab
将组合后的结果返回给 Redmine 核心,然后进行渲染
module RateUsersHelperPatch
def self.included(base) # :nodoc:
base.send(:include, InstanceMethods)
base.class_eval do
alias_method_chain :user_settings_tabs, :rate_tab
end
end
module InstanceMethods
# Adds a rates tab to the user administration page
def user_settings_tabs_with_rate_tab
tabs = user_settings_tabs_without_rate_tab
tabs << { :name => 'rates', :partial => 'users/rates', :label => :rate_label_rate_history}
return tabs
end
end
end
需要注意的是,这种包装只能针对每个方法进行一次。如果多个插件使用此技巧,则只有最后一个 alias_method_chain
评估是有效的,之前的所有评估都将被忽略。
alias_method_chain
是一个相当高级的方法,但它也非常强大。
在 Redmine 插件中使用 Rails 回调¶
当您想挂钩到所有保存/创建的问题时,您最好使用 Rails 回调 而不是 Redmine 钩子。主要原因是在创建新问题时不触发:controller_issues_edit_before_save
钩子。例如,请参阅 Eric Davis 的“Kanban 插件”中的实现
- http://github.com/edavis10/redmine_kanban/blob/000cf175795c18033caa43082c4e4d0a9f989623/init.rb#L10
- http://github.com/edavis10/redmine_kanban/blob/000cf175795c18033caa43082c4e4d0a9f989623/lib/redmine_kanban/issue_patch.rb#L13
这将确保每次保存(新或更新)问题时都会运行 issue.update_kanban_from_issue
。
如果您只想挂钩到新问题,则可以使用 before_create
回调而不是 after_save
回调。如果您想确保在您的代码执行之前问题确实已成功保存,则最好使用 after_create
-回调。
在 MyPage 中挂钩¶
常见问题解答¶
- 为什么我的块的下拉选择没有本地化?下拉框中的条目名称根据惯例由插件的语言文件中的条目组成。此条目必须与“我的网站”块文件名相同,例如 redmine/vendor/plugins/<myplugin_folder>/app/views/my/blocks/<myblocks_view_file_name>.erb。因此,您需要在您的语言文件中添加一行 "<myblocks_view_file_name>: <在此处输入 my 块配置中的下拉项翻译>",例如 redmine/vendor/plugins/<myplugin_folder>/config/locale/en.yml。
如果此字符串未在语言文件中定义,则始终使用不带扩展名的文件名 <myblocks_view_file_name> 作为下拉标签。