---
title: AST を使って ActiveRecord の where の条件式をブロックで記述しよう
tags: 
author: [osyo](https://www.docswell.com/user/pink_bangbi)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/DKJ4WWWP71.jpg?width=480
description: https://ginza-rails.connpass.com/event/226024/
published: December 23, 21
canonical: https://www.docswell.com/s/pink_bangbi/KLJRD5-2021-12-23-205811
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/DKJ4WWWP71.jpg)

AST を使って ActiveRecord の
where の条件式を
ブロックで記述しよう
【オンライン開催】銀座Rails#38


# Page. 2

![Page Image](https://bcdn.docswell.com/page/PLE1YYYX7G.jpg)

今日話すこと！


# Page. 3

![Page Image](https://bcdn.docswell.com/page/8GEWGGGKJ2.jpg)

今日話すこと！
`User.where(&quot;? &lt;= age&quot;, 20)`
を


# Page. 4

![Page Image](https://bcdn.docswell.com/page/W47ZLLLNJ3.jpg)

今日話すこと！
`User.where(&quot;? &lt;= age&quot;, 20)`
を
`User.where { 20 &lt;= :age }`
とかけるようにしたい！！！


# Page. 5

![Page Image](https://bcdn.docswell.com/page/KYJ6WWW9JV.jpg)

自己紹介
名前：osyo
Twitter : @pink_bangbi
github : osyo-manga
ブログ : Secret Garden(Instrumental)


# Page. 6

![Page Image](https://bcdn.docswell.com/page/5GJ5MMMDJ4.jpg)

自己紹介
名前：osyo
Twitter : @pink_bangbi
github : osyo-manga
ブログ : Secret Garden(Instrumental)
RubyKaigi Takeout 2021
Use Macro all the time ~ マクロを使いまくろ ~


# Page. 7

![Page Image](https://bcdn.docswell.com/page/4LE3WW9QE5.jpg)

自己紹介
名前：osyo
Twitter : @pink_bangbi
github : osyo-manga
ブログ : Secret Garden(Instrumental)
RubyKaigi Takeout 2021
Use Macro all the time ~ マクロを使いまくろ ~
銀座Rails
これからの Ruby と今の Ruby について
12月25日にリリースされる Ruby 3.0 に備えよう！


# Page. 8

![Page Image](https://bcdn.docswell.com/page/V8EDKKGW7G.jpg)

自己紹介
名前：osyo
Twitter : @pink_bangbi
github : osyo-manga
ブログ : Secret Garden(Instrumental)
RubyKaigi Takeout 2021
Use Macro all the time ~ マクロを使いまくろ ~
銀座Rails
これからの Ruby と今の Ruby について
12月25日にリリースされる Ruby 3.0 に備えよう！
BuriKaigi2021
Ruby 2.0 から Ruby 3.0 を駆け足で振り返る


# Page. 9

![Page Image](https://bcdn.docswell.com/page/4V7PKK3XJ8.jpg)

自己紹介
名前：osyo
Twitter : @pink_bangbi
github : osyo-manga
ブログ : Secret Garden(Instrumental)
RubyKaigi Takeout 2021
Use Macro all the time ~ マクロを使いまくろ ~
銀座Rails
これからの Ruby と今の Ruby について
12月25日にリリースされる Ruby 3.0 に備えよう！
BuriKaigi2021
Ruby 2.0 から Ruby 3.0 を駆け足で振り返る
Ruby 3.1 で楽しみな機能は debug.gem と Hash のショートハンド


# Page. 10

![Page Image](https://bcdn.docswell.com/page/L2JVVV43JQ.jpg)

なんで where にブロックを渡したいの？


# Page. 11

![Page Image](https://bcdn.docswell.com/page/Z5EGLL1YJL.jpg)

なんで where にブロックを渡したいの？
できたら面白そうじゃん


# Page. 12

![Page Image](https://bcdn.docswell.com/page/V4JQYYD67P.jpg)

アジェンダ


# Page. 13

![Page Image](https://bcdn.docswell.com/page/DK74WWZME1.jpg)

アジェンダ
既存の実装を紹介


# Page. 14

![Page Image](https://bcdn.docswell.com/page/PLJ1YYRYEG.jpg)

アジェンダ
既存の実装を紹介
AST を利用して Ruby のコードを SQL 文に変換してみる


# Page. 15

![Page Image](https://bcdn.docswell.com/page/8GJWGG1172.jpg)

アジェンダ
既存の実装を紹介
AST を利用して Ruby のコードを SQL 文に変換してみる
activerecord-where_with_block をつくったので紹介


# Page. 16

![Page Image](https://bcdn.docswell.com/page/W4EZLLPX73.jpg)

伝えたいこと


# Page. 17

![Page Image](https://bcdn.docswell.com/page/KY76WWMP7V.jpg)

伝えたいこと
AST というデータ構造に付いて少しでも学んでもらいたい


# Page. 18

![Page Image](https://bcdn.docswell.com/page/5G75MMZP74.jpg)

伝えたいこと
AST というデータ構造に付いて少しでも学んでもらいたい
AST を利用する事でもっと Ruby のコードをメタ的に扱える


# Page. 19

![Page Image](https://bcdn.docswell.com/page/V9J299RZER.jpg)

伝えたいこと
AST というデータ構造に付いて少しでも学んでもらいたい
AST を利用する事でもっと Ruby のコードをメタ的に扱える
AST を使ったらこんな事が実現できるんだよ！を体験してもらいたい


# Page. 20

![Page Image](https://bcdn.docswell.com/page/5DEY44DNJM.jpg)

既存の実装


# Page. 21

![Page Image](https://bcdn.docswell.com/page/2VJNYY6V78.jpg)

activerecord-refinements
Refinements を利用してブロック内でのみ `Symbol#==` などを再定義している実装
元々は Ruby 2.0 の Refinements 実装時に実験的に作られた gem
ブロック内の :name == &#039;matz&#039; は table[:name].eq &#039;matz&#039; を返す
`Symbol#==`
だと `table[col].eq val` を呼び出すような実装
現在はブロック内で `using` を適用する事ができなくなっており動かない
ブロック内でのみ再定義した Symbol#== が有効になる
1
#
2
User.where { :name == &#039;matz&#039; }.to_sql
3
# =&gt; SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;name&quot; = &#039;matz&#039;
4
5
User.where { :name =~ &#039;tender%&#039; }.to_sql
6
# =&gt; SELECT &quot;users&quot;.* FROM &quot;users&quot; (&quot;users&quot;.&quot;name&quot; LIKE &#039;tender%&#039;)


# Page. 22

![Page Image](https://bcdn.docswell.com/page/GYE9PPLVJ3.jpg)

activerecord-blockwhere
`#method_missing`
`#method_missing`
を利用してブロック内のメソッド呼び出しをフックしている実装
の戻り値が `arel_table[name]` を返す実装
ブロック内の `id.eq(1)` は `arel_table[:id].eq(1)` を返す
1
Person.where { id.eq(1) }.to_sql
2
# =&gt; SELECT &quot;people&quot;.* FROM &quot;people&quot;
3
WHERE &quot;people&quot;.&quot;id&quot; = 1
で &amp;&amp; を模倣している
4
# &amp;
5
Person.where { id.eq(1) &amp; name.matches(&#039;%alice%&#039;) }.to_sql
6
# =&gt; SELECT &quot;people&quot;.* FROM &quot;people&quot;
7
WHERE (&quot;people&quot;.&quot;id&quot; = 1 AND &quot;people&quot;.&quot;name&quot; LIKE &#039;%alice%&#039;)
関連先を join する
8
#
9
Person.where { name.eq(&#039;alice&#039;) &amp; entries.name.matches(&#039;%hello%&#039;) }.to_sql
10
# =&gt; SELECT &quot;people&quot;.* FROM &quot;people&quot;
11
#
INNER JOIN &quot;entries&quot; ON &quot;entries&quot;.&quot;person_id&quot; = &quot;people&quot;.&quot;id&quot;
12
#
WHERE (&quot;people&quot;.&quot;name&quot; = &#039;alice&#039; AND &quot;entries&quot;.&quot;name&quot; LIKE &#039;%hello%&#039;)


# Page. 23

![Page Image](https://bcdn.docswell.com/page/5GE8DDX4ED.jpg)

[PR #39445] Where with block
Rails で提案されている実装
ブロックの引数に対してカラムを参照して Arel の処理を呼び出す
動的に元のモデルのカラムのメソッドを定義して処理をフックしている
また `#method_missing` を利用して関連先のテーブルに対してのクエリも記述できる
ブロックの引数に対してクエリを記述する
1
#
2
Post.where { |post| post[:updated_at].gt(1.day.ago) }
3
は method_missing 経由で呼び出している
4
# comments
5
Post.joins(comments: :user).where { |post| post.comments.user[:first_name].eq(&quot;John&quot;) }
6
こっちはブロックの引数なしでクエリを記述する
7
#
8
Post.where { updated_at.gt(1.day.ago) }
9
関連先のクエリを記述する
10
#
11
Post.joins(comments: :user).where { comments.user.first_name.eq(&quot;John&quot;) }


# Page. 24

![Page Image](https://bcdn.docswell.com/page/MLELMM8V7R.jpg)

AST とは


# Page. 25

![Page Image](https://bcdn.docswell.com/page/54JMYY6NJW.jpg)

AST とは


# Page. 26

![Page Image](https://bcdn.docswell.com/page/ZPJR99P479.jpg)

AST とは
AST とは Abstract Syntax Tree の略
日本語だと抽象構文木と呼ばれるもの


# Page. 27

![Page Image](https://bcdn.docswell.com/page/NPEXQQ3ZJX.jpg)

AST とは
AST とは Abstract Syntax Tree の略
日本語だと抽象構文木と呼ばれるもの
Ruby のコードを抽象化し、扱いやすくしたデータ構造


# Page. 28

![Page Image](https://bcdn.docswell.com/page/63EK99YVED.jpg)

AST とは
AST とは Abstract Syntax Tree の略
日本語だと抽象構文木と呼ばれるもの
Ruby のコードを抽象化し、扱いやすくしたデータ構造
AST を利用することで Ruby のコードをメタデータ的に扱うことができる
今回は `RubyVM::AbstractSyntaxTree` を利用する


# Page. 29

![Page Image](https://bcdn.docswell.com/page/4L73WWVQ75.jpg)

AST とは
AST とは Abstract Syntax Tree の略
日本語だと抽象構文木と呼ばれるもの
Ruby のコードを抽象化し、扱いやすくしたデータ構造
AST を利用することで Ruby のコードをメタデータ的に扱うことができる
今回は `RubyVM::AbstractSyntaxTree` を利用する
AST は『種類』と『子ノード』の2つの情報を持っておりそれが再帰的なデータ構造にな
っている


# Page. 30

![Page Image](https://bcdn.docswell.com/page/V87DKK8WJG.jpg)

AST とは
AST とは Abstract Syntax Tree の略
日本語だと抽象構文木と呼ばれるもの
Ruby のコードを抽象化し、扱いやすくしたデータ構造
AST を利用することで Ruby のコードをメタデータ的に扱うことができる
今回は `RubyVM::AbstractSyntaxTree` を利用する
AST は『種類』と『子ノード』の2つの情報を持っておりそれが再帰的なデータ構造にな
っている
AST の種類は構文ごとに細かく分かれていて100種類以上ある


# Page. 31

![Page Image](https://bcdn.docswell.com/page/4VJPKK8XE8.jpg)

コード
1
抽象構文木
src = &quot;:name == &#039;homu&#039;&quot;
SCOPE
2
3
ast = RubyVM::AbstractSyntaxTree.parse(src)
4
上の AST のデータ構造
5
# Ruby
6
pp ast
7
# =&gt; (SCOPE@1:0-1:15
8
#
tbl: []
9
#
args: nil
10
#
body:
11
#
12
#
args
body
[]
nil
OPCALL
(OPCALL@1:0-1:15 (LIT@1:0-1:5 :name) :==
(LIST@1:9-1:15 (STR@1:9-1:15 &quot;homu&quot;) nil))
13
LIT
:==
LIST
の種類
14
# ast
15
pp ast.type # =&gt; :SCOPE
16
tbl
自身の子ノード
17
#
18
pp ast.children
19
# =&gt; [[],
20
#
nil,
21
#
(OPCALL@1:0-1:15 (LIT@1:0-1:5 :name) :==
22
#
(LIST@1:9-1:15 (STR@1:9-1:15 &quot;homu&quot;) nil))]
:name
STR
&#039;homu&#039;
nil


# Page. 32

![Page Image](https://bcdn.docswell.com/page/L2EVVVN3EQ.jpg)

AST の対応表（一部）
type
コード
AST
`LIT`
`1`
`[:LIT, [1]]`
`STR`
`&quot;string&quot;`
`[:STR, [&quot;string&quot;]]`
`VCALL`
`func`
`[:VCALL, [:func]]`
`CALL`
`func.bar`
`[:CALL, [[:VCALL, [:func]], :bar, nil]]`
`QCALL`
`func&amp;.bar`
`[:QCALL, [[:VCALL, [:func]], :bar, nil]]`
文字列リテラル
メソッド呼び出し
`.` 呼び出し
`&amp;.` 呼び出し
`OPCALL`
`1 + a`
`[:OPCALL, [[:LIT, [1]], :+, [:LIST, [[:VCALL,
[:a]], nil]]]]`
演算子呼び出し
`AND`
`a &amp;&amp; b`
`[:AND, [[:LIT, [1]], [:VCALL, [:b]]]]`
&amp;&amp; 演算子
NOTE: 実データは `RubyVM::AST::Node` になるがわかりやすく配列で表記している
意味
数値やシンボルリテ
ラルなど


# Page. 33

![Page Image](https://bcdn.docswell.com/page/Z57GLLKYEL.jpg)

AST を利用して Ruby のコードから
SQL 文に変換してみよう！


# Page. 34

![Page Image](https://bcdn.docswell.com/page/V4EQYYN6JP.jpg)

AST を利用して Ruby のコードから
SQL 文に変換してみよう！
1
↓
:name == &quot;homu&quot; &amp;&amp; 20 &lt;= :age


# Page. 35

![Page Image](https://bcdn.docswell.com/page/DKJ4WWGM71.jpg)

AST を利用して Ruby のコードから
SQL 文に変換してみよう！
1
:name == &quot;homu&quot; &amp;&amp; 20 &lt;= :age
↓
1
name = &quot;homu&quot; AND 20 &lt;= age


# Page. 36

![Page Image](https://bcdn.docswell.com/page/PLE1YYDY7G.jpg)

実装をライブコーディング


# Page. 37

![Page Image](https://bcdn.docswell.com/page/8GEWGGY1J2.jpg)

Gem 化したよ！！！


# Page. 38

![Page Image](https://bcdn.docswell.com/page/W47ZLLXXJ3.jpg)

activerecord-where_with_block
のブロック内のコードが SQL として展開される
実装は AST……ではなくて kenma というライブラリのマクロ機能を使ってる
`where`
間接的に AST を使っているのでセーフ
シンボルリテラルをカラムとして参照するようになる
1
#
2
puts User.where { :name == &quot;homu&quot; }.to_sql
3
# =&gt; SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;name&quot; = &#039;homu&#039;
4
#
5
puts User.where { &quot;homu&quot; == :name }.to_sql
6
# =&gt; SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;name&quot; = &#039;homu&#039;
7
シンボルと値を逆にしても動作する
変数やメソッドも参照できる
8
#
9
def age; 20 end
10
name = &quot;homu&quot;
11
puts User.where { :name == name || :age &lt; age }.to_sql
12
# =&gt; SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE (&quot;users&quot;.&quot;name&quot; = &#039;homu&#039; OR &quot;users&quot;.&quot;age&quot; &lt; 20)
13
ブロック内に式を書くとその結果が SQL に反映される
14
#
15
puts User.where { :name == &quot;homu#{&quot;homu&quot;}&quot; &amp;&amp; :age &lt; (1 + 2) }.to_sql
16
# =&gt; SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;name&quot; = &#039;homuhomu&#039; AND &quot;users&quot;.&quot;age&quot; &lt; 3


# Page. 39

![Page Image](https://bcdn.docswell.com/page/KYJ6WW4PJV.jpg)

activerecord-where_with_block
`&amp;&amp;`
で `AND` したりインスタンス変数が参照できるのがおしゃれポイント
ブロック内でインスタンス変数
1
#
2
@name = &quot;mami&quot;
3
puts User.where { :name == @name }.to_sql
4
# =&gt; SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;name&quot; = &#039;mami&#039;
5
を書くと SQL 文の AND として展開する
6
# &amp;&amp;
7
puts User.where { :name == &quot;homu&quot; &amp;&amp; :age &lt; 20 }.to_sql
8
# =&gt; SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE &quot;users&quot;.&quot;name&quot; = &#039;homu&#039; AND &quot;users&quot;.&quot;age&quot; &lt; 20
9
アソシエーションを参照する
10
#
11
puts User.joins(:comments).where { :comments.text == &quot;OK&quot; &amp;&amp; :name == &quot;homu&quot; }.to_sql
12
# =&gt; SELECT &quot;users&quot;.*
13
#
14
#
15
#
16
#
17
#
18
#
FROM &quot;users&quot;
INNER
JOIN &quot;comments&quot;
ON &quot;comments&quot;.&quot;user_id&quot; = &quot;users&quot;.&quot;id&quot;
WHERE &quot;comments&quot;.&quot;text&quot;
AND &quot;users&quot;.&quot;name&quot;
= &#039;OK&#039;
= &#039;homu&#039;


# Page. 40

![Page Image](https://bcdn.docswell.com/page/5GJ5MMQPJ4.jpg)

まとめ


# Page. 41

![Page Image](https://bcdn.docswell.com/page/V9E299PZ7R.jpg)

まとめ


# Page. 42

![Page Image](https://bcdn.docswell.com/page/5D7Y445NEM.jpg)

まとめ
AST を使うことでメタプロ以上の事ができる
普段フックできないような処理をフックできる


# Page. 43

![Page Image](https://bcdn.docswell.com/page/2VENYYNVJ8.jpg)

まとめ
AST を使うことでメタプロ以上の事ができる
普段フックできないような処理をフックできる
今回やった事は実質トランスコンパイルでは？？？
SQL 文じゃなくてもっと別の言語に変換できると面白そう
いろいろと応用ができそう！！


# Page. 44

![Page Image](https://bcdn.docswell.com/page/GY79PPRVE3.jpg)

まとめ
AST を使うことでメタプロ以上の事ができる
普段フックできないような処理をフックできる
今回やった事は実質トランスコンパイルでは？？？
SQL 文じゃなくてもっと別の言語に変換できると面白そう
いろいろと応用ができそう！！
ブロック内のコードを AST に変換できるの強い


# Page. 45

![Page Image](https://bcdn.docswell.com/page/5G78DDW47D.jpg)

まとめ
AST を使うことでメタプロ以上の事ができる
普段フックできないような処理をフックできる
今回やった事は実質トランスコンパイルでは？？？
SQL 文じゃなくてもっと別の言語に変換できると面白そう
いろいろと応用ができそう！！
ブロック内のコードを AST に変換できるの強い
AST で遊ぶのたのしいよね！！！


