Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
AST を使って ActiveRecord の where の条件式をブロックで記述しよう
Search
osyo
October 29, 2021
Programming
1.3k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
AST を使って ActiveRecord の where の条件式をブロックで記述しよう
https://ginza-rails.connpass.com/event/226024/
osyo
October 29, 2021
More Decks by osyo
See All by osyo
5分で話せる Ruby 3.1
osyo
0
210
Vim の開発環境自慢
osyo
5
3.1k
Use Macro all the time ~ マクロを使いまくろ ~ 感想戦
osyo
0
350
Use Macro all the time ~ マクロを使いまくろ ~ (English)
osyo
3
480
Use Macro all the time ~ マクロを使いまくろ ~ (日本語)
osyo
0
2.4k
月単位でイテレーションする
osyo
0
380
Ruby 3.0 で変わった private と attr_xxx
osyo
1
830
Ruby 2.0 から Ruby 3.0 を駆け足で振り返る
osyo
0
2.6k
12月25日にリリースされる Ruby 3.0 に備えよう!
osyo
1
3.5k
Other Decks in Programming
See All in Programming
Security issues being discussed on Web Platforms
petamoriken
0
1k
モデルのリファクタリングが難しいと思ったら、そもそも複雑だったのはビジネス仕様だった ? / is-the-business-domain-the-real-complexity
hatsu38
0
380
JRuby: Past, Present, and Future
headius
0
170
スマート反転とウェブアクセシビリティ
camiha
0
210
Starting & Sustaining Code-Based E2E Testing for Non-Coding QA Teams( #jasstniigata )
teyamagu
PRO
1
490
setup-vp GitLab対応の裏側
naokihaba
0
120
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
160
What We Talk About When We Talk About XP
m_seki
2
670
アクセシビリティから考える情報設計
high_g_engineer
0
380
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
300
変化を抱擁するドキュメントの作り方 - ビジネスルール駆動開発がもたらす、コードとの新しい関係
ioki
2
220
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
280
Featured
See All Featured
Darren the Foodie - Storyboard
khoart
PRO
4
3.9k
Become a Pro
speakerdeck
PRO
31
6.3k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
330
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
50
10k
Color Theory Basics | Prateek | Gurzu
gurzu
1
470
Build The Right Thing And Hit Your Dates
maggiecrowley
39
3.4k
Reality Check: Gamification 10 Years Later
codingconduct
0
2.3k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
203
76k
HDC tutorial
michielstock
2
880
Utilizing Notion as your number one productivity tool
mfonobong
4
590
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
580
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
280
Transcript
AST を使って ActiveRecord の where の条件式を ブロックで記述しよう 【オンライン開催】銀座Rails#38
今日話すこと!
今日話すこと! User.where("? <= age", 20) を ` `
今日話すこと! User.where("? <= age", 20) を User.where { 20 <=
:age } とかけるようにしたい!!! ` ` ` `
自己紹介 名前:osyo Twitter : @pink_bangbi github : osyo-manga ブログ :
Secret Garden(Instrumental)
自己紹介 名前:osyo Twitter : @pink_bangbi github : osyo-manga ブログ :
Secret Garden(Instrumental) RubyKaigi Takeout 2021 Use Macro all the time ~ マクロを使いまくろ ~
自己紹介 名前: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 に備えよう!
自己紹介 名前: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 を駆け足で振り返る
自己紹介 名前: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 のショートハンド
なんで where にブロックを渡したいの?
なんで where にブロックを渡したいの? できたら面白そうじゃん
アジェンダ
アジェンダ 既存の実装を紹介
アジェンダ 既存の実装を紹介 AST を利用して Ruby のコードを SQL 文に変換してみる
アジェンダ 既存の実装を紹介 AST を利用して Ruby のコードを SQL 文に変換してみる activerecord-where_with_block をつくったので紹介
伝えたいこと
伝えたいこと AST というデータ構造に付いて少しでも学んでもらいたい
伝えたいこと AST というデータ構造に付いて少しでも学んでもらいたい AST を利用する事でもっと Ruby のコードをメタ的に扱える
伝えたいこと AST というデータ構造に付いて少しでも学んでもらいたい AST を利用する事でもっと Ruby のコードをメタ的に扱える AST を使ったらこんな事が実現できるんだよ!を体験してもらいたい
既存の実装
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%') ` ` ` ` ` ` ` `
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%') ` ` ` ` ` ` ` ` ` `
[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") } ` `
AST とは
AST とは
AST とは AST とは Abstract Syntax Tree の略 日本語だと抽象構文木と呼ばれるもの
AST とは AST とは Abstract Syntax Tree の略 日本語だと抽象構文木と呼ばれるもの Ruby
のコードを抽象化し、扱いやすくしたデータ構造
AST とは AST とは Abstract Syntax Tree の略 日本語だと抽象構文木と呼ばれるもの Ruby
のコードを抽象化し、扱いやすくしたデータ構造 AST を利用することで Ruby のコードをメタデータ的に扱うことができる 今回は RubyVM::AbstractSyntaxTree を利用する ` `
AST とは AST とは Abstract Syntax Tree の略 日本語だと抽象構文木と呼ばれるもの Ruby
のコードを抽象化し、扱いやすくしたデータ構造 AST を利用することで Ruby のコードをメタデータ的に扱うことができる 今回は RubyVM::AbstractSyntaxTree を利用する AST は『種類』と『子ノード』の2つの情報を持っておりそれが再帰的なデータ構造にな っている ` `
AST とは AST とは Abstract Syntax Tree の略 日本語だと抽象構文木と呼ばれるもの Ruby
のコードを抽象化し、扱いやすくしたデータ構造 AST を利用することで Ruby のコードをメタデータ的に扱うことができる 今回は RubyVM::AbstractSyntaxTree を利用する AST は『種類』と『子ノード』の2つの情報を持っておりそれが再帰的なデータ構造にな っている AST の種類は構文ごとに細かく分かれていて100種類以上ある ` `
コード 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
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 になるがわかりやすく配列で表記している ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
AST を利用して Ruby のコードから SQL 文に変換してみよう!
AST を利用して Ruby のコードから SQL 文に変換してみよう! 1 :name == "homu"
&& 20 <= :age ↓
AST を利用して Ruby のコードから SQL 文に変換してみよう! 1 :name == "homu"
&& 20 <= :age ↓ 1 name = "homu" AND 20 <= age
実装をライブコーディング
Gem 化したよ!!!
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 ` `
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' ` ` ` `
まとめ
まとめ
まとめ AST を使うことでメタプロ以上の事ができる 普段フックできないような処理をフックできる
まとめ AST を使うことでメタプロ以上の事ができる 普段フックできないような処理をフックできる 今回やった事は実質トランスコンパイルでは??? SQL 文じゃなくてもっと別の言語に変換できると面白そう いろいろと応用ができそう!!
まとめ AST を使うことでメタプロ以上の事ができる 普段フックできないような処理をフックできる 今回やった事は実質トランスコンパイルでは??? SQL 文じゃなくてもっと別の言語に変換できると面白そう いろいろと応用ができそう!! ブロック内のコードを AST
に変換できるの強い
まとめ AST を使うことでメタプロ以上の事ができる 普段フックできないような処理をフックできる 今回やった事は実質トランスコンパイルでは??? SQL 文じゃなくてもっと別の言語に変換できると面白そう いろいろと応用ができそう!! ブロック内のコードを AST
に変換できるの強い AST で遊ぶのたのしいよね!!!