Help us understand the problem. What is going on with this article?

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

概要

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
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
  • 状態の取得、変更

に関するメソッドのみ記述するのが良いです。
ビジネスロジックはここで実装してはいけません。

Policy

クラス名の命名規則として、接尾辞にPolicyを付与します。
Punditなどを使って、認可に関する処理をここで実装します。

app/policies/post_policy.rb
class PostPolicy < ApplicationPolicy
  def update?
    user.admin?
  end
end

Service

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

app/services/create_user_service.rb
class CreateUserService
  def call
    User.create!
  end
end

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

いわゆるビューです。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした