MONGOID-5881 Persist empty array set via raw attribute write in before_save#6157
Merged
Conversation
…e_save When a before_save callback sets an embeds_many association to an empty array via raw attribute write (e.g. self[:labels] ||= []), the empty array was not being persisted. The proxy's update_attributes_hash method unconditionally deleted the association key from the parent attributes when the target was empty. This happened during lazy proxy initialization inside as_attributes (for inserts), which occurred after the callback had already written [] to the attributes hash. By the time the insert document was assembled, the key was gone. The fix: only delete the key when it is absent or nil. If it is already [], the caller explicitly set it to an empty array, and that intent must be preserved so the database receives the empty array.
Contributor
There was a problem hiding this comment.
Pull request overview
Fixes a regression where an embeds_many set to an empty array via a raw attribute write in a before_save callback could be dropped during inserts, causing [] not to be persisted.
Changes:
- Adjust
EmbedsMany::Proxy#update_attributes_hashto preserve an explicitly-set empty array in the parent’s attributes. - Add regression specs covering raw attribute writes in
before_savefor both inserts and updates.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| lib/mongoid/association/embedded/embeds_many/proxy.rb | Updates proxy attribute-hash synchronization to avoid deleting explicitly-set empty arrays. |
| spec/mongoid/association/embedded/embeds_many/proxy_spec.rb | Adds regression coverage for persisting [] when set via raw attribute write in a before_save callback. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
comandeo-mongo
approved these changes
Jun 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes MONGOID-5881: setting an
embeds_manyassociation to an empty array via a raw attribute write (e.g.self[:labels] ||= []) in abefore_savecallback was not being persisted to the database.Root cause
The regression was introduced by the MONGOID-4397 fix (commit
e08fff0e5, Mongoid 8.0+). That commit addedupdate_attributes_hashtoEmbedsMany::Proxy, which deletes the association key from the parent's attributes hash whenever the proxy target is empty:On insert,
as_attributesiterates over all embedded relations and callssend(:labels)to lazily initialize each proxy. When the proxy initializes with an empty target (from the[]the callback just wrote),update_attributes_hashdeletes the key.add_attributes_for_relationthen sees neither the key nor a non-blank relation, returns early, and the insert document is assembled withoutlabels: [].On update, the bug does not trigger because
generate_atomic_updates(which readschanges) runs before_descendants.each(which lazily initializes the proxy), so the dirty state is captured while the key is still present.Fix
In
update_attributes_hash, only delete the key when it is absent ornil. If the key is already[], a caller explicitly set it to an empty array and that intent must be preserved:Tests
Two new examples under
when trying to persist the empty list→when set via raw attribute write in a before_save callback:The existing
does not persist the empty listexample (covering the never-set case) continues to pass, confirming the fix does not cause spurious empty arrays to appear in documents that never had the association set.