Closed23

Rails の strict_loading を Deep Dive したら mysql2 client の C 実装に辿り着いた

dak2dak2

ざっくり流れとしては、strict_loading を呼び出したらインスタンス変数を設定して、呼び出し時に設定有無によって raise するか決まる

dak2dak2

Deep Dive したら MySQL2::Client gem の C 実装にたどり着いた

dak2dak2
diff --git a/activerecord/test/cases/strict_loading_test.rb b/activerecord/test/cases/strict_loading_test.rb
index e7b848e3be..52a0bab808 100644
--- a/activerecord/test/cases/strict_loading_test.rb
+++ b/activerecord/test/cases/strict_loading_test.rb
@@ -21,22 +21,22 @@ def test_strict_loading!
     developer = Developer.first
     assert_not_predicate developer, :strict_loading?
 
-    assert developer.strict_loading!
-    assert_predicate developer, :strict_loading?
+    # assert developer.strict_loading!
+    # assert_predicate developer, :strict_loading?
 
-    assert_raises ActiveRecord::StrictLoadingViolationError do
-      developer.audit_logs.to_a
-    end
+    # assert_raises ActiveRecord::StrictLoadingViolationError do
+    #   developer.audit_logs.to_a
+    # end
 
-    assert_not developer.strict_loading!(false)
-    assert_not_predicate developer, :strict_loading?
+    # assert_not developer.strict_loading!(false)
+    # assert_not_predicate developer, :strict_loading?
 
     assert_nothing_raised do
       developer.audit_logs.to_a
     end
 
-    assert developer.strict_loading!(mode: :n_plus_one_only)
-    assert_predicate developer, :strict_loading_n_plus_one_only?
+    # assert developer.strict_loading!(mode: :n_plus_one_only)
+    # assert_predicate developer, :strict_loading_n_plus_one_only?
   end

Relotion 辿る最小限のテストケースに修正

dak2dak2

exec_queries メソッドの exec_main_query が実際に処理してそう

https://github.com/rails/rails/blob/2e7c3d942fdafead2cfcbf1cdf7d95a5673cd3e9/activerecord/lib/active_record/relation.rb#L1403-L1421

ちなみに rows 変数は attributes のハッシュが入ってくる

{"id" => 1, "name" => "David", "salary" => 80000, "firm_id" => nil, "mentor_id" => nil, "legacy_created_at" => 2025-01-26 09:26:50.74603 UTC, "legacy_updated_at" => 2025-01-26 09:26:50.74603 UTC, "legacy_created_on" => 2025-01-26 09:26:50.74603 UTC, "legacy_updated_on" => 2025-01-26 09:26:50.74603 UTC}

その後の instantiate_records でインスタンスの attributes として set してるよう
records 変数が Model のクラスで records.first.attributes したら rows のハッシュと同じものが取得できる

instantiate_records は色々あって下記の instantiate_instance_of メソッドを呼んでる
https://github.com/rails/rails/blob/2e7c3d942fdafead2cfcbf1cdf7d95a5673cd3e9/activerecord/lib/active_record/persistence.rb#L309-L314

init_with_attributes メソッドでインスタンス変数に詰めて ActiveRecord::Core 自身を返してるみたい

https://github.com/rails/rails/blob/2e7c3d942fdafead2cfcbf1cdf7d95a5673cd3e9/activerecord/lib/active_record/core.rb#L497-L509

dak2dak2

klass.allocate が気になったので調べてみたら Ruby のメソッドみたい
コンストラクタを処理せずにクラスのインスタンスだけ返す

https://docs.ruby-lang.org/ja/latest/method/Class/i/allocate.html

先ほどの例だと、ユーザー定義クラスのコンストラクタを呼び出すのではなく、空のインスタンスだけを作って初期化処理は FW 側で行うために利用してそう

dak2dak2

ActiveRecord::Relation のメソッドを呼び出した際に SQL クエリが発行されてる
one? とか many? とか

dak2dak2

当初の Rails の strict_loading を読むところから、Deep Dive しすぎた

dak2dak2

Summary

  • ActiveRecord::Relation のメソッドを呼び出した際に load メソッドが走って SQL クエリが発行されてる
    • いわゆる遅延実行
    • ActiveRecord::Relation のメソッドだけなのかは不明
  • Class#allocate はコンストラクタ実行せずにインスタンスだけ作る
dak2dak2

More Deep Dive

  • ActiveSupport::Dependencies.interlock.permit_concurrent_loadsrunning ロックの話
  • rb_thread_call_without_gvl の GVL の話
このスクラップは2025/01/26にクローズされました