ActiveRecordの「<<」は何してる?
こんなよくある感じのActiveRecordのクラスがあります。
class Blog< ActiveRecord::Base has_many :entries end class Entry < ActiveRecord::Base validates_uniqueness_of :entry_url, :scope=>:blog_id belongs_to :blog end
Blogが複数のEntryを持っていて,Blogのidが同じならばentry_urlが一意であるというようなコードです。以下のようにしてEntryのインスタンスを追加できます。
e = Entry.create(:enrty_url=>'hoge') blog.entries << e
このときEntryのインスタンスであるeが更新されて,DBのblog_idカラムにBlogのインスタンスであるblogのidの値がセットされます。
さてここで「blog.entries << e」を何度か繰り返しても特にエラーは出ません。blog.entriesにはどんどん追加されていく。でもDBには何も追加されない。「同じオブジェクトは追加できんよ」とエラーになるか,異なるidでどんどんDBに追加されるのかと思いましたが,どちらでも無いようです。
「<<」の右辺側が更新されたときのエラーをとれないと困ることがあります。上のように『Entryのurlは所属しているBlogのidのスコープ内で一意』なんてときです。blog_idは「<<」するまで確定しませんから,ここでエラーを補足したいのですが,やり方が分からない。何か勘違いしているのか,「<<」は使っちゃいけないのか,謎は深まる。
【2007.02.26 追記】id:kdmsnrさんのコメントを見て実験してみた。
1) Entryのインスタンスをcreateする(既に存在するentry_urlと同じ値を持つ)
2) DB上にblog_idがnullの状態でレコードが作成される
3) blog.entries << eする
4) 「validates_uniqueness_of :entry_url, :scope=>:blog_id」に違反するのでfalseが返ってくる
5) 何もしないとDB上には「blog_idがnullの状態のレコード」が残ってしまう
となるので,外部キーのスコープで一意制限されるインスタンスをcreateした場合には,『<<』の戻り値を見て明示的に消さないといけない。1)でcreateの代わりにnewを使うとこの問題は回避できるみたい。ただしDBには格納されていなくてもblog.entriesには追加される(blog.entries.sizeは増えていく)ので注意。またblog.saveもfalseを返すようになる。blog.entries.each {|e| puts e.valid?} と各要素にvalid?メソッドを送れば正しいかどうかの判定ができる。