コンユウメモ @kon_yu

作ったガラクタとか、旅行とかの話

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分岐はコード見通しはわかりにくくなりそう
refs: