今回は以下のようなSQL生成の過程を学んでみます。
product = Arel::Table.new(:products)
product.project('*').where(product[:price].eq(1000)).to_sql
# "SELECT * FROM `products` WHERE `products`.`price` = 1000"
まずは
product[:price].eq(1000)
の部分から読んでみます。
以下、目次となります。
product[:price]ってなんでしょう。
[] 関数は、lib/arel/table.rbに定義されています。
module Arel
class Table
# 色々略しています
def [] name
::Arel::Attribute.new self, name
end
end
end
どうやら単にArel::Attributeのインスタンスを返しているようです。
pryで見てみましょう。
[8] pry(main)> product[:price]
=> #<struct Arel::Attributes::Attribute
relation=
#<Arel::Table:0x007f994e018d78
@aliases=[],
@columns=nil,
@engine=ActiveRecord::Base,
@name="products",
@primary_key=nil,
@table_alias=nil>,
name=:price>
Attributeクラスとはなんでしょう。ソースを見てみます。
lib/arel/attributtes/attribute.rbです。
module Arel
module Attributes
class Attribute < Struct.new :relation, :name
include Arel::Expressions
include Arel::Predications
include Arel::AliasPredication
include Arel::OrderPredications
include Arel::Math
###
# Create a node for lowering this attribute
def lower
relation.lower self
end
end
class String < Attribute; end
class Time < Attribute; end
class Boolean < Attribute; end
class Decimal < Attribute; end
class Float < Attribute; end
class Integer < Attribute; end
class Undefined < Attribute; end
end
Attribute = Attributes::Attribute
end
relationとnameという変数を持つクラスのようです。
なにやら色々includesしていますね。どんな関数を持っているのか、pryで見てみます。
eq 関数は Arel::Predications に定義されているようです。ということで、今度は lib/arel/predications.rb を見てみます。
module Arel
module Predications
# 色々略
def eq other
Nodes::Equality.new self, Nodes.build_quoted(other, self)
end
end
end
Nodes::Equalityのインスタンスを作っているようです。
こいつは単にleft, rightという変数を持っているだけのクラスです。
initializeの第一引数はleftに、第二引数はrightに値をいれています。
ということは、EqualityのleftにはAttributes::Attributeが、rightには other(今回の場合は1000) が入る事となります。
よって、
product[:price].eq(1000)
という操作を行うことで、以下のような構造を持ったArel::Nodes::Equalityが取得できます。
なお、Arel::Nodes は to_sqlが実行可能です。
[2] pry(main)> product[:price].eq(1000).to_sql
=> "`products`.`price` = 1000"
WHERE句の中身が取得できましたね。
whereは前回のproject関数とほぼ同じような実装です。
単にSelectCoreノードのwheresに先ほどのEqualityノードを入れてるだけです。
where関数の実装は、lib/arel/tree_manager.rb にあります。
# いろいろ省略
module Arel
class TreeManager
include Arel::FactoryMethods
def where expr
if Arel::TreeManager === expr
expr = expr.ast
end
@ctx.wheres << expr
self
end
end
end
@ctxは前回でてきましたよね。@ast.cores.lastです。
最初に戻って。以下のコード実行後のSelectManagerの@astの状態は以下の図のようになります。
product.project('*').where(product[:price].eq(1000))
# "SELECT * FROM `products` WHERE `products`.`price` = 1000"
今回は単純でした。whereの実装は前回のprojectの仕組みとほぼ同じ感じです。
今回のメモです。