153.9K Views
October 22, 22
スライド概要
皆さんは User.where(active: true).order(:age) の結果がなにを返すかご存知ですか。
User の配列が出力されるから配列が返ってくる?いいえ、違います。
普段なにげなく使っている ActiveRecord ですがこのセッションでは実際に内部でどのように動作しているのかを少し覗いてみましょう。
User.where(active: true).order(:age) がなにを返すのか、またどのようにして ActiveRecord が SQL 文を生成しているのかをライブコーディングを交えながら解説します。
普段 Rails を使っている人がもう1歩進んだ Rails の知識を一緒に学んで行きましょう。
https://kaigionrails.org/2022/talks/osyo/
Ruby / Vim / C++
ActiveRecord::Relation って なに? Kaigi on Rails 2022
問題. 次はどういう出力になる? 1 2 3 4 5 6 2 / 54 User.create(name: "mami", age: 15, active: true) User.create(name: "mado", age: 14, active: false) User.create(name: "homu", age: 14, active: true) pp User.where(active: true).order(:age) # => ???
答え. 1 2 3 4 5 6 3 / 54 User.create(name: "mami", age: 15, active: true) User.create(name: "mado", age: 14, active: false) User.create(name: "homu", age: 14, active: true) pp User.where(active: true).order(:age) # => ???
答え. 1 User.create(name: "mami", age: 15, active: true) 2 User.create(name: "mado", age: 14, active: false) 3 User.create(name: "homu", age: 14, active: true) 4 5 pp User.where(active: true).order(:age) 6 # => [#<User:0x00007f9a07cc0d28 id: 3, name: "homu", age: 14, active: true>, 7 # #<User:0x00007f9a07cc0be8 id: 1, name: "mami", age: 15, active: true>] `User` 4 / 54 の配列を出力する
問題. では .order の戻り値のクラスは? 1 pp User.where(active: true).order(:age) 2 # => [#<User:0x00007f9a07cc0d28 id: 3, name: "homu", age: 14, active: true>, 3 # #<User:0x00007f9a07cc0be8 id: 1, name: "mami", age: 15, active: true>] 4 5 pp User.where(active: true).order(:age).class 6 # => ??? 5 / 54
問題. では .order の戻り値のクラスは? 1 pp User.where(active: true).order(:age) 2 # => [#<User:0x00007f9a07cc0d28 id: 3, name: "homu", age: 14, active: true>, 3 # #<User:0x00007f9a07cc0be8 id: 1, name: "mami", age: 15, active: true>] 4 5 pp User.where(active: true).order(:age).class 6 # => ??? `.order(:age)` 5 / 54 は配列っぽい値を返しているので `Array` ?
問題. では .order の戻り値のクラスは? 1 pp User.where(active: true).order(:age) 2 # => [#<User:0x00007f9a07cc0d28 id: 3, name: "homu", age: 14, active: true>, 3 # #<User:0x00007f9a07cc0be8 id: 1, name: "mami", age: 15, active: true>] 4 5 pp User.where(active: true).order(:age).class 6 # => ??? は配列っぽい値を返しているので `Array` ? いいえ、違います `.order(:age)` 5 / 54
問題. では .order の戻り値のクラスは? 1 pp User.where(active: true).order(:age) 2 # => [#<User:0x00007f9a07cc0d28 id: 3, name: "homu", age: 14, active: true>, 3 # #<User:0x00007f9a07cc0be8 id: 1, name: "mami", age: 15, active: true>] 4 5 pp User.where(active: true).order(:age).class 6 # => ??? は配列っぽい値を返しているので `Array` ? いいえ、違います そう、 `.order` が返しているのが `ActiveRecord::Relation` に… `.order(:age)` 5 / 54
答え. 1 pp User.where(active: true).order(:age) 2 # => [#<User:0x00007f9a07cc0d28 id: 3, name: "homu", age: 14, active: true>, 3 # #<User:0x00007f9a07cc0be8 id: 1, name: "mami", age: 15, active: true>] 4 5 pp User.where(active: true).order(:age).class 6 # => User::ActiveRecord_Relation じゃない!!? 6 / 54
自己紹介 名前:osyo github : osyo-manga Rails エンジニア 好きな Ruby の機能は Refinements Use Macro all the time ~ マクロを使いまくろ ~ RubyKaigi Takeout 2021 [slides en / ja] Let’s collect type info during Ruby running and automatically generate an RBS file! RubyKaigi 2022 [slides en / ja]
アジェンダ 1. 今日話したいこと 2. 諸注意 3. `ActiveRecord::Relation` とは 4. `User.where(active: true).order(:age)` した時になにが起きる? 5. scope とクラスメソッド 6. SQL が生成されるまでの流れ 7. まとめ 8 / 54
1. 今日話したいこと 9 / 54
今日話したいこと 10 / 54
今日話したいこと `ActiveRecord::Relation` という言葉に慣れてもらいたい いきなり `ActiveRecord::Relation` って言われてもギョッとしない 10 / 54
今日話したいこと `ActiveRecord::Relation` という言葉に慣れてもらいたい いきなり `ActiveRecord::Relation` って言われてもギョッとしない 他人がコードを調べてる風景を見てもらいたい 実際にどうやってコードを調べたりとかしているのかを感じてもらいたい 10 / 54
今日話したいこと `ActiveRecord::Relation` という言葉に慣れてもらいたい いきなり `ActiveRecord::Relation` って言われてもギョッとしない 他人がコードを調べてる風景を見てもらいたい 実際にどうやってコードを調べたりとかしているのかを感じてもらいたい 普段何気なく使っている `ActiveRecord` に対する1歩進んだ知識を学ん でみよう! `ActiveRecord` もらいたい 10 / 54 は内部でこんなことをやっているんだよーっていうのを感じて
2. 諸注意 11 / 54
各バージョン Ruby 3.1.2 ActiveRecord 7.0.4 12 / 54
使用するテーブル `users` 1 2 3 4 5 6 7 13 / 54 テーブルのスキーマ ActiveRecord::Schema.define do create_table :users, force: true do |t| t.string :name t.integer :age t.boolean :active end end
今日はなさないこと ActiveRecord のデータ構造について ActiveRecord::Relation の個々の細かい実装や機能解説 関連付け SQL 文自体について 14 / 54
ライブコーディングで使用する Ruby の機能 15 / 54
ライブコーディングで使用する Ruby の機能 #method メソッドのメタ情報を取得するメソッド 定義位置や定義されているクラス情報など取得できる 15 / 54
ライブコーディングで使用する Ruby の機能 #method メソッドのメタ情報を取得するメソッド 定義位置や定義されているクラス情報など取得できる .ancestors クラスの継承リストを取得するメソッド 15 / 54
ライブコーディングで使用する Ruby の機能 #method メソッドのメタ情報を取得するメソッド 定義位置や定義されているクラス情報など取得できる .ancestors クラスの継承リストを取得するメソッド .const_source_location 定数の定義位置を取得するメソッド 15 / 54
ライブコーディングで使用する Ruby の機能 #method メソッドのメタ情報を取得するメソッド 定義位置や定義されているクラス情報など取得できる .ancestors クラスの継承リストを取得するメソッド .const_source_location 定数の定義位置を取得するメソッド .const_get 指定された名前の定数を取得するメソッド 15 / 54
3. ActiveRecord::Relation とは 16 / 54
ActiveRecord には色々な機能がある 17 / 54
ActiveRecord には色々な機能がある DB の操作(作成・更新・削除) 17 / 54
ActiveRecord には色々な機能がある DB の操作(作成・更新・削除) DB からのレコードの読み込み 17 / 54
ActiveRecord には色々な機能がある DB の操作(作成・更新・削除) DB からのレコードの読み込み モデルのバリデーション 17 / 54
ActiveRecord には色々な機能がある DB の操作(作成・更新・削除) DB からのレコードの読み込み モデルのバリデーション モデルのアトリビュート 17 / 54
ActiveRecord には色々な機能がある DB の操作(作成・更新・削除) DB からのレコードの読み込み モデルのバリデーション モデルのアトリビュート マイグレーション処理 etc… 17 / 54
ActiveRecord には色々な機能がある DB の操作(作成・更新・削除) DB からのレコードの読み込み <- 主にここに関連する機能 モデルのバリデーション モデルのアトリビュート マイグレーション処理 etc… 18 / 54
ActiveRecord::Relation とは 19 / 54
ActiveRecord::Relation とは SQL 文を Ruby 上で抽象的にかけるようにするためのライブラリ `WHERE 19 / 54 句` や `ORDER BY 句` を Ruby のメソッドとして呼び出せる
ActiveRecord::Relation とは SQL 文を Ruby 上で抽象的にかけるようにするためのライブラリ `WHERE 句` や `ORDER BY 句` を Ruby のメソッドとして呼び出せる 指定した条件から SQL を構築して読み込んでくる部分を担っている 19 / 54
ActiveRecord::Relation とは SQL 文を Ruby 上で抽象的にかけるようにするためのライブラリ `WHERE 句` や `ORDER BY 句` を Ruby のメソッドとして呼び出せる 指定した条件から SQL を構築して読み込んでくる部分を担っている 条件を指定して 1 # 2 relation = User.where(active: true).order(:created_at).limit(10) 3 4 # SQL 5 pp relation.load 6 # => [#<User:0x00007fd89ad85980 id: 1, name: "mami", age: 15, active: true>, 7 # #<User:0x00007fd89ad852f0 id: 3, name: "homu", age: 14, active: true>] 実際に 19 / 54 を実行してレコードを読み込んでくる
ActiveRecord::Relation に組み込まれているモジュール 20 / 54
ActiveRecord::Relation に組み込まれているモジュール ActiveRecord::FinderMethods `#find` `#find_by` `#first` `#last` `#exists?` 20 / 54 etc…
ActiveRecord::Relation に組み込まれているモジュール ActiveRecord::FinderMethods `#find` `#find_by` `#first` `#last` `#exists?` ActiveRecord::QueryMethods etc… `#select` `#includes` `#joins` `#order` `#where` `#limit` `#distinct` `#order` 20 / 54 etc…
ActiveRecord::Relation に組み込まれているモジュール ActiveRecord::FinderMethods `#find` `#find_by` `#first` `#last` `#exists?` ActiveRecord::QueryMethods etc… `#select` `#includes` `#joins` `#order` `#where` `#limit` `#distinct` `#order` ActiveRecord::Calculations `#sum` `#count` `#pluck` 20 / 54 etc… etc…
ActiveRecord::Relation に組み込まれているモジュール ActiveRecord::FinderMethods `#find` `#find_by` `#first` `#last` `#exists?` ActiveRecord::QueryMethods etc… `#select` `#includes` `#joins` `#order` `#where` `#limit` `#distinct` `#order` ActiveRecord::Calculations `#sum` `#count` `#pluck` etc… ActiveRecord::SpawnMethods `#merge` `#except` `#only` 20 / 54 etc… etc…
じゃあ User::ActiveRecord_Relation って??? 21 / 54
User::ActiveRecord_Relation って結局なに? 22 / 54
User::ActiveRecord_Relation って結局なに? `ActiveRecord::Relation` `ActiveRecord::Base` に生成される 22 / 54 を継承しているクラス を継承したクラスを定義すると内部クラスとして暗黙的
User::ActiveRecord_Relation って結局なに? `ActiveRecord::Relation` `ActiveRecord::Base` に生成される を継承しているクラス を継承したクラスを定義すると内部クラスとして暗黙的 や `where` , `order` など `ActiveRecord::Relation` で定義 されているメソッドの戻り値はこのクラスのインスタンスオブジェクト になる `joins` 1 2 3 22 / 54 pp User.joins(:comments).class # => User::ActiveRecord_Relation pp User.where(active: true).class # => User::ActiveRecord_Relation pp User.order(:created_at).class # => User::ActiveRecord_Relation
User::ActiveRecord_Relation の注意点 23 / 54
User::ActiveRecord_Relation の注意点 `ActiveRecord_Relation` 参照できない 1 2 3 4 5 23 / 54 は private 定数になっているので `::` では で参照できない # NG: :: User::ActiveRecord_Relation で取得する必要がある # OK: const_get User.const_get(:ActiveRecord_Relation)
User::ActiveRecord_Relation の注意点 `User::ActiveRecord_Relation#name` が再定義されている 出力の仕方によっては `ActiveRecord::Relation` が表示されるので注意 1 2 3 4 5 6 7 8 9 10 11 12 24 / 54 これは になる # User::ActiveRecord_Relation puts User.const_get(:ActiveRecord_Relation) # => User::ActiveRecord_Relation これは になる # ActiveRecord::Relation puts User.const_get(:ActiveRecord_Relation).name # => ActiveRecord::Relation # # p # では と表記される ActiveRecord::Relation#inspect ActiveRecord::Relation User.all => #<ActiveRecord::Relation []>
3. ActiveRecord::Relation とは、のまとめ 25 / 54
3. ActiveRecord::Relation とは、のまとめ `ActiveRecord::Relation` `WHERE 25 / 54 は SQL を抽象的に書くためのライブラリ 句` や `ORDER BY 句` をメソッドとして呼び出せる
3. ActiveRecord::Relation とは、のまとめ `ActiveRecord::Relation` `WHERE は SQL を抽象的に書くためのライブラリ 句` や `ORDER BY 句` をメソッドとして呼び出せる `ActiveRecord::Relation` 装している が `joins` や `where` , `order` などを実 基本的に `User::ActiveRecord_Relation == ActiveRecord::Relation` と いう認識で OK 25 / 54
3. ActiveRecord::Relation とは、のまとめ `ActiveRecord::Relation` `WHERE は SQL を抽象的に書くためのライブラリ 句` や `ORDER BY 句` をメソッドとして呼び出せる `ActiveRecord::Relation` 装している が `joins` や `where` , `order` などを実 基本的に `User::ActiveRecord_Relation == ActiveRecord::Relation` と いう認識で OK を継承すると暗黙的に `User::ActiveRecord_Relation` に定義される `class User < ActiveRecord::Base` 25 / 54
3. ActiveRecord::Relation とは、のまとめ `ActiveRecord::Relation` `WHERE は SQL を抽象的に書くためのライブラリ 句` や `ORDER BY 句` をメソッドとして呼び出せる `ActiveRecord::Relation` 装している が `joins` や `where` , `order` などを実 基本的に `User::ActiveRecord_Relation == ActiveRecord::Relation` と いう認識で OK を継承すると暗黙的に `User::ActiveRecord_Relation` に定義される なぜ `User::ActiveRecord_Relation` が必要なのかは後述 `class User < ActiveRecord::Base` 25 / 54
4. `User.where(active: true).order(:age)` した時になにが起きる? 26 / 54
ActiveRecord::Relation のメソッドを呼び出すと…? 1 2 3 4 5 6 7 8 27 / 54 pp User.where(active: true).class # => User::ActiveRecord_Relation pp User.where(active: true).order(:created_at).class # => User::ActiveRecord_Relation pp User.where(active: true).order(:created_at).limit(10).class # => User::ActiveRecord_Relation
ActiveRecord::Relation のメソッドを呼び出すと…? 1 2 3 4 5 6 7 8 pp User.where(active: true).class # => User::ActiveRecord_Relation pp User.where(active: true).order(:created_at).class # => User::ActiveRecord_Relation pp User.where(active: true).order(:created_at).limit(10).class # => User::ActiveRecord_Relation 基本的に `User::ActiveRecord_Relation` のインスタンスオブジェク トを返す 27 / 54
ActiveRecord::Relation のメソッドを呼び出すと…? 1 2 3 4 5 6 7 8 27 / 54 pp User.where(active: true).class # => User::ActiveRecord_Relation pp User.where(active: true).order(:created_at).class # => User::ActiveRecord_Relation pp User.where(active: true).order(:created_at).limit(10).class # => User::ActiveRecord_Relation 基本的に `User::ActiveRecord_Relation` のインスタンスオブジェク トを返す なので戻り値に対して `ActiveRecord::Relation` のメソッドをチェー ンして呼び出す事ができる
ここで重要なのは 28 / 54
ここで重要なのは `where` や `order` を呼び出しただけでは まだ DB からレコードを読み込んでいないこと 28 / 54
ActiveRecord::Relation がレコードを読み込むタイミングは? 1 2 3 4 5 29 / 54 これだけではレコードは読み込まれない # relation = User.where(active: true) チェーンしても同様 # relation2 = relation.order(:created_at)
ActiveRecord::Relation がレコードを読み込むタイミングは? 1 2 3 4 5 これだけではレコードは読み込まれない # relation = User.where(active: true) チェーンしても同様 # relation2 = relation.order(:created_at) `ActiveRecord::Relation` 遅延して SQL が実行される 29 / 54 では必要になるまで読み込まれない
ActiveRecord::Relation がレコードを読み込むタイミングは? 1 2 3 4 5 これだけではレコードは読み込まれない # relation = User.where(active: true) チェーンしても同様 # relation2 = relation.order(:created_at) `ActiveRecord::Relation` 遅延して SQL が実行される これはチェーンしても同様 29 / 54 では必要になるまで読み込まれない
ActiveRecord::Relation がレコードを読み込むタイミングは? 1 2 3 4 5 これだけではレコードは読み込まれない # relation = User.where(active: true) チェーンしても同様 # relation2 = relation.order(:created_at) `ActiveRecord::Relation` 遅延して SQL が実行される では必要になるまで読み込まれない これはチェーンしても同様 なので `User.where(active: true)` しただけではレコードは読み込ま れない 29 / 54
ActiveRecord::Relation がレコードを読み込むタイミングは? これだけではレコードは読み込まれない 1 # 2 relation = User.where(active: true) 3 4 # #load SQL 5 users = relation.load 6 pp users 7 # => [#<User:0x00007fd89ad85980 id: 1, name: "mami", age: 15, active: true>, 8 # #<User:0x00007fd89ad852f0 id: 3, name: "homu", age: 14, active: true>] が呼ばれて初めて 30 / 54 が実行されてレコードを読み込む
ActiveRecord::Relation がレコードを読み込むタイミングは? これだけではレコードは読み込まれない 1 # 2 relation = User.where(active: true) 3 4 # #load SQL 5 users = relation.load 6 pp users 7 # => [#<User:0x00007fd89ad85980 id: 1, name: "mami", age: 15, active: true>, 8 # #<User:0x00007fd89ad852f0 id: 3, name: "homu", age: 14, active: true>] が呼ばれて初めて `#load` 30 / 54 が実行されてレコードを読み込む などの特定のメソッドが呼ばれた時に初めて読み込まれる
ActiveRecord::Relation がレコードを読み込むタイミングは? これだけではレコードは読み込まれない 1 # 2 relation = User.where(active: true) 3 4 # #load SQL 5 users = relation.load 6 pp users 7 # => [#<User:0x00007fd89ad85980 id: 1, name: "mami", age: 15, active: true>, 8 # #<User:0x00007fd89ad852f0 id: 3, name: "homu", age: 14, active: true>] が呼ばれて初めて が実行されてレコードを読み込む などの特定のメソッドが呼ばれた時に初めて読み込まれる 他には `#records` や `#to_a` などを呼び出した時にも読み込まれる `#load` 30 / 54
他にレコードを読み込むタイミングは? 1 2 3 4 5 31 / 54 だけではまだ読み込まれない の結果を受け取り が呼び出され # User.where(active: true) # pp User.where(active: true) # ActiveRecord::Relation#pretty_print # pp User.where(active: true) が 内部で レコードが読み込まれる
他にレコードを読み込むタイミングは? 1 2 3 4 5 だけではまだ読み込まれない の結果を受け取り が呼び出され # User.where(active: true) # pp User.where(active: true) # ActiveRecord::Relation#pretty_print # pp User.where(active: true) が 内部で レコードが読み込まれる 他には `#inspect` や `#pretty_print` が呼ばれたタイミングでも読み 込まれる `#inspect` 31 / 54 や `p` したとき `#pretty_print` は `pp` の内部で呼ばれる
他にレコードを読み込むタイミングは? 1 2 3 4 5 だけではまだ読み込まれない の結果を受け取り が呼び出され # User.where(active: true) # pp User.where(active: true) # ActiveRecord::Relation#pretty_print # pp User.where(active: true) が 内部で レコードが読み込まれる 他には `#inspect` や `#pretty_print` が呼ばれたタイミングでも読み 込まれる `#inspect` や `p` したとき `#pretty_print` は `pp` の内部で呼ばれる なので `pp User.where(active: true)` すると自動的に読み込みが行 われる 31 / 54
他にレコードを読み込むタイミングは? 1 2 3 4 5 6 7 8 9 32 / 54 を呼ぶとその時点でレコードが読み込まれる # each User.where(active: true).each do |user| puts "#{user.name}" end これは他の メソッドを呼び出したときも同様 # Enumerable User.order(:created_at).map do |user| "#{user.name} : #{user.age}" end
他にレコードを読み込むタイミングは? 1 2 3 4 5 6 7 8 9 を呼ぶとその時点でレコードが読み込まれる # each User.where(active: true).each do |user| puts "#{user.name}" end これは他の メソッドを呼び出したときも同様 # Enumerable User.order(:created_at).map do |user| "#{user.name} : #{user.age}" end また `#each` も内部で `#records` を呼び出している 32 / 54
他にレコードを読み込むタイミングは? 1 2 3 4 5 6 7 8 9 を呼ぶとその時点でレコードが読み込まれる # each User.where(active: true).each do |user| puts "#{user.name}" end これは他の メソッドを呼び出したときも同様 # Enumerable User.order(:created_at).map do |user| "#{user.name} : #{user.age}" end また `#each` も内部で `#records` を呼び出している これにより各種 `Enumerable` のメソッドを呼び出したタイミングでも レコードが読み込まれる事になる 32 / 54
4. User.where(active: true).order(:age) のまとめ 33 / 54
4. User.where(active: true).order(:age) のまとめ や `order` の戻り値は `User::ActiveRecord_Relation` のイ ンスタンスオブジェクトを返す `where` 33 / 54
4. User.where(active: true).order(:age) のまとめ や `order` の戻り値は `User::ActiveRecord_Relation` のイ ンスタンスオブジェクトを返す なので `where` や `order` などをチェーンして呼び出す事ができる `where` 33 / 54
4. User.where(active: true).order(:age) のまとめ や `order` の戻り値は `User::ActiveRecord_Relation` のイ ンスタンスオブジェクトを返す なので `where` や `order` などをチェーンして呼び出す事ができる `where` や `order` しただけでは DB からレコードは読み込まれない `where` 33 / 54
4. User.where(active: true).order(:age) のまとめ や `order` の戻り値は `User::ActiveRecord_Relation` のイ ンスタンスオブジェクトを返す なので `where` や `order` などをチェーンして呼び出す事ができる `where` や `order` しただけでは DB からレコードは読み込まれない レコードを読み込む場合は任意のメソッドを呼び出す必要がある `where` 普段使いであれば内部で暗黙的に呼び出される事が多い 33 / 54
5. scope とクラスメソッド 34 / 54
scope とクラスメソッド
1
2
3
4
5
6
7
8
9
10
11
12
13
35 / 54
class User < ActiveRecord::Base
scope :active, -> {
where(active: true)
}
def self.inactive
where(active: false)
end
end
の戻り値に対して
やクラスメソッドを呼び出すことができる
# order
scope
User.order(:created_at).active
User.order(:created_at).inactive
scope とクラスメソッド
1
2
3
4
5
6
7
8
9
10
11
12
13
35 / 54
class User < ActiveRecord::Base
scope :active, -> {
where(active: true)
}
def self.inactive
where(active: false)
end
end
の戻り値に対して
やクラスメソッドを呼び出すことができる
# order
scope
User.order(:created_at).active
User.order(:created_at).inactive
scope で定義した処理やクラスメソッドを `ActiveRecord::Relation`
メソッドの戻り値から呼び出すことができる
scope を定義するとなにが起きる? 36 / 54
scope を定義するとなにが起きる? 37 / 54
scope を定義するとなにが起きる? `scope` を定義すると `User.active` クラスメソッドが定義される メソッドの定義が `scope` に渡した `proc` になる 37 / 54
scope を定義するとなにが起きる? `scope` を定義すると `User.active` クラスメソッドが定義される メソッドの定義が `scope` に渡した `proc` になる 同じタイミングで `User::GeneratedRelationMethods` に同名のメソ ッドが定義される このメソッドは `User.active` を呼び出している `User::GeneratedRelationMethods` は `User::ActiveRecord_Relation` に `include` されているモジュール 37 / 54
scope を定義するとなにが起きる? `scope` を定義すると `User.active` クラスメソッドが定義される メソッドの定義が `scope` に渡した `proc` になる 同じタイミングで `User::GeneratedRelationMethods` に同名のメソ ッドが定義される このメソッドは `User.active` を呼び出している `User::GeneratedRelationMethods` は `User::ActiveRecord_Relation` に `include` されているモジュール 37 / 54 なので `#order` の戻り値に対して `scope` のメソッドを呼び出すこと ができる
scope を定義するとなにが起きる?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
38 / 54
class User < ActiveRecord::Base
# scope
User.active
#
User::GeneratedRelationMethods#active
scope :active, -> {
where(active: true)
}
end
同様に
を定義すると
が定義される
は User::GeneratedRelationMethods を
の戻り値に対して #active を呼び出せる
# User::ActiveRecord_Relation
# include
order
User.order(:created_at).active
しているので
クラスメソッドとしても呼び出せる
#
User.active
メソッドが定義される
クラスメソッドを呼び出すとなにが起きる? 39 / 54
クラスメソッドを呼び出すとなにが起きる? 40 / 54
クラスメソッドを呼び出すとなにが起きる? クラスメソッドを定義した場合も `scope` と同様に `User::GeneratedRelationMethods` 経由でメソッドが呼び出される `User::GeneratedRelationMethods#inactive` `User.inactive` 40 / 54 が呼び出される メソッド内で
クラスメソッドを呼び出すとなにが起きる? クラスメソッドを定義した場合も `scope` と同様に `User::GeneratedRelationMethods` 経由でメソッドが呼び出される `User::GeneratedRelationMethods#inactive` `User.inactive` が呼び出される メソッド内で ただし、最初からメソッドが定義されているのではなくて `#inactive` が最初に呼び出された時にはじめて `User::GeneratedRelationMethods#inactive` が定義される `method_missing` 40 / 54 が利用されている
クラスメソッドを呼び出すとなにが起きる? 1 class User < ActiveRecord::Base 2 # 3 def self.inactive 4 where(active: false) 5 end 6 end 7 8 # `User::GeneratedRelationMethods#inactive` 9 pp User.order(:created_at).method(:inactive).source_location # => nil 10 11 # #inactive 12 # User::GeneratedRelationMethods#inactive 13 User.order(:created_at).inactive 14 pp User.order(:created_at).method(:inactive).source_location 15 # => ["/home/path/to/.rbenv/versions/3.1.2/lib/ruby/gems/3.1.0/gems/activerecord7.0.4/lib/active_record/relation/delegation.rb", 66] この時点ではクラスメソッドのみが定義される まだ は定義されていない メソッドを呼び出したタイミングで初めて が定義される 41 / 54
5. scope とクラスメソッド、のまとめ 42 / 54
5. scope とクラスメソッド、のまとめ やクラスメソッドは `User::ActiveRecord_Relation` 経由で 呼び出すことができる `scope` 42 / 54
5. scope とクラスメソッド、のまとめ やクラスメソッドは `User::ActiveRecord_Relation` 経由で 呼び出すことができる 内部で暗黙的に `User::GeneratedRelationMethods` に対してメソッ ドが定義される `scope` の処理を呼び出す `User::GeneratedRelationMethods#active` クラスメソッドを呼び出す `User::GeneratedRelationMethods#inactive` `scope` 42 / 54
5. scope とクラスメソッド、のまとめ やクラスメソッドは `User::ActiveRecord_Relation` 経由で 呼び出すことができる 内部で暗黙的に `User::GeneratedRelationMethods` に対してメソッ ドが定義される `scope` の処理を呼び出す `User::GeneratedRelationMethods#active` クラスメソッドを呼び出す `User::GeneratedRelationMethods#inactive` `scope` に対して `scope` やクラスメソッドを呼び 出すために `User::ActiveRecord_Relation` がモデルクラスごとに定 義されている `ActiveRecord::Relation` 42 / 54
6. SQL が生成されるまでの流れ 43 / 54
DB からレコードを読み込むまでの流れ 44 / 54
DB からレコードを読み込むまでの流れ 以下を実行したらなにが起きるのか 1 2 44 / 54 relation = User.where(active: true).order(:created_at).limit(10) relation.load
DB からレコードを読み込むまでの流れ 以下を実行したらなにが起きるのか 1 2 relation = User.where(active: true).order(:created_at).limit(10) relation.load 1. `ActiveRecord::Relation#load` でレコードの読み込みを実行する 44 / 54
DB からレコードを読み込むまでの流れ 以下を実行したらなにが起きるのか 1 2 relation = User.where(active: true).order(:created_at).limit(10) relation.load 1. `ActiveRecord::Relation#load` でレコードの読み込みを実行する 2. `relation` の情報から SQL のノード情報を生成 44 / 54
DB からレコードを読み込むまでの流れ 以下を実行したらなにが起きるのか 1 2 relation = User.where(active: true).order(:created_at).limit(10) relation.load 1. `ActiveRecord::Relation#load` でレコードの読み込みを実行する 2. `relation` の情報から SQL のノード情報を生成 3. `ActiveRecord::Base.connection.select_all` にノード情報を渡す 44 / 54
DB からレコードを読み込むまでの流れ 以下を実行したらなにが起きるのか 1 2 relation = User.where(active: true).order(:created_at).limit(10) relation.load 1. `ActiveRecord::Relation#load` でレコードの読み込みを実行する 2. `relation` の情報から SQL のノード情報を生成 3. `ActiveRecord::Base.connection.select_all` にノード情報を渡す 4. 実際に SQL を実行し、レコードを読み込んでくる 44 / 54
DB からレコードを読み込むまでの流れ 以下を実行したらなにが起きるのか 1 2 relation = User.where(active: true).order(:created_at).limit(10) relation.load 1. `ActiveRecord::Relation#load` でレコードの読み込みを実行する 2. `relation` の情報から SQL のノード情報を生成 3. `ActiveRecord::Base.connection.select_all` にノード情報を渡す 4. 実際に SQL を実行し、レコードを読み込んでくる 5. 読み込んできたレコードからモデルクラスのオブジェクトを生成 44 / 54
DB からレコードを読み込むまでの流れ 以下を実行したらなにが起きるのか 1 2 relation = User.where(active: true).order(:created_at).limit(10) relation.load 1. `ActiveRecord::Relation#load` でレコードの読み込みを実行する 2. `relation` の情報から SQL のノード情報を生成 <- ここの話をする 3. `ActiveRecord::Base.connection.select_all` にノード情報を渡す 4. 実際に SQL を実行し、レコードを読み込んでくる 5. 読み込んできたレコードからモデルクラスのオブジェクトを生成 45 / 54
や `order` を呼び出した時に なにが起きているのか見ていこう `where` 46 / 54
limit や order を呼ぶとなにが起きるのか
1
2
3
4
5
6
7
8
9
10
11
47 / 54
limited = User.limit(10)
#
pp limited.instance_eval { @values }
# => {:limit=>10}
これで内部データを参照する事ができる
ordered = limited.order(:created_at)
pp ordered.instance_eval { @values }
# => {:limit=>10,
#
:order=>
#
[#<Arel::Nodes::Ascending:0x00007f10000337b8
#
@expr="\"created_at\"">]}
limit や order を呼ぶとなにが起きるのか
1
2
3
4
5
6
7
8
9
10
11
limited = User.limit(10)
#
pp limited.instance_eval { @values }
# => {:limit=>10}
これで内部データを参照する事ができる
ordered = limited.order(:created_at)
pp ordered.instance_eval { @values }
# => {:limit=>10,
#
:order=>
#
[#<Arel::Nodes::Ascending:0x00007f10000337b8
#
@expr="\"created_at\"">]}
`relation`
47 / 54
に `limit` などの情報が保存される
limit や order を呼ぶとなにが起きるのか
1
2
3
4
5
6
7
8
9
10
11
limited = User.limit(10)
#
pp limited.instance_eval { @values }
# => {:limit=>10}
これで内部データを参照する事ができる
ordered = limited.order(:created_at)
pp ordered.instance_eval { @values }
# => {:limit=>10,
#
:order=>
#
[#<Arel::Nodes::Ascending:0x00007f10000337b8
#
@expr="\"created_at\"">]}
に `limit` などの情報が保存される
`relation` のメソッドを呼ぶたびにその情報が引き継がれていく
`relation`
47 / 54
limit と limit! の違い 1 2 3 4 5 6 7 8 9 10 11 48 / 54 all = User.all ではレシーバを書き換えない # limit all.limit(10) pp all.instance_eval { @values } # => {} ではレシーバを書き換える # limit! all.limit!(10) pp all.instance_eval { @values } # => {:limit=>10}
limit と limit! の違い 1 2 3 4 5 6 7 8 9 10 11 all = User.all ではレシーバを書き換えない # limit all.limit(10) pp all.instance_eval { @values } # => {} `limit` 48 / 54 ではレシーバを書き換える # limit! all.limit!(10) pp all.instance_eval { @values } # => {:limit=>10} はレシーバを書き換えない
limit と limit! の違い 1 2 3 4 5 6 7 8 9 10 11 all = User.all ではレシーバを書き換えない # limit all.limit(10) pp all.instance_eval { @values } # => {} ではレシーバを書き換える # limit! all.limit!(10) pp all.instance_eval { @values } # => {:limit=>10} はレシーバを書き換えない `limit!` ではレシーバを書き換える `limit` 48 / 54
where の場合は? 49 / 54
where の場合は?
`where`
49 / 54
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
の場合はより複雑な情報を保持している
whered = User.where(active: true)
pp whered.instance_eval { @values }
# => {}
# => {:where=>
#
#<ActiveRecord::Relation::WhereClause:0x00007f65a72ca920
#
@predicates=
#
[#<Arel::Nodes::Equality:0x00007f65a72ca9e8
#
@left=
#
#<struct Arel::Attributes::Attribute
#
relation=
#
#<Arel::Table:0x00007f65a72cf448
#
@klass=User(id: integer, name: string, age: integer, active: boolean),
#
@name="users",
#
@table_alias=nil,
#
@type_caster=
#
#<ActiveRecord::TypeCaster::Map:0x00007f65a72cf3a8
#
@klass=User(id: integer, name: string, age: integer, active: boolean)>>,
#
name="active">,
#
@right=
#
#<ActiveRecord::Relation::QueryAttribute:0x00007f65a72cab50
#
@name="active",
#
@original_attribute=nil,
#
@type=
#
#<ActiveModel::Type::Boolean:0x00007f65a72cb758 @limit=nil, @precision=nil, @scale=nil>,
#
@value_before_type_cast=true>>]>}
ノード情報の作成 50 / 54
ノード情報の作成 SQL を実行する際に `relation` が持っている `limit` や `order`, `where` 情報を元にしてノード情報が作成される 50 / 54
ノード情報の作成 SQL を実行する際に `relation` が持っている `limit` や `order`, `where` 情報を元にしてノード情報が作成される このノード情報を扱っているのが Arel と呼ばれる `ActiveRecord` に内 包されているライブラリ 50 / 54
ノード情報の作成 SQL を実行する際に `relation` が持っている `limit` や `order`, `where` 情報を元にしてノード情報が作成される このノード情報を扱っているのが Arel と呼ばれる `ActiveRecord` に内 包されているライブラリ Arel を使用する事でより厳密な SQL のノード情報を扱う事ができる 50 / 54
ノード情報の作成 SQL を実行する際に `relation` が持っている `limit` や `order`, `where` 情報を元にしてノード情報が作成される このノード情報を扱っているのが Arel と呼ばれる `ActiveRecord` に内 包されているライブラリ Arel を使用する事でより厳密な SQL のノード情報を扱う事ができる この Arel がノード情報から SQL 文への変換を行っている 50 / 54
Arel から SQL 文を取得する 1 2 3 4 5 6 7 8 9 10 11 51 / 54 relation = User.where(active: true).order(:created_at).limit(10) が保持している情報から arel 情報を取得 # relation arel = relation.arel を参照して 文を文字列として取得する事ができる # arel SQL puts arel.to_sql # => SELECT "users".* # FROM "users" # WHERE "users"."active" = ? # ORDER BY "created_at" ASC LIMIT ?
Arel から SQL 文を取得する 1 2 3 4 5 6 7 8 9 10 11 relation = User.where(active: true).order(:created_at).limit(10) が保持している情報から arel 情報を取得 # relation arel = relation.arel を参照して `relation` 51 / 54 文を文字列として取得する事ができる # arel SQL puts arel.to_sql # => SELECT "users".* # FROM "users" # WHERE "users"."active" = ? # ORDER BY "created_at" ASC LIMIT ? から Arel を作成する
Arel から SQL 文を取得する 1 2 3 4 5 6 7 8 9 10 11 relation = User.where(active: true).order(:created_at).limit(10) が保持している情報から arel 情報を取得 # relation arel = relation.arel を参照して 文を文字列として取得する事ができる # arel SQL puts arel.to_sql # => SELECT "users".* # FROM "users" # WHERE "users"."active" = ? # ORDER BY "created_at" ASC LIMIT ? から Arel を作成する Arel から SQL 文を作成する `relation` 51 / 54 `relation.to_sql` でも同じような結果が得られる
6. SQL が生成されるまでの流れ、のまとめ 52 / 54
6. SQL が生成されるまでの流れ、のまとめ `ActiveRecord` とを行っている 52 / 54 では DB からレコードを読み込んでくるまでに様々なこ
6. SQL が生成されるまでの流れ、のまとめ `ActiveRecord` とを行っている では DB からレコードを読み込んでくるまでに様々なこ `ActiveRecord::Relation` を扱っている `where` 52 / 54 では SQL 文を作成するための抽象的な情報 や `order` を呼び出した時にそのデータを `relation` が保持する
6. SQL が生成されるまでの流れ、のまとめ `ActiveRecord` とを行っている では DB からレコードを読み込んでくるまでに様々なこ `ActiveRecord::Relation` を扱っている `where` では SQL 文を作成するための抽象的な情報 や `order` を呼び出した時にそのデータを `relation` が保持する Arel というライブラリを使用してより具体的なノード情報から SQL 文を 作成している 52 / 54
7. まとめ 53 / 54
まとめ 54 / 54
まとめ `ActiveRecord::Relation` れるライブラリ 54 / 54 は DB からレコードを取得する時に利用さ
まとめ `ActiveRecord::Relation` は DB からレコードを取得する時に利用さ れるライブラリ `where` や `order` といった Ruby 上で SQL 文を抽象的に記述する事が できる 54 / 54
まとめ `ActiveRecord::Relation` は DB からレコードを取得する時に利用さ れるライブラリ `where` や `order` といった Ruby 上で SQL 文を抽象的に記述する事が できる この資料は実際の `ActiveRecord` のコードを読んで書いています みんなもどんどん `ActiveRecord` の実装を読んでみよう!!! 54 / 54