Ruby Freaks Lounge

第43回 Rails 3を支える名脇役たち その1 - Arel -

この記事を読むのに必要な時間:およそ 5.5 分

Arelを使ってみよう

さて,それでは,さっそく生のArelを実際に使ってみましょう。

例えば,以下のようなデータベースがあったとします(例はSqlite3を使用)⁠

books.sqlite3

create table authors(id integer primary key autoincrement not null, name varchar);
create table books(id integer primary key autoincrement not null, title varchar, price integer, published_date date, author_id integer);

執筆時現在の最新バージョン(0.4.0)では,Arelからデータベースに接続するためにはActiveRecord::Baseのconnectionを利用しています。そこで,ちょっと横着ですが,例えば以下のようにすれば手っ取り早くArelでこのDBに接続することができます。

require 'rubygems'
require 'arel'
require 'sqlite3'
require 'active_record'

ActiveRecord::Base.configurations = {'development' => {:adapter => 'sqlite3', :database => 'books.sqlite3'}}
ActiveRecord::Base.establish_connection('development')

Arel::Table.engine = Arel::Sql::Engine.new(ActiveRecord::Base)

books = Arel::Table.new :books
puts books.to_sql

これを実行すると,以下のようなSQL文が出力されるはずです。

出力されるSQL

SELECT     "books"."id", "books"."title", "books"."price", "books"."published_date", "books"."author_id"
FROM       "books"

それでは,このbooksテーブルに対する問い合わせ条件を追加してみましょう。

books = Arel::Table.new :books
books = books.where(books[:title].eq('Head First Rails'))
puts books.to_sql

出力されるSQL

SELECT     "books"."id", "books"."title", "books"."price", "books"."published_date", "books"."author_id"
FROM       "books" WHERE     "books"."title" = 'Head First Rails'

以下のように,さらに複雑な条件を指定したり他テーブルと結合したりすることもできます。

books = Arel::Table.new :books
authors = Arel::Table.new(:authors).where(authors[:name].eq('david'))
books = books.join(authors).on(books[:author_id].eq(authors[:id]))
books = books.where(books[:price].gt(3000))
books = books.order(books[:published_date])
books = books.take(10)
puts books.to_sql

出力されるSQL

SELECT     "books"."id", "books"."title", "books"."price", "books"."published_date", "books"."author_id", "authors"."id", "authors"."name"
FROM       "books" INNER JOIN "authors" ON "books"."author_id" = "authors"."id"
WHERE     "books"."price" > 3000 AND "authors"."name" = 'david'
ORDER BY  "books"."published_date" ASC
LIMIT 10

このように,Arelによるクエリは,小さな条件を表す一つ一つのメソッド呼び出しをどんどんメソッドチェインしながら組み立てていく形で作られており,最後にcallされた時点で全ての条件を重ね合わせたものを評価するような仕組みになっています。上の例だけだと,昔のO/Rマッパーでよくあった "Criteria" クラス的なものとどこが違うんだ?と疑問に思われるかもしれませんが,ArelではTable.newやwhere等のメソッドの戻り値がそれぞれRelation(のサブクラス)のオブジェクトになっていて,それぞれのRelationが条件そのものを表すと同時に何重にもチェインすることができ,そしてそれ自体が実行可能でもある,というところが特徴的なところです。

このRelationはあくまで「関係」を表す数学的な概念のインスタンスであって,実際にcallされた時点で初めてコンテキストに応じて遅延評価されて適切な形式でクエリに変換されます。

このおかげで,プログラマーから見ると,Arelによるクエリは最小単位の Relation をどんどんメソッドチェインしながら組み立てていくようになっていて,最後に評価された時点ですべての条件を重ね合わせた結果のみが実体化されるという仕組みになっていることが理解いただけたかと思います。

これこそまさに,中学校あたりで図1のような「ベン図」を書きながら習った,あの「集合演算」のイメージそのものですよね。

図1 ⁠ベン図」で書かれた集合演算のイメージ

図1 「ベン図」で書かれた集合演算のイメージ

Arelはこのようにして,関数型由来のダイナミズムというRubyの持つ特性をうまく生かしたアプローチで,複雑な概念をシンプルなインターフェイスに落とし込むことに成功しているのです。

著者プロフィール

松田明(まつだあきら)

フリーランスのRailsプログラマー/Railsコンサルタント。
流しのフェロー。現職は株式会社groovesフェロー。
地域Rubyハッカーコミュニティ"Asakusa.rb"主催。
一児(美少女)の父。