現在開発しているあるプロジェクトで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などのメソッドが書き換えられています。

  1. 直接書き換えているのではなく、対象のモジュールなどに既存のメソッドを上書きするモジュールをprependで追加しています。