GuideForProject » 履歴 » バージョン 10
NAITOH Jun, 2013/04/14 21:26
RESTfulな実装に変更。
1 | 1 | Mitsuyoshi Yoshida | h1. プロジェクトで利用可能にする |
---|---|---|---|
2 | |||
3 | プラグインをプロジェクトごとに使用できるようするためにはプロジェクトモジュールとしての登録とプロジェクトメニューへの項目の追加が必要となります。 |
||
4 | |||
5 | |||
6 | h2. モジュールと permission の設定 |
||
7 | |||
8 | モジュールの登録ではモジュール名とアクションごとの実行する権限(permission)を指定します。 |
||
9 | |||
10 | |_. 権限 |_. アクション | |
||
11 | 10 | NAITOH Jun | | データの表示 | index, show | |
12 | | データの管理 | new, edit, create, update, destory, preview | |
||
13 | 1 | Mitsuyoshi Yoshida | |
14 | 8 | NAITOH Jun | 権限の指定は init.rb の Redmine::Plugin.register メソッドで行います。次の記述をブロック内に追加してください。 |
15 | 1 | Mitsuyoshi Yoshida | |
16 | <pre><code class="ruby"> |
||
17 | project_module :standard do |
||
18 | 10 | NAITOH Jun | permission :view_foos, :foos => [:index, :show] |
19 | permission :manage_foos, :foos => [:new, :edit, :create, :update, :destroy, :preview], |
||
20 | 1 | Mitsuyoshi Yoshida | :require => :member |
21 | end |
||
22 | </code></pre> |
||
23 | |||
24 | *project_module* メソッドの引数でモジュール名(standard)を指定し、ブロック内で *permission* メソッドを呼び出して権限の指定をしています。 |
||
25 | |||
26 | permission メソッドの書式は次の形です。 |
||
27 | |||
28 | <pre><code class="ruby"> |
||
29 | permission(name, hash, options={}) |
||
30 | </code></pre> |
||
31 | |||
32 | 第一引数で権限の名前を指定します。 |
||
33 | 第二引数にはコントローラ名をキー、アクション名の配列を値としたハッシュを渡します。サンプルではコントローラは一つしかありませんが、複数あることもあるので、ハッシュで指定できるようになっています。 |
||
34 | 第三引数はオプションです。 |
||
35 | |||
36 | Rails では次のようなスタイルがよくとられるので慣れておきましょう。 |
||
37 | 2 | Mitsuyoshi Yoshida | |
38 | 1 | Mitsuyoshi Yoshida | * メソッドのオプション的な指定は最後の引数でシンボル名をキーとしたハッシュを使ってまとめて渡す |
39 | 3 | Mitsuyoshi Yoshida | * ハッシュが一つのペアしか持たなかったり、最後の引数などで括弧 {} が省略できる場合は省略する |
40 | 1 | Mitsuyoshi Yoshida | |
41 | このオプションの *:require* の指定を使って、データ管理の権限(:manage_foos)ではプロジェクトメンバー以外の Non member や Anonymous(ログインしていない人)では使えないようにしています。 |
||
42 | |||
43 | それでは Redmine を起動して、プラグインをプロジェクトで使用できるようにしましょう。 |
||
44 | |||
45 | まず、管理メニューにあるロールと権限の *権限レポート* でチェックをいれて権限を与えます。 |
||
46 | |||
47 | !permission.png! |
||
48 | |||
49 | 次にプラグインを使いたいプロジェクトの設定で Standard モジュールを有効にします。 |
||
50 | |||
51 | !module_add.png! |
||
52 | |||
53 | このときの表示は指定したモジュール名などがほぼそのまま表示されているだけですが、これを日本語に変更する方法は[[GuideI18n|国際化]]のところで説明します。 |
||
54 | |||
55 | |||
56 | h2. プラグインメニューへの追加 |
||
57 | |||
58 | プロジェクトでモジュールを有効にしてもまだ何も変わりません。実際にこのプラグインを使えるようにするためにはプロジェクトメニューに項目を追加する必要があります。 |
||
59 | プロジェクトメニューにはこのプラグインで最初に表示させたいページに対応するアクションを追加します。サンプルでは foos コントロールの index アクションとなります。 |
||
60 | |||
61 | メニュー項目の追加も init.rb で行います。先ほどの記述の後に以下を加えます。 |
||
62 | |||
63 | <pre><code class="ruby"> |
||
64 | 6 | Mitsuyoshi Yoshida | menu :project_menu, :standard, { :controller => 'foos', :action => 'index'}, :param => :project_id |
65 | 1 | Mitsuyoshi Yoshida | </code></pre> |
66 | |||
67 | メニューの項目の追加は *menu* メソッドを使います。 |
||
68 | |||
69 | <pre><code class="ruby"> |
||
70 | menu(menu, item, url, options={}) |
||
71 | </code></pre> |
||
72 | |||
73 | 第 1 引数はメニューの名前です。ここではプロジェクトメニューを指定します。指定できるメニューは [[GuideSampleSpec|サンプル仕様]] で説明した 4 つです。 |
||
74 | |||
75 | |_. メニュー |_. メニュー名 | |
||
76 | | トップメニュー | :top_menu | |
||
77 | | アカウントメニュー | :account_menu | |
||
78 | | プロジェクトメニュー | :project_menu | |
||
79 | | アプリケーションメニュー | :application_menu | |
||
80 | |||
81 | 第 2 引数はメニューの項目名です。 |
||
82 | 第 3 引数はメニューをクリックしたときのリンク先の URL を指定します。 |
||
83 | 6 | Mitsuyoshi Yoshida | 第 4 引数はオプションです。ここでは :param オプションで *:project_id* のキーに *プロジェクト識別名* を割り当てています。 |
84 | 1 | Mitsuyoshi Yoshida | |
85 | |||
86 | 6 | Mitsuyoshi Yoshida | h2. URL の指定方法 |
87 | 1 | Mitsuyoshi Yoshida | |
88 | 前節のメニューで行った URL の指定方法は *link_to* などでの他の Rails のリンクの指定方法と同じです。 |
||
89 | URL の指定方法についてもう少し詳しく説明しましょう。 |
||
90 | |||
91 | 6 | Mitsuyoshi Yoshida | |
92 | h2. Rails の機能 |
||
93 | |||
94 | 1 | Mitsuyoshi Yoshida | URL として文字列を指定すると単純にその文字列が url として使用されますが、ハッシュで指定するとそれを元に URL を生成します。 |
95 | |||
96 | 指定するキーはコントローラ名(:controller)、アクション名(:action)と ID名(:id) で、そのキーの値を使って次のようなアドレスとなります。 |
||
97 | |||
98 | <pre> |
||
99 | サーバアドレス/コントローラ名/アクション名/ID名 |
||
100 | </pre> |
||
101 | |||
102 | 6 | Mitsuyoshi Yoshida | ID 名は省略可能で省略されると ID 名の部分はなくなります。 |
103 | param で追加された値は http の GET メソッドによる引数として渡されます。 GET メソッドについては[[GuideNewAction|後述]]しますが、URL にパラメーターを付ける方法だと思っておいてください。 |
||
104 | サンプルのアドレスは以下のようになります。(プロジェクトの識別名は demo にしています) |
||
105 | 1 | Mitsuyoshi Yoshida | |
106 | <pre> |
||
107 | 6 | Mitsuyoshi Yoshida | http://127.0.0.1:3000/foos/index?project_id=demo |
108 | 1 | Mitsuyoshi Yoshida | </pre> |
109 | |||
110 | このアドレスの指定によりコントロールのアクションが実行されることになります。この場合は FoosController の index メソッドが呼ばれます。 |
||
111 | ID は *params* というハッシュクラスの変数に格納されてます。params は $ マークは付いてませんが、グローバル変数のようなものだと考えてください。 |
||
112 | |||
113 | |||
114 | 6 | Mitsuyoshi Yoshida | h3. Redmine の場合 |
115 | 1 | Mitsuyoshi Yoshida | |
116 | 6 | Mitsuyoshi Yoshida | Rails のアプリケーションではこの :id にプロジェクトの識別名を入れてつかうことが多いです。 |
117 | しかし、 Redmine ではチケットなどで、プロジェクトの識別名を :id を使わずに :project_id に入れて、チケットなどの ID を :id に入れています。サンプルでもそれにあわせるようにしています。 |
||
118 | 1 | Mitsuyoshi Yoshida | |
119 | 10 | NAITOH Jun | しかし、チケットを表示した場合などの URL をみてもらうと Redmine では @?project_id=demo@ のような表記はありません。 これは Rails のルーティング機能を使って URL を変えているためです。 |
120 | 6 | Mitsuyoshi Yoshida | |
121 | 10 | NAITOH Jun | なお Redmine 1.4 以降ではデフォルトのルート設定が提供されなくなりました。このため各プラグインは適切なルートを *config/routes.rb* に設定する必要があります。 |
122 | 6 | Mitsuyoshi Yoshida | |
123 | 10 | NAITOH Jun | h2. ルーティング |
124 | 6 | Mitsuyoshi Yoshida | |
125 | 8 | NAITOH Jun | URL を設定するため config/routes.rb にどのように設定するかといった指定を記述します。 |
126 | 10 | NAITOH Jun | ここでは、 Redmine にあわせてメニューのアドレスが次のようになるようにしたいのですが、今回は Rails の RESTful 用の仕組みである resources マクロの機能を使います。 |
127 | 6 | Mitsuyoshi Yoshida | |
128 | 1 | Mitsuyoshi Yoshida | <pre> |
129 | 10 | NAITOH Jun | http://127.0.0.1:3000/projects/demo/foos |
130 | 1 | Mitsuyoshi Yoshida | </pre> |
131 | 6 | Mitsuyoshi Yoshida | |
132 | config/routes.rb に以下の内容のファイルを作成して下さい。 |
||
133 | |||
134 | 1 | Mitsuyoshi Yoshida | <pre><code class="ruby"> |
135 | # Plugin's routes |
||
136 | # See: http://guides.rubyonrails.org/routing.html |
||
137 | 10 | NAITOH Jun | |
138 | 1 | Mitsuyoshi Yoshida | Rails.application.routes.draw do |
139 | 10 | NAITOH Jun | resources :projects do |
140 | resources :foos |
||
141 | end |
||
142 | 1 | Mitsuyoshi Yoshida | end |
143 | </code></pre> |
||
144 | |||
145 | 10 | NAITOH Jun | この resources の設定で7つの標準ルートが追加されました。生成されたルートを確認するには *rake routes* コマンドを使います。 |
146 | |||
147 | <pre> |
||
148 | $ rake routes |
||
149 | </pre> |
||
150 | 実行結果の Foos コントローラーに関する部分を抜粋したものが下記になります。 |
||
151 | <pre> |
||
152 | project_foos GET /projects/:project_id/foos(.:format) foos#index |
||
153 | POST /projects/:project_id/foos(.:format) foos#create |
||
154 | new_project_foo GET /projects/:project_id/foos/new(.:format) foos#new |
||
155 | edit_project_foo GET /projects/:project_id/foos/:id/edit(.:format) foos#edit |
||
156 | project_foo GET /projects/:project_id/foos/:id(.:format) foos#show |
||
157 | PUT /projects/:project_id/foos/:id(.:format) foos#update |
||
158 | DELETE /projects/:project_id/foos/:id(.:format) foos#destroy |
||
159 | </pre> |
||
160 | この各列に示されている情報は、左から順に(オプションの)ルート名、HTTPメソッド、ルートのパス、コントローラアクションになります。 |
||
161 | |||
162 | このルートのパス設定に従った URL に指定の HTTP メソッドでアクセスすることで、目的としたコントローラのアクションが実行されます。 |
||
163 | たとえば、 /projects/demo/foos/5 に GET メソッドでアクセスすると 5 のデータ詳細を表示、PUT メソッドでアクセスすると 5 のデータを更新、DELETE メソッドでアクセスすると 5 のデータを削除します。 5 のデータを狙って新規作成といった動作は想定していないので、 create メソッドでは、 :id を指定しない URL になっています。 |
||
164 | |||
165 | 今回 Redmine の実装をまねてアクションの名前をつけましたが、Redmine 自身が RESTful な実装なため標準ルートをそのまま適用できました。 |
||
166 | |||
167 | ただし、標準ルートには preview アクションが含まれていないので、個別に追加しましょう。 |
||
168 | 次のように修正します。 |
||
169 | |||
170 | <pre><code class="ruby"> |
||
171 | # Plugin's routes |
||
172 | # See: http://guides.rubyonrails.org/routing.html |
||
173 | |||
174 | Rails.application.routes.draw do |
||
175 | resources :projects do |
||
176 | resources :foos do |
||
177 | post 'preview', :on => :collection |
||
178 | put 'preview', :on => :member |
||
179 | end |
||
180 | end |
||
181 | end |
||
182 | </code></pre> |
||
183 | |||
184 | <pre> |
||
185 | $ rake routes |
||
186 | </pre> |
||
187 | 実行結果の Foos コントローラーに関する部分を抜粋したものが下記になります。 |
||
188 | preview アクションのルート設定が 2つ追加されました。 |
||
189 | <pre> |
||
190 | preview_project_foos POST /projects/:project_id/foos/preview(.:format) foos#preview |
||
191 | preview_project_foo PUT /projects/:project_id/foos/:id/preview(.:format) foos#preview |
||
192 | project_foos GET /projects/:project_id/foos(.:format) foos#index |
||
193 | POST /projects/:project_id/foos(.:format) foos#create |
||
194 | new_project_foo GET /projects/:project_id/foos/new(.:format) foos#new |
||
195 | edit_project_foo GET /projects/:project_id/foos/:id/edit(.:format) foos#edit |
||
196 | project_foo GET /projects/:project_id/foos/:id(.:format) foos#show |
||
197 | PUT /projects/:project_id/foos/:id(.:format) foos#update |
||
198 | DELETE /projects/:project_id/foos/:id(.:format) foos#destroy |
||
199 | </pre> |
||
200 | 6 | Mitsuyoshi Yoshida | |
201 | |||
202 | 1 | Mitsuyoshi Yoshida | h2. index ページの表示 |
203 | |||
204 | Redmine を再起動してモジュールを追加したプロジェクトを開いてみましょう。プロジェクトメニューに *Standard* が追加されています。 |
||
205 | |||
206 | !sample_proj_menu.png! |
||
207 | |||
208 | このメニューをクリックして下さい。 index.html.erb の内容のページが表示されます。 |
||
209 | |||
210 | !index_no_project.png! |
||
211 | |||
212 | コントローラの index メソッドの中にはまだ何も書いていません。それにもかかわらず、 index ページが表示されています。これはコントローラのアクションが実行されるとアクション名のメソッドが実行され、デフォルトの状態ではアクション名に対応する views のファイルが表示されるためです。 |
||
213 | |||
214 | |||
215 | h2. @project の設定 |
||
216 | |||
217 | ただし、表示されたページよく見ると何か変なことに気づくと思います。これは他のプロジェクトメニューと違いプロジェクトが開かれていないためです。このページをプロジェクトに属するものするためには FoosController クラスのインスタンス変数 *@project* に値を設定する必要があります。 |
||
218 | |||
219 | 8 | NAITOH Jun | |
220 | |||
221 | app/controllers/foos_controller.rb の FoosController クラスの末尾に次のような @project を設定する *find_project* メソッドの定義を追加します。外から呼ばれることはありませんので、private メソッドにしています。 |
||
222 | 6 | Mitsuyoshi Yoshida | <pre><code class="ruby"> |
223 | class FoosController < ApplicationController |
||
224 | 7 | Mitsuyoshi Yoshida | unloadable |
225 | # : |
||
226 | private |
||
227 | def find_project |
||
228 | @project = Project.find(params[:project_id]) |
||
229 | rescue ActiveRecord::RecordNotFound |
||
230 | render_404 |
||
231 | end |
||
232 | 6 | Mitsuyoshi Yoshida | end |
233 | </code></pre> |
||
234 | 1 | Mitsuyoshi Yoshida | |
235 | 6 | Mitsuyoshi Yoshida | params[:id] にはプロジェクトの識別名が入っています。 |
236 | Project はプロジェクト用のモデルクラスです。 *find* メソッドは引数にプロジェクト識別名を渡すと、データベースから識別名が一致するプロジェクトを探し、Project のオブジェクトを返すクラスメソッドです。 |
||
237 | |||
238 | Project クラスも ActiveRecord::Base クラスを継承して作成されており、データがみつからないと *ActiveRecord::RecordNotFound* の例外が発生します。コントローラ内で例外が発生すると通常の http サーバの場合 Internal Error のページになってしまいます。(WEBrick では詳細なエラー情報の画面になります。) |
||
239 | この例外をキャッチして *render_404* を実行しています。 これはファイルが見つかりませんといった http の 404 エラーのページを表示するメソッドです。 |
||
240 | |||
241 | 定義した find_project メソッドを index メソッドの中で呼び出せばプロジェクト内のページとして表示されます。 |
||
242 | 1 | Mitsuyoshi Yoshida | |
243 | |||
244 | h2. before_filter |
||
245 | |||
246 | アクションは index だけではありません。他のアクションのメソッドでも同様に find_project を呼び出す必要があります。じゃ、 index の find_project をコピーして、他のメソッドにもペーストしていって ... と思った人はちょっと待ってください。 それでは同じことを何度も書く羽目になります。 Rails には DRY(Don't Repeat Yourself) という繰り返し禁止の方針がありました。何か繰り返しを避ける方法があるはずです。 |
||
247 | |||
248 | 実際、避ける方法は用意されています。それは *before_filter* です。これでメソッドを指定するとそのメソッドはアクションのメソッドの前に呼ばれるようになります。 |
||
249 | こちらはコントローラクラスの先頭の方に追加します。 |
||
250 | |||
251 | <pre><code class="ruby"> |
||
252 | class FoosController < ApplicationController |
||
253 | unloadable |
||
254 | before_filter :find_project |
||
255 | # : |
||
256 | </code></pre> |
||
257 | |||
258 | h2. authorize |
||
259 | |||
260 | before_filter はカンマで区切って複数のメソッドを指定することが出来ます。もう一つ追加してみましょう。 |
||
261 | <pre><code class="ruby"> |
||
262 | class FoosController < ApplicationController |
||
263 | unloadable |
||
264 | before_filter :find_project, :authorize |
||
265 | # : |
||
266 | </code></pre> |
||
267 | |||
268 | 追加した *authorize* はページを開いた時にログインしていない場合はログインのページ行く Redmine のメソッドです。通常、プロジェクトメニューから開かれるので、ログインしていることが多いと思いますが、ブラウザで前に開いていたページをもう一度開くといった設定がしてある場合などでは直接ページが指定されることもあります。そういったときにもちゃんとログインできるようにすべてのアクションの前に実行しておく必要があります。 |
||
269 | |||
270 | 権限の設定で index や show では設定によってはログインしてなくても、表示できる場合があるのではと疑問を持った方もいると思いますが、そういう場合も Redmine がログインしないように上手く処理してくれます。 |
||
271 | |||
272 | 権限の判定には現在のプロジェクトの情報が必要となります。before_filter は追加した順に実行されますので必ず find_project, authorize の順で記述して下さい。 |
||
273 | |||
274 | ここまで設定して、もう一度 [Standard] のメニューを選択してみてください。今度はプロジェクトのページとして開かれているはずです。 |
||
275 | |||
276 | !index_no_tab.png! |
||
277 | |||
278 | まだ、少し変なところがあります。メニュー項目が選択状態になっていませんね。これを解決するにはコントローラ内で *menu_item* を使ってメニュー項目を指定します。 |
||
279 | |||
280 | <pre><code class="ruby"> |
||
281 | menu_item :メニュー項目のシンボル |
||
282 | </code></pre> |
||
283 | |||
284 | このメソッドで指定しなくてもメニュー項目が選択状態になることがあります。実はどうしたらそうなるのかは私もよく分かりません。とりあえず、選択状態にならないときはこのメソッドで指定してみてください。 |
||
285 | |||
286 | これまでの記述を追加したコントローラのファイルの中身は次のようになります。 |
||
287 | |||
288 | <pre><code class="ruby"> |
||
289 | class FoosController < ApplicationController |
||
290 | unloadable |
||
291 | menu_item :standard |
||
292 | before_filter :find_project, :authorize |
||
293 | |||
294 | |||
295 | def index |
||
296 | end |
||
297 | |||
298 | |||
299 | def new |
||
300 | end |
||
301 | |||
302 | |||
303 | def show |
||
304 | end |
||
305 | |||
306 | |||
307 | def edit |
||
308 | end |
||
309 | |||
310 | private |
||
311 | def find_project |
||
312 | 9 | NAITOH Jun | @project = Project.find(params[:project_id]) |
313 | 1 | Mitsuyoshi Yoshida | rescue ActiveRecord::RecordNotFound |
314 | render_404 |
||
315 | end |
||
316 | end |
||
317 | </code></pre> |
||
318 | |||
319 | これでプロジェクトメニューにきちんと登録できるようになりました。 |
||
320 | |||
321 | !index_tab.png! |
||
322 | |||
323 | ---- |
||
324 | |||
325 | | [[プラグイン開発ガイド|^]] | [[GuideControlSkelton|<<]] | [[GuideI18n|>>]] | |