アットウィキロゴ

PORO(プレーン・オールド・ルビー・オブジェクト)


PORO(プレーン・オールド・ルビー・オブジェクト)とは

Rails 7〜8 時代のアプリ設計では、モデルやコントローラの肥大化を防ぐための中心的な概念として PORO が強く推奨されます。

PORO(Plain Old Ruby Object)とは、ActiveRecord などのフレームワーク機能に依存しない、ただの Ruby クラス のことです。

サービスクラス・フォームオブジェクト・クエリオブジェクトなど、Rails でビジネスロジックを整理するための代表的な手法がここに含まれます。


PORO を使う理由(2025 年時点)

● 1. Fat Model / Fat Controller を避けるため

Rails 3〜4 の頃はモデルに何でも詰め込む傾向でしたが、 Rails 7〜8 では モデルはデータの責務に限定 することが推奨されています。

● 2. 保守性とテスト性を上げるため

PORO は Rails に依存しないため:

  • 単体テストが書きやすい
  • 振る舞いを明確に分離できる
  • モジュール化しやすい

といったメリットがあります。

● 3. Turbo / Stimulus 時代の Rails では「薄いコントローラ」が前提

コントローラは「値の受け渡し」だけに集中し、 実処理は PORO に寄せる のが標準的な構成です。


主な PORO パターン

1. サービスクラス(Service Object)

複雑なビジネスロジックを 1 クラスにまとめる方式。 Rails 8 時代でも最もよく使われる PORO。

▼ PORO側

# app/services/post/create_service.rb
class Post::CreateService
  def initialize(user:, params:)
    @user = user
    @params = params
  end

  def call
    Post.create(@params.merge(user: @user))
  end
end

▼ コントローラ側

def create
  result = Post::CreateService.new(
    user: current_user,
    params: post_params
  ).call

  redirect_to result
end

2. フォームオブジェクト(Form Object)

複数モデルをまたぐフォームや、独自バリデーションをまとめたいときに利用します。

Rails 7〜8 では ActiveModel::Model を使う方式が定番。

▼ 例

# app/forms/user_signup_form.rb
class UserSignupForm
  include ActiveModel::Model

  attr_accessor :email, :password

  validates :email, presence: true
  validates :password, length: { minimum: 8 }

  def save
    return false unless valid?
    User.create(email: email, password: password)
  end
end

3. クエリオブジェクト(Query Object)

複雑な SQL や検索処理をモデルから切り離すパターン。

# app/queries/post_search_query.rb
class PostSearchQuery
  def initialize(keyword:)
    @keyword = keyword
  end

  def call
    Post.where("title LIKE ?", "%#{@keyword}%")
  end
end

4. Presenter / Decorator(表示専用クラス)

ビューでの表示ロジックをまとめる方式。 Rails 8 でも delegate を使った簡易 Presenter が一般的。

class PostPresenter
  def initialize(post)
    @post = post
  end

  def formatted_title
    "★ #{@post.title}"
  end
end

Rails 7〜8 で PORO が特に重要になった理由

● Turbo(Streams / Frames)で「薄いコントローラ」が必須に

非同期 UI が主流となり、 コントローラは役割を限定する必要性が増した。

● ActiveRecord コールバックの複雑化を避けるため

複雑な処理をコールバックに書くと、 Turbo との組み合わせでバグになりやすい。

→ その処理は PORO に逃がすのがベスト。

● テストのしやすさ

PORO は Rails に依存しないため System Spec や Request Spec を書かずとも 純粋な Ruby 単体テストで検証できる。


PORO のテスト例(RSpec)

require "rails_helper"

RSpec.describe Post::CreateService do
  it "投稿を作成できる" do
    user = create(:user)
    service = described_class.new(user: user, params: { title: "abc" })

    post = service.call

    expect(post.title).to eq("abc")
    expect(post.user).to eq(user)
  end
end

まとめ

Rails 8 時代の PORO は次の役割を担います。

  • Fat Model / Fat Controller を防ぐ中心的存在
  • Turbo 時代の「薄いコントローラ」に不可欠
  • テストが圧倒的に書きやすい
  • 代表的な種類は Service / Form / Query / Presenter
  • Rails 3〜4 時代よりも利用場面が増加
最終更新:2025年12月10日 07:39