最近给公司的 redmine 写了个提高 issue 流程的自动化程度插件。
redmine 官网上的开发者文档并不多,即使看完后,许多时候你也不知道接下来需要怎么做。好在 redmine 是用“约定高于配置”的 Rails 开发的,其命名相对规范。有必要时,可以猜测下对应的接口叫什么名字。实在摸不到门,阅读 redmine 和网上开源出来的 redmine 插件代码也是条路。
为了节省有同样需求的同行的时间,拯救我们日益凋亡的脑细胞,我在这里特意整理了开发过程中摸索出来的一些小技巧。
redmine 插件目录结构
.
├── app
│ ├── controllers
│ ├── models
│ └── views
│ └── settings
├── assets
│ └── javascripts
├── config
│ └── routes.rb
├── init.rb
├── lib
│ └── $plugin
其中 init.rb 是插件的入口。插件的逻辑主要放到 lib/$plugin 里面。其中 $plugin 应该是你的插件的名字。app 目录功能跟 Rails 的同名目录一样。
比如你想在 redmine 中引入新的模型,或者加个路由,可以分别到 app/models 和 app/controllers 添加。不过别忘了修改 config/routes.rb 中的规则。要加个邮件模板,也是到 app/views 下加。至于 assets 里的资源文件,会在应用启动时拷贝到 public/plugin_assets/$plugin 路径下。
redmine 主要模型
Rails 数据库表有两种,一种命名是模型名的复数;另一种是 模型A复数_模型B复数,存储两个模型间的关系。
所以当我们列出数据库上的表时,我们便得到一份 redmine 模型清单。
public | attachments | table | redmine
public | auth_sources | table | redmine
public | boards | table | redmine
public | changes | table | redmine
public | changeset_parents | table | redmine
public | changesets | table | redmine
public | changesets_issues | table | redmine
public | comments | table | redmine
public | custom_field_enumerations | table | redmine
public | custom_fields | table | redmine
public | custom_fields_projects | table | redmine
public | custom_fields_roles | table | redmine
public | custom_fields_trackers | table | redmine
public | custom_values | table | redmine
public | documents | table | redmine
public | email_addresses | table | redmine
public | enabled_modules | table | redmine
public | enumerations | table | redmine
public | groups_users | table | redmine
public | import_items | table | redmine
public | imports | table | redmine
public | issue_categories | table | redmine
public | issue_relations | table | redmine
public | issue_statuses | table | redmine
public | issues | table | redmine
public | journal_details | table | redmine
public | journals | table | redmine
public | member_roles | table | redmine
public | members | table | redmine
public | messages | table | redmine
public | news | table | redmine
public | open_id_authentication_associations | table | redmine
public | open_id_authentication_nonces | table | redmine
public | projects | table | redmine
public | projects_trackers | table | redmine
public | queries | table | redmine
public | queries_roles | table | redmine
public | repositories | table | redmine
public | roles | table | redmine
public | roles_managed_roles | table | redmine
public | schema_migrations | table | redmine
public | settings | table | redmine
public | time_entries | table | redmine
public | tokens | table | redmine
public | trackers | table | redmine
public | user_preferences | table | redmine
public | users | table | redmine
public | versions | table | redmine
public | watchers | table | redmine
public | wiki_content_versions | table | redmine
public | wiki_contents | table | redmine
public | wiki_pages | table | redmine
public | wiki_redirects | table | redmine
public | wikis | table | redmine
许多模型名都可以见名知意,比如 issue 对应中文界面上的 问题、tracker 对应中文界面上的 追踪。
不过有些命名和其中文译名差别不小,比如 journal 对应的是问题页面下的 说明、custom_field 对应的是 自定义属性。 这只能多在 rails console 或 pry 里试试看了。
hooks 和 patch
开发 redmine 插件基本靠两组 API,一个是 hook,另一个是 patch。 前者可以让你在 controller/view 特定的阶段插入自己的逻辑;后者可以覆盖掉原有的逻辑,抑或给 model 特定的阶段插入
对应的官方文档: hook:http://www.redmine.org/projects/redmine/wiki/Hooks patch:http://www.redmine.org/projects/redmine/wiki/Plugin_Internals
我就补充下文档没有的。
这两者需要在 init.rb 里完成初始化:
... require '$plugin/hooks' # 引入 `lib/$plugin/hooks.rb`,让 hooks 生效
... Rails.application.config.to_prepare do
# 打上 Monkey Patch
unless Issue.include?(AutoLicense::IssuePatch)
Issue.send(:include, AutoLicense::IssuePatch)
end
...
patch 主要的应用场景是覆盖掉 model 的 after_save 回调,触发自己的自动化逻辑。
def self.included(base)
base.send(:include, InstanceMethods)
base.class_eval do
after_save :after_save_trigger
end
end module InstanceMethods
def after_save_trigger
p self # 这里的 self 是具体的 model 实例
当然覆盖掉其他 Rails callback,比如 xx_create/xx_destroy,也是挺有用的。
hook 主要的应用场景是在 view 或 controller 执行流程中嵌入自己的逻辑。比如在特定 controller action 中加代码:
class PluginControllerHookListener
|