draperやactive_decoratorを使ってViewに書いてしまったロジックを減らす

ワールドカップ面白かったですね。各試合を楽しんで観ていました(ダイジェストですが)技術推進室の中村です。ドイツ強かった。
Railsで開発していると、Modelの値をViewに表示する時に、そのまま表示するのではなく、ロジックを書いて整形して表示するということがあると思います。 そういう場合helperを使うというのも手ではありますが、最近ではMVC(Model View Controller)のViewとModelの間にPresenter(Decorator)という層を設けて、Viewで行なっていたロジック(特にModelに関連するロジック)をPresenterに任せるやり方をよくします。 Presenter(Decorator)については下記を参考にしてください。
Railsではそういう時に使うgemとしてdraperやactive_decoratorがあります。ここではdraperとactive_decoratorそれぞれについて簡単に紹介したいと思います。
draperを使ってみる
まずはインストールから。
1.Gemfileにgem ‘draper’
を追加してbundle installを実行します。
gem 'draper'
bundle install
これでインストールは終わりです。
2.続いてRailsのgenerateコマンドを使ってファイルを生成します。今回はUserというModelに対するPresenterを作成します。
bundle exec rails g decorator user
するとapp/decorators/user_decorator.rb
が生成されます。
3.続いて生成されたapp/decorators/user_decorator.rb
にメソッドを追加します。今回は名前を最初の5文字で切るメソッドを追加します
このshort_nameメソッドの中のobjectにはUserDecoratorクラスのインスタンスが入っています。
4.最後にControllerです。このUserDecoratorを使ってUserの値を取得するように変更します。今回は詳細表示の画面の時に呼ばれるshowメソッドの中を変更します。
これでshow.html.erb(詳細ページのテンプレート)の中では@user.short_nameとするだけでname属性の値のうち最初の5文字が表示されます。
次にページングのgemと一緒に使う場合についてふれたいと思います。今回はkaminariを使います。
Railsでページングを実装する場合、最近はkaminariというgemを使うケースが多いと思います。
で、このkaminariを使って取得したModelの配列(例えば今回の例のようにUserというModelの場合、User.page(1).per(20)で取得したUserの配列)の一要素に対して、draperで定義したメソッドを適用するには一工夫必要です。何もしなければエラーが出てうまく動作しません。
じゃ、何をすれば良いのか・・・。
それはズバリ、config/initializers/draper.rb
を作成しこれに以下のコードを追加すれば良いのです。それだけです。
この件はここに詳細が書いてありますので、興味のある方はご一読していただければと思います
次にController側で以下のようにUserDecoratorを使うようにコードを修正します。
で、ちゃんとshort_nameメソッドが使えるかindex.html.erbを表示してテストしてみると使えることが確認できると思います。
active_decoratorを使ってみる
それでは次にactive_decoratorを使ってみましょう。active_decoratorもbundle installした後、以下のようにrailsのgenerateコマンドをたたくとapp/decorators
以下にファイルが生成されます。
gem 'active_decorator' $ bundle install $ bundle exec rails g decorator user
作成されたapp/decorators/user_decorator.rb
に上のサンプルと同じにshort_nameメソッドを追加します。
追加したら、View側でその追加したメソッドを使うようにしてみましょう。上の例と同じようにshow.html.erbとindex.html.erbを変更します。
たったこれだけです。draperに比べてそれ程多くの記述をしなくても、同じようなことができます。
どちらを使うか
active_decoratorのコードを見てみるとAbstractController:: Renderingにview_assigns_with_decoratorメソッドを追加してalias_method_chainを使ってview_assignsメソッドを置き換えています。更にViewのソースも一部同じようにメソッドの置き換えをしています。
このメソッドの置き換えにより利用する側は多くのコードを追加・変更する事なくactive_decoratorの機能を使えるようになっています。 その点draperは自分で使う機能をDecoratorに取り込む必要があります(今回の場合だったらdecorates_findersを)。利用する側のコードの追加・変更も若干増えます。
ただdraperはactive_decoratorに比べて機能が豊富です。更にdraperはModelをDecoratorクラスがラップする形になっていますので(今回の例でいうとUserクラスをUserDecoratorがラップしている)、元々Modelにあるメソッドと同じ名前のメソッドをDecoratorに定義しても、どちらも使えます。逆にactive_decoratorはModelに既にあるメソッドと同じ名前のメソッドを定義したらエラーが発生します。
Modelの値を整形して表示する必要がある場合や表示するのにロジックが発生する場合など、今回紹介したdraperやactive_decoratorを使って実装するとView側のソースが整理されて良いと思います。