ActiveDecoratorでビューからロジックを切り離せ
目的
ActiveDecoratorはDecoratorを作成する。 ビューファイルにif else if elseがあると、ビューファイルの可読性が落ちメンテナンスがしづらくなる。 また、HTMLをコーダーが作ったものをに、ErbやSlimにてrubyのコードを追記する場合に 変更範囲箇所が多くなり、もとのHTMLとかけ離れた構成になりがちになる
また、表示内容の場合分けをテスト可能にすることができる。
ActiveDecorator選定理由
似たgemにDraper(これが一番メジャー)がある。ActiveDecoratorはコントローラでDecoratorの設定をしなくても良いので、コードがすっきりする。コントローラでDecoratorのメソッドを呼んで場合分け出来たりするので、機能の分離を強制するためにもActiveDecoratorを採用した
検証環境
インストール
Gemfileに"active_decorator"を追加し、bundle install
gem "active_decorator"
Decoratorを自動生成
既存のモデルに対してDecoratorを生成する
> bin/rails g decorator user
create app/decorators/user_decorator.rb
invoke rspec
create spec/decorators/user_decorator_spec.rb
ActiveDecoratorは、モデル名Decoratorクラスをモデル名をビューで自動でデコレート(拡張)してくれるので、命名規則は崩さないこと、ファットになってきたらモジュール分割してincludeするべし
Decoratorの書き方
Decoratorファイル
app/decocators/user_decorator.rb
module UserDecorator # 共通処理を書きたい場合 # PeopleDecoratorモジュールをインポート include PeopleDecorator # 文字列だけの場合 def full_name_with_bracket "[ #{first_name} #{last_name} ]" end # タグで囲んでクラスを付ける場合(1つのタグで囲む場合のみ) # こういうのが出力できる<span class="class_name">John Doe</span> def full_name_with_tag content_tag :span, "#{first_name} #{last_name}", class: "class_name" end # 分岐もこのようにかける def true_false_method if first_name.present? "I have first name." else "I don't have first name! Come on Daddy, please give me first name!!" end end # partial パーシャルを表示する場合 def render_partial render partial: "shared/xxxx" end end
複数のDecoratorに渡る共通処理
上記でインポートされるDecoratorはの作成、シンプルなモジュールで良い
app/decocators/people_decorator.rb
module PeopleDecorator # 複数のdecoratorにまたがる共通処理 def full_name "#{first_name} #{last_name}" end end
使えるViewHelperについて
他にもActionView::Helpersを使えるそうなのでRailsのビューヘルパー(View Helper)のまとめに乗っているものは使えるだろう
specファイル
デコレートされたクラスを作成するために Model名.extend Decoratorクラス名 でデコレートしてあげる ※ Decoratorの自動生成時にできるのでそれを真似ればよい
spec/decorators/user_decorator_spec.rb
require "rails_helper" describe UserDecorator do # ここがみそ let(:user) { create(:user).extend UserDecorator } subject { user } it { should be_a User } describe "full_name" do subject { user.full_name } it { is_expected.to eq "#{user.first_name} #{user.last_name}" } end # link_to とかのViewhelperを使ったテストをする場合は、extendしてやる let(:user_w_viewhelper) do create(:user).extend(UserDecorator).extend(ActionView::Helpers) end end
コントローラでの呼び出し
特に何もしなくて良い
ビューファイルでの呼び出し方
以下の様な感じで、単純に呼んでやれば良い Erbだとこんな感じ
<%= current_user.full_name_with_bracket %> <%= current_user.full_name_with_tag %> <%= current_user.full_name %> <%= current_user.true_false_method %> <%= current_user.render_partial %>
使いどころ
- ユーザの権限によって出す文言が変わる場合
- 有料/無料ユーザの表示の出し分け
- スマフォでアクセスされた場合に省略する文字列があるとき
- 状態によって表示が変わる場合、xx中、xxx待ち、xxx完了の時など
- 表示するpartial分岐はコード見通しはわかりにくくなりそう