Ruby on Rails 3.0 日記
第10回: 完了したタスクの一覧
前回は、タスクを完了する finish アクションを実装しました。
今回は、完了したタスクだけを表示する done アクションと、完了したタスクを未完了に戻す unfinish アクションを作ります。
ルーティングの修正
エディタで config/routes.rb を開いて、次のように修正してください。
Nchak::Application.routes.draw do
resources :tasks, :only => [ :index, :create ] do
put :finish, :on => :member
put :unfinish, :on => :member
get :done, :on => :collection
end
end
前回の記事で書いたように :on オプションに :collection を指定すると、レコードの集合を対象とするアクションへの経路(route)が定義されます。
経路の数が多くなってきたので、rake routes コマンドでちょっと確認してみましょう。
$ rake routes
(in /home/kuroda/work/nchak)
finish_task PUT /tasks/:id/finish(.:format) {:controller=>"tasks", :action=>"finish"}
unfinish_task PUT /tasks/:id/unfinish(.:format) {:controller=>"tasks", :action=>"unfinish"}
done_tasks GET /tasks/done(.:format) {:controller=>"tasks", :action=>"done"}
GET /tasks(.:format) {:controller=>"tasks", :action=>"index"}
tasks POST /tasks(.:format) {:controller=>"tasks", :action=>"create"}
done アクションの実装
done アクションのコードは、index アクションとほとんど同じです。app/controllers/tasks_controller.rb を次のように修正してください。
class TasksController < ApplicationController
def index
@task = Task.new
@tasks = Task.all(:conditions => { :done => false }, :order => "due_date")
end
def done
@task = Task.new
@tasks = Task.all(:conditions => { :done => true }, :order => "due_date")
render :action => 'index'
end
(省略)
end
実は、Rails 3.0 で導入された where メソッドや order メソッドを利用すると、もっと簡潔に記述できます。
class TasksController < ApplicationController
def index
@task = Task.new
@tasks = Task.where(:done => false).order("due_date")
end
def done
@task = Task.new
@tasks = Task.where(:done => true).order("due_date")
render :action => 'index'
end
(省略)
end
Pratic Naik 氏のブログ記事によれば、all メソッドに :conditions オプションや :order オプションを渡す方法は、Rails 3.1 で廃止予定(DEPRECATED)になり、Rails 3.2 で廃止されるそうです。
scope
コントローラからモデルにコードを移すことによって、さらに index/done アクションを単純化しましょう。
まず、app/model/task.rb を次のように修正します。
class Task < ActiveRecord::Base
scope :done, where(:done => true).order("due_date")
scope :undone, where(:done => false).order("due_date")
end
scope メソッドは、Rails 2.x までは named_scope という名前でした。
すると、tasks コントローラはこう書き換えられます。
class TasksController < ApplicationController
def index
@task = Task.new
@tasks = Task.undone
end
def done
@task = Task.new
@tasks = Task.done
render :action => 'index'
end
(省略)
end
コントローラの記述は、できるかぎり単純化したいものです。
ビューの修正
app/views/tasks/index.html.erb を修正してください。
(省略) <%= navigation_links %> <table class="tasks"> <col class="name" /> <col class="due_date" /> <col class="commands" /> <%= render @tasks %> </table>
タスクテーブルの直前にカスタムヘルパーメソッド navigation_links を追加しています。
app/helpers/tasks_helper.rb を開いて、navigation_links を実装します。
module TasksHelper
def navigation_links
items = []
items << link_or_text('未完了タスク', :tasks)
items << link_or_text('完了したタスク', [ :done, :tasks ])
content_tag(:ul, :class => 'navigation') { items.join.html_safe }
end
private
def link_or_text(text, resource)
html_class = current_page?(resource) ? 'selected' : nil
content_tag(:li, :class => html_class) do
link_to_unless_current(text, resource)
end
end
end
navigation_links メソッドの中身はそれほど複雑ではありませんが、この連載で説明していないメソッドをたくさん使っているので、Rails 初心者の方はチンプンカンプンかもしれません。細かく説明し始めると長くなってしまうので、ここは「こんなものか」と納得していただくとして、次に進みます(スミマセン)。
(訂正) 読者の方からの指摘を受けて、ヘルパーメソッドnavigation_linksの4行目のブロック内部を items.join から items.join.html_safe に修正しました。html_safeは、文字列に「安全である」という印を付ける(正確に言うと、ActiveSupport::SafeBufferのインスタンスに変える)メソッドです。Rails 3.0.0の時は修正前の書き方でも正常に動いたのですが、Rails 3.0.3では意図しないエスケープが行われて、画面表示が乱れてしまいます。(2010/01/06)
次に public/stylesheets/navigation.css を作成します。
ul.navigation {
width: 560px;
margin: 15px auto 5px;
list-style:none;
padding:0;
}
ul.navigation li {
display: inline;
margin-right: 1px;
background-color: #666;
border: solid 1px #ccc;
padding: 5px;
}
ul.navigation li.selected {
background-color: #eee;
}
ul.navigation li a {
color: #fff;
text-decoration: none;
}
ブラウザで動作確認
まだ未完成ですが、ブラウザで動作を確認しましょう。

