Skip to content

MONGOID-5881 Persist empty array set via raw attribute write in before_save#6157

Merged
jamis merged 1 commit into
mongodb:masterfrom
jamis:5881-save-empty-array
Jun 15, 2026
Merged

MONGOID-5881 Persist empty array set via raw attribute write in before_save#6157
jamis merged 1 commit into
mongodb:masterfrom
jamis:5881-save-empty-array

Conversation

@jamis

@jamis jamis commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description

Fixes MONGOID-5881: setting an embeds_many association to an empty array via a raw attribute write (e.g. self[:labels] ||= []) in a before_save callback 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 added update_attributes_hash to EmbedsMany::Proxy, which deletes the association key from the parent's attributes hash whenever the proxy target is empty:

def update_attributes_hash
  if _target.empty?
    _base.attributes.delete(_association.store_as)  # ← the problem
  else
    _base.attributes.merge!(...)
  end
end

On insert, as_attributes iterates over all embedded relations and calls send(:labels) to lazily initialize each proxy. When the proxy initializes with an empty target (from the [] the callback just wrote), update_attributes_hash deletes the key. add_attributes_for_relation then sees neither the key nor a non-blank relation, returns early, and the insert document is assembled without labels: [].

On update, the bug does not trigger because generate_atomic_updates (which reads changes) 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 or nil. If the key is already [], a caller explicitly set it to an empty array and that intent must be preserved:

_base.attributes.delete(stored) unless _base.attributes[stored] == []

Tests

Two new examples under when trying to persist the empty listwhen set via raw attribute write in a before_save callback:

  • on a new record — the previously failing INSERT case
  • on an existing record — the UPDATE case with a freshly-loaded document (proxy uninitialized at save time)

The existing does not persist the empty list example (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.

…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.
@jamis jamis requested a review from a team as a code owner June 11, 2026 16:23
@jamis jamis requested review from comandeo-mongo and Copilot June 11, 2026 16:23
@jamis jamis added the bug Fixes a bug, with no new features or broken compatibility label Jun 11, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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_hash to preserve an explicitly-set empty array in the parent’s attributes.
  • Add regression specs covering raw attribute writes in before_save for 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.

Comment thread lib/mongoid/association/embedded/embeds_many/proxy.rb
@jamis jamis merged commit 5f81f30 into mongodb:master Jun 15, 2026
76 checks passed
@jamis jamis deleted the 5881-save-empty-array branch June 15, 2026 16:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Fixes a bug, with no new features or broken compatibility

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants