2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-12-18

この記事は ex-crowdworks Advent Calendar 2024の19日目の記事です。

はじめに

今年、株式会社クラウドワークスを退職した@nisyuuです。News Connectをよく聞いています。
エンジニアとしてクラウドワークステック(旧クラウドテック)というフリーランスと企業をマッチングするエージェントサービスを開発していました。

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

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

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

特徴

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

利用例

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

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

メリット

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

デメリット

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

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

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

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

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

# 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オブジェクトを使用します。

# 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. ビューの作成

フォームを作成します。

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

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?