プロジェクト

全般

プロフィール

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|>>]] |