😕

Rails accepts_nested_attributes_forは変更の無いレコードをバリデーションしないようだ。

に公開

ネステッドアットリビュートを使ったフォームの実装していました。
変更が無い子レコードにはバリデーションしておらず驚きました。

# frozen_string_literal: true

require "bundler/inline"

gemfile(true) do
  source "https://rubygems.org"

  git_source(:github) { |repo| "https://github.com/#{repo}.git" }

  gem "activerecord", "~> 7.0.8"
  gem "sqlite3", "~> 1.4"
  gem "enumerize"
  gem 'pry', '~> 0.14.2'
end

require "active_record"
require "minitest/autorun"
require "logger"
require "pry"
require "enumerize"

ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: ":memory:")
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
    t.string :status
  end

  create_table :comments, force: true do |t|
    t.integer :post_id
    t.string :name
  end
end

class Post < ActiveRecord::Base
  extend Enumerize
  enumerize :status, in: %i(draft publish), scope: true, predicates: { prefix: true }

  has_many :comments
  accepts_nested_attributes_for :comments, allow_destroy: true

  validates :status, presence: true
  validates :comments, length: { minimum: 1 }, unless: :status_draft?
end

class Comment < ActiveRecord::Base
  belongs_to :post
  validates :name, presence: true, if: -> { post_id? && !post.status_draft? }
end

class BugTest < Minitest::Test
  def test_association_stuff
    post = Post.create!(status: :draft, comments_attributes: [{ name: nil }, { name: nil }])
    assert_equal 2, post.comments.count
    assert_equal true, post.comments.first.valid?
    assert_equal true, post.comments.second.valid?

    post.update!(status: :publish, comments_attributes: [{ id: 1, name: '太郎' }])
    assert_equal true, post.comments.first.valid?
    assert_equal false, post.comments.second.valid?
  end
end

◆ 調べたメモ
callerで呼び出しているメソッドを遡りました。
activemodel-7.2.2.1/lib/active_model/validations/presence.rb:7
activerecord-7.2.2.1/lib/active_record/validations/presence.rb:10
activemodel-7.2.2.1/lib/active_model/validator.rb:155
activemodel-7.2.2.1/lib/active_model/validator.rb:151

activemodel-7.2.2.1/lib/active_model/validator.rb:151に、
以下のようにbinding.pryを置いて渡ってくるレコードを確認しました。
binding.pry if record.class.table_name == "comments" && self.kind == :presence && self.attributes == [:name]

変更のないレコードが渡ってきていない == バリデーションされていないよう。

Discussion