はじめに
デザインパターンは、ソフトウェア開発の一般的な問題を解決するため、様々なプログラムで再利用できるソリューションのことです。RailsはすでにModel-View-Controller(MVC)というデザインパターンを実装しており、コードをより適切に整理しています。
この記事にデザインパターンの定義、メリットとデメリットについて少し説明したいです。それにRailsによく使われているデザインパターンを紹介し、それぞれの例を検討します。
デザインパターンとは
デザインパターンは、アプリケーションを開発する際に使用した方がいいベストプラクティスの一部であり、読みやすさを向上させ、将来的にそれをより適切に維持できるようにします。デザインパターンに慣れていない場合は、レシピのようなものです。デザインパターンもよくある間違いを減らすのにも役立ちます。
デザインパターンは具体的なコードではなく、使用すべき概念とプラクティスにすぎないことを理解することが重要です。 この記事、または他のデザインパターンに関するガイドでは、アプリケーションにすぐにプラグインできるコードはありません。 その代わりに、各パターンの背後にある設計原理と、それが特定の問題をどのように解決するかを学びます。
Railsによく使われているデザインパターン
このセクションでは、Ruby on Railsアプリケーションで使用される最も一般的なデザインパターンと、各パターンのアーキテクチャの概要を示す簡単な使用例について説明します。
Service
Serviceパターンは、1つのタスクだけを実行するクラスの概念です。例えば、以下の例にはCheckoutServiceというクラスを作成して、決済の処理を対応します。
class ChargesController < ApplicationController
 def create
   CheckoutService.new(params).call
   redirect_to charges_path
 rescue Stripe::CardError => exception
   flash[:error] = exception.message
   redirect_to new_charge_path
 end
end
class CheckoutService
  DEFAULT_CURRENCY = "USD".freeze
  def initialize options = {}
    options.each_pair do |key, value|
      instance_variable_set "@#{key}", value
    end
  end
  def call
    Stripe::Charge.create charge_attributes
  end
  private
  attr_reader :email, :source, :amount, :description
  def currency
    @currency || DEFAULT_CURRENCY
  end
  def amount
    @amount.to_i * 100
  end
  def customer
    @customer ||= Stripe::Customer.create customer_attributes
  end
  def customer_attributes
    {
      email: email,
      source: source
    }
  end
  def charge_attributes
    {
      customer: customer.id,
      amount: amount,
      description: description,
      currency: currency
    }
  end
end
そうしたらControllerのコードは短く、わかりやすくなります。
Value Object
Value Objectパターンは、値のみを返すメソッドを提供する単純なクラスを作成することです。
class EmailValues
    def initialize(email)
        @email = email
    end
    
    def name
        email.match(/([^@]*)/).to_s
    end
    
    def domain
        email.split("@").last
    end
    
    def to_h
        { name: name, domain: domain }
    end
    
    private
    attr_reader :email
end
上記のクラスは、メールの値を解析し、それに関連するデータを返すクラスです。
Presenter
Presenterパターンは、RailsのView内で使用される複雑なロジックを分離するのことです。
Viewファイル:
<% presenter @post do |post_presenter| %>
 <% content_for :header do %>
   <%= post_presenter.meta_title %>
   <%= post_presenter.meta_description %>
   <%= post_presenter.og_type %>
   <%= post_presenter.og_title %>
   <%= post_presenter.og_description %>
   <%= post_presenter.og_image %>
 <% end %>
 <%= post_presenter.image%>
 <h1> <%= post_presenter.title %> </h1>
 <p>  <%= post_presenter.content %> </p>
 <%= post_presenter.author_name %>
 <p> <%= post_presenter.published_text %></p>
<% end %>
Presenterクラス:
class PostPresenter
  def initialize(post)
    @post = post
  end
  def published_text
    published ? '公開' : '非公開'
  end
  
  ... # 他のメソッド
end
Viewをできるだけ単純にして、ビジネスロジックをViewの中に入れないようにした方が良いです。そうするとコードが読みやすくなって、テストも簡単にできます。
Decorator
Decoratorパターンは、既存のオブジェクトに新しい機能を追加するために、新しい機能を含むの他のオブジェクトを作成します。 クラスのインスタンスを変更するだけで、既存のクラスに新しい静的サブクラスを定義する必要はありません。
class Post
    def content
        content
    end
end
class PostDecorator < SimpleDelegator
    def content
        modal.content.truncate(50)
    end
    def self.decorate(post)
        new(post)
    end
    private
    def model
        __getobj__
    end
end
@post = Post.first
puts @post.content # content
@post = PostDecorator.decorate(@post)
puts @post.content # content truncated
Policy Object
Policy Objectパターンは、検証ロジックをモデルから分離します。モデルはポリシーの検証実装を認識せず、ブール値のみを受け取ります。
class PostPolicy
    def initialize(post)
       @post = post
    end
    private
    attr_reader :post
    def has_email?
        post.email?
    end
    def has_author?
        post.author?
    end
end
class PostHandler
    def uploadPost(post)
        policy = PostPolicy.new(post)
        if (policy.has_email? && policy.has_author?)
            # DBに保存する
        else
            puts "エラーが発生しました。"
        end
    end
end
Query Object
Query Objectパターンは、コントローラーとモデルから再利用可能なクラスにクエリロジックを抽出できるようにするデザインパターンです。
class PopularVideoQuery
  def call(relation)
    relation.where(type: :video).where("view_count > ?", 100)
  end
end
class ArticlesController < ApplicationController
  def index
    relation = Article.accessible_by(current_ability)
    @articles = PopularVideoQuery.new.call(relation)
  end
end
# reuse
class Attachment < ActiveRecord::Base
  # t.string :type
  # t.integer :view_count
end
PopularVideoQuery.new.call(Attachment.all).to_sql
# "SELECT \"attachments\".* FROM \"attachments\" WHERE \"attachments\".\"type\" = 'video' AND (view_count > 100)"
PopularVideoQuery.new.call(Article.all).to_sql
# "SELECT \"articles\".* FROM \"articles\" WHERE \"articles\".\"type\" = 'video' AND (view_count > 100)"
終わりに
使用されているデザインパターンはたくさんありますので、この記事にすべてを書いていません。 デザインパターンは役に立つですが、注意して使用する必要があります。 それらを正しく使用しないと、コードに悪影響を及ぼし、複雑になりすぎて、開発時間が長くなり可能性があります。
参考