予習 Rails3 (1) Arel

Rails3 では ActiveRecord の部分で Arel という関係代数モデルのモジュールが使われるということなので少しだけ予習しておく。

CREATE TABLE articles
(
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
title TEXT,
body TEXT,
author TEXT,
create_date TIMESTAMP,
update_date TIMESTAMP
);

CREATE TABLE comments
(
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
parent_id INTEGER NOT NULL,
title TEXT,
author TEXT,
mail TEXT,
url TEXT,
body TEXT,
create_date TIMESTAMP,
update_date TIMESTAMP
);

CREATE TABLE num
(
id INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY,
num INTEGER
);

というようなテーブルを作って

require 'rubygems'
require 'mysql'
require 'arel'
require 'active_record'

ActiveRecord::Base.configurations = {
    'default' => {
        :adapter => 'mysql',
        :database => 'test',
        :username => 'username',
        :password =>'password'}
    }
ActiveRecord::Base.establish_connection('default')

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

atcls = Arel::Table.new(:articles)
atcls = atcls.where(atcls[:author].eq('me')).order(atcls[:id].desc).take(100).skip(4)
atcls = atcls.project(atcls[:id].as('number')).where(atcls[:id].gt('10'))
atcls = atcls.group(atcls[:author])

puts atcls.to_sql

結果はこんな感じ

SELECT     `article`.`id` AS 'number' FROM       `article` WHERE     `article`.`author` = 'me' AND `article`.`id` > 10 ORDER BY  `article`.`id` DESC LIMIT 4, 100

JOINもやってみる

cmnts = Arel::Table.new(:comment)
cmnts = cmnts.order(cmnts[:update_date].desc)
ya_atcls = Arel::Table.new(:article)
atcls_with_cmnts = cmnts.join(ya_atcls).on(ya_atcls[:id].eq(cmnts[:parent_id]))

puts atcls_with_cmnts.to_sql
SELECT     `comment`.`id`, `comment`.`parent_id`, `comment`.`title`, `comment`.`author`, `comment`.`mail`, `comment`.`url`, `comment`.`body`, `comment`.`create_date`, `comment`.`update_date`, `article`.`id`, `article`.`title`, `article`.`body`, `article`.`author`, `article`.`create_date`, `article`.`update_date` FROM       `comment` INNER JOIN `article` ON `article`.`id` = `comment`.`parent_id`

LIKE 検索は matches を使う

atcls = atcls.where(atcls[:author].matches('me%'))

INはArrayを使って

array = [1,2,3];
atcls = atcls.where(atcls[:id].in(array))

集計も可能

n = Arel::Table.new(:num)
n = n.project(n[:num].sum.as('sum'))
#n = n.project(n[:num].average.as('sum'))
#n = n.project(n[:num].maximum.as('sum'))

実を言うと Rails 開発で ActiveRecord を使うのはあまり好きではない。
もちろん小さなアプリをテンポよく作っていくのには最適なのだけど、後で負荷が高くなった時のチューニングやサーバの再構成という局面においては他の部分と結合が密なのでかなり厄介な代物になってしまう。なので負荷が高くなりそうなアプリを開発する場合はActiveRecordを使わずにデータベースとのやりとりにはRPCなどを利用してバックエンドのインターフェースを構築し、Rails 側のコントローラと接続することで疎結合を実現させ、負荷が高くなった時点でサーバを切り離し処理内容に応じて間に処理キューを溜めるサーバをさらに挟んだりといったことを行っている。
そういう仕組みが必要になることが見込める時点で Rails 案件ではないのかもしれないけど・・・。