diff --git a/lib/mongoid/association/embedded/embeds_many/proxy.rb b/lib/mongoid/association/embedded/embeds_many/proxy.rb index f794aa3962..90741f8602 100644 --- a/lib/mongoid/association/embedded/embeds_many/proxy.rb +++ b/lib/mongoid/association/embedded/embeds_many/proxy.rb @@ -601,7 +601,12 @@ def as_attributes # @api private def update_attributes_hash if _target.empty? - _base.attributes.delete(_association.store_as) + # Only remove the key if it is absent or nil. If it is already + # an empty array, the caller explicitly set it to [] (e.g. via + # a raw write_attribute call), and that intent must be preserved + # so the empty array is persisted to the database. + stored = _association.store_as + _base.attributes.delete(stored) unless _base.attributes[stored] == [] else _base.attributes.merge!(_association.store_as => _target.map(&:attributes)) end diff --git a/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb b/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb index 5b70621b8a..7f3386eb04 100644 --- a/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +++ b/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb @@ -4524,6 +4524,39 @@ class DNS::Record expect(reloaded_band).not_to have_key(:labels) end end + + context 'when set via raw attribute write in a before_save callback' do + after { Band.reset_callbacks(:save) } + + let(:reloaded_band) { Band.collection.find(_id: band._id).first } + + context 'on a new record' do + before { Band.before_save { self[:labels] ||= [] } } + + let(:band) { Band.create! } + + it 'persists the empty list' do + expect(reloaded_band).to have_key(:labels) + expect(reloaded_band[:labels]).to eq [] + end + end + + context 'on an existing record' do + # Reload the band from MongoDB so the labels proxy has not been + # initialized, which is the scenario that triggers the bug. + let(:band) { Band.find(Band.create!._id) } + + before do + Band.before_save { self[:labels] ||= [] } + band.save! + end + + it 'persists the empty list' do + expect(reloaded_band).to have_key(:labels) + expect(reloaded_band[:labels]).to eq [] + end + end + end end context 'when using assign_attributes with an already populated array' do