-
Type: New Feature
-
Resolution: Unresolved
-
Priority: Major - P3
-
None
-
Affects Version/s: None
-
Component/s: Persistence
-
None
-
Ruby Drivers
Currently, when updating embedded docs on a model, Mongoid will smartly do a "diff" of existing docs vs. target docs. The docs which were "added" in this diff will be set via the atomic $push operated, while removed docs will be removed with the $pull operator.
This is in general a very nice way to do things, because it means that multiple users updating the same object at the same time won't overwrite each other's embedded doc edits. It should remain the "default".
However, it has two drawbacks:
- It does not preserve ordering of docs. For example, suppose model X has existing embedded docs [A, B]. If I change its docs of [C, D, A] in Mongoid, then it will do $pull [B] and $push [C, D], leaving the final order as [A, C, D] rather than [C, D, A].
- It can lead to duplicate insertion of the same new doc, if you are not using object IDs when inserting.
Therefore, there should be a way that on a given update operation, you can force the embedded doc field to use a $set rather than a $push/$pull.
Importantly, I think this option should be a parameter to the `.save!` method, rather than a declarative option inside the model itself, because it is often controller-context dependent on how you save.
Currently I manually hack this in my code like this:
class Customer include Mongoid::Document embeds_many Phone attr_accessor :force_overwrite_phones def atomic_updates(_ = false) mods = super return unless force_overwrite_phones mods['$push']&.delete('phones') mods['$pull']&.delete('phones') mods[:conflicts]&.[]('$push')&.delete('phones') mods[:conflicts]&.[]('$pull')&.delete('phones') mods['$set'] ||= {} mods['$set']['phones'] = send(:phones).map(&:attributes) mods end end bob = Customer.first bob.phones += [Phone.new(number: '+12223334444')] bob.save! # does not overwrite bob.phones += [Phone.new(number: '+12225556666')] bob.force_overwrite_phones = true bob.save! # does overwrite