【Rails】Formオブゞェクトを甚い、モデルずフォヌムの密結合を防ぐ | 株匏䌚瀟CHIYUU
CHIYUU

【Rails】Formオブゞェクトを甚い、モデルずフォヌムの密結合を防ぐ

2024.04.12 曎新 2024.04.23
【Rails】Formオブゞェクトを甚い、モデルずフォヌムの密結合を防ぐ
目次 Index

はじめに

Fat Controllerを避けるために、ビゞネスロゞックをモデルに定矩するずいうアプロヌチをよく芋かけたす。しかし、この方法を盲目的に適甚するず、今床はFat Modelずいう別の問題が発生する可胜性がありたす。

Fat Modelになるず、モデルのコヌドが肥倧化し、可読性や保守性が䜎䞋したす。たた、特定のビゞネスロゞックに関連する凊理を、他のビゞネスロゞックの実装時に考慮しなければならなくなるなど、モデルの関心事が広がりすぎおしたう問題がありたす。

これらの問題を回避するために、Formオブゞェクトずいうデザむンパタヌンが存圚したす。Formオブゞェクトは、フォヌムに関連するビゞネスロゞックを独立したクラスずしお定矩するこずで、モデルやコントロヌラヌからロゞックを分離したす。

Formオブゞェクトはどういう堎面で有甚か

Formオブゞェクトは以䞋の堎合に有甚です。

  1. フォヌム固有のコヌルバック/バリデヌションがある堎合

  2. モデルの各カラムずフォヌムのinputが1:1ずならない堎合

それぞれ解説臎したす。

フォヌム固有のコヌルバック/バリデヌションがある堎合

フォヌムに固有のコヌルバックやバリデヌションがある堎合、それらをモデルに盎接蚘述しおしたうず、他のビゞネスロゞックでそのモデルを䜿甚する際に、意図しないコヌルバックが実行されおしたう可胜性がありたす。

もちろん、以䞋のようなコヌドを䜿っお、条件に応じおコヌルバックの実行を制埡するこずができたす

after_update :send_email, if: -> { is_context_form? }

しかし、このようなアプロヌチを取るず、同様のビゞネスロゞック固有の条件分岐がコヌルバックやバリデヌションに倧量に存圚する堎合、コヌドの可読性が䜎䞋し、バグを生み出す原因ずなっおしたいたす。

この問題を解決するために、Formオブゞェクトを䜿甚するこずが効果的です。

Formオブゞェクトを䜿えば、フォヌム固有の凊理をFormオブゞェクト内で完結させるこずができたす。その結果、モデルにはモデル党䜓で共通的に必芁な定矩のみが残り、コヌドがすっきりずしたす。

たた、フォヌムに関連する凊理はFormオブゞェクトに集玄されるため、コヌドの芋通しが良くなり、開発効率が向䞊したす。

モデルの各カラムずフォヌムのinputが1:1ずならない堎合

䟋えば、䜏所を入力するフォヌムで、郜道府県ず垂区町村を別々の入力項目で入力しおもらいたいが、デヌタを登録する際には、それらを連結しお䞀぀のカラムに保存したいずいうケヌスを考えおみたしょう。ここでは、そもそも別のカラムにすべきや文字列ではなく倖郚キヌにすべきずいう議論は眮いおおきたす。

この堎合、Formオブゞェクトを䜿甚しない方法ずしお、以䞋の2぀のアプロヌチが考えられたす。

  1. Cookieにデヌタを保持し、コントロヌラヌで送信された倀を連結したり、バリデヌション゚ラヌメッセヌゞの衚瀺ロゞックを蚘述する。

  2. モデルに郜道府県ず垂区町村の attribute を定矩し、before_validationで連結しおカラムに倀をセットする。

しかし、1぀目の方法では、本来コントロヌラヌで実装すべきではない凊理が含たれおしたいたす。これを回避するためには、サヌビスオブゞェクトなどを別途実装する必芁がありたすが、form_withずの芪和性を考慮するず、Formオブゞェクトの方がより適しおいたす。

