中/大規模開発のためのRails設計パターン

  • 47
    いいね
  • 1
    コメント

概要

appディレクトリ以下のファイル構成は下記のようにします。

$ tree app/ -d -L 1
app
├── callbacks
├── controllers
├── decorators
├── exceptions
├── factories
├── models
├── services
├── tasks
├── utils
├── validators
├── values
└── views

クラス設計

Callback

クラス名の命名規則として、接尾辞にCallbackを付与します。
ActiveRecordのコールバック処理をまとめます。

  • after_find
  • after_initialize
  • before_validation
  • after_validation
  • before_save
  • before_create / before_update
  • after_create / after_update
  • after_save
  • after_commit
  • after_rollback
  • after_touch
  • before_destroy
  • after_destroy
app/callbacks/user_callback.rb
class UserCallback
  def before_create(user)
    user.name = default_name(user)
  end

  private

  def default_name(user)
    user.email.split('@').first
  end
end
app/models/user.rb
class User < ActiveRecord::Base
  before_create UserCallback.new
end

Controller

クラス名の命名規則として、接尾辞にControllerを付与します。
Controllerの仕事としては、

  • パラメーターやセッションの状態
  • Validation結果による処理の分岐
  • テンプレートの出し分け

に留めて、具体的なビジネスロジックについては
後述するService, Validator等に任せます。

Decorator

クラス名の命名規則として、接尾辞にDecoratorを付与します。
ModelViewの中間に位置し、ModelViewに実装されやすい表示ロジック/フォーマットといったプレゼンテーション層の責務を引き受けます。
Draperというgemを使用すると扱いやすいです。

app/decorators/article_decorator.rb
class ArticleDecorator < Draper::Decorator
  def published_at
    object.published_at.strftime("%A, %B %e")
  end
end
app/controllers/article_controller.rb
class ArticlesController < ApplicationController
  def index
    @articles = ArticlesDecorator.decorate(Article.all)
  end
end

Exception

クラス名の命名規則として、接尾辞にErrorを付与します。
例外を使う場合にraiseに文字列を渡すのではなく、
ここにStandardErrorを継承したカスタム例外クラスを実装して、それを使うようにします。

Factory

クラス名の命名規則として、接尾辞にFactoryを付与します。
Factoryでは主に以下の3種類の処理を行います。

  • 複数のオブジェクトから単一のデータオブジェクトの生成
  • データ構造を持たないデータからデータオブジェクトの生成
  • 共通のインターフェースを持つオブジェクトの透過的生成

Model

Modelには

  • association
  • scope
  • enum
  • データベースレベルでの制約(not null, unique等)に関するvalidation
  • 状態の取得、変更

に関するメソッドのみ記述するのが良いです。

Service

クラス名の命名規則として、接尾辞にServiceを付与します。
特に理由がなければ、基本的に「動詞(+目的語)+Service」という名前のクラス(例:CreateUserService)で、
callという名前のクラスメソッドのみを公開します(private methodは自由です)。
(参考 : Rails のサービスクラスでのマイルールとちょっとしたコツ
専門性の高い処理はここで実装します。
DBのトランザクションはこのレイヤーで管理するようにします。

Task

クラス名の命名規則として、接尾辞にTaskを付与します。
rake taskの実処理を記述します。
rakeファイルにはTaskへのパラメーター受け渡しのみを記述し、実際の処理はTaskに記述します。

Util

クラス名の命名規則として、接尾辞にUtilを付与します。
複数のクラスから共通で使われる汎用的な(=専門性の低い)処理をまとめます。

Validator

クラス名の命名規則として、接尾辞にValidatorを付与します。
独自バリデーションを実装する際にはここに置きます。

app/validators/user_validator.rb
class UserValidator < ActiveModel::Validator
  def validate(record)
    unless record.email.include? '@'
      record.errors[:email] << 'Should include @ in email'
    end
  end
end
app/models/user.rb
class User < ActiveRecord::Base
  include ActiveModel::Validations
  validates_with UserValidator
end

Value

オブジェクト指向で言うバリューオブジェクトをここに置きます。
DateURIなどが、Rubyの標準ライブラリのバリューオブジェクトになります。
特に下記の特性があるものをValueとして実装します。

  • 保持した値は決して変更されることがない
  • オブジェクトを識別する一意となる値を保持していない
  • 全プロパティ値が同じなら、同じオブジェクトである
  • ActiveRecordのプロパティに使用する

下記は、学生の成績を管理するバリューオブジェクトの例です。

app/values/rating.rb
class Rating
  include Comparable

  attr_accessor :score

  def initialize(score)
    @score = score
  end

  def <=>(other)
    @score - other.score
  end

  def to_s
    case @score
    when 0..59
      'F'
    when 60..69
      'C'
    when 70..79
      'B'
    when 80..89
      'A'
    else
      'A+'
    end
  end
end
app/models/student.rb
class Student < ActiveRecord::Base
  def rating
    @rating ||= Rating.new(score)
  end
end

View

いわゆるビューです。

依存関係

上記のクラス設計の依存関係を図にすると下記のようになります。
patterns.png

この投稿は Ruby on Rails Advent Calendar 201611日目の記事です。