Ruby
Rails
RubyOnRails
ソースコードリーディング

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

以上。