GuideActionElse » 履歴 » バージョン 5
NAITOH Jun, 2013/04/14 21:33
RESTfulな実装に変更。
1 | 5 | NAITOH Jun | h1. その他のアクション(詳細表示、編集フォーム、更新、削除、プレビュー) |
---|---|---|---|
2 | 1 | Mitsuyoshi Yoshida | |
3 | 5 | NAITOH Jun | それでは残った 5 つのアクションを一気に片付けていきましょう。 |
4 | 1 | Mitsuyoshi Yoshida | |
5 | * 詳細表示( show ) |
||
6 | * 削除( destroy ) |
||
7 | 5 | NAITOH Jun | * 編集フォーム( edit ) |
8 | * 更新( update ) |
||
9 | 4 | NAITOH Jun | * プレビュー( preview ) |
10 | 1 | Mitsuyoshi Yoshida | |
11 | |||
12 | h2. Foo オブジェクト(@foo)の取得 |
||
13 | |||
14 | 5 | NAITOH Jun | show, destroy, edit, update のアクションでは params[:id] に入っている id から対象となる Foo のオブジェクトを取得する必要があります。 |
15 | 3 つのメソッドで同じことを行います。こういうときには *before_filter* の出番です。ただし、 index や new, create, preview では必要ありません。 before_filter では *:only* オプションで実行する対象を指定することも、 *:except* オプションで実行しない対象を指定することも出来ます。サンプルでは 3 つのアクションだけですが、チケットのようにコピーや移動などのアクションを追加したくなるかもれません。そこで :except で index と new, create, preview を指定することにします。 |
||
16 | 1 | Mitsuyoshi Yoshida | |
17 | find_project と同じように find_foo メソッドを追加します。 |
||
18 | |||
19 | <pre><code class="ruby"> |
||
20 | class FoosController < ApplicationController |
||
21 | unloadable |
||
22 | menu_item :standard |
||
23 | before_filter :find_project, :authorize |
||
24 | 5 | NAITOH Jun | before_filter :find_foo, :except => [:index, :new, :create, :preview] |
25 | 1 | Mitsuyoshi Yoshida | |
26 | # : |
||
27 | |||
28 | private |
||
29 | def find_project |
||
30 | 3 | Mitsuyoshi Yoshida | @project = Project.find(params[:project_id]) |
31 | 1 | Mitsuyoshi Yoshida | rescue ActiveRecord::RecordNotFound |
32 | render_404 |
||
33 | end |
||
34 | |||
35 | def find_foo |
||
36 | 3 | Mitsuyoshi Yoshida | @foo = Foo.find_by_id(params[:id]) |
37 | 1 | Mitsuyoshi Yoshida | render_404 unless @foo |
38 | end |
||
39 | end |
||
40 | </code></pre> |
||
41 | |||
42 | ActiveRecord::Base クラスでは ruby のメタプログラミングを利用して find_by_XXX という名前のクラスメソッドで XXX というメンバをキーとしてテーブルを検索することが出来ます。ここでは *Foo.find_by_id* で id の値が一致するオブジェクトを検索しています。 |
||
43 | ただし、 Project.find と違って見つからなかった場合は nil を返すだけで例外は発生しません。 |
||
44 | |||
45 | |||
46 | h2. 詳細表示( show アクション ) |
||
47 | |||
48 | 詳細表示のコントローラでは、 Foo オブジェクトを取得してビューを生成するだけなので、 show メソッドはスケルトン作成時のままで何も追加することはありません。 |
||
49 | |||
50 | <pre><code class="ruby"> |
||
51 | def show |
||
52 | end |
||
53 | </code></pre> |
||
54 | |||
55 | ビュー(show.html.erb) で @foo オブジェクトの中身を表示することになります。 |
||
56 | 表示は Redmine で定義されている CSS クラスを使って、ちょっとチケット風にしています。 |
||
57 | |||
58 | 5 | NAITOH Jun | <pre><code class="erb"> |
59 | 1 | Mitsuyoshi Yoshida | <h2><%=h l(:label_foo) %>#<%= @foo.id %></h2> |
60 | |||
61 | <div class="issue"> |
||
62 | |||
63 | <div class="subject"> |
||
64 | <h3><%= @foo.subject %></h3> |
||
65 | </div> |
||
66 | |||
67 | 2 | Mitsuyoshi Yoshida | <% unless @foo.description.blank? %> |
68 | 1 | Mitsuyoshi Yoshida | <p><strong><%=l(:field_description)%></strong></p> |
69 | <div class="wiki"> |
||
70 | 2 | Mitsuyoshi Yoshida | <%= textilizable @foo.description %> |
71 | 1 | Mitsuyoshi Yoshida | </div> |
72 | <% end %> |
||
73 | |||
74 | </div></code></pre> |
||
75 | |||
76 | 説明のパラメータは CSS クラスを *wiki* にし、 *textilizable* メソッドを使うことによって、wiki を解析した結果を出力しています。 |
||
77 | |||
78 | |||
79 | また、詳細表示ページでは右上に *編集* と *削除* リンクをつけます。 |
||
80 | |||
81 | 5 | NAITOH Jun | <pre><code class="erb"> |
82 | 1 | Mitsuyoshi Yoshida | <div class="contextual"> |
83 | <%= link_to_if_authorized(l(:button_edit), |
||
84 | 3 | Mitsuyoshi Yoshida | {:action => 'edit', :project_id => @project, :id => @foo.id}, |
85 | 1 | Mitsuyoshi Yoshida | :class => 'icon icon-edit') %> |
86 | <%= link_to_if_authorized(l(:button_delete), |
||
87 | 3 | Mitsuyoshi Yoshida | {:action => 'destroy', :project_id => @project, :id => @foo.id}, |
88 | 5 | NAITOH Jun | :confirm => l(:text_are_you_sure), :method => :delete, |
89 | 1 | Mitsuyoshi Yoshida | :class => 'icon icon-del') %> |
90 | </div> |
||
91 | </code></pre> |
||
92 | |||
93 | 5 | NAITOH Jun | 削除用リンクには *:confirm=>l(:text_are_you_sure)* オプションをつけています。これによりリンク実行前に確認ダイアログを表示されるようになります。なお、そのままでは GET メソッドがよばれるため *DELETE メソッド* に変更しています。 |
94 | 1 | Mitsuyoshi Yoshida | |
95 | この詳細表示ページの実行結果は次のようになります。 |
||
96 | |||
97 | !show.png! |
||
98 | |||
99 | |||
100 | h2. 削除( destroy アクション ) |
||
101 | |||
102 | destroy はコントローラのスケルトンで作成していませんでした。そこで中身だけでなく、定義からコントローラ(foos_controller.rb)に記述することになります。 |
||
103 | |||
104 | <pre><code class="ruby"> |
||
105 | def destroy |
||
106 | 3 | Mitsuyoshi Yoshida | @foo.destroy |
107 | 5 | NAITOH Jun | redirect_to project_foos_path(@project) |
108 | 1 | Mitsuyoshi Yoshida | end |
109 | </code></pre> |
||
110 | |||
111 | Foo オブジェクトの *destroy* メソッドを使ってデータベースからデータを削除しています。 |
||
112 | その後、そのままだと destroy.html.erb のビューを表示しようとしますので、 redirect_to を使って一覧表示ページを表示しています。 |
||
113 | |||
114 | |||
115 | h2. 編集( edit アクション ) |
||
116 | |||
117 | 5 | NAITOH Jun | 編集のコントローラでは、 Foo オブジェクトを取得してビューを生成するだけなので、 edit メソッドはスケルトン作成時のままで何も追加することはありません。 |
118 | 1 | Mitsuyoshi Yoshida | |
119 | <pre><code class="ruby"> |
||
120 | def edit |
||
121 | end |
||
122 | </code></pre> |
||
123 | |||
124 | 5 | NAITOH Jun | ビュー(edit.html.erb) は次のようになります。 |
125 | 1 | Mitsuyoshi Yoshida | |
126 | 5 | NAITOH Jun | <pre><code class="erb"> |
127 | <h2><%=h l(:label_foo) %>#<%= @foo.id %></h2> |
||
128 | 1 | Mitsuyoshi Yoshida | <%= labelled_form_for :foo, @foo, |
129 | 5 | NAITOH Jun | :url => project_foo_path(@project), |
130 | :html => {:multipart => true, :id => 'foo-form'} do |f| %> |
||
131 | 1 | Mitsuyoshi Yoshida | <%= render :partial => 'foos/form', :locals => {:form => f} %> |
132 | <%= f.submit l(:button_edit) %> |
||
133 | 5 | NAITOH Jun | <%= preview_link( preview_project_foo_path(@project), 'foo-form' ) %> |
134 | 1 | Mitsuyoshi Yoshida | <% end %> |
135 | <div id="preview" class="wiki"></div> |
||
136 | </code></pre> |
||
137 | 4 | NAITOH Jun | |
138 | 1 | Mitsuyoshi Yoshida | labelled_form_for の :url の指定は new では :id のパラメータを省略していましたが、 edit では :id のパラメータを指定する必要があります。 |
139 | 5 | NAITOH Jun | プレビュー用のリンクを追加していますが、後はラベルなどの表示を変えているだけで new とほぼ同じです。 |
140 | 1 | Mitsuyoshi Yoshida | |
141 | |||
142 | 5 | NAITOH Jun | h2. 更新( update アクション ) |
143 | |||
144 | update はコントローラのスケルトンで作成していませんでした。そこで中身だけでなく、定義からコントローラ(foos_controller.rb)に記述することになります。 |
||
145 | |||
146 | update アクションはフォームの [編集] ボタンから呼ばれます。 |
||
147 | create アクションと同じような感じで作成します。 |
||
148 | コントローラは次のようになります。 |
||
149 | |||
150 | <pre><code class="ruby"> |
||
151 | def update |
||
152 | @foo.attributes = params[:foo] |
||
153 | if @foo.save |
||
154 | flash[:notice] = l(:notice_successful_update) |
||
155 | redirect_to project_foo_path(@project, @foo.id) |
||
156 | end |
||
157 | rescue ActiveRecord::StaleObjectError |
||
158 | flash.now[:error] = l(:notice_locking_conflict) |
||
159 | end |
||
160 | </code></pre> |
||
161 | |||
162 | create の場合と同様に save メソッドを使ってデータベースに保存しています。 create との違いは次の 3 点です。 |
||
163 | |||
164 | * id 番号は同じにしなければならないので、 create の引数ではなく *attributes* でパラメータのみ変更 |
||
165 | * 同時に編集している人がいるとエラーで例外が発生するので、その場合にはエラーメッセージを表示 |
||
166 | * *POST* リクエストではなく *PUT* リクエスト |
||
167 | |||
168 | その後、そのままだと update.html.erb のビューを表示しようとしますので、 redirect_to を使って詳細表示ページを表示しています。 |
||
169 | |||
170 | 3 | Mitsuyoshi Yoshida | h2. プレビュー( preview アクション ) |
171 | 4 | NAITOH Jun | |
172 | preview もコントローラのスケルトンで作成していませんでした。同様に定義からコントローラ(foos_controller.rb)に記述します。 |
||
173 | |||
174 | <pre><code class="ruby"> |
||
175 | def preview |
||
176 | @text = params[:foo][:description] |
||
177 | render :partial => 'common/preview' |
||
178 | end |
||
179 | </code></pre> |
||
180 | 1 | Mitsuyoshi Yoshida | |
181 | 4 | NAITOH Jun | これが edit アクションの preview_link から呼ばれることになります。 |
182 | ここでは、Redmine のヘルパーメソッドの preview 機能を呼び出してプレビュー表示を実現しています。 |
||
183 | |||
184 | |||
185 | 1 | Mitsuyoshi Yoshida | h2. レイアウト |
186 | |||
187 | 最後に html ページのタイトルをつけましょう。 |
||
188 | |||
189 | 他の文書やチケットを参考にタイトルの付け方を次のようにします。 |
||
190 | |||
191 | |_. ページ |_. タイトル | |
||
192 | | 一覧表示、新規作成 | (プロジェクト名) - 標準 - Redmine | |
||
193 | | 詳細表示、編集 | (プロジェクト名) - (題名) - Redmine | |
||
194 | |||
195 | html のタイトルを付けるには *html_title* メソッドをビュー内で呼び出します。 |
||
196 | このとき前後のプロジェクト名と Redmine の文字は Redmine が自動的に付けます。 |
||
197 | |||
198 | 例えば、 index.html.erb に次のコードを追加します。 |
||
199 | |||
200 | 5 | NAITOH Jun | <pre><code class="erb"> |
201 | 1 | Mitsuyoshi Yoshida | <% html_title(l(:label_standard)) %> |
202 | </code></pre> |
||
203 | |||
204 | このときプロジェクト名が "デモ" とするとタイトルは次のようになります。 |
||
205 | |||
206 | <pre> |
||
207 | デモ - 標準 - Redmine |
||
208 | </pre> |
||
209 | |||
210 | それでは早速、他のビューにもこの文をコピペしてと .... |
||
211 | もう何がいいたいかお分かりですね。 繰り返しを避ける方法があります。 |
||
212 | |||
213 | コントローラの before_filter のようにビューすべてにコードを追加したい場合には *レイアウト* というものを使用します。 |
||
214 | |||
215 | レイアウト用の html.erb ファイルは *app/views/layouts* ディレクトリにおきます。 |
||
216 | スケルトンでは layouts は作成されていませんので、 layouts ディレクトリを作って app/views/layouts/standard.html.erb ファイルを作成します。このときファイル名は何でもかまいません。 |
||
217 | |||
218 | 5 | NAITOH Jun | <pre><code class="erb"> |
219 | 1 | Mitsuyoshi Yoshida | <% html_title((@foo && !@foo.subject.blank?) ? @foo.subject : l(:label_standard)) %> |
220 | <%= render :file => "layouts/base" %> |
||
221 | </code></pre> |
||
222 | |||
223 | html_title の引数は決めたタイトルになるように条件で文字列を変えています。 |
||
224 | |||
225 | 最後の行でデフォルトのレイアウトを出力しています。 |
||
226 | この layouts/base は Redmine の *app/views/layouts/base.html.erb* ファイルを指しています。このファイルはレイアウトを指定しない場合に使用されているものです。レイアウトに standard.html.erb を指定すると使用するレイアウトはそちらに置き換わってしまいます。 タイトルの指定だけの追加とするためには standard.html.erb 内で再度 base のレイアウトを呼び出す必要があります。 |
||
227 | |||
228 | レイアウトの指定はコントローラのクラス定義内に次のコードを追加します。 |
||
229 | |||
230 | <pre><code class="ruby"> |
||
231 | layout 'standard' |
||
232 | </code></pre> |
||
233 | |||
234 | これでどのアクションに対しても layouts/standard.html.erb の内容がビューのページに付けられてタイトルが変更されるようになります。 |
||
235 | |||
236 | *これでサンプルプラグインは完成です!!* |
||
237 | |||
238 | 最終的なコントローラのファイル(foos_controller.rb)は以下のようになります。 |
||
239 | |||
240 | 3 | Mitsuyoshi Yoshida | <pre><code class="ruby"> |
241 | class FoosController < ApplicationController |
||
242 | 1 | Mitsuyoshi Yoshida | unloadable |
243 | menu_item :standard |
||
244 | before_filter :find_project, :authorize |
||
245 | 5 | NAITOH Jun | before_filter :find_foo, :except => [:index, :new, :create, :preview] |
246 | 1 | Mitsuyoshi Yoshida | |
247 | layout 'standard' |
||
248 | |||
249 | def index |
||
250 | @foos = Foo.find(:all, :conditions => ["project_id = #{@project.id} "]) |
||
251 | end |
||
252 | |||
253 | |||
254 | 3 | Mitsuyoshi Yoshida | def new |
255 | 5 | NAITOH Jun | @foo = Foo.new() |
256 | end |
||
257 | |||
258 | def create |
||
259 | 1 | Mitsuyoshi Yoshida | @foo = Foo.new(params[:foo]) |
260 | @foo.project_id = @project.id |
||
261 | |||
262 | 5 | NAITOH Jun | if @foo.save |
263 | 1 | Mitsuyoshi Yoshida | flash[:notice] = l(:notice_successful_create) |
264 | 5 | NAITOH Jun | redirect_to project_foo_path(@project, @foo.id) |
265 | 1 | Mitsuyoshi Yoshida | end |
266 | end |
||
267 | |||
268 | 4 | NAITOH Jun | def show |
269 | 1 | Mitsuyoshi Yoshida | end |
270 | |||
271 | 3 | Mitsuyoshi Yoshida | |
272 | 5 | NAITOH Jun | def update |
273 | @foo.attributes = params[:foo] |
||
274 | if @foo.save |
||
275 | flash[:notice] = l(:notice_successful_update) |
||
276 | redirect_to project_foo_path(@project, @foo.id) |
||
277 | 1 | Mitsuyoshi Yoshida | end |
278 | rescue ActiveRecord::StaleObjectError |
||
279 | flash.now[:error] = l(:notice_locking_conflict) |
||
280 | end |
||
281 | |||
282 | 5 | NAITOH Jun | def edit |
283 | end |
||
284 | 3 | Mitsuyoshi Yoshida | |
285 | 1 | Mitsuyoshi Yoshida | def destroy |
286 | @foo.destroy |
||
287 | 5 | NAITOH Jun | redirect_to project_foos_path(@project) |
288 | 3 | Mitsuyoshi Yoshida | end |
289 | |||
290 | |||
291 | def preview |
||
292 | @text = params[:foo][:description] |
||
293 | render :partial => 'common/preview' |
||
294 | 1 | Mitsuyoshi Yoshida | end |
295 | |||
296 | 3 | Mitsuyoshi Yoshida | |
297 | 1 | Mitsuyoshi Yoshida | private |
298 | def find_project |
||
299 | @project = Project.find(params[:project_id]) |
||
300 | rescue ActiveRecord::RecordNotFound |
||
301 | render_404 |
||
302 | 3 | Mitsuyoshi Yoshida | end |
303 | 1 | Mitsuyoshi Yoshida | |
304 | def find_foo |
||
305 | @foo = Foo.find_by_id(params[:id]) |
||
306 | render_404 unless @foo |
||
307 | end |
||
308 | end |
||
309 | </code></pre> |
||
310 | |||
311 | |||
312 | |||
313 | --- |
||
314 | |||
315 | | [[プラグイン開発ガイド|^]] | [[GuideNewAction|<<]] | [[GuideConclusion|>>]] | |