こんにちは、鈴木です。
「Rails: alias_method_chain: 既存の処理を修正する常套手段」では alias_method_chain をご紹介しました。Ruby2.0 の Module.prepend に置き換えられる運命かもしれないものの、世の中の多くのライブラリに規律と便利さを与え続けてきたその心は決して忘れない。ということで、今回は ActiveSupport の Module#alias_method_chain のコード(現時点の最新 v3.2.12)を見てみます。
Module#alias_method_chain
Module#alias_method_chain は core_ext/module/aliasing.rb で定義されています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def alias_method_chain(target, feature) # Strip out punctuation on predicates or bang methods since # e.g. target?_without_feature is not a valid method name. aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 yield(aliased_target, punctuation) if block_given? with_method = "#{aliased_target}_with_#{feature}#{punctuation}" without_method = "#{aliased_target}_without_#{feature}#{punctuation}" alias_method without_method, target alias_method target, with_method case when public_method_defined?(without_method) public target when protected_method_defined?(without_method) protected target when private_method_defined?(without_method) private target end end |
Ruby におけるメソッド名は「gsub!」「nil?」「name=」のように最後が「!」「?」「=」となる場合があります。
以下の部分ではメソッド名から「!」「?」「=」を分離しています。例えば「alias_method_chain :name=, ...」と呼び出した場合は、aliased_target には「name」、punctuation には「=」が入ります。
そして、ブロックが指定されている場合は、aliased_target と punctuation を引数にブロックを呼び出しています(この動作はコードを読んで初めて知りました)。
1 2 3 4 |
# Strip out punctuation on predicates or bang methods since # e.g. target?_without_feature is not a valid method name. aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 yield(aliased_target, punctuation) if block_given? |
次の部分では「xxx_with_xxx」と「xxx_without_xxx」という文字列を構築し、alias_method でエイリアスを設定しています。
この部分が alias_method_chain が行うメインの仕事ですね。
1 2 3 4 5 |
with_method = "#{aliased_target}_with_#{feature}#{punctuation}" without_method = "#{aliased_target}_without_#{feature}#{punctuation}" alias_method without_method, target alias_method target, with_method |
最後に、以下の部分で元のメソッドと同じ可視性になるように設定しています。
1 2 3 4 5 6 7 8 |
case when public_method_defined?(without_method) public target when protected_method_defined?(without_method) protected target when private_method_defined?(without_method) private target end |
まとめ
ほとんど予備知識として知っていたことの再確認になりましたが、alias_method_chain にブロックを渡せることは初めて知りました。
他にも core_ext/module/aliasing.rb には alias_attribute というメソッドも定義されていました。
こちらは属性のエイリアスを作成するためのもので、「alias_attribute :title, :name」のように使用します。
コードを読めば何かしら新しい発見がありますね!