頑張るときはいつも今

自称エンジニアのゴリラです。日々精進。

【Rails】Decoratorについての忘備録

記事を書くことになった背景

業務で知った内容です。

1月に開発未経験でのWebエンジニアに転職しているのですが、

まずは既存のアプリケーションの改修や小さい新機能を追加することが多くあります。

リソースの状態(大体5パターン)に応じて、Viewに表示させる項目を切り替える必要がありました。

そこでの先輩との会話

私「モデルにリソースの状態に応じて判断メソッドを書けば終わりっすね!」

先輩「Viewでしか使わないからDecoratorでもいいかもなー」

私「分からん!!」

と言う状況だったのですが、調べてみたらシンプルに感激したので記事に残します。

記事で書く内容

  • Decoratorパターンについての説明

  • Railsに置けるDecoratorについて

  • Decoratorパターンを簡単に実装するgem Draperの説明

Decoratorパターンとは

GoFデザインパターンの一つ

Javaで学ぶデザインパターンには下記のように例えられています。

ある元となるクラスに対して機能を拡張していく際に利用できるデザインパターンになると思われる。

まず中心となるスポンジケーキのようなオブジェクトがある それに飾り付けとなる機能を一皮一皮被せていって、より目的にあったオブジェクトに仕上げていくのです。

正直、頭の中でお花畑が見えましたが、使うと何が嬉しいかについては、下記のように理解しました。

  • 元となるクラスを継承して、飾り枠(ConcreteDecorator)に機能を拡張していく
    • 元となるクラスに対して変更が発生しない
  • 飾り枠をたくさん用意して組み合わせること多様なパターンの機能拡張ができる
    • 表示などをカスタマイズする際には最適なパターンになるかも
    • 元のクラスに対してコードを追加していく訳ではないので、1つのクラスが肥大化していくことを防ぐことができる

ただ、ConcreteDecorator自体が肥大化したり、似たようなクラスが

増えてくると言うアンチパターンと隣合わせと言うことは認識しておかなければなりません。

実際にコードを書いてみた系は、別の記事を参照してください。。

クラス図

一般的なDecoratorパターンのクラス図は下記のようになります。

f:id:wa_football_1120:20200125204722p:plain

  • Component

    • 機能を追加するときになる元となる抽象クラス
  • ConcreteComponent

  • Decorator

    • 装飾の元となる抽象クラス
    • Componentをインスタンス変数としてもち、飾り付けの対象を知っている
  • ConcreteDecorator

    • 実際に装飾の仕方を定義しているクラス
    • これを追加していくことで、多様な拡張が可能になる

RailsにおけるDecoratorについて

大まかな特徴としては、

  • Viewでしか使わないModelのメソッドをdecoratorを使うことで切り離すことができる
  • decorator内ではhelperを利用することができる
    • link_toといったViewで使うメソッドをdecoratorで定義することができる

decoratorを使うと何が嬉しいか言うと、

  • ビジネスロジックとプレゼンテーション(表示)に関わる関心を分離することができる

  • 上記を実装することで、1つのモデルが肥大化することを防ぐことができる

ことではないかと思います。

ただ、上のDecoratorパターンのところにも書きましたが、

Decoratorが肥大化する懸念があることについては認識しておく必要があります。

複数のモデルで使われる共通のロジックについてはhelperに記載した方がいいでしょう。

Draperについて

Draperって?

Draperは、decorator(プレゼンテーション)の実装を簡単にしてくれるgemになります。

ど素人目線で行ってしまうと、ある特定のモデルかつViewでしか使わないメソッドはdecoratorに書いた方がいいよっていう感じです。

特徴としては、

  • ViewとModelの中間に位置するVM(View Model)
    • Vue.jsとかで言われるVMMVとは異なる
  • プレゼンテーションのロジックやフォーマットの処理を実装することができるようになる

使いかた

ライブラリのインストール

おなじみのGemfileに下記を記述して、bundle installしてください。

# Gemfile
gem 'draper'

# Gemのインストール
bundle install

Decorator作成

bundle exec rails g decorator User

Running via Spring preloader in process 284
      create  app/decorators/user_decorator.rb
      invoke  rspec
      create    spec/decorators/user_decorator_spec.rb

こんな感じでViewで利用するhelperを書くことができます。

class UserDecorator < Draper::Decorator
  # 全ての属性にアクセスできるようにする
  delegate_all

  def welcome_message
    if user_signed_in? && !current_user.confirmed?
      return "ゲストユーザ(登録が完了していません)"
    else
      return "ようこそ、#{name}"
    end
  end
end

コントローラではdecoratorメソッドで、上で定義したメソッドを使えるようにしてあげます。

def index
     @user = User.find(id: ~~).decorate
end

そしてViewからは、以下のようにアクセスすればOKです。

View内で、If分岐させなくて済むのがポイントです。

h1
   = @user.welcome_message

まとめ

いつも通りの雑な記事ではありますが、学んだことは下記。

  • RailsではDecoratorを使うとことで、Viewでしか使わないロジックを分離できる
    • ModelやViewのコードが肥大化せずにすむ
  • Decoratorを使う際にはgemのdraperを使うのがおすすめ
  • Controllerでは抽出したモデルに対してdecorateメソッドを使ってあげる