LoginSignup
8
3

More than 5 years have passed since last update.

Rails の Virtual Attributes と Nested Attributes の組み合わせでハマった話

Last updated at Posted at 2018-03-06

解決までに時間がかかったので備忘録として残す。
以下のようなモデルで登録/更新処理をしたときに User モデルの before_validation が呼ばれない事があって、原因がわからずハマった。

app/models/team.rb
class Team < ApplicationRecord
  has_many :users

  accepts_nested_attributes_for :users, allow_destroy: true
end
app/models/user.rb
class User < ApplicationRecord
  belongs_to :team

  attr_writer :first_name, :last_name

  before_validation :set_name

  private

  def set_name
    self.name = "#{@last_name} #{@first_name}"
  end
end

ちなみにコントローラーの実装は以下のような感じ。

app/controllers/teams_controller.rb
class TeamsController < ApplicationController
  # ...

  def create
    @team = Team.new(team_params)

    return render(:new) if @team.invalid?

    @team.save!
  end

  def update
    @team = Team.find(params[:id])
    @team.attributes = team_params

    return render(:edit) if @team.invalid?

    @team.save!
  end

  private

  def team_params
    params
      .require(:team)
      .permit(:name, :logo, users_attributes: [:id, :_destroy, :first_name, :last_name, :gender, :birthday])
  end
end

結論としては、Virtual Attributes の変更はモデルの changed? で検知ができないため、Virtual Attributes のみ更新した時に changed? == false となってバリデーションが実行されないことが原因。
以下のように validates_associated を親モデルに追加すると指定した子モデルで常にバリデーションが実行される。

app/models/team.rb
class Team < ApplicationRecord
  has_many :users

  accepts_nested_attributes_for :users, allow_destroy: true

  # 追加
  validates_associated :users
end
8
3
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
8
3