Help us understand the problem. What is going on with this article?

class Order < ActiveRecord::Base; end;が実行されると何が起こるのか?

More than 1 year has passed since last update.

この記事は、2017/09/30(土)に書きました。

はじめに

Orderは適当な名前です。なんでもいいです。
railsを使ったことがあるひとなら、ActiveRecord::Base を継承したモデルをつくったことが必ずあると思いますが、モデルを作った時に何が行われているかは普段気にしないとおもいます。だが、その部分を理解することはActiveRecord::Baseを継承したモデルの理解をする上で重要だと思い今回しっかり追ってみました。

class Order < ActiveRecord::Base
end

これが実行されると何が行われるのか?

diffを強調のニュアンスで使っています。

動作環境

  • rails 5.1.0
  • ruby 2.4.1p11



ActiveRecord::Baseクラスを継承するだけで実行されるメソッドがある?

ActiveRecord::Baseクラスの中でDelegatte::DelegateCacheがextendされている。
[activerecord-5.1.0/lib/active_record/base.rb]

 class Base
    extend ActiveModel::Naming

    extend ActiveSupport::Benchmarkable
    extend ActiveSupport::DescendantsTracker

    extend ConnectionHandling
    extend QueryCache::ClassMethods
    extend Querying
    extend Translation
    extend DynamicMatchers
    extend Explain
    extend Enum
+   extend Delegate::DelegateCache

つまり、Delegate::DelegateCacheモジュールに定義されているメソッドをクラス・メソッドとして使えるようになる。


どのようなメソッドが実行されるの?(inheritedメソッド)

Delegate::DelegateCache モジュールにrubyのinheritedメソッドがオーバーライドしてある。

[activerecord-5.1.0/lib/active_record/relation/delegation.rb]

def inherited(child_class)
+   child_class.initialize_relation_delegate_cache
    delegate = child_class.relation_delegate_class(ActiveRecord::Associations::CollectionProxy)
    delegate.include ActiveRecord::Associations::CollectionProxy::DelegateExtending
    super
end

# child_class => Order

child_classはOrderクラスを指している。このメソッドはchild_class.initialize_relation_delegate_cache が肝。
これは何をするのかというと、 child_classが所有しているインスタンス変数@@relation_delegate_cacheに以下のようなhashを保持する。

@relation_delegate_cache = {
    ActiveRecord::Relation                      => Order::ActiveRecord_Relation,
    ActiveRecord::Associations::CollectionProxy => Order::ActiveRecord_Associations_CollectionProxy,
    ActiveRecord::AssociationRelation           => Order::ActiveRecordAssociation
}

例えばOrder::ActiveRecord_Relationはどのようなクラスかというと、

  1. ClassSpecificRelationモジュールの持つメソッドをクラス・メソッドに持つ
  2. ActiveRecord::Relationをsuperclassに持つ

という大事な性質のあるクラスである。


動的に生成されるクラス(Order::ActiveRecord_Relation)がどのように生成されるか?

child_class.initialize_relation_delegate_cache が肝だと先程いったのでその中身を見てみると、

[activerecord-5.1.0/lib/active_record/relation/delegation.rb]

def initialize_relation_delegate_cache
    @relation_delegate_cache = cache = {}
    [
        ActiveRecord::Relation,
       ActiveRecord::Associations::CollectionProxy,
       ActiveRecord::AssociationRelation
    ].each do |klass|
+       delegate = Class.new(klass) {
+           include ClassSpecificRelation
+       }

+       mangled_name = klass.name.gsub("::".freeze, "_".freeze)

+       # ここでOrder::ActiveRecord_Relationの中身がdlegateになる
+       const_set mangled_name, delegate
        private_constant mangled_name
        cache[klass] = delegate
    end
end

説明すると、klass=ActiveRecord::Relationの時、このクラスを継承してなおかつ、ClassSpecificReflationモジュールをインクルードしたクラスdelegateを動的に生成し、selfがOrderなのでconst_setでdelegateに名前をつけると、
delegateにOrder::ActiveRecord_Relationという名前がつくわけである。あとは生成したクラス(Order::ActiveRecord_Relation)をキーがActiveRecord::Relationでhashとしてキャッシュしている。
ActiveRecord::Baseが継承されたときだけ1度だけ行われるメソッドなので使いまわすにはキャッシュしておく必要があるのである。

以上のような仕組みでOrder::ActiveRecord_Relationクラスが動的に生成される。

ではこのように動的に生成されたOrder::ActiveRecord_Relationクラスはどのように使われるのか?

一例としては、

Order.where(id:1)のようにメソッドが呼ばれた時、内部的にOrder::ActiveRecord_Relationのインスタンス(relation)が生成され、それに対してwhereが実行されるのである。
※Order.whereとrelation.whereのwhereは別物のメソッドである。

この例は、
Order.where(name: "name_1", description: "description_1")はどのような順番でメソッドが実行されるのか?
で説明します。たぶん。。。。


まとめ

class Order < ActiveRecord::Base; end; が実行されると、Order::ActiveRecord_RelationOrder::ActiveRecord_Associations_CollectionProxyOrder::ActiveRecordAssociationクラスが動的に生成される。例えば、Order::ActiveRecord_Relationクラスのインスタンスは、Order.where(id:1) を語る上で重要なインスタンスである。

Orderクラスがこの3つのクラスをインスタンス変数@relation_delegate_cacheに保持している。

・Order::ActiveRecord_Relation
・Order::ActiveRecord_Associations_CollectionProxy
・Order::ActiveRecordAssociation

以上。

yukihirop
気の向くまま。意の向くままにコードを書くプログラマー。 役に立つツールを作るのって本当に難しい。
https://creator-of-what.yukihirop.me/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした