LoginSignup
64
60

More than 5 years have passed since last update.

railsのnested attributesの一通りの使い方

Last updated at Posted at 2016-03-27

バージョン

rails 4.2.1

概要

rails のnested attributesは、上手く使えばコードがごっそり減る素晴らしい機能だ。
しかし、どうもドキュメントが断片的で、包括したイケてる解説ページが無い。
なので、書きました。
異常系の動きが独特なのでハマる。

ケーススタディ

※記憶を頼りに書いたので動作検証してません(ごめんなさい、余裕があったらやります)

ユーザー -1-(0..*)- 住所という関連があって、ユーザー情報を作成するコントローラーで住所も一緒に入力させます。

schema.rb
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

models
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
users_controller.rb
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
users/_form.html.slim
= 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

config/locales/ja.yml
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'も必要。ハマリポイント。

64
60
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
64
60