この記事は、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
はどのようなクラスかというと、
- ClassSpecificRelationモジュールの持つメソッドをクラス・メソッドに持つ
- 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_Relation
・Order::ActiveRecord_Associations_CollectionProxy
・Order::ActiveRecordAssociation
クラスが動的に生成される。例えば、Order::ActiveRecord_Relation
クラスのインスタンスは、Order.where(id:1)
を語る上で重要なインスタンスである。
Orderクラスがこの3つのクラスをインスタンス変数@relation_delegate_cacheに保持している。
・Order::ActiveRecord_Relation
・Order::ActiveRecord_Associations_CollectionProxy
・Order::ActiveRecordAssociation
以上。