Class
クラスに実装されているメソッド。
クラスのサブクラスが定義された時、新しく生成されたサブクラスを引数にインタプリタから呼び出されます。このメソッドが呼ばれるタイミングはクラス定義文の実行直前です。
とあるように、あるクラスのサブクラスが定義された時に必ず呼び出される関数を定義することができる。
class Foo
def Foo.inherited(subclass)
puts "class \"#{self}\" was inherited by \"#{subclass}\""
end
end
class Bar < Foo
puts "executing class body"
end
# => class "Foo" was inherited by "Bar"
# executing class body
Active Recordではinheritedメソッドが活用されている。
例えば、ActiveRecord::Relation
クラスの動的生成においてである。ActiveRecord::Base
を継承したModelクラスを定義すると、作成したModelクラスの名前空間ごとにActiveRecord_Relation
クラスが定義される。
以下のような内部クラスが自動的に定義される。
User::ActiveRecord_Relation
なぜModelごとにActiveRecord::Relation
クラスが必要になるかについては、以下の資料がとても詳細かつ分かりやすい(→ クラスメソッドやScopeを呼び出すため。)
inheritedメソッドはこの、ActiveRecord_Relation
クラスの動的生成に一役買っている。
例として、Userモデルからeager_loadを呼び出したとする。 以下はいずれもActiveRecord::Relationクラスで定義されている。(厳密にはmixinされるモジュール)
def eager_load(*args)
check_if_method_has_arguments!(__callee__, args)
spawn.eager_load!(*args)
end
def eager_load!(*args) # :nodoc:
self.eager_load_values |= args
self
end
ActiveRecord::Base
でDelegation::DelegateCache
がmixinされる
module ActiveRecord
class Base
...
extend Delegation::DelegateCache
...
end
end
ActiveRecord::BaseとDelegateCacheモジュールのinherited
メソッドにより、ActiveRecord::Base
のサブクラス(今回の例だとUserクラス)を定義したタイミングで、User::ActiveRecord_Relation
クラスが作成される。
User.eager_load(:profile)
とすると、User::ActiveRecord_Relation
クラスに定義されているeager_load
が呼び出されることになる。
module DelegateCache
...
def initialize_relation_delegate_cache
@relation_delegate_cache = cache = {}
Delegation.delegated_classes.each do |klass|
delegate = Class.new(klass) {
include ClassSpecificRelation
}
include_relation_methods(delegate)
mangled_name = klass.name.gsub("::", "_")
const_set mangled_name, delegate
private_constant mangled_name
cache[klass] = delegate
end
end
def inherited(child_class)
child_class.initialize_relation_delegate_cache
super
end
...
end
例えば、以下のようなメソッドについて、RSpecでテストを作成しているとする。
def hoge
User.eager_load(:profile).find_each do |user|
...
user.save!
rescue => e
Rails.logger.warn(e)
end
end
ブロック内で例外が発生した場合をテストするために、user.save!
をスタブして例外を返すようにしたい。
動的に定義されるクラスをinstance_doubleでモックするために、eager_load
をはじめとしたメソッドチェーンをすべてスタブしていくことにする。(テストの仕方がまずいというのは一旦おいておく)
その上で、eager_load
メソッドの返すオブジェクトのクラスが
User.eager_load(:profile).class
=> User::ActiveRecord_Relation
であることがわかった上で
User::ActiveRecord_Relation
が何であるか?をActive Recordのソースコードから読み取ることができる。
User::ActiveRecord_Relation
はinheritedメソッドによってサブクラスの定義時に作成され、元はActiveRecord::Relation
であることから、以下のようにモックすることができる。
describe 'User' do
describe 'class method hoge' do
context 'when raised error in find_each block' do
let(:user) { create(:user) }
before do
relation = instance_double(ActiveRecord::Relation)
allow(User).to receive(:eager_load).and_return(relation)
allow(user).to receive(:save!).and_raise('error!!')
allow(relation).to receive(:find_each).and_yield(user)
end
it 'logged error message' do
expect(Rails.logger).to receive(:warn).with("error!!")
User.hoge
end
end
end
end