ActiveSupportを読む: ActiveSupport::Concern

こんにちは、鈴木です。

先日、「ActiveSupport::Concern でハッピーなモジュールライフを送る」で ActiveSupport::Concern をご紹介しましたが、きっと裏側では色々なテクニックが使われているのではないでしょうか。

ということで ActiveSupport::Concern が裏側でどのような処理を行なっているのか、ActiveSupport::Concern のコードを読んでみます!

 

ActiveSupport::Concern のコードを読む

ソースコードはこのようになっていました。

extended, append_features, included という 3 つのメソッドが定義されていますね。

 

extended

最初に extended から見ていきましょう。

extended はモジュールが extend された時に呼び出されるメソッドですが、ここではbase に対して変数 @_dependencies を設定しています。

変数名から想像すると、依存関係に関する何かが @_dependencies に保持されるのでしょう。

 

included

次に included を見てみましょう。

base が nil かどうかで分岐しています。

base が nil になるのは、以下のように呼び出された場合です。

この時はブロックを @_included_block に保存しています。

base が nil 以外になるのは、どのような場合でしょうか。それは以下のように ActiveSupport::Concern で extend したモジュールが include された場合です。

このときは特別な処理は行わず、単純に元の処理(super)を呼び出しています。

 

append_features

最後は append_features です。

append_features はあまり見慣れないメソッドですが、ActiveSupport が定義するメソッドではなく、Ruby のメソッドです(see Module#append_features)。

若干ボリュームのあるメソッドですが、@_dependencies が定義されているかどうかで条件分岐していますね。

どちらに分岐するのか

例として、以下のコードで考えてみましょう。

include している場所は 3 箇所、コメントで (1), (2), (3) と書いているところですが、(1) と (2) については M1 と M2 は ActiveSupport::Concern で extend していますので、@_dependencies が定義されている場合の分岐に入ります。

(3) については、C は ActiveSupport::Concern で extend していませんので、@_dependencies が定義されていない場合の分岐に入ります。

それでは、条件分岐のそれぞれの処理を見ていきましょう。

@_dependencies が定義されている場合

この時は @_dependencies に self を追加しているだけです。

デフォルトの処理(super)を呼び出していないので、この時点で base にメソッドは追加されません。

※append_features のデフォルトの動作は「クラスやモジュール(base)に self の機能を追加する」であり、それを行わずに return しているからです。

@_dependencies が定義されていない場合

最初に「base < self」であるかチェックしています。

「base < self」は base の継承階層の上位に self が存在する場合に true となります。

これはモジュールが複数回 include された場合を考慮したものであり、2 回目以降は何も行わないためのチェックです。

次の行を見ると、@_dependencies に蓄えられているモジュールを順番に base に include しています。

@_dependencies に保持していたモジュールを順番に base に include しています。

以下のコードでいえば、M1 を M2 が include して、M2 を M3 が include して、M3 を C が include していますが、上記の処理で C に対して M1, M2 が include されることになります。そして注意ですが、この時点で M3 は C に include されません。というのも、@_dependencies に M3 は含まれていないからです。

次の行では super を呼び出しています。ここでやっと、M3 が C に include されます(※正確には「M3 の機能が C に追加」されます。include すると append_features が呼び出され、append_features のデフォルトの処理は「base に self の機能を追加」されるので。ややこしい・・)。

次は ClassMethods が定義されていれば、それで base を extend (クラスメソッドに追加)しています。

その次は InstanceMethods が定義されていれば、それを base に include します。(※InstanceMethods を定義する方法は非推奨となったため、警告メッセージが表示されます。新しいコードを書く場合は、普通にモジュール内にメソッド定義します)。

最後は included で指定されたブロックを base の class_eval に渡しています。

 

まとめ

ActiveSupport::Concern がどのように実装されているのか見てきましたが、いかがでしたでしょうか。

append_features を使っているコードを見るのは初めてでしたし、読み解くのは大変でしたが、得るものは大きかったです。

 

Happy Programming!

 

Comments are closed, but you can leave a trackback: Trackback URL.