Skip to content

Commit

Permalink
Backport MONGOID-5352 to 7.5-stable (#5646)
Browse files Browse the repository at this point in the history
* Backport MONGOID-5352 to 7.4-stable (#5286)

* MONGOID-5352 assign_attributes is working incorrectly since 7.3.4 (#5274)

* MONGOID-5352 add fix for this issue, other tests failing

* MONGOID-5352 fix two tests, two to go

* MONGOID-5352 fix all tests

* MONGOID-5352 fix some tests

* MONGOID-5352 don't set caches before setting association

* MONGOID-5352 rework solution

* Update lib/mongoid/association/embedded/batchable.rb

* MONGOID-5352 answer comments

* MONGOID-5352 update comment

* Add relevant changes from MONGOID-4869

* fix test

* remove test for remvoed code

* Fix mongoid_spec.rb:55 expectation of renamed disconnect!/close in 'mongo' gem

---------

Co-authored-by: Neil Shweky <[email protected]>
  • Loading branch information
jasonpenny and Neilshweky authored Jun 27, 2023
1 parent 61eda97 commit 4fb3901
Show file tree
Hide file tree
Showing 5 changed files with 188 additions and 4 deletions.
23 changes: 20 additions & 3 deletions lib/mongoid/association/embedded/batchable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ def batch_remove(docs, method = :delete)
def batch_replace(docs)
if docs.blank?
if _assigning? && !empty?
_base.delayed_atomic_sets.clear
_base.delayed_atomic_sets.delete(path)
clear_atomic_path_cache
_base.add_atomic_unset(first)
target_duplicate = _target.dup
pre_process_batch_remove(target_duplicate, :delete)
Expand All @@ -92,7 +93,8 @@ def batch_replace(docs)
_base.delayed_atomic_sets.clear unless _assigning?
docs = normalize_docs(docs).compact
_target.clear and _unscoped.clear
_base.delayed_atomic_unsets.clear
_base.delayed_atomic_unsets.delete(path)
clear_atomic_path_cache
inserts = execute_batch_set(docs)
add_atomic_sets(inserts)
end
Expand Down Expand Up @@ -234,7 +236,22 @@ def normalize_docs(docs)
#
# @return [ String ] The atomic path.
def path
@path ||= _unscoped.first.atomic_path
@path ||= if _unscoped.empty?
Mongoid::Atomic::Paths::Embedded::Many.position_without_document(_base, _association)
else
_unscoped.first.atomic_path
end
end

# Clear the cache for path and atomic_paths. This method is used when
# the path method is used, and the association has not been set on the
# document yet, which can cause path and atomic_paths to be calculated
# incorrectly later.
#
# @api private
def clear_atomic_path_cache
self.path = nil
_base.instance_variable_set("@atomic_paths", nil)
end

# Set the atomic path.
Expand Down
19 changes: 19 additions & 0 deletions lib/mongoid/atomic/paths/embedded/many.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ def position
locator = document.new_record? ? "" : ".#{document._index}"
"#{pos}#{"." unless pos.blank?}#{document._association.store_as}#{locator}"
end

class << self

# Get the position of where the document would go for the given
# association. The use case for this function is when trying to
# persist an empty list for an embedded association. All of the
# existing functions for getting the position to store a document
# require passing in a document to store, which we don't have when
# trying to store the empty list.
#
# @param [ Document ] parent The parent document to store in.
# @param [ Association ] association The association.
#
# @return [ String ] The position string.
def position_without_document(parent, association)
pos = parent.atomic_position
"#{pos}#{"." unless pos.blank?}#{association.store_as}"
end
end
end
end
end
Expand Down
21 changes: 21 additions & 0 deletions spec/mongoid/association/embedded/embeds_many/proxy_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true

require "spec_helper"
require_relative '../embeds_many_models.rb'

describe Mongoid::Association::Embedded::EmbedsMany::Proxy do

Expand Down Expand Up @@ -4649,4 +4650,24 @@ class DNS::Record
end
end
end

context "when using assign_attributes with an already populated array" do
let(:post) { EmmPost.create! }

before do
post.assign_attributes(company_tags: [{id: BSON::ObjectId.new, title: 'a'}],
user_tags: [{id: BSON::ObjectId.new, title: 'b'}])
post.save!
post.reload
post.assign_attributes(company_tags: [{id: BSON::ObjectId.new, title: 'c'}],
user_tags: [])
post.save!
post.reload
end

it "has the correct embedded documents" do
expect(post.company_tags.length).to eq(1)
expect(post.company_tags.first.title).to eq("c")
end
end
end
121 changes: 121 additions & 0 deletions spec/mongoid/association/embedded/embeds_many_models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,124 @@ class EmmOuter

field :level, :type => Integer
end

class EmmCustomerAddress
include Mongoid::Document

embedded_in :addressable, polymorphic: true, inverse_of: :work_address
end

class EmmFriend
include Mongoid::Document

embedded_in :befriendable, polymorphic: true
end

class EmmCustomer
include Mongoid::Document

embeds_one :home_address, class_name: 'EmmCustomerAddress', as: :addressable
embeds_one :work_address, class_name: 'EmmCustomerAddress', as: :addressable

embeds_many :close_friends, class_name: 'EmmFriend', as: :befriendable
embeds_many :acquaintances, class_name: 'EmmFriend', as: :befriendable
end

class EmmUser
include Mongoid::Document
include Mongoid::Timestamps

embeds_many :orders, class_name: 'EmmOrder'
end

class EmmOrder
include Mongoid::Document

field :amount, type: Integer

embedded_in :user, class_name: 'EmmUser'
end

module EmmSpec
# There is also a top-level Car class defined.
class Car
include Mongoid::Document

embeds_many :doors
end

class Door
include Mongoid::Document

embedded_in :car
end

class Tank
include Mongoid::Document

embeds_many :guns
embeds_many :emm_turrets
# This association references a model that is not in our module,
# and it does not define class_name hence Mongoid will not be able to
# figure out the inverse for this association.
embeds_many :emm_hatches

# class_name is intentionally unqualified, references a class in the
# same module. Rails permits class_name to be unqualified like this.
embeds_many :launchers, class_name: 'Launcher'
end

class Gun
include Mongoid::Document

embedded_in :tank
end

class Launcher
include Mongoid::Document

# class_name is intentionally unqualified.
embedded_in :tank, class_name: 'Tank'
end
end

# This is intentionally on top level.
class EmmTurret
include Mongoid::Document

embedded_in :tank, class_name: 'EmmSpec::Tank'
end

# This is intentionally on top level.
class EmmHatch
include Mongoid::Document

# No :class_name option on this association intentionally.
embedded_in :tank
end

class EmmPost
include Mongoid::Document

embeds_many :company_tags, class_name: "EmmCompanyTag"
embeds_many :user_tags, class_name: "EmmUserTag"
end


class EmmCompanyTag
include Mongoid::Document

field :title, type: String

embedded_in :post, class_name: "EmmPost"
end


class EmmUserTag
include Mongoid::Document

field :title, type: String

embedded_in :post, class_name: "EmmPost"
end

8 changes: 7 additions & 1 deletion spec/mongoid_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,14 @@
end

it "disconnects from all active clients" do
method_name = if Gem::Version.new(Mongo::VERSION) >= Gem::Version.new('2.19.0')
:close
else
:disconnect!
end

clients.each do |client|
expect(client.cluster).to receive(:disconnect!).and_call_original
expect(client.cluster).to receive(method_name).and_call_original
end
Mongoid.disconnect_clients
end
Expand Down

0 comments on commit 4fb3901

Please sign in to comment.