Elaborating on Lam Phan's answer, validation errors for each child are accessible as a property of the child itself.
foo = Foo.create(bars_attributes: [{title: nil},{ title: "foo" }, {title: nil}])
=> #<Foo:0x0000559761e83c50 id: nil, created_at: nil, updated_at: nil>
foo.bars.map(&:errors).map(&:details)
=> [{:title=>[{:error=>:blank}]}, {}, {:title=>[{:error=>:blank}]}]
foo.bars.map(&:errors).map(&:messages)
=> [{:title=>["can't be blank"]}, {}, {:title=>["can't be blank"]}]
On a different, arguably less elegant path, with a custom method for validation, it's possible to cycle on the received nested records, adding an error that registers the index of the failing ones.
class Foo < ApplicationRecord
has_many :bars
accepts_nested_attributes_for :bars
validate :bars_validator
def bars_validator
children_valid = bars.map(&:valid?).all?
bars.each_with_index do |b,i|
b.errors.each do |err|
msg = "#{i}: #{err.attribute} #{err.message}"
errors.add(:bars, msg, index: i, src: err)
end
end
children_valid
end
end
The index is then retrieved through errors.details
foo = Foo.create(bars_attributes: [{ title: nil }, { title: "foo" }, { title: nil }])
=> #<Foo:0x00005583f6607a50 id: nil, created_at: nil, updated_at: nil>
foo.errors.details
=>
{:"bars.title"=>[{:error=>:blank}],
:bars=>
[{:error=>"0: title can't be blank", :index=>0, :src=>#<ActiveModel::Error attribute=title, type=blank, options={}>},
{:error=>"2: title can't be blank", :index=>2, :src=>#<ActiveModel::Error attribute=title, type=blank, options={}>}]}