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