はじめに
デザインパターンは、ソフトウェア開発の一般的な問題を解決するため、様々なプログラムで再利用できるソリューションのことです。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)"
終わりに
使用されているデザインパターンはたくさんありますので、この記事にすべてを書いていません。 デザインパターンは役に立つですが、注意して使用する必要があります。 それらを正しく使用しないと、コードに悪影響を及ぼし、複雑になりすぎて、開発時間が長くなり可能性があります。
参考