まちゅダイアリー
2006-10-04 (水)
■ Rails に(再)挑戦 3日目 - 更新順序で並び替え
昨日はビューの見栄えを変えるところまで。 今日はロジック(モデルとコントローラ)に手を加えてみよう。 今の状況だと、各ページは id 順(つまり古い順)に並んでいる。 これを、更新した順(新しい順)に表示されるようにする。
更新順に並べるために、まずは Page クラスへ更新時刻とついでに作成時刻を追加する。
マイグレーション
migration の機能を使って、データベースの pages テーブルに更新時刻と作成時刻を追加する。 マイグレーションファイルの雛形は、 script/generate で生成できる。
$ script/generate migration AddUpdatedToPage
exists db/migrate
create db/migrate/002_add_updated_to_page.rb
pages テーブルに対して、生成時刻 (created) と更新時刻 (updated) のカラムを追加する。 検索速度を速くするために、それぞれにはインデックスを張るようにしている。
$ vi db/migrate/002_add_updated_to_page.rb
class AddUpdatedToPage < ActiveRecord::Migration
def self.up
add_column :pages, :created, :time
add_index :pages, :created
add_column :pages, :updated, :time
add_index :pages, :updated
end
def self.down
remove_column :pages, :created
remove_column :pages, :updated
end
end
rake コマンドを使って、データベースのスキーマを修正する。
$ rake migrate (in /home/machu/work/misc/rails/rn) == AddUpdatedToPage: migrating ================================================ -- add_column(:pages, :created, :time) -> 0.0275s -- add_index(:pages, :created) -> 0.0084s -- add_column(:pages, :updated, :time) -> 0.0300s -- add_index(:pages, :updated) -> 0.0040s == AddUpdatedToPage: migrated (0.0734s) =======================================
コントローラの修正
データベースの準備ができたので、NoteController に手を加える。
一覧表示 (list) にて、更新時刻の新しい順に並び替えるようにオプション(:order => 'updated DESC')を指定する。 それから、新規作成 (create) と更新 (update) の時に、生成時刻と更新時刻と設定するようにした。
$ vi app/controllers/note_controller.rb
class NoteController < ApplicationController
def list
@page_pages, @pages = paginate :pages,
:per_page => 10, :order => 'updated DESC'
end
def create
@page = Page.new(params[:page])
@page.created = Time.now
@page.updated = Time.now
if @page.save
# 中略
end
end
def update
@page = Page.find(params[:id])
@page.updated = Time.now
if @page.update_attributes(params[:page])
# 中略
end
end
end
ビューの修正
せっかく生成時刻と更新時刻を記録するようにしたので、画面にも表示するようにビューを修正する。 具体的には Page の共通ビュー (_page.rhtml) に下記の2行を追加している。 このビューは一覧表示 (list) と個別表示 (show) で共通に使われるようにしているので、一箇所を変えれば一覧表示と個別表示の両方に反映される。 共通化しておいて良かった。
$ vi app/views/note/_page.rhtml
created: <%= page.created.strftime('%Y-%m-%d %H:%M') if page.created %>
updated: <%= page.updated.strftime('%Y-%m-%d %H:%M') if page.updated %>
データの移行
これまでに作られたページには生成時刻も更新時刻も含まれていない。 そこで、コンソール (script/console) を使って、既存のデータに生成時刻と更新時刻を設定する。 迷ったけど、とりあえず現在時刻を両者に設定することにした。
$ script/console Loading development environment. >> Page.find(:all).each do |page| ?> page.created = Time.now >> page.updated = Time.now >> page.save >> end
これで、新規に作ったページや更新したページが、上に表示されるようになった。
■ Subversion への登録
この時点で、いったんソースコードをリポジトリ (Subversion) に保存しておこう。 Rubyist - バリケンのRuby日記 - Subversionの練習を参考にしながら、不要なファイルやディレクトリを登録対象から除外する。
$ svn add * $ svn remove --force log/* $ svn propset svn:ignore "*.log" log $ svn remove --force tmp/sessions/* $ svn propset svn:ignore "*" tmp/ $ svn propset svn:ignore "*" tmp/sessions $ cp db/development.sqlite3 . $ svn remove db/development.sqlite3 $ svn propset svn:ignore "*.sqlite3" db/ $ svn commit -m 'ignore database' db $ mv development.sqlite3 db
ついでに trac のリポジトリブラウザでソースを見られるようにした。 これで安心して、ソースを修正できるよ。
■ 更新時刻の設定をコントローラからモデルへ
さっきの例では、ページの更新時刻をコントローラー (note_controller.rb) で設定していた。 でもよく考えると、コントローラのアクションが増えるごとに、同じような処理を書かないといけない。 既に、 NoteController の create と update で、同じ処理(@page.updated = Time.now)が書かれているし。 コントローラをシンプルに書けるのが Rails のいい所らしいので、処理をコントローラ (note_controller.rb) からモデル (page.rb) へと移す。
Rails のモデル (ActiveRecord::Base) にはコールバックという仕組みがあって、生成や更新時などに実行したい処理を登録できるようになっている。 生成時 (before_create) と更新時 (before_save) にそれぞれ現在時刻を設定する処理を追加した。
Index: app/models/page.rb
===================================================================
--- app/models/page.rb (リビジョン 2)
+++ app/models/page.rb (作業コピー)
@@ -1,4 +1,7 @@
class Page < ActiveRecord::Base
+ before_create {|model| model.created = Time.now }
+ before_save {|model| model.updated = Time.now }
+
def title
body.each {|title| return title.chomp }
end
そして、先ほど追加したコントローラ (note_controller.rb) の create と update の処理を削除する。
Index: app/controllers/note_controller.rb
===================================================================
--- app/controllers/note_controller.rb (リビジョン 2)
+++ app/controllers/note_controller.rb (作業コピー)
@@ -23,8 +23,6 @@
def create
@page = Page.new(params[:page])
- @page.created = Time.now
- @page.updated = Time.now
if @page.save
flash[:notice] = 'Page was successfully created.'
redirect_to :action => 'list'
@@ -39,7 +37,6 @@
def update
@page = Page.find(params[:id])
- @page.updated = Time.now
if @page.update_attributes(params[:page])
flash[:notice] = 'Page was successfully updated.'
redirect_to :action => 'show', :id => @page
これでもちゃんと、生成時刻と更新時刻が設定されていることを確認。 うん。大丈夫そうだ。
リポジトリへも登録しておいた。
さらに簡略化
さらに調べてみると、モデルに created_on と updated_on というカラムを作っておくと、生成時刻と更新時刻を Rails が自動的にセットしてくれるらしい。 惜しい…「_on」が足りなかった。 でも仮に、この機能を知らずに created_on, updated_on なんて名前を付けていたら、それはそれで不思議な挙動に悩んでいただろうな。 モデルのコールバックの使い方も覚えられたし、まぁいいか。
とりあえず、 created を created_on に、 update を updated_on に変更する。 まずはデータベースのマイグレーションから。
$ vi db/migrate/003_rename_update_of_page.rb
class RenameUpdateOfPage < ActiveRecord::Migration
def self.up
remove_index :pages, :created
remove_index :pages, :updated
rename_column :pages, :created, :created_on
rename_column :pages, :updated, :updated_on
add_index :pages, :created_on
add_index :pages, :updated_on
end
def self.down
remove_index :pages, :created_on
remove_index :pages, :updated_on
rename_column :pages, :created_on, :created
rename_column :pages, :updated_on, :updated
add_index :pages, :created
add_index :pages, :updated
end
end
この内容でマイグレーションを実行すると、なぜか既存の時刻の内容が消えてしまった (created_on と updated_on が空になった) 。 まぁ、今はサンプルデータだから支障は無いんだけど。
他にもモデル・コントローラ・ビューを修正する。 Webブラウザからアクセスして、ちゃんと更新順に並ぶことを確認した。
■ 時刻の整形をビューからヘルパーへ
さらにコードの見直し。 生成時刻と更新時刻を表示する時に、時刻のフォーマットを整形しているんだけど、これをビュー (_page.rhtml) に直接書いていた。 ビューの中に nil チェック(if page.created の箇所)が入っているし、時刻のフォーマットを変えるたびに複数の箇所を修正しないといけなくて面倒。
created: <%= page.created_on.strftime('%Y-%m-%d %H:%M') if page.created %>
updated: <%= page.updated_on.strftime('%Y-%m-%d %H:%M') if page.updated %>
Rails では、ビューの見栄えを整えるために、ヘルパーという仕組みが用意されていて、ここで定義したメソッドはビューから直接呼べるようになってる(ヘルパーは module なので、きっとビューに include されるんだろう)。 昨日使った文字を切り取る truncate メソッドも、デフォルトで用意されているヘルパーの一つみたい。
ヘルパーを独自に用意する場合は、 app/helpers/note_helper.rb を編集する。 とりあえず、時刻を整形する show_time メソッドを追加する。
module NoteHelper
def show_time(time)
time ? time.strftime('%Y-%m-%d %H:%M') : ''
end
end
ビュー (_page.rhtml) 側はヘルパーを使うように修正する。
created: <%=h show_time(page.created_on) %> updated: <%=h show_time(page.updated_on) %>
見た目や動きは変わらないけど、コードはすっきりした。 リポジトリにも登録して、おしまい。

