Skip to content
9 changes: 8 additions & 1 deletion lib/mongoid/association/nested/many.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class Many
# This attempts to perform 3 operations, either one of an update of
# the existing association, a replacement of the association with a new
# document, or a removal of the association.
#
# It raises an argument error if the attributes are not a Hash or an
# Array of key/value pairs.
#
# @example Build the nested attrs.
# many.build(person)
Expand All @@ -32,8 +35,12 @@ def build(parent, options = {})
attributes.each do |attrs|
if attrs.is_a?(::Hash)
process_attributes(parent, attrs.with_indifferent_access)
else
elsif attrs.is_a?(Array) && attrs.length > 1 && attrs[1].respond_to?(:with_indifferent_access)
process_attributes(parent, attrs[1].with_indifferent_access)
elsif attrs.is_a?(Array) && attrs.length.even?
process_attributes(parent, Hash[*attrs].with_indifferent_access)
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Hash[*attrs] assumes attrs is an array with an even number of elements (key-value pairs). If attrs has an odd number of elements, this will raise an ArgumentError. Add validation or rescue logic to handle malformed input gracefully.

Suggested change
process_attributes(parent, Hash[*attrs].with_indifferent_access)
if attrs.respond_to?(:to_a) && attrs.to_a.length.even?
process_attributes(parent, Hash[*attrs].with_indifferent_access)
else
# Malformed input: skip or handle as needed
# Optionally, log a warning here
next
end

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need more guards here in case it can't be cast to a hash?

Copy link
Contributor

@jamis jamis Dec 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's probably smart to check that array has an even number of elements, before creating a hash from it. I think an empty Hash is okay here -- I can imagine someone doing Person.new(children: {}) (thus creating a new person with a single child).

We should probably also raise an exception in the final else clause, so that we never have data being silently ignored. A simple ArgumentError would probably suffice, indicating that the arguments aren't supported or something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed - I didn't add any extra testing for this, I can't think of any scenarios where an uneven number of fields would be provided, but I agree that it's a good thing to check for.

else
raise ArgumentError, "Attributes for nested association '#{association.name}' must be a Hash or an Array of key/value pairs."
end
end
end
Expand Down
16 changes: 16 additions & 0 deletions spec/integration/associations/embeds_many_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -327,4 +327,20 @@ class Comment
end
end
end

context "when a hash is provided instead of an array for an embeds_many association" do
let(:post) { EmbedsManySpec::Post.new(title: 'Broken post', comments: { content: 'Comment' }) }

it "does not raise an error on initialization" do
expect { post }.to_not raise_error
end

it "does not raise an error when accessing the association" do
expect { post.comments }.to_not raise_error
end

it "allows building new documents on the association" do
expect(post.comments.build).to be_a EmbedsManySpec::Comment
end
end
end
Loading