ActiveRecord のお勉強


Rails を勉強する!とこのブログを立ち上げて以来、残念ながら Rails の勉強はあまり進んでいない。その大きな理由は、Rails はかなり大きいフレームワークだからではないか。Javaで言ったら Struts + Hybernate + JUnit といった感じで、フレームワーク数個分に相当するのだ。


個人的な感想としては、中でも Model 部分に相当する O/R マッパーの ActiveRecord がよくできていると感じた。そこで、るびまの記事などを参考に ActiveRecord の学習に挑戦。


ActiveRecord::Base の rdoc が非常にわかりやすい。これは、(Ruby のルートディレクトリ)/lib/ruby/gems/1.8/doc/activerecord(バージョン)/rdoc/index.htmlから探しだせる。


ActiveRecord::Base が持つメソッドについていろいろ調べてみた。


1.接続方法


ActiveRecord を独立したライブラリとして使うのは簡単だ。

require 'active_record'

ActiveRecord::Base.establish_connection(
:adapter => "mysql",
:host => "localhost",
:username => "myuser",
:password => "mypass",
:database => "somedatabase"
)


とかするだけ。あまりに簡単にデータベースに接続できるのであっけにとられた。


2.find() について


大きく分けて4つの方法がある


(1) find(Integer) を使う方法
(2) find(Symbol, :conditions => ...)を使う方法
(3) find(Symbol, 配列) を使う方法
(4) find_by_XXX() を使う方法


(1) が一番単純で、find(1) とかすると、id = 1 のレコードが検索される。・・・と思ったら、マニュアルを読むと、find(1,2,3) とか find([1,2,3]) とかアリらしい。見つからないと例外送出。find(1, 2, 3) とか複数レコードを返す場合でも、容赦なく例外送出される。(うーん、そうなるとなんで、この(1) の場合だけ見つからなかった場合、例外になるのかがよくわからないな。ま、いずれにしろ作者はこの場合では、「見つかって当たり前なコードを書けよ」というメッセージをプログラマに送っているわけだ)


(2) ActiveRecord::Base::find() で、:condtions を指定する場合、まず第1引数には :first か :all を指定する。次に :conditions を指定するが、これも実にいろいろあって、


[1] :conditions => "col1 <> 3" (SQL を直接指定)
[2] :conditions => ["col1 <> ?" , col1 ] (SQL 文をパラメータ指定。SQL インジェクション対策を自動的にやってくれる)
[3] :conditions => { :col1 => 1 , :col 2 => 2 } ( "col1 = 1 AND col2 = 2" という SQL 文が生成される)


と3種類に分かれる。要するに :conditions が文字列・配列・ハッシュのいずれかで、動きが変わってくるということだね。


(3) 配列を使う
(2)-[2] と本質的に同じことなのだが、? の代わりに名前つきプレースホルダを使う。


find(:all, ["col1 = :col1", {:col1 => 1}])


とか使う。


(4) find_by_(カラム名)() を使う (Dynamic attribute-based finders)


find_by_id(1)
find_by_title("my title")


とかする。内部的には "TITLE = 'my title'" とかに変換されているものと思われる。


Note: find(Integer) と find_by_id(Integer) の違い

基本的には同じなのだが、find(Integer)だと、レコードが存在しないとき例外が送出される。find_by_id(Integer) はnil が返るだけ。ちなみに find(:first, :conditions => "1=2") などとしても、例外ではなく nil が返る。


"_and_" で複数条件をつなげる find_by_id_and_title(1, "entry1title") などという方法もある。


さらには、find_by_XXX() のほかにも


find_or_create_by_XXX()
find_or_initialize_by_XXX()


なんていうものまである。


ここまでは、1個の ActiveRecord::Base オブジェクトを返すものだったが、実は、find_all_by_XXX() 系列のメソッドもあり、これは Base オブジェクトの配列を返す。理論的に言って、find_all_or_create_by_ とか find_all_or_initialize_by_とかはないだろう。


ちなみに


find_or_create()
find_or_intialize()


なんていうメソッドはない。


この手のメソッド群は、実際には、元から定義されているものではなく、「メソッドがないよ」と method_missing() に飛んできたときに、メソッド名を構文解析をして、それを適当な SQL 文に変換しているらしい。


3. カラムに任意のオブジェクトを入れる
serialize()


4. Single table inheritance(単一テーブル継承)
継承関係にある複数のオブジェクトを表現するために単一テーブルにすべてのオブジェクトのすべてのカラムをぶち込んでおく。あとは "type" というカラムを追加して、どのクラスに属するレコードか区別可能にしておく。


DB をそうやって準備しておけば、ActiveRecord 側では、単に


class Company < ActiveRecord::Base; end
class Firm < Company; end
class Client < Company; end
class PriorityClient < Client; end


とかしておくだけ。うーむなんて DRY。どのカラムがどのクラスに属するのかは ActiveRecord は関知しないようだ。それはアプリ側で適当に考えて対処する。


以下の例について考える。


class Animal < ActiveRecord::Base; end
class Dog < Animal; end


たとえば id = 1, type = "Animal" のとき
Dog.find(1)
を実行すると、レコードが見つからないという例外が送出される。
(ダウンキャスト禁止)


逆に id = 2, type = "Dog" なレコードがあるとき、
Animal.find(2)
は OK。
(アップキャスト OK)


以上で、参照系はだいたい終わり。次回は更新系の勉強かな。