ActiveRecordを支える技術 – Arelとは何者なのか?(全5回) その5 – まとめ


2014年 05月 07日

その1 – その4 まで、Arelの内部構造について説明してきました。
魔法のような技術に思われるArelも、意外と単純な構造をしていました。

今回は、Arelの内部構造をGraphvizで出力する方法を紹介します。
図で構造を出力してみると、クエリがどういうノードで構成されているのかイメージでき、Arelへの理解がもっと深まるかと思います。

以下、目次となります。

内部構造をGraphvizで出力する

その2 – その4 までArelの内部構造を図で表現してきました。
例えば、

product = Arel::Table.new(:products)
product.project('*').to_sql 
# select * from products;

のArel内部構造は、

02-revise-45a86e02-508f-a2a3-0c51-aaaa3506f663.png

のような図で表現してきました。

実はこの図、Arelで出力可能です。

上記コードをArel to_dotで出力すると、以下のような図が取得可能です。

12-5963e4c3-f6c9-4c9f-d5a1-284b1f7756ba.png

内部構造をGraphvizで出力する – graphvizインストールとto_dot

Arel内部構造をGraphvizで出力するためには、graphvizをインストールしている必要があります。
macをお使いなら、

brew install graphviz

でGraphvizのインストールが可能です。

インストール後、Gemfileに以下を追加して、bundle installしてください。

gem 'ruby-graphviz'

これでgraphvizの準備は完了です。

Arelの構造はto_dotと呼ばれる関数で出力可能です。

product = Arel::Table.new(:products)
product.project('*').to_dot
13-9443588d-d1b5-5a03-40e7-1af136f41d92.png

このto_dotで出力した結果をGraphvizに食わせてあげれば良い訳です。

require 'graphviz'
def gviz(arel)
  GraphViz.parse_string(arel.to_dot).output(png: 'arel_internal.png')
end

product = Arel::Table.new(:products)
gviz(product.project('*'))

色々なsqlをgraphvizで出力してみる

今回が最終回ですが、紹介しきれなかったクエリ達も多く存在します。
ほんの一部ですが、紹介できてないクエリをgraphvizで出力してみます。

興味のある方は、ご自身でクエリを組み立て、to_dotして構造を眺めてみてはいかがでしょうか。

色々なsqlをgraphvizで出力してみる – order, limit, offset

product = Arel::Table.new(:products)
product
  .project('*')
  .order(product[:price])
  .take(3)
  .skip(3)
  .to_sql

# mysqlの場合の出力
# "SELECT  * FROM `products`   
# ORDER BY `products`.`price` LIMIT 3 OFFSET 3"
14-8102b7ed-a194-f8e4-919b-8f6f66e287d5.png

色々なsqlをgraphvizで出力してみる – orを含むsql

product = Arel::Table.new(:products)
product
  .project('*')
  .where(product[:price].eq(1000).or(product[:price].gt(2000)))
# "SELECT * FROM `products`  
#  WHERE (`products`.`price` = 1000 OR `products`.`price` > 2000)"
15-31237d1f-c830-f11d-6c3d-81e56a625705.png

色々なsqlをgraphvizで出力してみる – existを含むsql

product = Arel::Table.new(:products)
corporation = Arel::Table.new(:corporations)

exist_query = product
  .where(product[:corporation_id].eq(corporation[:id]))
  .project('1')
  .exists
corporation
  .where(exist_query)
  .project('*')
  .to_sql
# "SELECT * FROM `corporations` 
# WHERE EXISTS (
#   SELECT 1 FROM `products` 
#   WHERE `products`.`corporation_id` = `corporations`.`id`
# )"
16-1dd901a1-2387-1f53-c4e8-c9d8f0aae003.png

まとめ

その1からその5まで、Arelの内部構造を見てきました。
いかがでしたでしょうか。
こうしてみると、意外と構造も単純だし、ソースもそんなに長くないので読みやすいです。

ActiveRecordは内部でArelを利用しています。

ただ、Arelがうまい具合にActiveRecordに融合されているかというと、そうでもないのが現状です。

時として、Arelを強く意識したコードを書かないといけない時もありますし、そもそもActiveRecordでサポートしていない記述も存在します。
(例を挙げると、union, union all等はActiveRecordレベルではサポートしていないので、Arelでクエリを作る必要があります)

Arelを理解していれば、ActiveRecordを使って複雑なクエリを組み立てるときに役立ちます。
ActiveRecord自体の拡張や、独自のO/R Mapperなんかも作れたりしそうです。

色々な可能性のあるArel, 現在も rails/arel にて活発に開発が行われています。
今後も注目していきたいプロダクトです。