もりきゅうです。
今回は Ruby on Rails (以下 RoR) を構成するライブラリ群のうち ActiveRecord について掘り下げていきます。
ActiveRecord (以下 AR) は RLR 第 4 回 に取り上げられたように、Ruby での O/R マッピングライブラリのひとつです。 AR を使えば、簡単かつ効率的にリレーショナルデータベース上の情報を Ruby オブジェクトとして扱うことができます。その理論的なところは P of EAA: Active Record を見ていただくことにして、ここではその実際的な使い方を見ていきます。
さて、RoR は非常に早く進化してきましたが、コアはそれほど変わっていません。 AR も使い方はそうそう変わらないだろうと思いますので、一通りその機能を見ていきたいと思います。
今回は、チュートリアルではなく主にリファレンスとして、AR に関する情報を日本語で提供できるように書いてみました。
はじめに、RoR と切り離して AR を使うための簡単なサンプルを紹介します。 次に、AR のソース構造を概観し、AR の test を読んでいきます。
後半はマニュアルの翻訳を試みました。 関連のうち、特に has_many と belongs_to について、生成されるメソッドを概観します。 そして callback, validation といったよく使う機能を見ていきます。 最後に、AR クラス・オブジェクトのリファレンスを載せました。
なお、今回は activerecord-1.10.1 を対象としています。
AR オブジェクトの基本的な扱い方を簡単におさらいしておきましょう。
RoR は全て含めると非常に大きいので、なかなか把握しづらいかもしれません。 そこで、今回は AR を単体で使ってみることにします。 AR を RoR から独立したひとつのライブラリとして扱います。
ここでは RDBMS として MySQL を使うことにします。
rubima という database に rails という user で接続するとします。
users table を作ります。
DB の準備ができました。AR で接続してみましょう。
AR オブジェクトを作るには new メソッドを使います。
create メソッドを使うこともできます。create は new したあと save (DB に格納) します。
引数として、カラム名と値をハッシュにして与えます。
また、ブロックを使うこともできます。
もちろん、作ってから値を設定してもいいです。
なお、new で作った AR オブジェクトは
のように save するまでは DB に格納されません。
検索・抽出を行うには find メソッドを使います。
find のほか find_first, find_all, find_by_sql といったメソッドがあります。
base.rb から例を引用しておきます。 オプションの意味を理解するには若干 SQL の知識が必要です。 詳細はマニュアルを読んでください。
id で検索する例:
find first の例:
find all の例:
クエリに変数を適用するときには、直接 #{} で文字列の中に埋め込むのではなく、パラメータとして渡したほうが安全です。 例えば (base.rb 参照) user_name と password 変数を埋め込むとします。
authenticate_unsafely のように書いたとき、user_name や password にエスケープされていない引用符を含めることができれば、任意のSQLを実行させることができてしまいます。 このようなときは authenticate_safely のように ? を用いて値を渡しましょう。 ? に渡った値はサニタイズされます (文字列ならエスケープされて引用符で囲まれます)。
? がたくさん付くようになると、パラメータの順序を覚えるのがうっとおしくなります。 このようなときは ? の代わりに :名前 を使ってパラメータに名前を付けると良いでしょう。 このときは対応する値をハッシュで与えます。
AR を理解する第一歩として、まず AR の構成を知るところから始めましょう。
まず、ディレクトリ構造を見てみましょう。
ソースツリーは次のようになっています。
lib/ を見てください。
acts/ は list, tree といったデータ構造を AR で実現するための拡張です。 詳細はマニュアルをご覧ください。
associations/ は lib/associations.rb が読み込みます。 ここでは外部キーに基づく関連 (relationship) を実装しています。 belongs_to, has_one, has_many, has_and_belongs_to_many といった関連の定義はここにあります。
connection_adapters/ では DB アダプタを実装しています。
vendor/ には RoR の外部から提供されているライブラリが置かれています。
wrappers/ にはカラム値の変換プラグインが置かれています。今のところ YAML だけです。
さて、require ‘active_record’ あるいは require_gem ‘activerecord’ したときに読み込まれるのは lib/active_record.rb です。このファイルを見てみましょう。 すると、fixtures.rb 以外のファイルはすべてここで require され、各 module が ActiveRecord::Base.class_eval の中で include されていることが分かります。
そして、各 module には次のような仕掛けがあります。 例えば validations.rb:
self.append_features(base) は include 時に呼ばれるのでしたね。 そして module ClassMethods は base.extend されているので、ここで定義されたメソッドは base (= ActiveRecord::Base) のクラスメソッドとして扱われます。ライブラリを分割する手法として非常に参考になります。
また、validations.rb では alias_method を使ってメソッドを置き換えています。
結局、module の多くは ActiveRecord::Base クラスに対する定義になっています。
AR を単体として見るために、AR 自身の test を取り上げます。
AR には RDoc で生成された良くできたマニュアルが付いていますが、それでもソースを読む価値は常にあります。 AR はテスト駆動で作られているため、test に AR の仕様が具体的にコードの形で表われます。 test を読むと、仕様の微妙な箇所や実装の妥協点がよく分かります。
test を読むとっかかりとして、本稿では connection.rb と Topic, Reply クラスだけ解説します。
-I “connections/native_mysql” は $: の設定であり、require ‘connection’ が読み込む connection.rb のパスを指定しています。
ここでは MySQL アダプタを用いる例を見ていきますが、どのアダプタでも基本は同じです。
ここで require ‘fixtures/course’; db2 = ‘activerecord_unittest2’; Course.establish_connection といった部分は、複数のDBを関連付けて扱う multiple_db_test.rb で使われます。この機能については今回取り上げません。詳細は multiple_db_test.rb を読んでください。
なので、今回扱う範囲では、connection.rb は次のもので十分です。
ここで logger を除いてみると、ここで行っているのは ActiveRecord::Base.establish_connection の呼び出しのみということになります。 establish_connection は引数を見て分かるとおり、DBに接続して、その接続を維持します。これ以降、この接続は ActiveRecord::Base.connection で参照できます。
次に base_test.rb を見ると、頭に require ‘abstract_unit’ があります。
ここではライブラリの require を行っています。require ‘connection’ もここにあります。
これはみなさんお馴染みの Test::Unit ライブラリです。
ここで AR ライブラリを読み込んでいます。 fixtures だけ別になっています。fixtures は test のみで使われるからです。
これはデバッグ用のライブラリです。 binding_of_caller, breakpoint の詳細は複雑かつ興味深いものですが、今回は避けます。
ここで先の connection.rb を読み込みます。
create_fixtures を定義していますが、現在は create_fixtures よりも fixtures を使うのが普通です。
fixtures のパスはこのように指定します。
ここまで特に難しくはないですね。
base_test.rb に戻ります。
これも、もうみなさんは見慣れたであろう ActiveRecord::Base から継承した AR クラスの定義です。 普段は、script/generator model で作っておくファイルですね。
この Topic クラスでは、いくつかARの機能が見て取れます。
has_many は1対多の関連付けに用います。 普通はふたつのテーブルを使いますが、Topic は Reply と共に Single table inheritance を構成します。
親 (Topic) を削除するときは子 (Reply) も削除するという指定です。
外部キーを parent_id にします。:foreign_key を省略したときは、外部キーの名前は相手のテーブル名から作られます。 逆に言うと、キー名を自由に決めていいときは、AR の仕様に合わせて名前を決めたほうがいいでしょう。
content を YAML 形式で格納します。
コールバックです。create, destroy 時に実行するメソッドを指定しています。
これは Topic から継承する Reply クラスで使うことを想定しているものと考えられます。 Reply ではない Topic では parent_id は nil のはずですから、find は常に失敗します。
ここで Topic に対応するDBスキーマを見ておきましょう。 MySQL のものは以下にあります。
注目したいのは replies_count, parent_id, type カラムです。これらは Reply との関係で使われます。 type カラムはクラス名を格納します。これは Single table inheritance の肝です。 id カラムは auto_increment な primary key です。 そのほかのカラムは単純な値の入れ物です。
topic.rb で何度も Reply について言及しましたので、reply.rb も合わせて見ておきましょう。
belongs_to は関連するふたつのテーブルのうち association id を持つテーブル側に指定する関連付けでしたね。
topics table は parent_id を association id として、ひとつのテーブルで継承関係を作ります。
この指定により replies_count カラムを自動的に更新します。
これらは validation と呼ばれる機能です。値のチェックを行います。
ActiveRecord::Base#attribute_present?(カラム名) は、カラムが存在してかつ nil? でも empty? でもない、ようするに、値を持っていることを確認します。
attr_accessible は、アクセスできるカラム名を (緩やかに) 制限します。attr_protected と対になります。
Reply からさらに継承しています。Topic から見ると二段階の has_many になります。
参考: 特に一般化したリスト構造やツリー構造を扱う際には acts mixin が使えます。
base_test.rb ではほかにも多くのテーブルを扱っていますが、とりあえず Topic と Reply で留めておきます。 base_test.rb の test を読むと、AR の仕様が見えてくると思います。
以降は、関連、コールバック、validation について、マニュアルを読みつつ見ていきます。
Topic, Reply を元に、AR の関連 (relationship) についてまとめてみます。
Topic, Reply は AR クラスです。よって new, find など AR クラスのメソッドは全て使えます。
さらに has_many, belongs_to の指定によってメソッドが追加されます。
Topic クラスで has_many :replies を宣言すると、次のメソッドが追加されます。
関連付けられた reply の配列を返します。 もし reply がなければ空配列を返します。
Reply を使えば
と書けます。
Topic#replies の結果はキャッシュされます。 同じ topic で再度 topic.replies を呼び出したときはキャッシュされた値が返ります。 force_reload = true にすると、リロードします (キャッシュを削除して DB から読み直します)。
指定された reply を追加します。
DB 上は外部キーに topic.id を設定します。
指定された reply を取り除きます。
DB 上は外部キーに NULL を設定します。
もし :dependent => true であれば reply は destroy されます。
全ての reply を取り除きます。ただし reply は destroy されません。
reply がなければ真を返します。
これは
と同等です。
reply の個数を返します。
Reply を使えば
と書けます。
counter_cache が有効であればキャッシュの値が使われます。
reply を検索・抽出します。 引数は Base.find と同じ規則です。
Reply を使えば
と書けます。
新たな reply オブジェクトを生成します。 引数は Base.new と同じ規則です。
new と同様、すぐに save はされません。
Reply を使えば
と書けます。
新たな reply オブジェクトを生成し、save します (validation チェックは行います)。
Reply を使えば
あるいは
と書けます。
has_many はオプションのハッシュをとることができます。
全て書く余白がないので ;)、オプションについては割愛します。
Reply クラスで belongs_to :topic を宣言すると、次のメソッドが追加されます。
関連付けられた topic を返します。なければ nil を返します。
topic を関連付けます。DB 上は外部キーに topic.id を設定します。
新たな topic を生成し、そして、この reply を関連付けます。 save しません。
新たな topic を生成し、そして、この reply を関連付けます。 save します。
belongs_to はオプションのハッシュをとることができます。
詳しくはマニュアルをご覧ください。
(callbacks.rb の部分的な翻訳です)
(new_record な) AR オブジェクトを save するときに呼ばれるコールバックは次の通りです。
9 つのコールバックがありますね。 コールバックは、Active Record ライフサイクルのそれぞれの状態に反応し準備するための、とても大きな力となります。
コールバックの例:
上書き可能なコールバックメソッドに加えて、コールバックマクロでコールバックを登録できます。 その主な利点は、マクロは継承階層の影響を受けずにコールバックキューに振る舞いを追加できることです。
例えば:
さて、 Topic#destroy を実行すると、destroy_author だけ呼ばれます。 Reply#destroy を実行すると、destroy_author と destroy_readers が呼ばれます。
同じ振る舞いを、上書き可能なメソッドで実装した場合と比較してみましょう。
こうすると、Reply#destroy は destroy_readers だけ実行し、destroy_author は実行しません。
なので、 あるコールバックが全階層で呼ばれることを保証したいときには、コールバックマクロを使い、 それぞれの派生先で留めたいときは、上書きメソッドを使えばいいでしょう。 上書きメソッドは、super を呼んで派生元のコールバックを行うか決めることができます。
重要: 継承関係がコールバックキューに対して正しく働くためには、 関連を指定する前にコールバックを指定する必要があります。 そうしないと、コールバックを登録している親の前に子を load したときに、継承されません。
コールバックには 4 つのタイプがあります。
メソッドリファレンスとコールバックオブジェクトは、推奨されるアプローチです。 proc を使ったインラインメソッドは、ときには適しています (例えば mix-in するとき)。 インライン eval メソッドは推奨されません。
after_find と after_initialize は、パフォーマンスの制約から、(def after_find のように) 明確に定義されたときだけ実行されます。
bafore_* の戻り値を false にすると、それ以降のコールバック全てと、そのコールバックに関連付けられたアクションがキャンセルされます。 after_* の戻り値を false にすると、それ以降のコールバック全てがキャンセルされます。
コールバックは定義された順に呼ばれますが、モデル上にメソッドとして定義されたコールバックは最後に呼ばれます。
(validations.rb の部分的な翻訳です)
AR オブジェクトは Base#validate (あるいは validate_on_create validate_on_update) を上書きすることで validation を実装できます。 これらのメソッドは、オブジェクトの状態を検査でき、カラムがある値を持つこと (empty でない、範囲内にある、ある正規表現にマッチするといったようなこと) を保証できます。
validation の例:
errors オブジェクトは、それぞれの AR オブジェクトに対して自動的に生成されます。 より高度な validation については ActiveRecord::Validations::ClassMethods をご覧ください。
ActiveRecord は ActionPack から独立していますが、連携させて用いることが多いです。 ここでは ActionPack の ActiveRecord 対応機能をいくつか取り上げます。
ActionController の create, update action で AR オブジェクトを save するときに、値をチェックしたいことがあります。
値をチェックするには、モデルクラスに validation メソッドを定義して、 望ましくない値を持つ場合に errors.add(カラム名, メッセージ) でエラーを追加します。
errors が存在すると (errors.empty? でないと) save に失敗することになります (ActiveRecord::Base#save が false を返します)。 ActionController 側では、普通、save に成功すれば redirect_to し、失敗すれば render で元のページを表示します。
errors.on(カラム名) は対応するメッセージを返します。そのカラム名に対応するエラーがないときは nil を返します。 これを編集用テンプレートに埋め込むと、save に失敗した理由を表示することができます。
また、text_field といった helper メソッドは、カラム名に対応するエラーが存在するときにはタグを <div class=”fieldWithErrors”>…</div> で囲みます。 これを利用すると、スタイルシートを使ってエラーの原因となった入力フォーム要素を目立たせることができます。
RoR の session 機能は CGI::Session を用いていて、標準では PStore を用いてファイルとして格納しています。 この格納方法は取り替えることができ、そのひとつに ActiveRecordStore があります。 これは session を AR オブジェクトとして扱い、DB に格納します。
date_helper を使うとひとつのカラムに対して複数の値を渡すことになります。 この複数の値をひとつにまとめているのは ActionPack でしょうか。それとも ActiveRecord でしょうか。 これは ActiveRecord です。 ActiveRecord::Base#attributes= がそれを実現しています。
RoR は cattr_accessor という accessor 宣言を用意しています。 これはクラス変数用の attr_accessor です。 例えば、
とすれば、@@primary_key_prefix_type は
で読み書きできるようになります。
ここでは特に名前の設定に関するクラス変数をまとめておきます。
全てのプライマリキーカラム名の頭に付加する prefix のタイプを指定します。
:table_name を指定すると、Product クラスはプライマリキーカラムとして “id” の代わりに “productid” を探します。
:table_name_with_underscore を指定すると、Product クラスはプライマリキーカラムとして “id” の代わりに “product_id” を探します。
これは全 AR クラスに共通の設定になります。
全てのテーブル名の頭に付加する prefix 文字列を指定します。
“basecamp_” を設定すると、テーブル名は “basecamp_projects”, “basecamp_people” などとなります。 これは共有される DB の名前空間を作るのに便利です。
デフォルトは空文字列です。
table_name_prefix と同様に働きますが、頭ではなく後ろに付加されます (“_basecamp” を設定すると “projects_basecamp”, “people_basecamp” となります)。
デフォルトは空文字列です。
テーブル名をクラス名の複数形にするかを指定します。
true なら、Product クラスのデフォルトテーブル名は products になります。false なら、product になります。
table/class 名前付けの規則の詳細については、table_name を見てください。
デフォルトは true です。
AR のクラスメソッドをまとめてみます。 ただし、ここでは base.rb で定義されているものだけ扱います。 すでに書いた new, create, find, find_* は除きます。
データ操作を行うメソッドは、おおまかにみて、SQL を直接扱うものと AR オブジェクトを通すものに区別できます。
destroy と delete は、DB から該当行を削除します。 削除という意味ではどちらも同じですが、実行の仕方に違いがあります。
destroy は find で得た AR オブジェクトを destroy します。このとき、AR のコールバックは全て働きます。 例えば、:dependent による子の削除は、コールバック (before_destroy) によって実現されているので、destroy したときに行われます。
delete は AR オブジェクトを生成せずに直接 SQL (delete from …) を発行します。 そのため、コールバックは働きません。:dependent による削除は行われません。
destroy_all, delete_all は、条件に合う行を削除します。 条件の書き方は find と同じです。
destroy_all, delete_all は destroy, delete と同じ関係にあります。
update は、複数のカラムを更新し、save します。
update は find(id) して得られる AR オブジェクトに対して update_attributes(attributes) を行います。 update_attributes(attributes) は self.attributes = attributes して save します。戻り値は save の戻り値です。
update_all は、直接 SQL を使った更新を行います。updates は SQL 文です。 delete_all と同様、直接 SQL (update …) を発行します。
count は、条件に合う行数を返します。 直接 SQL (select count(*) from …) を発行します。joins は追加 SQL 文です。
count_by_sql は直接 SQL を発行します。sql は SQL 文で、select count(*) from … で書き始めます。 戻り値は、最初のカラムを to_i した値です。
increment_counter, decrement_counter は、カウンタとして扱うカラムの値を +1, -1 します。
update_all を用いて実現されています。直接 SQL を発行します。
上に書いたような SQL を用いたデータ操作のためのメソッドとは別に、Ruby レベルでの動作を設定するためのクラスメソッドがあります。 これらは AR のクラス定義で関数的に使います。
なお、Ruby 側の属性 (attribute) と DB 側のカラム (column) は区別されますが、ここでは全てカラムと書いています。
attr_protected は、カラムへの代入を制限します。制限するのは new や attributes= のようにオブジェクトのカラムを一括して扱うメソッドに対してであって、カラムに対応するメソッドを使って代入することは制限しません。
attr_protected で指定されたカラム名の配列を返します。
attr_accessible は、attr_protected とは逆に、(一括した) 代入を許すカラム名を指定します。
attr_accessible で指定されたカラム名の配列を返します。
serialize は、シリアライズするカラムを指定します。 カラムへの格納する値を YAML で変換します。これにより、Ruby の配列やハッシュをそのまま読み書きできるようになります。
class_name を指定すると、カラムに格納するオブジェクトのクラスを制限できます。
serialize で指定されたカラム名をキーとし、クラスを値とするハッシュを返します。
DB テーブルやキーの名前を扱うクラスメソッドです。
AR クラスに対応する DB テーブル名を返します。 これは AR クラスの継承に対応しています。 例えば、Reply < Topic < ActiveRecord ならば、Reply 上でも Topic の table_name が返ります。
プライマリキーを返します。標準では “id” ですが、オプションによってはテーブル名が付加されたりします。
AR クラスの継承を行う際、DB 側にクラス名を保存します。 inheritance_column はクラス名の保存先となるカラム名です。
これらは、対応する getter メソッドを再定義します。 以前の getter メソッドは original_* に alias されるので (table_name なら original_table_name)、block の中で original_* メソッドを使うと、以前の規則を元に名前を作ることができます。
DB テーブル名に対応する AR クラス名を返します。 table_name メソッドの逆です。
カラムオブジェクト (ActiveRecord::Base::Column) を扱うクラスメソッドです。
カラムオブジェクトの配列を返します。
カラム名をキーとし、カラムオブジェクトを値とするハッシュを返します。
カラム名の配列を返します。
カラムオブジェクトのうち、プライマリキーや継承クラス名キーそして _count, _id で終わる名前のカラムに対応するものを除いた配列を返します。
カラム名をキーとし、カラムに対応するメソッド名 (attr, attr=, attr?, attr_before_type_cast) のシンボル値を値とするハッシュの配列を返します。
サニタイズした値を返します。connection.quote の委譲です。
Benchmark.measure を用いたベンチマークを行います。
logger によるログをとらないブロックを提供します。
AR のインスタンスメソッドをまとめます。
id の値を返します。 id は DB のプライマリキーに対応します。
キャストする前の id の値を返します。
quote した id の値を返します。
id の値を設定します。
save されていない (まだ DB 上に格納されていない) オブジェクトであるか。
DB に格納します。true を返します。
new_record であれば insert し、そうでなければ update します。
ただし、validation チェックに引っかかった場合は格納しません。このときは false を返します。
save されていれば (new_record? でなければ) DB から行を削除します (SQL 文 (delete from …) を発行します)。
また、どちらにしても freeze します。
AR オブジェクトのクローンを作成します。この際 id を未設定にするので、new_record として扱います。
ひとつのカラムを更新し、save します。 これは特に、既存 record の boolean フラグの更新に役立ちます。
戻り値は save の戻り値です。 ただし、validation チェックは回避されます。
複数のカラムを更新し、save します。
self.attributes = attributes して save します。戻り値は save の戻り値です。
0 で初期化したあと、+1, -1 します。数値を扱うカラムでのみ動作します。
increment, decrement したあと、save します。
真偽を入れ替えます。self を返します。
toggle したあと、save します。
リロードします (キャッシュを破棄し、DB から値を取り込みます)。self を返します。
カラム値を取得します。
得られるのはキャスト後の値です。例えば DATE カラムの “2004-12-12” は Date.new(2004, 12, 12) として得られます。
read_attribute メソッド (protected) の alias です。
カラム値を設定します。
write_attribute メソッド (protected) の alias です。
ハッシュで一度に複数のカラム値を設定します。 attr_protected, attr_accessible の制限を受けます。
カラム名をキーとし、カラム値 (キャスト後) を値とするハッシュを返します。
attribute はカラム名です。 その名前のカラムが存在してかつ値が nil? でも empty? でもなければ true を、そうでなければ false を返します。
ソートしたカラム名の配列を返します。
name に対応するカラムオブジェクトを返します。
ごめんなさい。力尽きました。orz 今回もさまざまな形でご意見をいただきました。感謝いたします。
もりきゅうは ミッタシステム のプログラマです。
著者の連絡先は moriq@moriq.com です。