概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Rails allows a module with extend ActiveSupport::Concern to be prepended | Saeloun Blog
- 原文公開日: 2020/09/23
- 著者: Vamsi Pavan Mahesh
- サイト: blog.saeloun.com
Rails: ActiveSupport::Concernをextendしたモジュールをprependする機能(翻訳)
Railsにconcernsをprepend
するためのサポートが追加されました(ba2bea5e)。
prepend
とは
あるモジュールをクラスに
prepend
すると、そのモジュールは継承チェインの(そのクラス自身よりも手前の)冒頭に挿入される。
Rubyのインスタンスメソッドをprepend
する場合
module Population
def preferred_transport
"by walk"
end
end
class Human
prepend Population
def preferred_transport
"by air"
end
end
Human.new.preferred_transport #=> by walk
上のコード例では、PopulationモジュールがHumanクラスでprepend
されています。
これによって、Populationモジュールにあるpreferred_transport
は、探索チェイン内のHumanクラスにある(同名の)preferred_transport
よりも優先されるようになりました。
以下のようにHumanクラスでancestors
を呼ぶことでこのことを確認できます。
Human.ancestors
#=> [Population, Human, Object, Kernel, BasicObject]
Rubyのクラスメソッドをprepend
する場合
module Population
def self.prepended(base)
class << base
prepend ClassMethods
end
end
module ClassMethods
def count
5000
end
end
end
class Human
prepend Population
def self.count
1000
end
end
Human.count #=> 5000
上のコード例では、ClassMethodsモジュール内にあるメソッドをprepended
フックで拡張し、Populationモジュールのcount
クラスメソッドがHumanクラスの(同名の)count
クラスメソッドより優先されるようにしています。
Human.ancestors
#=> [Population, Human, Object, Kernel, BasicObject]
Railsのconcernsをprepend
する
Railsのconcernsにprepend
のサポートが追加されたことで、上述の2つのコード例を以下のようにずっとシンプルにまとめられるようになりました。
module Population
extend ActiveSupport::Concern
def preferred_transport
"by walk"
end
class_methods do
def count
5000
end
end
end
class Human
prepend Population
def self.count
1000
end
def preferred_transport
"by air"
end
end
Human.new.preferred_transport #=> by walk
Human.count #=> 5000
このコード例では、先ほどのコード例のようにprepended
フックを用いてクラスメソッドを手動で追加する必要はありません。
Humanクラスの探索チェインをancestors
で調べると以下のような感じになります。
[Population, Human, ActiveSupport::ForkTracker::CoreExtPrivate, ActiveSupport::ForkTracker::CoreExt, ActiveSupport::Dependencies::ZeitwerkIntegration::RequireDependency, ActiveSupport::ToJsonWithActiveSupportEncoder, Object, PP::ObjectMixin, JSON::Ext::Generator::GeneratorMethods::Object, ActiveSupport::Tryable, ActiveSupport::Dependencies::Loadable, Kernel, BasicObject]
また、以下のようにモジュールがクラスにprepend
されるときに、prepended
ブロックを用いて任意のカスタムコードを実行することもできます。
module Population
extend ActiveSupport::Concern
prepended do
puts "the module is prepended"
end
def preferred_transport
"by walk"
end
end
このprepended
ブロックは、そのモジュールがクラスにprepend
された後で実行されます。これはincluded
ブロックの動作と似ています。
注意: ひとつのモジュール内にprepended
ブロックを複数置くことはできません。
prepended
ブロックを複数宣言しようとするとActiveSupport::Concern::MultiplePrependBlocks
例外がraiseされます。
prepend
はconcerning
でも動作します。その場合、以下のようにconcerning
ブロックが始まる直前にprepend: true
を定義する必要があります。
class Human
concerning :Preferences, prepend: true do
def preferred_transport
"by walk"
end
class_methods do
def count
5000
end
end
end
end
この場合Humanクラスの探索チェインは以下のような感じになります。
[Human::Preferences, Human, ActiveSupport::ForkTracker::CoreExtPrivate, ActiveSupport::ForkTracker::CoreExt, ActiveSupport::Dependencies::ZeitwerkIntegration::RequireDependency, ActiveSupport::ToJsonWithActiveSupportEncoder, Object, PP::ObjectMixin, JSON::Ext::Generator::GeneratorMethods::Object, ActiveSupport::Tryable, ActiveSupport::Dependencies::Loadable, Kernel, BasicObject]