Railsアプリケーションの開発において、ビジネスロジックの整理やコードの可読性を向上させるために「サービスクラス」と「フォームオブジェクト」がよく使われます。これらはそれぞれ異なる役割を持ち、適切に使用することでアプリケーションの構造をよりクリーンに保つことができます。
この記事では、サービスクラスとフォームオブジェクトの役割、利点、そして具体的な実装例を紹介します。
#サービスクラスとは?
サービスクラスは、特定のタスクやビジネスロジックを担当するクラスで、主にコントローラーやモデルに集中しがちな複雑なロジックを分離するために使用されます。サービスクラスを使うことで、コードの責務を明確にし、再利用可能でテストしやすい構造を作成できます。
サービスクラスの利点
- 単一責任の原則に従う: 各サービスクラスは1つのタスクを実行するため、コードの責任を明確にし、メンテナンスしやすくなります。
- ビジネスロジックの分離: コントローラーやモデルから複雑なビジネスロジックを分離し、コードの可読性を向上させます。
- テストのしやすさ: ビジネスロジックをサービスクラスに切り出すことで、単体テストが容易になります。
サービスクラスの実装例
以下は、ユーザーの登録を担当するサービスクラスの例です。
# app/services/user_registration_service.rb class UserRegistrationService def initialize(user_params) @user_params = user_params end def call user = User.new(@user_params) if user.save send_welcome_email(user) user else false end end private def send_welcome_email(user) UserMailer.welcome_email(user).deliver_later end end
使用方法:
result = UserRegistrationService.new(params[:user]).call if result flash[:notice] = "User registered successfully" else flash[:alert] = "Failed to register user" end
#フォームオブジェクトとは?
フォームオブジェクトは、複数のモデルにまたがるフォーム入力を扱うためのオブジェクトです。Railsのデフォルトでは、1つのフォームは1つのモデルに対応する形で設計されていますが、フォームオブジェクトを使うことで、複数のモデルを扱う複雑なフォームのバリデーションや処理を1つのクラスにカプセル化できます。
フォームオブジェクトの利点
- バリデーションの統合: 複数のモデルにまたがるバリデーションロジックを1つのオブジェクトに統合できます。
- フォームに特化したロジックを保持: コントローラーやモデルからフォーム特有のロジックを分離し、コードの責任を明確にします。
- テストのしやすさ: フォームオブジェクトを使うことで、フォームのバリデーションや保存ロジックを個別にテストできます。
フォームオブジェクトの実装例
以下は、ユーザーとそのプロフィール情報を同時に登録するためのフォームオブジェクトの例です。
# app/forms/user_registration_form.rb class UserRegistrationForm include ActiveModel::Model attr_accessor :name, :email, :password, :bio validates :name, :email, :password, presence: true validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } validates :password, length: { minimum: 6 } def save return false unless valid? ActiveRecord::Base.transaction do user = User.create!(name: name, email: email, password: password) Profile.create!(user: user, bio: bio) end true rescue ActiveRecord::RecordInvalid false end end
使用方法:
form = UserRegistrationForm.new(params[:user]) if form.save flash[:notice] = "User and profile created successfully" else flash[:alert] = "Failed to create user and profile" end
#サービスクラスとフォームオブジェクトの使い分け
サービスクラスを使うべき場合
- 単一のタスクやアクションを処理する場合: たとえば、ユーザーの登録やメール送信、データのインポート/エクスポートなど、明確な単一の責任を持つ場合。
- 複数のモデルや外部サービスに関与するロジックを整理したい場合: APIとのやり取り、データベースのクエリ、外部APIの呼び出しなど、複雑な処理が必要な場合。
フォームオブジェクトを使うべき場合
- 複数のモデルにまたがるフォーム入力を処理する場合: 例えば、ユーザーの登録と同時にプロフィール情報や関連するデータも一括で処理する場合。
- 特定のフォームに関連するロジックを整理したい場合: バリデーションやカスタムバリデーション、特定のデータ変換を行いたい場合。
サービスクラスとフォームオブジェクトを組み合わせる
サービスクラスとフォームオブジェクトを組み合わせることで、より柔軟で管理しやすいコードを書くことができます。フォームオブジェクトでフォームのバリデーションとデータ準備を行い、サービスクラスで実際のアクションを処理するというパターンが一般的です。
組み合わせの例
以下の例では、フォームオブジェクトでユーザーの入力をバリデーションし、サービスクラスを使ってデータベースへの保存やメール送信を行っています。
# app/forms/user_registration_form.rb class UserRegistrationForm include ActiveModel::Model attr_accessor :name, :email, :password, :bio validates :name, :email, :password, presence: true validates :email, format: { with: URI::MailTo::EMAIL_REGEXP } validates :password, length: { minimum: 6 } def submit return false unless valid? UserRegistrationService.new(self).call end end # app/services/user_registration_service.rb class UserRegistrationService def initialize(form) @form = form end def call ActiveRecord::Base.transaction do user = User.create!(name: @form.name, email: @form.email, password: @form.password) Profile.create!(user: user, bio: @form.bio) send_welcome_email(user) end true rescue ActiveRecord::RecordInvalid false end private def send_welcome_email(user) UserMailer.welcome_email(user).deliver_later end end
使用方法:
form = UserRegistrationForm.new(params[:user]) if form.submit flash[:notice] = "User and profile created successfully" else flash[:alert] = "Failed to create user and profile" end
#まとめ
- サービスクラスは、特定のタスクやアクションを実行するためのクラスで、複雑なビジネスロジックを整理するのに役立ちます。
- フォームオブジェクトは、複数のモデルにまたがるフォームの入力を処理するためのオブジェクトで、バリデーションやフォームに特化したロジックを持たせることができます。
- 両者を適切に使い分け、必要に応じて組み合わせることで、よりクリーンでメンテナンスしやすいコードを実現できます。アプリケーションの要件に応じて、どちらを使うか、または両方を使うかを判断しましょう。