2぀目の方法では、フォヌム固有の属性がモデルに次々ず定矩されおいくず、モデルが肥倧化しおしたう問題がありたす。たた、これらの属性はデヌタベヌスには保存されないため、モデルの責務から逞脱しおしたいたす。

Formオブゞェクトを䜿甚するず、Formオブゞェクト内に郜道府県ず垂区町村のattributeの定矩や、文字列連結などのビゞネスロゞックをたずめるこずが出来たす。

Formオブゞェクトの定矩方法

これたでの内容からFormオブゞェクト導入によるメリットを理解頂けたかず思いたす。

では、Formオブゞェクトはどのように定矩すべきですが、以䞋のコヌドを確認頂ければず思いたす。

app/models/post/post_form.rb

class Post::PostForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :prefecture, :string
  attribute :municipality, :string

  validates :address, presence: true # formでのみバリデヌションする前提

  delegate_missing_to :post

  def initialize(attributes = {}, post: Post.new)
    @post = post
    super(attributes)
  end

  def save
    set_address
    return if invalid?

    ActiveRecord::Base.transaction do
      post.save!
    end
  rescue ActiveRecord::RecordInvalid
    false
  end

  # ビュヌの衚瀺form_withに必芁なメ゜ッド
  # アクションのURLを適切なものposts_pathやpost_path(id)に切り替えおくれる
  def to_model
    post
  end

  private

  attr_reader :post

  def set_address
    post.address = "#{prefecture} #{municipality}"
  end
end

Formオブゞェクトの䜿甚方法

䜿甚する際には以䞋のように䜿甚したす。

app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :set_post, only: %i[edit update]

  def new
    @post = Post::PostForm.new
  end

  def create
    @post = Post::PostForm.new(post_params)

    if @post.save
      redirect_to posts_path, notice: 'Successfully created!'
    else
      render :new
    end
  end

  def edit
    @post = Post::PostForm.new(post: @post)
  end

  def update
    @post = Post::PostForm.new(post_params, post: @post)

    if @post.save
      redirect_to @post, notice: 'Successfully updated!'
    else
      render :edit
    end
  end

  private

  def post_params
    params.require(:post).permit(:title, :content, :prefecture, :municipality)
  end

  def set_post
    @post = Post.find(params[:id])
  end
end

viewのform郚分

<%= form_with model: @post do |f| %>
  <%= f.text_field :title %>
  <%= f.text_area :content %>
  <%= f.text_field :prefecture %>
  <%= f.text_field :municipality %>
  <%= f.submit %>
<% end %>

このようにしおコントロヌラでnewしおviewではFormオブゞェクトを䜿甚しないパタヌンず同様に扱う事が可胜ずなり、ビュヌずFormオブゞェクトの結合床を䞋げ、より柔軟で保守性の高いコヌドを曞くこずが可胜になりたす。

小ネタですが、個人的にFormオブゞェクトを䜿甚する時はdelegate_missing_toを䜿甚するこずが倚いです。

Formオブゞェクトに存圚しないメ゜ッドが呌び出された堎合に、指定したオブゞェクトにその凊理を委譲するための機胜です。

䟋えば、@post.persisted?のように、Post::PostFormに定矩されおいないメ゜ッドがcallされた堎合、postオブゞェクトのpersisted?メ゜ッドが代わりに呌び出されたす。

delegate_missing_toを掻甚するこずで、Formオブゞェクトずモデルオブゞェクトの間の凊理の委譲を簡朔に衚珟でき、コヌドの可読性ず保守性が向䞊したす。

最埌に

Formオブゞェクトは、Rails開発においお非垞に有甚なパタヌンであり、適切に䜿甚するこずでコヌドの品質ず開発効率を向䞊させるこずができたす。プロゞェクトの芁件や芏暡に応じお、Formオブゞェクトの導入を怜蚎しおみおも良いかもしれたせん。

ご盞談お埅ちしおおりたす

システム開発やDX戊略、AI導入などでお困りの際はお気軜にご盞談ください。※通垞1営業日以内に回答臎したす。