「完了したタスク」をクリックすると…

「Task 0」だけが表示されます。
unfinish アクション
完了したタスクを元に戻す unfinish アクションを作りましょう。
app/controllers/tasks_controller.rb を次のように修正してください。
class TasksController < ApplicationController
(省略)
def finish
@task = Task.find(params[:id])
@task.update_attribute(:done, true)
redirect_to :back
end
def unfinish
@task = Task.find(params[:id])
@task.update_attribute(:done, false)
redirect_to :back
end
end
これは簡単ですね。
次に、app/views/tasks/_task.html.erb を修正します。
<tr> <td><%= task.name %></td> <td><%= task.due_date %></td> <td><%= finish_or_unfinish_link(task) %></td> </tr>
最後に、app/helpers/tasks_helper.rb を開いて、finish_or_unfinish_link を実装します。
module TasksHelper
(省略)
def finish_or_unfinish_link(task)
if task.done?
link_to('戻す', [ :unfinish, task ], :method => :put)
else
link_to('完了', [ :finish, task ], :method => :put)
end
end
private
(省略)
end
訂正: この記事の発表時には、finish_or_unfinish_link メソッドにおいて :method => :put の記述がなく、正しく動作しませんでした。訂正いたします。(2010/05/20)
ブラウザに戻ってページの再読込をすると、リンク文字列が「戻す」に変わります。

「戻す」をクリックすると…

そして、「未完了タスク」をクリックすると…

イイ感じです。
(2010/05/16)
記事に関するご質問は、 hermes@oiax.jp までメールでお送りください。
ウェブサイト構築の発注先を検討されているお客様は、ご相談フォームをご利用ください。
- はじめに
- 第1回: インストール (2010/04/24)
- 第2回: 新規アプリケーションの作成 (2010/04/25)
- 第3回: コントローラとビューの生成(1) (2010/04/28)
- 第4回: コントローラとビューの生成(2) (2010/04/29)
- 第5回: データベーステーブルとモデル (2010/05/01)
- 第6回: タスクの一覧 (2010/05/02)
- 第7回: タスクの新規追加フォーム (2010/05/09)
- 第8回: タスクの新規追加 (2010/05/09)
- 第9回: タスクの完了 (2010/05/15)
- 第10回: 完了したタスクの一覧 (2010/05/16)
- 第11回: Bundler (2010/05/17)
- 第12回: ユーザー認証(1) (2010/05/18)
- 第13回: ユーザー認証(2) (2010/05/19)
- 第14回: ユーザーとタスクの関連づけ (2010/05/29)
- 第15回: Devise によるユーザー認証機能の日本語化(1) (2010/10/09)
- 第16回: Devise によるユーザー認証機能の日本語化(2) (2010/10/15)
- 第17回: Devise によるユーザー認証機能の日本語化(3) (2010/10/16)

