38
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

はじめに

デザインパターンは、ソフトウェア開発の一般的な問題を解決するため、様々なプログラムで再利用できるソリューションのことです。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)"

終わりに

使用されているデザインパターンはたくさんありますので、この記事にすべてを書いていません。 デザインパターンは役に立つですが、注意して使用する必要があります。 それらを正しく使用しないと、コードに悪影響を及ぼし、複雑になりすぎて、開発時間が長くなり可能性があります。

参考

38
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?