13
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Reformの仕組み・使い方

Last updated at Posted at 2018-04-19

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

gem 'trailblazer'
gem 'trailblazer-rails'

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

Model

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

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を作り編集する。

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のインスタンスを作ってみる。

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でも引数に渡すことができる。

# 駄目な例
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を書いてみる

<%= 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して保存する。

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以下。ここには初期化時に使われた変数がまるごとそのまま入る。保存時にも使われる。

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

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メソッドが走り、データが保存される。

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は近年機運が高めってきているので、なにか良さげなものを感じたらぜひ使ってみてほしい。

13
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?