Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

今日話すこと!

Slide 3

Slide 3 text

今日話すこと! User.where("? <= age", 20) を ` `

Slide 4

Slide 4 text

今日話すこと! User.where("? <= age", 20) を User.where { 20 <= :age } とかけるようにしたい!!! ` ` ` `

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

自己紹介 名前: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 に備えよう!

Slide 8

Slide 8 text

自己紹介 名前: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 を駆け足で振り返る

Slide 9

Slide 9 text

自己紹介 名前: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 のショートハンド

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

アジェンダ

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

伝えたいこと

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

既存の実装

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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 # => SELECT "people".* FROM "people" WHERE "people"."id" = 1 3 4 # & で && を模倣している 5 Person.where { id.eq(1) & name.matches('%alice%') }.to_sql 6 # => SELECT "people".* FROM "people" WHERE ("people"."id" = 1 AND "people"."name" LIKE '%alice%') 7 8 # 関連先を join する 9 Person.where { name.eq('alice') & entries.name.matches('%hello%') }.to_sql 10 # => SELECT "people".* FROM "people" 11 # INNER JOIN "entries" ON "entries"."person_id" = "people"."id" 12 # WHERE ("people"."name" = 'alice' AND "entries"."name" LIKE '%hello%') ` ` ` ` ` ` ` ` ` `

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

AST とは

Slide 25

Slide 25 text

AST とは

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

AST を利用して Ruby のコードから SQL 文に変換してみよう! 1 :name == "homu" && 20 <= :age ↓

Slide 35

Slide 35 text

AST を利用して Ruby のコードから SQL 文に変換してみよう! 1 :name == "homu" && 20 <= :age ↓ 1 name = "homu" AND 20 <= age

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Gem 化したよ!!!

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

まとめ

Slide 41

Slide 41 text

まとめ

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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