勉強会 第16回
Rails 勉強会に参加した。前半は、RSpec 後半は ActiveRecord に関するセッションだった。
たまたま二つとももろはしさんオーナーのセッションだった。
RSpec に関しては、もろはしさん熱い布教活動(?)を受けた。私の理解では、RSpec は JUnit のようなテストユニットに似ているが、Behavior Driven Development (BDD) という考え方に基づいて作られており、テストというより仕様を機械的に記述することを目標にしているようだ。思想的な背景はよくわからないが、アジャイル開発におけるテスト駆動開発を思想的に純化させたもののように思える。なかなか興味深かった。
もろはしさんは、ActiveRecord の関連テーブルがらみでいろいろ不明な点を持っていたらしく、それを検証するのがセッションのメインとなった。たしかに私にとっても、has_many, belongs_to とかで他のテーブルと関連を持たせたときの更新系の処理がどうなっているのかなかなかわかりづらかった。やはり、ここらへんの動きは、テスト(あるいはRSpec) で記述しておくと頭が整理されるような気がする。
それにしても・・・私は正直、他の O/R マッパーをよく知らないが、ActiveRecord はため息が出るほど美しい。純粋主義者はいろいろ文句をつけるのかもしれないが、いま私のしている泥臭いプログラミングに比べたら・・・。いったい Rails のようなフレームワークを世間はどう受容していくのだろうか?そして、その先にはどんな世界がまっているのだろうか?
もろはしさんの、わたしたち初学者への気配りも印象的だった。ありがとうございました。
ActiveRecord 更新系
Rails のバージョンは 1.2.3 を想定。
基本的に断りがない場合は ActiveRecord::Base のインスタンスメソッドまたはクラスメソッドについての話である。
1. 基本的に更新は save で行う
save と save! の違い
save : 失敗したら false を返す。
save!: 失敗したら RecordInvalid 例外を投げる。
Dave Thomas 「Rails によるアジャイル Web アプリケーション開発」によると、この2つの使い分けは、
save: コントローラのアクションメソッドとして呼び出されることを想定。エラーを画面に表示することを考えると、例外を投げるのはまずいので true/false を返す。
save!: バッチ的な処理で使うべき。
とのこと。
2. update_attribute メソッドとは?
update_attribute(:title, "entry2title_modified")
とすると、直接 この属性の変化をデータベースに書き込む。
実際には update_attribute() で指定しなかった属性も同時に
DB 更新されるらしい。
実際ソースコードを見ると、
# File lib/active_record/base.rb, line 1584
def update_attribute(name, value)
send(name.to_s + '=', value)
save
end
となっていて、単純に save しているだけだとわかる。
3. 楽観的ロック
たとえば、同じレコードを2人のユーザが同時に更新用画面で表示して、それぞれデータベースを更新する、というケースはときどき起こりうる。こんなときに、各自がそれぞれ更新してしまうと、一方で行われた更新が失敗してしまう。
こんなとき、更新するときに、すでに他のプロセスが更新していないかチェックし、更新されていたら、エラーとするやり方を「楽観的ロック」と呼ぶそうだ。
Rails の場合 lock_version という int 型のカラムをテーブルに追加するだけで、Rails は「ああ、楽観的ロックをかけたいんだな」と判断して自動的にやってくれる。
具体的には、テーブル上の lock_version と レコードオブジェクトの lock_version を比べて、同じだったら、更新するときに lock_version を + 1 する。違っていたらエラーとする。
一定時間、すべてのレコードに対する他のプロセスによるアクセスを禁止する悲観的ロックにくらべると、確かに仕組みは単純ながら、スループットも落ちないし、いいやり方だな。どうせ、同時に書き込むことはまずないしね。(構成管理ツールの CVS は楽観的ロックなのに対して、SourceSafe は悲観的ロックなわけだな)
4. 削除
クラスメソッド系(delete / delete_all / destroy_all ) とインスタンスメソッド系(destroy)の2つがある。
(1) クラスメソッド系
delete(Integer or Array) # id を指定
delete_all(conditions = nil) # conditions は find() の conditions と同じもの
destroy_all(conditions = nil)
delete_all と destroy_all の違いは、delete_all はいきなりごそっと DB からレコードを削除するのに対して、 destroy_all はコールバックを呼び出すということ。(これもネタ元は「Web アプリケーション開発」)
(2) インスタンスメソッド系
これは簡単。
たとえば、article.destory とやれば articles テーブルの該当レコードが削除される。