プラグイン インターナル » 履歴 » バージョン 9
Mitsuyoshi Yoshida, 2011/07/04 00:30
1 | 1 | Mitsuyoshi Yoshida | "Plugin Internals":http://www.redmine.org/projects/redmine/wiki/Plugin_Internals の日本語訳です。(結構、意訳しています。よく言えば超訳 ?) |
---|---|---|---|
2 | |||
3 | 2 | Mitsuyoshi Yoshida | {{>toc}} |
4 | |||
5 | 6 | Mitsuyoshi Yoshida | h1. プラグイン インターナル |
6 | 1 | Mitsuyoshi Yoshida | |
7 | プラグイン開発関連の情報はこのページに書いていきましょう。 |
||
8 | 7 | Mitsuyoshi Yoshida | (訳注 : このページは redmine.org のページの翻訳なので、実際にプラグイン開発関連の情報を追加したい場合は [[プラグイン Tips]] の方に追加してください) |
9 | 1 | Mitsuyoshi Yoshida | |
10 | |||
11 | h2. プラグインが動作する Redmine のバージョンを指定する。 |
||
12 | |||
13 | プラグインで Redmine 本体の機能を使ったり、Redmine のページを改造するようなプラグインを作ったりすると、ある特定のバージョンの Redmine でしかプラグインが動作しなくなることがあります。 |
||
14 | |||
15 | このような場合、プラグインが動作する Redmine のバージョンを指定する必要が出てきます。 "requires_redmine":http://rdoc.info/github/asoltys/redmine/master/Redmine/Plugin:requires_redmine メソッドを使えばこれが実現できます。(この機能は "#2162":http://www.redmine.org/issues/2162 で提案され、"r2042":http://www.redmine.org/projects/redmine/repository/revisions/2042 で実際に実装されました) |
||
16 | 3 | Mitsuyoshi Yoshida | このメソッドを使えば簡単、確実にプラグインが動作する Redmine のバージョンを指定することが出来ます。プラグインがロードされる際、 このメソッドを記述していると要求する Redmine のバージョンを満たしていない場合には、未対応のバージョンですとといったメッセージを出力してロードを中止します。 |
17 | 1 | Mitsuyoshi Yoshida | |
18 | 8 | Mitsuyoshi Yoshida | 例えば IIda さんの "Wiki extensions プラグイン":https://bitbucket.org/haru_iida/redmine_wiki_extensions/src/8e162f7a04bb/init.rb#cl-38 では以下のように使用されています。 |
19 | 1 | Mitsuyoshi Yoshida | |
20 | <pre><code class="ruby"> |
||
21 | Redmine::Plugin.register :redmine_wiki_extensions do |
||
22 | name 'Redmine Wiki Extensions plugin' |
||
23 | # : |
||
24 | version '0.3.5' |
||
25 | requires_redmine :version_or_higher => '1.1.0' |
||
26 | |||
27 | # : |
||
28 | </code></pre> |
||
29 | |||
30 | h2. Redmine の本体機能のオーバーロード |
||
31 | |||
32 | Rails は [[GuideRails|MVC構造]] になっています。プラグインで Redmine 本体の機能を変えようした時、MVC のうちビューの場合にはコントローラやモデルと違って Redmine 本体のものをプラグインのもので上書きするオーバーロードの方法をとることになります。 |
||
33 | |||
34 | コントローラやビューをプラグインで書き変えた場合に Redmine/Rails がどのような動作をとるか説明します。ここでのプラグインの名前は @MyPlugin@ としています。 |
||
35 | コントローラだけ説明していますが、モデルの場合も同じような流れになります。 |
||
36 | |||
37 | *コントローラ(モデル)* |
||
38 | |||
39 | # Rails の起動の開始 |
||
40 | # Rails フレームワークをロード |
||
41 | # 各プラグインのロード |
||
42 | ## MyPlugin 内で @IssueController@ を見つけると、その @show@ アクションの定義を見にいきます。 |
||
43 | # @<redmine_folder>/app@ から Rails アプリケーション(Redmine)をロード |
||
44 | ## そこで再度 IssueController を見つけ、アプリケーションの @show@ アクションの定義を見にいきます。 |
||
45 | ## ここでプラグインで定義された @show@ アクションはアプリケーションのものに上書きされてしまいます。これは Rails というよりも Ruby の仕様上そうなるようになっています。 |
||
46 | # Rails の起動が完了して、サーバが立ち上がる |
||
47 | |||
48 | *ビュー* |
||
49 | |||
50 | ビューの場合もコントローラとほぼ同じようにロードされますが、少し違うところがあります。これは Redmine のパッチ機能のためです。 |
||
51 | |||
52 | # Rails の起動の開始 |
||
53 | # Rails フレームワークをロード |
||
54 | # 各プラグインのロード |
||
55 | ## @<redmine_folder>/vendor/plugins/my_plugin/app/views@ 以下にディレクトリを見つけると、それを views のパッチの *先頭に追加* します。 |
||
56 | # @<redmine_folder>/app@ から Rails アプリケーション(Redmine)をロード |
||
57 | # Rails の起動が完了して、サーバが立ち上がる |
||
58 | # サーバに要求がきて、ビューの描画が必要になる |
||
59 | # Rails は要求されたアクションに合うテンプレートを探した後、プラグインのテンプレートをロード |
||
60 | これはプラグインのビューがパッチとして *先頭に追加* されていたためです。 |
||
61 | # Rails はプラグインのビューを表示 |
||
62 | |||
63 | 4 | Mitsuyoshi Yoshida | なぜ、 MVC のうちビューだけこのようになっているかというと、 モデルやコントローラの場合には Ruby のモジュールのインクルードを使えば、こちらは簡単に機能を拡張することが出来るからです。 |
64 | プラグインで Redmine 本体の機能を変えたい場合にも Redmine 本体のモデルやコントローラのメソッドなどは上書きするべきではありませし、実際そういった機能の API は Redmine では用意されていません。 |
||
65 | 1 | Mitsuyoshi Yoshida | しかし、ビューの場合には Redmine の本体の機能を上書きする方法をとることになります。Rails ではビューはモデルやコントローラと比べるとちょっとトリッキーな方法で機能が実現されいて、拡張するよりも書き換える機能の方が使い勝手がいいためです。 |
66 | |||
67 | 5 | Mitsuyoshi Yoshida | Redmine 本体の表示を変えるには @<redmine_folder>/app/views@ 以下のファイルと全く同じ名前のファイルをプラグインのディレクトリに置いておくだけです。そうするとそちらが表示の際に使われるようになります。例えばプロジェクトのインデックスページを書き換えたい場合、 @<redmine_folder>/vendor/plugins/my_plugin/app/views/projects/index.rhtml@ のファイルを作成します。 |
68 | 1 | Mitsuyoshi Yoshida | |
69 | |||
70 | h2. Redmine の本体機能の拡張 |
||
71 | |||
72 | 先ほどモデルやコントローラはオーバーロードしないと説明しましたが、まれに書き換えくなることはあります。そのような場合、かわりに次の方法をとります。 |
||
73 | |||
74 | * モデルやコントローラに新しいメソッドを追加する |
||
75 | * 既存のメソッドをラップする |
||
76 | |||
77 | |||
78 | h3. 新しいメソッドの追加 |
||
79 | |||
80 | 新しいメソッドを追加する方法の分かりやすい例は Eric Davi さんの "Budget plugin":https://github.com/edavis10/redmine-budget-plugin/blob/5076b1c88b57c2068aa92cdf694769dbd22d061a/lib/issue_patch.rb にあります。 |
||
81 | このプラグインではチケットモデルクラスに @deliverable_subject@ というメソッドを追加しています。 |
||
82 | |||
83 | <pre><code class="ruby"> |
||
84 | module IssuePatch |
||
85 | def self.included(base) # :nodoc: |
||
86 | base.send(:include, InstanceMethods) |
||
87 | end |
||
88 | |||
89 | module InstanceMethods |
||
90 | # Wraps the association to get the Deliverable subject. Needed for the |
||
91 | # Query and filtering |
||
92 | def deliverable_subject |
||
93 | unless self.deliverable.nil? |
||
94 | return self.deliverable.subject |
||
95 | end |
||
96 | end |
||
97 | end |
||
98 | end |
||
99 | </code></pre> |
||
100 | |||
101 | |||
102 | h3. 既存メソッドのラップ |
||
103 | |||
104 | 5 | Mitsuyoshi Yoshida | Eric Davis さんの "Rate plugin":https://github.com/edavis10/redmine_rate/blob/4666ddb10e1061ca3ef362735d0d264676b99024/lib/rate_users_helper_patch.rb には既存のメソッドをラップするいい例となる記述があります。 |
105 | 1 | Mitsuyoshi Yoshida | ここでは "alias_method_chain":http://blog.livedoor.jp/sasata299/archives/51166404.html を使って @UsersHelper@ の @user_settings_tabs@ メソッドをラップして、 DB にチケットを保存するタイミングで処理を追加しています。 |
106 | @user_settings_tabs@ が Redmine から呼ばれる時の流れは次のようになります。 |
||
107 | |||
108 | |||
109 | # Redmine 本体が UsersHelper#user_settings_tabs を呼び出す |
||
110 | 5 | Mitsuyoshi Yoshida | # user_settings_tabs が実行される (これは実際には user_settings_tabs_with_rate_tab です) |
111 | # user_settings_tabs_with_rate_tab がもともとの user_settings_tabs を呼び出す。(元のものは user_settings_tabs_without_rate_tab と名前が変更されています) |
||
112 | 1 | Mitsuyoshi Yoshida | # 元のメソッドの結果にプラグイン用のデータを追加 |
113 | 5 | Mitsuyoshi Yoshida | # user_settings_tabs_with_rate_tab は Redmine 本体の結果にプラグイン用の結果を結合したものを返す |
114 | 1 | Mitsuyoshi Yoshida | |
115 | <pre><code class="ruby"> |
||
116 | module RateUsersHelperPatch |
||
117 | def self.included(base) # :nodoc: |
||
118 | base.send(:include, InstanceMethods) |
||
119 | |||
120 | base.class_eval do |
||
121 | alias_method_chain :user_settings_tabs, :rate_tab |
||
122 | end |
||
123 | end |
||
124 | |||
125 | module InstanceMethods |
||
126 | # Adds a rates tab to the user administration page |
||
127 | def user_settings_tabs_with_rate_tab |
||
128 | tabs = user_settings_tabs_without_rate_tab |
||
129 | tabs << { :name => 'rates', :partial => 'users/rates', :label => :rate_label_rate_history} |
||
130 | return tabs |
||
131 | end |
||
132 | end |
||
133 | end |
||
134 | </code></pre> |
||
135 | |||
136 | 5 | Mitsuyoshi Yoshida | alias_method_chain は小さな拡張用メソッドですが、とても強力です。 |
137 | 1 | Mitsuyoshi Yoshida | |
138 | |||
139 | 5 | Mitsuyoshi Yoshida | h2. Rails のコールバックの使用 |
140 | 1 | Mitsuyoshi Yoshida | |
141 | 9 | Mitsuyoshi Yoshida | チケットの保存や作成など際にプラグインで処理を追加したい場合、すべてのチケットに対して処理を追加したいならば、 Redmine の[[プラグイン ホック|ホック機能]] よりも Rails の "コールバック":http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html を使った方がいいと思います。 |
142 | 5 | Mitsuyoshi Yoshida | その主な理由は新しいチケットを作ったとき、:controller_issues_edit_before_save のホックにメソッドを追加したとしてもこちらは呼び出されないためです。 |
143 | 1 | Mitsuyoshi Yoshida | |
144 | Rails のコールバックを利用した例は Eric Davis さんの *"Kanban plugin"* を見てください。 |
||
145 | |||
146 | * "init.rb#L10":http://github.com/edavis10/redmine_kanban/blob/000cf175795c18033caa43082c4e4d0a9f989623/init.rb#L10 |
||
147 | * "issue_patch.rb#L13":http://github.com/edavis10/redmine_kanban/blob/000cf175795c18033caa43082c4e4d0a9f989623/lib/redmine_kanban/issue_patch.rb#L13 |
||
148 | |||
149 | このプラグインでは、新規作成や更新を含めたチケットのすべての保存のタイミングで @issue.update_kanban_from_issue@ が確実に実行されるようになっています。 |
||
150 | |||
151 | 5 | Mitsuyoshi Yoshida | もし、チケットを新規作成したい場合にだけ処理を追加したい場合には、 @before_create@ ではなく @after_save@ コールバックを使用してください。 @after_create@ コールバックではチケットの保存が成功したかどうかにかかわらず処理が呼び出されますが、 after_save を使うと実際に保存が成功された場合にだけ処理が実行されるようになっています。 |
152 | 1 | Mitsuyoshi Yoshida | |
153 | |||
154 | 5 | Mitsuyoshi Yoshida | h2. マイページへのブロックの追加 |
155 | 1 | Mitsuyoshi Yoshida | |
156 | 5 | Mitsuyoshi Yoshida | マイページのブロックに関して次のような質問がよくあります。 |
157 | 1 | Mitsuyoshi Yoshida | |
158 | 8 | Mitsuyoshi Yoshida | * マイページで、なぜかブロック選択用のドロップダウンメニューの項目名が翻訳されません。 |
159 | 1 | Mitsuyoshi Yoshida | |
160 | 8 | Mitsuyoshi Yoshida | ドロップダウンメニューの項目用の翻訳メッセージはプラグインのローケルファイルを記述する際、規約でどのエントリ名を使うか決められています。 |
161 | 5 | Mitsuyoshi Yoshida | そのエントリ名はブロック用のプラグインのファイル名と同じでなければなりません。例えばそのブロック用ファイルの名前が次のようなものだったとします。 |
162 | 1 | Mitsuyoshi Yoshida | |
163 | <pre> |
||
164 | <myplugin_folder>/app/views/my/blocks/<myblocks_view_file_name>.erb |
||
165 | </pre> |
||
166 | |||
167 | 5 | Mitsuyoshi Yoshida | メニュー項目を翻訳したい場合、プラグインのローケルファイル *<myplugin_folder>/confige/locale/en.yml* などには次の行を追加する必要があります。 |
168 | 1 | Mitsuyoshi Yoshida | |
169 | <pre><code class="yaml"> |
||
170 | <myblocks_view_file_name>: <ドロップダウンメニュー項目の表示名をここに記述します> |
||
171 | </code></pre> |
||
172 | |||
173 | 5 | Mitsuyoshi Yoshida | この決まった名前でローケルファイルに定義が記述されていないとメニュー項目は翻訳されていない状態になってしまいます。 |
174 | 1 | Mitsuyoshi Yoshida | |
175 | |||
176 | h2. 参照情報 |
||
177 | |||
178 | * http://www.redmine.org/boards/3/topics/show/5121 (Which version of Redmine I need to use your plugin?) |
||
179 | * http://www.redmine.org/boards/3/topics/show/4283 (Can a plugin modify the view of the projects page?) |
||
180 | * http://www.redmine.org/boards/3/topics/show/4095 (Rails Engines and extending the issue model) |