项目

常规

个人资料

操作

插件内部

本页将用作存储Redmine插件开发信息的中心位置。

覆盖Redmine核心

您可以在Redmine中覆盖视图,但不能覆盖控制器或模型。以下是在尝试覆盖一个虚构的插件MyPlugin的控制器(或模型)和视图时,Redmine/Rails的工作方式。

控制器(或模型)

  1. Rails引导并加载所有其框架
  2. Rails开始加载插件中的代码
  3. Rails在MyPlugin中找到IssueController,并看到它定义了一个show操作
  4. Rails加载所有其他插件
  5. Rails然后从../app加载应用程序
  6. Rails再次找到IssueController,并看到它也定义了一个show操作
  7. Rails(或Ruby)用../app中的操作覆盖了插件中的show操作
  8. Rails完成加载并提供服务请求

视图

视图加载非常相似,但有一个小的区别(因为Redmine对Engines的补丁)

  1. Rails引导并加载所有其框架
  2. Rails开始加载插件中的代码
  3. Rails在../vendor/plugins/my_plugin/app/views中找到一个视图目录,并将其预附加到视图路径上
  4. Rails加载所有其他插件
  5. Rails然后从../app加载应用程序
  6. Rails完成加载并提供服务请求
  7. 请求进来,需要渲染一个视图
  8. Rails查找匹配的模板,并由于它被预附加到视图路径上,所以加载插件的模板
  9. 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 时,代码路径如下:

  1. Redmine 核心调用 UsersHelper#user_settings_tabs
  2. UsersHelper#user_settings_tabs 运行(实际上是 UsersHelper#user_settings_tabs_with_rate_tab
  3. UsersHelper#user_settings_tabs_with_rate_tab 调用原始的 UsersHelper#user_settings_tabs(重命名为 UsersHelper#user_settings_tabs_without_rate_tab
  4. 结果中添加了一个新的 Hash
  5. 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 插件”中的实现
  1. http://github.com/edavis10/redmine_kanban/blob/000cf175795c18033caa43082c4e4d0a9f989623/init.rb#L10
  2. 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> 作为下拉标签。

参考

Jakob Fix 更新 近 2 年前 · 24 次修订