〜フォーム保存処理の責務が曖昧になるの厳しいって〜Railsで単体のモデルに依存しない保存処理を実装するためのFormオブジェクトパターンを実例付きで解説❗️

この記事は [ex-crowdworks Advent Calendar 2024](https://qiita.com/advent-calendar/2024/ex-cw)の19日目の記事です。

# はじめに
今年、株式会社クラウドワークスを退職した@nisyuuです。[News Connect](https://open.spotify.com/show/1EiRggj17tBgkoZHpv34Cn?si=3c8710e89f334c0d)をよく聞いています。
エンジニアとしてクラウドワークステック（旧クラウドテック）というフリーランスと企業をマッチングするエージェントサービスを開発していました。

Railsエンジニアの皆様は、フォームはどのように実装しているでしょうか？
単体のモデルに紐づく保存処理については、シンプルに実装できるかもしれませんが、複数モデルにまたがるような保存処理をするとバリデーションなどを含む処理の責務が曖昧になってしまいます。
このような場合、Formオブジェクトを使うことで責務を分離し、可読性を上げることができます。

# Formオブジェクトパターンとは

Formオブジェクトパターンは、Webアプリケーションのフォーム入力を処理する際に使用される設計パターンです。このパターンでは、フォームの入力データやそのバリデーション、データの保存処理などを専用のクラスに分離します。これにより、コントローラやモデルの役割が明確化し、コードの可読性と保守性が向上します。

## 特徴
- フォーム入力を専用のクラスに切り出す
- 複数のモデルにまたがる処理を一元管理
- バリデーションやビジネスロジックを整理

## 利用例
- ユーザー登録フォームで複数のモデルを扱う場合
- フォームが複雑でバリデーションが多岐にわたる場合

# Formオブジェクトパターンのメリットとデメリット

## メリット
- **コントローラの簡潔化**
  - コントローラのコードが短くなり、単純な処理に集中できる
- **複雑なフォームの管理**
  - 複数のモデルにまたがる処理を一つのクラスにまとめられる
- **テストの容易化**
  - ビジネスロジックやバリデーションをFormオブジェクトでテスト可能
- **再利用性の向上**
  - 共通するフォームロジックを一箇所に集約可能

## デメリット
- **コード量の増加**
  - 新たなクラスを作成するため、初期の実装コストが上がる
- **適用範囲の判断が難しい**
  - 単純なフォームでは過剰設計になる可能性がある
- **Railsの標準機能を超える学習コスト**
  - RailsのActiveRecordに慣れている開発者にとっては新しい概念の学習が必要

# RailsでのFormオブジェクトパターンの実装例

以下は、ユーザー登録フォームを例にFormオブジェクトを実装する方法です。このフォームでは、`User`モデルと`Profile`モデルのデータを一度に保存します。

## 1. Formオブジェクトの作成

まず、新しいクラスを作成します。

```ruby
# app/forms/user_registration_form.rb
class UserRegistrationForm
  include ActiveModel::Model

  attr_accessor :name, :email, :password, :profile_bio

  validates :name, presence: true
  validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
  validates :password, presence: true, length: { minimum: 6 }
  validates :profile_bio, presence: true

  def save
    return false unless valid?

    ActiveRecord::Base.transaction do
      user = User.create!(name: name, email: email, password: password)
      Profile.create!(user: user, bio: profile_bio)
    end
    true
  rescue ActiveRecord::RecordInvalid
    false
  end
end
```

## 2. コントローラでの使用

コントローラでFormオブジェクトを使用します。

```ruby
# app/controllers/users_controller.rb
class UsersController < ApplicationController
  def new
    @form = UserRegistrationForm.new
  end

  def create
    @form = UserRegistrationForm.new(user_registration_form_params)

    if @form.save
      redirect_to root_path, notice: 'ユーザー登録が完了しました'
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def user_registration_form_params
    params.require(:user_registration_form).permit(:name, :email, :password, :profile_bio)
  end
end
```

## 3. ビューの作成

フォームを作成します。

```erb
<!-- app/views/users/new.html.erb -->
<%= form_with model: @form, url: users_path do |f| %>
  <div>
    <%= f.label :name %>
    <%= f.text_field :name %>
  </div>

  <div>
    <%= f.label :email %>
    <%= f.email_field :email %>
  </div>

  <div>
    <%= f.label :password %>
    <%= f.password_field :password %>
  </div>

  <div>
    <%= f.label :profile_bio %>
    <%= f.text_area :profile_bio %>
  </div>

  <div>
    <%= f.submit '登録' %>
  </div>
<% end %>
```

# まとめ

Formオブジェクトパターンは、複雑なフォームロジックを管理しやすくする便利な設計パターンです。
複数のモデルを扱う場合や、バリデーションが複雑な場合に威力を発揮するのでフォーム実装の設計で検討してみると良いかもしれません。