画面へ表示するためだけのメソッドは、モデルには書かずに、ヘルパーに書く。というのはみなさんご存知のはずです。
ただ、そうすると今度はヘルパーが太ってくる。
最初はそこまで多くならないだろうと、一つのファイルに集めていたヘルパーメソッドたちが、徐々に増えてきて、どこにメソッドを書いたのかわからない、そんな経験ないでしょうか?
もし今その状況ならデコレータで解決できるかもしれません。
スポンサーリンク
デコレータとは
GoFのデザインパターンのひとつです。
- 作者: 結城浩
- 出版社/メーカー: ソフトバンククリエイティブ
- 発売日: 2004/06/19
- メディア: 大型本
- 購入: 51人 クリック: 762回
- この商品を含むブログ (397件) を見る
既存のモデルを変更することなく、あとから機能を追加していくデザインパターンです。
名前の通り装飾するという感じですね。
rubyではデコレータを自分で実装しなくても、gemが公開されています。
有名なのはdraperとactive_decoratorですが、今回は主にdraperについて書きます。
どちらが良いとははっきり言えないですが、ベースとなるモデルの属性をそのままメソッド名にしたかったので、draperを選びました。
draperを使う
Gemfileに追加します。&bundle install
gem 'draper'
今回はタイトルと著者、値段カラムをもったBookモデルを考えます。
ただし、画面に表示するときは、必ずどの画面でも「タイトル(著作者)」と表示することとします。
これぐらいなら、
<%= @book.title %>(<%= @book.author) %>)
としてもいいのですが、今回は実装例なので、かっこよく
<%= @book.title_for_display %>
みたいにしてみます。
モデルと画面、コントローラが実装できたら、以下のコマンドでデコレータを作ります
rails generate decorator book
すると、appディレクトリ以下に、decoratorsというディレクトリが作られ、その中にbook_decorator.rbが作られます。
ではbook_decorator.rbを編集しましょう。
class BookDecorator < Draper::Decorator def title_for_display "#{object.title}(#{object.author})" end end
簡単ですが、以上です。
次に、画面側でこのデコレータを使うようにしましょう
<% decorated_book = ::BookDecorator.decorate(@book) %> <p><%= decorated_book.title_for_display %> <p><%= decorated_book.price %>
できたー
と、思ってしまいますが、残念。じつはこのままでは、エラーが出てしまいます。
エラーの内容はdecorated_bookはpriceなんて知らないよ、です。
どうすればいいかというと、先ほどのデコレータに一行追加します
class BookDecorator < Draper::Decorator delegate_all def title_for_display "#{object.title}(#{object.author})" end end
delegate_allとすることで、デコレータが知らないメソッドはデコレートした元のモデルに処理を丸投げ(委譲)できるようになります。
これでデコレートされたモデルからもpriceが呼べるようになりました。
書籍一覧など、複数のインスタンスを一気にデコレートするにはdecorate_collectionメソッドが使えます。
<% decorated_books = ::BookDecorator.decorate_collection(@book) %> <% decorated_books .each do |decorated_book| %> <p><%= decorated_book.title_for_display %></p> <p><%= decorated_book.price %></p> <% end %>
みたいな感じです。
最後に、上記では、title_for_dispalyというメソッドを定義しましたが、元のモデルのカラム名と同じメソッド名でもOKです
class BookDecorator < Draper::Decorator delegate_all def title "#{object.title}(#{object.author})" end end
読者登録はこちらからお願いします。