概要
app
ディレクトリ以下のファイル構成は下記のようにします。
$ tree app/ -d -L 1
app
├── callbacks
├── controllers
├── decorators
├── exceptions
├── factories
├── models
├── policies
├── 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
class UserCallback
def before_create(user)
user.name = default_name(user)
end
private
def default_name(user)
user.email.split('@').first
end
end
class User < ActiveRecord::Base
before_create UserCallback.new
end
Controller
クラス名の命名規則として、接尾辞にControllerを付与します。
Controllerの仕事としては、
- パラメーターやセッションの状態
- Validation結果による処理の分岐
- テンプレートの出し分け
に留めて、具体的なビジネスロジックについては
後述するService, Validator等に任せます。
Decorator
クラス名の命名規則として、接尾辞にDecoratorを付与します。
ModelとViewの中間に位置し、ModelやViewに実装されやすい表示ロジック/フォーマットといったプレゼンテーション層の責務を引き受けます。
Draperというgemを使用すると扱いやすいです。
class ArticleDecorator < Draper::Decorator
def published_at
object.published_at.strftime("%A, %B %e")
end
end
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
- 状態の取得、変更
に関するメソッドのみ記述するのが良いです。
ビジネスロジックはここで実装してはいけません。
Policy
クラス名の命名規則として、接尾辞にPolicyを付与します。
Punditなどを使って、認可に関する処理をここで実装します。
class PostPolicy < ApplicationPolicy
def update?
user.admin?
end
end
Service
クラス名の命名規則として、接尾辞にServiceを付与します。
特に理由がなければ、基本的に「動詞(+目的語)+Service」という名前のクラス(例:CreateUserService)で、
callという名前のクラスメソッドのみを公開します(private methodは自由です)。
(参考 : Rails のサービスクラスでのマイルールとちょっとしたコツ)
専門性の高い処理はここで実装します。
DBのトランザクションはこのレイヤーで管理するようにします。
class CreateUserService
def call
User.create!
end
end
Task
クラス名の命名規則として、接尾辞にTaskを付与します。
rake task
の実処理を記述します。
rakeファイルにはTaskへのパラメーター受け渡しのみを記述し、実際の処理はTaskに記述します。
Util
クラス名の命名規則として、接尾辞にUtilを付与します。
複数のクラスから共通で使われる汎用的な(=専門性の低い)処理をまとめます。
Validator
クラス名の命名規則として、接尾辞にValidatorを付与します。
独自バリデーションを実装する際にはここに置きます。
class UserValidator < ActiveModel::Validator
def validate(record)
unless record.email.include? '@'
record.errors[:email] << 'Should include @ in email'
end
end
end
class User < ActiveRecord::Base
include ActiveModel::Validations
validates_with UserValidator
end
Value
オブジェクト指向で言うバリューオブジェクトをここに置きます。
DateやURIなどが、Rubyの標準ライブラリのバリューオブジェクトになります。
特に下記の特性があるものをValueとして実装します。
- 保持した値は決して変更されることがない
- オブジェクトを識別する一意となる値を保持していない
- 全プロパティ値が同じなら、同じオブジェクトである
- ActiveRecordのプロパティに使用する
下記は、学生の成績を管理するバリューオブジェクトの例です。
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
class Student < ActiveRecord::Base
def rating
@rating ||= Rating.new(score)
end
end
View
いわゆるビューです。