LoginSignup
13
13

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
13
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
13