I have made a patch that demonstrates the problem, and similar another patch that demonstrates the workaround proposed by Matt Jones. You can find them here created with git format-patch -o 3-2-stable-ticket_7247 3-2-stable-ticket_7247^^ on my branch (based on 3-2-stable). The problem is also in master.

I now see that the original summary seems to be misleading, now the problem seems more to be that the destruction of a nested attribute is not within the transaction that is rolled back on the parent model (in case of failing validations)

When validation occurs, the records are only marked as deleted but have not been deleted yet

The validations calls #length on the association, which just returns the size of the Array (including the deleted records)

validation passes and the record is saved and the associated object is deleted, which leaves inconsistent data

I'll write a patch but I'm not sure where we should adress the issue.

patching length to ignore entries, which are marked as deleted

This would be an easy fix but it's a hack and will almost for sure lead to other problems.

lib/active_record/associations/collection_association.rb:269

deflength
load_target.size
end

making the validator aware of deleted records

the validator currently only calls #length on the association so it's hard to subtract the deleted records. Also it does not seem to be the job of the validator in activemodel to account for a feature in activerecord

Personally I would lean towards your proposal to modify #length. That is also what Elan Duran has come up with. But I do like you and Elan share your worries regarding consequences other places in code. As a positive consequence it could also reveal code that actually should also use the new length (with deleted records uncounted), hence it will fix bugs that was otherwise not discovered yet :-|

@jarl-dk I think you should "@mention" said people, so that they get notified.

my primary concern to patching #length is that it will break the Array API because if you access the array, the records are still inside. This means length does no longer say how many records are in the array.

Philosophically: When things are deleted (marked for deletion), is it then reasonable that they are still accessible from Array API? Maybe not, so that should maybe also be "fixed". And then the (Array) API would be consistent again.

A more in depth investigation: I did tried to add the extra test as mentioned in #9917 (comment) and such test fails which makes it inconsistent between the errors messages (claiming that "pets is too short (minimum is 1)") and owner.pets.size which returns 1

@jarl-dk I tried your test patch on latest master and the second version passes. The first one still have a bug because on pirate update bird is deleted so pirate becomes invalid and bird is gone from the DB.

@Bounga: I expect the whole test to pass. Just as it does with the workaround. With the workaround, the bird is never deleted from the database. That is how all other validations work. If the validation fails the changes are never persisted in the database. That is how it should work for length validation as well.