バージョン
rails 4.2.1
概要
rails のnested attributesは、上手く使えばコードがごっそり減る素晴らしい機能だ。
しかし、どうもドキュメントが断片的で、包括したイケてる解説ページが無い。
なので、書きました。
異常系の動きが独特なのでハマる。
ケーススタディ
※記憶を頼りに書いたので動作検証してません(ごめんなさい、余裕があったらやります)
ユーザー -1-(0..*)- 住所という関連があって、ユーザー情報を作成するコントローラーで住所も一緒に入力させます。
ActiveRecord::Schema.define(version: 20160327072326) do
create_table "addresses", force: :cascade do |t|
t.string "postal_code", limit: 255
t.integer "user_id", limit: 4
end
create_table "users", force: :cascade do |t|
end
end
class User < ActiveRecord::Base
has_many :addreses, dependent: :destroy, inverse_of: :user
accepts_nested_attributes_for :addresses, allow_destroy: true, reject_if: :all_blank
end
class Address < ActiveRecord::Base
belongs_to :user, inverse_of: :address
validates :postal_code, format: { with: /¥A¥d{3}-?¥d{4}¥z/ }
end
class UsersController < ApplicatonController
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
redirect_to root_path, notice: 'user created'
else
render :new
end
end
private
def user_params
params.require(:user).permit(
addresses_attributes: [
:_destroy,
:id,
:postal_code,
],
)
end
end
= form_for @user do |f|
- if @user.errors.any?
h2 = "#{pluralize(@user.errors.count, "error")} prohibited this user from being saved:"
ul
- @user.errors.full_messages.each do |message|
li = message
div
= f.label :addresses
ol
= f.fields_for :addresses, Address.new do |addresses_f|
li
table
tr
th = addresses_f.label :postal_code
td = addresses_f.text_field :postal_code
ja:
activerecord:
attributes:
address: &address
postal_code: 郵便番号
user:
user/address: *address
validation errorについて
postal_codeに 'hogehoge'など入力したとき、以下のようになる。
@user.errors[:addresses] #=> []
@user.errors['addresses.postal_code'] #=> [ 'は不正な値です' ]
@user.addresses.last.errors[:postal_code] #=> [ 'は不正な値です' ]
どうやら、小モデルのerrorsを集積して、同種のerrorはuniqして、ということを自動でやってくれてる模様。
field_with_errorsもちゃんと付けてくれる。最強!
小モデルのerrorsを集積してくれないことがある
どういう条件(多分何かしらの設定不備)で、やってくれたりやってくれなかったりするのかわからないのですが、たまにあります。
その場合は、以下のようになる
@user.errors[:addresses] #=> [ 'は不正な値です' ]
@user.errors['addresses.postal_code'] #=> []
@user.addresses.last.errors[:postal_code] #=> [ 'は不正な値です' ]
これだと、viewで子のerrorsを拾ってuniqして表示…とやらなければいけなくなって大変。何にミスったらこうなるのか知りたい。
localeの設定
nested attributesの場合は 'parent/child'というキーに定義すれば良いということが書いてある
yamlのアンカーとエイリアス
'user/address'の情報と'address'の情報を2重管理するのが嫌な場合はyamlの機能を使ってまとめよう。
周辺知識
inverse_of
has_manyのオプションでこれを適切に指定すると、例えば
- userA
- addressA
- addressB
というとき、 addressAから見たuserAとaddressBから見たuserAが同一のインスタンスに見えてくれるらしい。
strong parameterで_destroyだけでなくidも許可する理由
accepts_nested_attributes_forのallow_destroy: trueためのもの。
'_destroy'が必要なのは見れば分かるが、実は'id'も必要。ハマリポイント。