FormObjectをうまく使いこなしたい

# はじめに

[Rails Advent Calendar 2020 16日目の記事](https://qiita.com/advent-calendar/2020/rails)です！

業務で毎日、Railsを触れていいコードが書けるように精進しています。

そんな中、今年一扱いに苦戦した（今も苦戦している）**FormObject**について、自分が考えることを共有しておきたいと思います！！


# FormObjectとは

そもそもFormObjectについて、「なにそれ？？？？」と思う方もいると思うでの簡単に紹介します！

簡単に言うと、**FormObject**は、Railsのデザインパターン**設計手法**の1つです。


## MVC

Railsの基本設計は、**MVC（Model, View, Controller）**です。

`View`は、ユーザーからの入力を受け付けます。その情報を`Controller`にわたし、`Controller`はその情報を整形・制御します。
そして、`Controller`は整形・制御した情報を`Model`に対して渡してあげます。
`Model`は、`Controller`から受け取った情報をもとにDBにアクセスし、データの保存・変更・削除を行い、
その結果を`Controller`や`View`に返します。
返ってきた結果は、`Controller`で整形・制御し`View`に表示させたり、制御・整形せずそのまま表示したりします。


これが基本的な`MVC`の流れですが、、、以下のようなことがあると思います。


1. 一つの`View`で複数の`Model`の属性を入力してもらい、DBを更新したい。
2. `Model`やDBの制限とは別に`View`側で制限をかけたい。
3. `Controller`での`View`から受け取った情報の整形が複雑化してしまって、見辛くなった。
...


これらの実装をMVCだけで行おうとすると、`Controller`か`Model`に責務が偏ってしまいます。
これでは、**コードが追いづらく**なったり、**影響範囲が大きく**なったりしてしまいます。

そうすると、**アプリの保守性が低いこと**になってしまうので、

新しい機能を追加したり、、、
修正したり、、、
DBの構造を変えたり、、

するときに**大変苦労な思いをすること**になってしまいます。


それらを防ぐための手法の一つとして、`FormObject`という概念が存在します。


## メリット・デメリット

`FormObject`を使用する一番の目的は、**責務を切り離すこと**だと思います。

`Controller`の肥大化の原因のひとつである、**`View`からの情報を整形するロジック**を`FormObject`でカプセル化する事で、
`Controller`の責務が一つ減ることになります。
これで`Controller`は、肥大化せずに済みますし、**単一責務**の状態になるので依存関係が少なくなり、**保守性は高く**なります。

また、**Modelの状態に関係ない**ロジックを`Model`にかくと、`Model`が肥大化してしまいます。そういったロジックを`FormObject`に書くことで、
`Model`の責務が一つ減り、肥大化を防ぐことができます。


このように、`Controller`や`Model`に偏りがちなロジックを`FormObject`にまとめることで、過剰責務にならずに済みます！！


ただ、上記のことはデメリットにもなり得ると思います。

やたら滅多に、`FormObject`にロジックを集約させると、今度は**FormObjectが肥大化してしまいます**。
これでは本末転倒です。。。
`Controller`と`Model`の`View`に関するロジックを`FromObject`に任せて、責務を切り離すことを目的に使用しているにもかかわらず、
`FormObject`の責務がでかくなってしまって、そこの保守性が低くなるのは良くありません。

そのため、使用する前はしっかりと**`FormObject`の責務を明確に**してから使用しましょう！


# FormObjectの責務とは？？？

`FormObject`がなんなのか大体わかってきました。

かなり便利な`FromObject`ですが、、、いろいろな記事をみて、実際に書いていく中で、ふと思ったことがあります。。。

> 「ここってFomObjectでやるべき？？？」
「ControllerやModelのロジックとかぶってない？？？」
「FormObjectの責務って、、、結局どこまで持てばいいの？？？」


これらを思ったきっかけを実際のコードを例に説明していきたいと思います。


## FormObjectのvalidate

FormObjectを使用するとき、`ActiveModel::Model`をincludeしてれば`Model`同様にvalidateを書けることができます。
例えば、記事の登録でタグ情報も入力するフォームがあったとします。

フォームでは、記事のタイトルと内容、タグの名前が必須であった場合、以下のようにかけます。

```ruby
class ArticlePostForm
  include ActiveModel::Model
  attr_accessor :article_body, :article_title, :tag_name

  validates :article_body, presence: true
  validates :article_title, presence: true
  validates :tag_name, presence: true
end
```

これでFormの入力に対して制限を設けることができました。ただ、記事とタグの`Model`にも以下のようなvalidateが設けてありました。

```ruby
class Article < ApplicationRecord
  validates :title, presence: true, uniqueness: true
  validates :body, presence: true
  validates :is_display, inclusion: [true, false]
end

class Tag < ApplicationRecord
  validates :name, presence: true, uniqueness: true
end
```

`FormObject`でのvalidateと`Model`でのvalidateでは、**意味は違います。**
`FormObject`でのvalidateでは**入力された値に対する制限**なのに対して、`Model`でのvalidateでは**DBに保存する際の制限**です。

ただ、今回では検証している属性が`FormObject`と`Model`でかぶってしまっているので、

**FormObjectのvalidateは必要なのか？**

という疑問があります。


## FormObjectでのsave・update

記事を見ているとよく見かけるものです。
以下のように`FormObject`でActiveRecordの作成を行う`save`メソッドがあります。

```ruby
class ConractForm
  include ActiveModel::Model
  attr_accessor :first_name, :last_name, :email, :body

  def save
    return false if invalid?
    
    contact = Contact.new(first_name: first_name, last_name: last_name, email: email, body: body)
    if contact.save
      true
    else
      errors.add(:base, contact.errors.full_messages)
      false
    end
  end
end
```

`Controller`では、以下のように書けるはずです。

```ruby
class ContactController < ApplicationController
  def new
    @contact = ContactForm.new
  end

  def create
    @contact = ContactForm.new(set_params)
    if @contact.save
      flash.now = 'お問い合わせを承りました'
      redirect_to :contacts
    else
      flash.alert = '送信に失敗しました'
      render :new
    end
  end

  private

  def set_params
    params.require(:contact).permit(:first_name, :last_name, :email, :body)
  end
end
```

ここで、`FormObject`は**`Model`の生成・更新の役割を持っていいのかどうか**に疑問を持ちました。
`FormObject`は、**入力に対してロジック**を持たせるのが一般的だと思います（間違っていたらすいません。。。）

ここで僕は`FomObject`に対しての責務で、

「Viewの入力に対しての制御のみで`Model`の生成は別」と「validateをかけているから`Model`の生成までがいい」

の2通りの考え方できると思ったので、どちらに寄せるべきなのかを迷いました。


## FormObjectのエラーハンドリング

`Model`の生成において、validateによって発生したエラーを`FormObject`で取り扱うかどうかがどうなのか疑問に思いました。

`Model`のエラーと`FormObject`のエラーは**分けたほうがいいのか、一緒にすべきなのか**が一番悩ましいところです。。。

（ここは僕もまだ考えがまとまっていないので、知見が増えたらまた共有します。）


```ruby
def save
    # ここはFormObjectの入力のエラー
    return false if invalid?
    
    # ここはModel生成時のエラー
    contact = Contact.new(first_name: first_name, last_name: last_name, email: email, body: body)
    if contact.save
      true
    else
      errors.add(:base, contact.errors.full_messages)
      false
    end
  end
```

# まとめ

`FormObject`に対しての責務は、その都度考え、**その責務以上のことをさせないように実装する**ことが大事だと思っています。

ただ、僕自身も`FormObject`を完璧に把握・理解しているわけではなく、他にも設計思想があるので、そこらへんと組み合わせた
`FormObject`の考えもあるのかなと感じています。

# 参考サイト

[Form Object という選択肢を検討してみる](https://www.fundely.co.jp/blog/tech/2020/04/08/180009/)

[【Rails】FormObjectを使ってほしい](https://qiita.com/9ro/items/10e3676741e09ffb98bb)

[Railsのデザインパターン: Formオブジェクト](https://product-development.io/posts/rails-design-pattern-form-objects)

[【Rails】Form Objectを使ってModelに依存しないFormを作成する](https://tech.mof-mof.co.jp/blog/rails-form-object/)