現在開発しているあるプロジェクトでActiveDecoratorを利用しています。 普段はActiveRecordを継承しているモデルに対してViewで利用するためのメソッドを拡張するために使っているのですが、それ以外の、例えばActiveModelやStructなど任意のモデルに対して使いたいケースが出てきました。 そこで、そういった場合に使えるのか調査したので、それをまとめておこうと思います。
ここで調査した対象は、ActiveDecorator v1.4.0です。
基本的な使い方については、READMEを御覧ください。
TL; DR
結果から言うと、任意のRubyオブジェクトに対して使えます。
- コントローラー内で定義されたインスタンス変数
- ビュー内でpartial renderで呼び出したときの
locals
- 既にDecoratorが差し込まれたモデルのリレーション
上記のタイミングでDecoratorが差し込まれるようになっています。
Decoratorの対象はどう決まるのか
あるモデルに対してどのようにDecoratorが差し込まれるのかは、ActiveDecorator::Decorator#decorate
で定義されています。
このメソッドに渡される引数obj
がどのようなものなのかによってDecoratorの差し込み方が変わります。
ArrayまたはHashの場合
obj
の値それぞれのオブジェクトに対して、再帰的にdecorate
メソッドを呼び出し、Decoratorを差し込んでいきます。
ActiveRecordのリレーションの場合
例えばPost
モデルがhas_many :comments
のようにリレーションを持っている時にpost.comments
が引数obj
として渡された場合、
その結果のComment
オブジェクトにそれぞれに対してDecoratorが差し込まれます。
ActiveRecord::Baseを継承したオブジェクトの場合
つまり一般的にrails generator model
で生成されるようなモデルの場合、そのオブジェクトに対してDecoratorが差し込まれます。
それ以外のオブジェクトの場合
もちろん、この場合にも上記のActiveRecord::Baseを継承している場合と同様にDecoratorが差し込まれます。
差し込むDecoratorはどう選ばれるか
`ActiveDecorator::Decorator#decorator_forは、クラス(Classオブジェクト)を引数として渡すと、それに対応するDecoratorを返すプライベートメソッドです。
渡したクラスの名前の末尾にDecorator
が付与された名前のモジュールがあれば、それが差し込むデコレーターとして返されます。
例えば、Post
クラスであればPostDecorator
、ネームスペースが付いているBlog::Post
クラスであればBlog::PostDecorator
となります。
この末尾についているDecorator
を別の文字列に置き換えたい場合は、READMEに記載があるように、initializerなどでconfig.decorator_suffix = 'Presenter'
のように設定することで変更できます。
Decoratorはどのタイミングで差し込まれるのか
前述のように、ActiveDecorator::Decorator#decorate
によって配列なりハッシュなりリレーションであろうとそれらの要素それぞれに対して命名規則に従って選ばれたDecoratorが存在すれば差し込まれることがわかりました。
次に、このdecorate
メソッドを呼び出すタイミングを確認していきます。
それがActiveDecorator::Railtie
で定義されています。
このRailstie
で定義されるinitializer
内で、ActionViewやActionController、ActionMailなどにdecorate
メソッドを呼び出すように特定のメソッドを書き換えています1。
ビュー内(ActionView)でのpartial render
ビューのテンプレート内で実行するrender partial:
で渡したlocals
の変数それぞれに対してdecorate
が実行されます。
コントローラー(ActionController)で(暗黙的なものも含めて)呼ばれるrender
コントローラーのアクション内で定義されたインスタンス変数に対してdecorate
が呼び出されます。
明示的にrender
しなくとも、暗黙的に実行されるrender
のタイミングで呼ばれるようです。
なので、ビュー内で利用できるインスタンス変数は既にDecoratorが差し込まれていることになります。
ActionMailerのrender
これはActionControllerと同様です。
ActiveRecordのリレーション
上述までのとおりに、リレーション自体がpartial renderのlocals
に渡した場合、それらの要素それぞれにDecoratorが差し込まれます。
先に出た例の場合、コントローラー内で@post = Post.find(params[:id])
と実行されると、@post
にはDecoratorが差し込まれます。
そして、ビュー内で@post.comments
を実行すると、そのそれぞれのComment
オブジェクトにもまたDecoratorが差し込まれます。
というようになるように、 initializer
の中でActiveRecord::Associations
などのメソッドが書き換えられています。
-
直接書き換えているのではなく、対象のモジュールなどに既存のメソッドを上書きするモジュールを
prepend
で追加しています。 ↩