Rails4でFormオブジェクトを作る際に気をつける3つのポイント
ActiveRecord / Rails / Ruby
この記事はRuby on Rails Advent Calendar 2014の2日目です。
1日目は@miyukkiさんの「結局Ruby on RailsとPHPってどっちが優れてるの?」でした。おつかれさまでした。
Formオブジェクトとは
Formオブジェクトはその名の通り入力フォーム用のオブジェクトです。
フォームとモデルがうまく対応しているときはActiveRecordをそのまま使えば良いのですが、 複数モデルを作りたかったりモデルとは違うValidationを行いたかったりする場合にはFormオブジェクトを使うと便利です。
Formオブジェクトのサンプルコードはこんな感じになります。
class Blog::SiteForm
include Virtus.model
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attribute :title, String
attribute :description, String
#追加するvalidationがあればこのあたりに書く
def site
@site ||= Blog::Site.new(title: title, description: description)
end
def site=(site)
@site = site
self.title = site.title
self.description = site.description
@site
end
def persisted?
site.persisted?
end
def save(user)
site.user = user
valid? && site.save
end
def url
if persisted?
Rails.application.routes.url_helpers.blog_site_path(site.id)
else
Rails.application.routes.url_helpers.blog_sites_path
end
end
def valid?
result = super
unless site.valid?
key = :title
site.errors[key].each do |error|
errors.add(key, error)
end
return false
end
return result
end
endこの例ではBlog::SiteがActiveRecordモデルになっています。
では、以下で気をつけるポイントを紹介します。
form_forにFormオブジェクトを渡してもURLが生成されない
form_forにActiveRecordオブジェクトを渡してもちゃんとURLが生成されません。
これはform_forが受け取ったオブジェクトのクラス名を元にURLを生成しているからです。
例えばBlog::Siteオブジェクトを渡せばblog_sites_pathヘルパーを実行してくれるのですが、Blog::SiteFormオブジェクトを渡すとblog_site_forms_pathヘルパーを実行しようとして失敗してしまいます。
なので、次のようにURLを直接指定しましょう。
- form_for @site_form, url: @site_form.url do
...追記(2014/12/3)
@joker1007さんから「FormオブジェクトのURLの渡し方について」という記事で突っ込みをいただきました。
今回のような場合はpolymorphic_pathを使って
- form_for @site_form, url: polymorphic_path(@site_form.site) doとした方が良さそうです。こうすればurl, persisted?メソッドは不要になります。
合わせて
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validationsはinclude ActiveModel::Modelに置き換えられるという指摘もいただいたのですが、手元のコードだとcreateアクション実行時にエラーが出てしまったので、こちらはひとまずこのままにしておきます。原因特定したら別途追記します。← Virtus.modelを使う場合は単純にはActiveModel::Modelはincludeできないようです。
validate_uniqueness_ofが使えない
一意性制約を検証するにはデータベースのUNIQUE制約を利用する必要があります。
ただ、Formオブジェクトからはデータベース制約を直接扱えません。
なので、validate_uniqueness_ofはActiveRecordオブジェクト(今回の例だとBlog::Siteオブジェクト)で実行する必要があります。
具体的にはサンプルコードのようにFormオブジェクトのvalid?メソッドでActiveRecordオブジェクトのvalid?を呼び出せば良いかと思います。
errorsは統合する必要がある
2つ目のポイントで紹介したようにFormオブジェクト以外のモデルでvalidationを行った場合は気をつけることがもう1つあります。
通常validationに失敗するとモデルオブジェクトのerrorsにエラー情報が格納されるのですが、その情報はFormオブジェクトのerrorsには自動的には統合されません。
なので、Formオブジェクトのエラーとして扱うには各オブジェクトのerrorsをFormオブジェクトに統合する必要があります。
サンプルコードではvalid?メソッドでその処理をしています。
def valid?
result = super
unless site.valid?
key = :title
site.errors[key].each do |error|
errors.add(key, error)
end
return false
end
return result
endまとめ
Formオブジェクトは単なるクラスなので、自分でいろいろと面倒を見る必要がありますが うまく使うとコードがすっきりするのでチャンスがあれば是非活用してみてください。
3日目は@awakiaさんです。よろしくお願いします。