Edited at

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

More than 1 year has passed since last update.

解決までに時間がかかったので備忘録として残す。

以下のようなモデルで登録/更新処理をしたときに 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