第10回: 完了したタスクの一覧
2010/05/16
前回は、タスクを完了する 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)
ブラウザに戻ってページの再読込をすると、リンク文字列が「戻す」に変わります。
「戻す」をクリックすると…
そして、「未完了タスク」をクリックすると…
イイ感じです。