Reformの仕組み・使い方

# tl;dr

Rails界隈でダウンロード数が増えているらしいtrailblazerというgemだが
既存の記法とはかなり違った印象を受けてしまう。
しかし、Trailblazerに含まれるReformというgemだけで使うなら
そこまで既存の記法から離れることはない。
また、Reformの仕組みを知ることで、trailblazerの動きの理解の助けにもなる。
ただ、Rails,Reform,Trailblazerはかなり多機能なので色々飛ばしているところがあるが勘弁してほしい。

# Reformとは
Reformはいわゆる__Form Object__用のGemである。簡単に言えば、2つの役割がある

* わかりやすいデータ構造でhtmlのformを作成することができる。
* 保存時のパラメータの検証・Modelへの受け渡しを容易にする。

# 環境
* Rails5（おおよそバージョンに関係なく動くはず）
* trailblazer(ver.2)

# セットアップ
Gemfileに以下の記述を追加して、bundle install

```ruby
gem 'trailblazer'
gem 'trailblazer-rails'
```
本来ならreformだけでいいのだが、trailblazerに含まれる他のライブラリへの依存が怖いので全部入れておく。

# Model
説明用に適当な親子構造を持ったModelを作成
Albumが親で、AlbumとArtistが1対1、AlbumとSongが1対多になるように作成。
has_manyやnested_attributesも設定しておく

```bash
rails g model Album album_title:string
rails g model Artist full_name:string birthday:datetime album:references
rails g model Song title:string  album:references
```

# Reformクラス作成
`app/forms/album_form.rb`を作り編集する。

```ruby:app/forms/album_form.rb
class AlbumForm < Reform::Form
	property :album_title
	
	# has_oneならproperty
	property :artist do
		property :title
		# birthdayは入れない
	end
	
	# has_manyならcollection
	collection :songs, populate_if_empty: Song do
		property :title
		
		validates :title, unique: true
	end
end
```

# ControllerにReformインスタンス作成
Controllerを作ってReformのインスタンスを作ってみる。

```ruby
class AlbumController < ApplicationController
	def show
		album = Album.includes(:artist, :song).find(params[:id])
		@form = AlbumForm.new(album)
	end
end
```

この時大事なのは、Reformオブジェクト作成時に入れる引数は、先程作ったReformクラスのpropertyやcollectionの名前でメソッドとして呼べることで形式でないといけないということである。
雰囲気でHashが渡せる気がするが、そんなことはない。
Modelならもちろん問題ないし、OpenStructやStructでも引数に渡すことができる。

```ruby
# 駄目な例
album = {album_title: 'アルバム', artist: {full_name: '歌手の名前'}}

# 大丈夫な例
# Model
album = Album.new
@form = AlbumForm.new(album)

# StructやOpenStruct
album = OpenStruct.new(album_title: 'アルバム', artist: OpenStruct.new(full_name: '歌手の名前'))
@form = AlbumForm.new(album)
```

# HTMLでFormタグと中身を書く
HTMLを書いてみる

```erb
<%= form_for @form do |f|%>
	<%= f.text_field :album_title %>

	<%= fields_for @form.artist do |artist| %>
		<%= f.text_field :full_name %>
    <% end %>
    
    <% f.submit '送信' %>
<% end %>
```

普通のform_forと変わらない。
ただ、Reformクラスのおかげで、どういう構造になっているのかがわかりやすい。
ActiveRecordだけだとModelにカラムが書いてないので、どういう構造なのか分かりづらかったりする。
FormObjectを作れるGemには他にもActiveModelやVirtusがあるが、こちらのほうが見やすい。

# Reformでデータを保存する
updateアクションに前述のFormをsubmitして保存する。

```ruby
class ArtistController < ApplicationController
	...
	def update
		# 乱暴だが、全パラメータを許可する
		params.permit!
		
		# 更新する対象のデータを入れる。
		album = Album.includes(:artists, :song).find(params[:id])
		album_form = AlbumForm.new(album)
		
		# バリデーション
		if album_form.validate params[:album]
			# 保存
			album_form.save
		else
			album_form.errors
		end
	end
end
```

## Reformクラスのデータの持ち方・保存に関して

実は、Reformはデータの保持する領域は2つある。
1. album_form直下のpropertyやcollectionで指定されたキー。初期化時に使われた変数からコピーされる。
2. album_form.model以下。ここには初期化時に使われた変数がまるごとそのまま入る。保存時にも使われる。

コードで表すと以下のようになる

```ruby
album_form = AlbumForm.new(album)

# アクセスできる
album_form.album_title
album.artist.full_name

# propertyにないからアクセスできない
album_form.artist.birthday

# modelにはalbumがそのまま代入される
album_form.model.album_tile

# modelにはそのまま入っているので、birthdayにもアクセスできる
album_form.model.artist.birthday
```

`album_form.validates params[:artist]`を実行すると
1の領域にparams内の値が入る。

`album_form.save`を実行すると、まず内部で`album_form.sync`が実行される。
これは1の領域にあるデータを2の領域にコピーする。
そのあと、各Modelのsaveメソッドが走り、データが保存される。

```ruby
params[:album] = {title: タイトル}

alubm = Album.new(title: '仮')
album_form = AlbumForm.new(album)

# album.title => 仮
# album_form.model.title => 仮
# album_form.title => 仮

album_form.validates params[:album]

# album.title => 仮
# album_form.model.title => 仮
# album_form.title => タイトル

# save時に自動でsycnが呼ばれる。ここでは明示的に呼び出している。
album_form.sync 

# album.title => タイトル
# album_form.model.title => タイトル
# album_form.title => タイトル

album.save
```

このようにreformでは、スムーズにModelへとデータをコピーできる。
デフォルトのrailsだと単体のModelならparamsの値をModelへ反映させるのは簡単だが、
入れ子状になってたりして複雑なデータ構造の場合は難しい。
nested_attributesを使うと多少ましになるが、paramsを加工する場合、子Modelのカラム名指定時にattributesをつけ忘れたりして大変だったりする。

それに比べると、validates, saveとするだけでデータ保存されるのはかなり楽だ。

## Validationについて
特定のページにだけ必要なバリデーションというのはよくある話だ。
こういうものはModelに書くと冗長になりがちだ。

Reformは、それぞれのReformクラスで特有のバリデーションを持てるようになっている。
`validates :title, unique: true`というのがそれに該当する。
これはRailsのModelのバリデーションと同じ構文が書ける。

ただしcollectionに関しては、`property_if_empty: Class`を指定するのを忘れると、バリデーションが動かない。（確か前述の1のデータ領域にデータを入れるときに何のクラスを元にデータを入れればいいかわからなくてエラーを起こしたはず。）

また、`album_form.validates params[:artist]`でバリデーションエラーがある場合は、
`form.errors`にエラー内容が入る。
railsと同様に扱えるため、`form.errors.full_messages`でエラーメッセージ全文を取得できる。

# 終わりに
今回はreformについてざっくり紹介した。
大体のイメージを掴んでもらえたらうれしい。

ただし、あくまでざっくりなので、掴んだイメージを元に公式のドキュメント見ていたらければと思う
http://trailblazer.to/gems/reform/

Form Objectは近年機運が高めってきているので、なにか良さげなものを感じたらぜひ使ってみてほしい。