From 702bcf88b5952bd3f731a0c3943f785feb7d287c Mon Sep 17 00:00:00 2001 From: Mike Saffitz Date: Thu, 18 Aug 2022 15:30:43 -0700 Subject: [PATCH 01/13] Patch https://github.com/mongodb/mongoid/pull/4144 --- lib/mongoid/contextual/memory.rb | 5 +- lib/mongoid/document.rb | 1 - lib/mongoid/persistable.rb | 3 +- lib/mongoid/persistable/creatable.rb | 2 +- lib/mongoid/persistable/deletable.rb | 2 +- lib/mongoid/persistable/updatable.rb | 4 +- lib/mongoid/positional.rb | 71 ------- lib/mongoid/relations/embedded/batchable.rb | 13 +- lib/mongoid/relations/touchable.rb | 2 +- spec/mongoid/positional_spec.rb | 222 -------------------- spec/mongoid/relations/embedded/one_spec.rb | 2 +- 11 files changed, 11 insertions(+), 316 deletions(-) delete mode 100644 lib/mongoid/positional.rb delete mode 100644 spec/mongoid/positional_spec.rb diff --git a/lib/mongoid/contextual/memory.rb b/lib/mongoid/contextual/memory.rb index 835152cdc2..023faae7ce 100644 --- a/lib/mongoid/contextual/memory.rb +++ b/lib/mongoid/contextual/memory.rb @@ -9,7 +9,6 @@ class Memory include Aggregable::Memory include Relations::Eager include Queryable - include Positional # @attribute [r] root The root document. # @attribute [r] path The atomic path. @@ -47,9 +46,7 @@ def delete doc.as_document end unless removed.empty? - collection.find(selector).update( - positionally(selector, "$pullAll" => { path => removed }) - ) + collection.find(selector).update("$pullAll" => { path => removed }) end deleted end diff --git a/lib/mongoid/document.rb b/lib/mongoid/document.rb index 09d4b0db2b..e7a6a0d327 100644 --- a/lib/mongoid/document.rb +++ b/lib/mongoid/document.rb @@ -1,5 +1,4 @@ # encoding: utf-8 -require "mongoid/positional" require "mongoid/evolvable" require "mongoid/extensions" require "mongoid/errors" diff --git a/lib/mongoid/persistable.rb b/lib/mongoid/persistable.rb index a846b87025..adfa651a20 100644 --- a/lib/mongoid/persistable.rb +++ b/lib/mongoid/persistable.rb @@ -27,7 +27,6 @@ module Persistable include Incrementable include Logical include Poppable - include Positional include Pullable include Pushable include Renamable @@ -209,7 +208,7 @@ def persist_or_delay_atomic_operation(operation) def persist_atomic_operations(operations) if persisted? selector = atomic_selector - _root.collection.find(selector).update(positionally(selector, operations)) + _root.collection.find(selector).update(operations) end end end diff --git a/lib/mongoid/persistable/creatable.rb b/lib/mongoid/persistable/creatable.rb index 1d5501753e..f142eb65a1 100644 --- a/lib/mongoid/persistable/creatable.rb +++ b/lib/mongoid/persistable/creatable.rb @@ -61,7 +61,7 @@ def insert_as_embedded _parent.insert else selector = _parent.atomic_selector - _root.collection.find(selector).update(positionally(selector, atomic_inserts)) + _root.collection.find(selector).update(atomic_inserts) end end diff --git a/lib/mongoid/persistable/deletable.rb b/lib/mongoid/persistable/deletable.rb index 1f58ca3d0f..bd1aa630d1 100644 --- a/lib/mongoid/persistable/deletable.rb +++ b/lib/mongoid/persistable/deletable.rb @@ -62,7 +62,7 @@ def delete_as_embedded(options = {}) _parent.remove_child(self) if notifying_parent?(options) if _parent.persisted? selector = _parent.atomic_selector - _root.collection.find(selector).update(positionally(selector, atomic_deletes)) + _root.collection.find(selector).update(atomic_deletes) end true end diff --git a/lib/mongoid/persistable/updatable.rb b/lib/mongoid/persistable/updatable.rb index 1633c1cc24..d5a44e036f 100644 --- a/lib/mongoid/persistable/updatable.rb +++ b/lib/mongoid/persistable/updatable.rb @@ -141,9 +141,9 @@ def update_document(options = {}) unless updates.empty? coll = _root.collection selector = atomic_selector - coll.find(selector).update(positionally(selector, updates)) + coll.find(selector).update(updates) conflicts.each_pair do |key, value| - coll.find(selector).update(positionally(selector, { key => value })) + coll.find(selector).update({ key => value }) end end end diff --git a/lib/mongoid/positional.rb b/lib/mongoid/positional.rb deleted file mode 100644 index 00ae9ffa99..0000000000 --- a/lib/mongoid/positional.rb +++ /dev/null @@ -1,71 +0,0 @@ -# encoding: utf-8 -module Mongoid - - # This module is responsible for taking update selectors and switching out - # the indexes for the $ positional operator where appropriate. - # - # @since 4.0.0 - module Positional - - # Takes the provided selector and atomic operations and replaces the - # indexes of the embedded documents with the positional operator when - # needed. - # - # @note The only time we can accurately know when to use the positional - # operator is at the exact time we are going to persist something. So - # we can tell by the selector that we are sending if it is actually - # possible to use the positional operator at all. For example, if the - # selector is: { "_id" => 1 }, then we could not use the positional - # operator for updating embedded documents since there would never be a - # match - we base whether we can based on the number of levels deep the - # selector goes, and if the id values are not nil. - # - # @example Process the operations. - # positionally( - # { "_id" => 1, "addresses._id" => 2 }, - # { "$set" => { "addresses.0.street" => "hobrecht" }} - # ) - # - # @param [ Hash ] selector The selector. - # @param [ Hash ] operations The update operations. - # @param [ Hash ] processed The processed update operations. - # - # @return [ Hash ] The new operations. - # - # @since 3.1.0 - def positionally(selector, operations, processed = {}) - if selector.size == 1 || selector.values.any? { |val| val.nil? } - return operations - end - keys = selector.keys.map{ |m| m.sub('._id','') } - ['_id'] - keys = keys.sort_by { |s| s.length*-1 } - process_operations(keys, operations, processed) - end - - private - - def process_operations(keys, operations, processed) - operations.each_pair do |operation, update| - processed[operation] = process_updates(keys, update) - end - processed - end - - def process_updates(keys, update, updates = {}) - update.each_pair do |position, value| - updates[replace_index(keys, position)] = value - end - updates - end - - def replace_index(keys, position) - # replace to $ only if that key is on the selector - keys.each do |kk| - if position =~ /^#{kk}\.\d+\.(.*)/ - return "#{kk}.$.#{$1}" - end - end - position - end - end -end diff --git a/lib/mongoid/relations/embedded/batchable.rb b/lib/mongoid/relations/embedded/batchable.rb index 09749cd0f9..8cb3478254 100644 --- a/lib/mongoid/relations/embedded/batchable.rb +++ b/lib/mongoid/relations/embedded/batchable.rb @@ -6,7 +6,6 @@ module Embedded # Contains behaviour for executing operations in batch on embedded # documents. module Batchable - include Positional # Insert new documents as a batch push ($pushAll). This ensures that # all callbacks are run at the appropriate time and only 1 request is @@ -37,9 +36,7 @@ def batch_insert(docs) def batch_clear(docs) pre_process_batch_remove(docs, :delete) unless docs.empty? - collection.find(selector).update( - positionally(selector, "$unset" => { path => true }) - ) + collection.find(selector).update("$unset" => { path => true }) post_process_batch_remove(docs, :delete) end _unscoped.clear @@ -57,9 +54,7 @@ def batch_clear(docs) def batch_remove(docs, method = :delete) removals = pre_process_batch_remove(docs, method) if !docs.empty? - collection.find(selector).update( - positionally(selector, "$pullAll" => { path => removals }) - ) + collection.find(selector).update("$pullAll" => { path => removals }) post_process_batch_remove(docs, method) end reindex @@ -130,9 +125,7 @@ def execute_batch_insert(docs, operation) self.inserts_valid = true inserts = pre_process_batch_insert(docs) if insertable? - collection.find(selector).update( - positionally(selector, operation => { path => inserts }) - ) + collection.find(selector).update(operation => { path => inserts }) post_process_batch_insert(docs) end inserts diff --git a/lib/mongoid/relations/touchable.rb b/lib/mongoid/relations/touchable.rb index f6f473fa6a..4726a4b46a 100644 --- a/lib/mongoid/relations/touchable.rb +++ b/lib/mongoid/relations/touchable.rb @@ -31,7 +31,7 @@ def touch(field = nil) touches = touch_atomic_updates(field) unless touches.empty? selector = atomic_selector - _root.collection.where(selector).update(positionally(selector, touches)) + _root.collection.find(selector).update(touches) end run_callbacks(:touch) true diff --git a/spec/mongoid/positional_spec.rb b/spec/mongoid/positional_spec.rb deleted file mode 100644 index 49f75d62b6..0000000000 --- a/spec/mongoid/positional_spec.rb +++ /dev/null @@ -1,222 +0,0 @@ -require "spec_helper" - -describe Mongoid::Positional do - - describe "#positionally" do - - let(:positionable) do - Class.new do - include Mongoid::Positional - end.new - end - - let(:updates) do - { - "$set" => { - "field" => "value", - "children.0.field" => "value", - "children.0.children.1.children.3.field" => "value" - }, - "$pushAll" => { - "children.0.children.1.children.3.fields" => [ "value", "value" ] - } - } - end - - context "when a child has an embeds many under an embeds one" do - - context "when selector does not include the embeds one" do - - let(:selector) do - { "_id" => 1, "child._id" => 2 } - end - - let(:ops) do - { - "$set" => { - "field" => "value", - "child.children.1.children.3.field" => "value", - } - } - end - - let(:processed) do - positionable.positionally(selector, ops) - end - - it "does not do any replacement" do - expect(processed).to eq(ops) - end - end - - context "when selector includes the embeds one" do - - let(:selector) do - { "_id" => 1, "child._id" => 2, "child.children._id" => 3 } - end - - let(:ops) do - { - "$set" => { - "field" => "value", - "child.children.1.children.3.field" => "value", - } - } - end - - let(:expected) do - { - "$set" => { - "field" => "value", - "child.children.$.children.3.field" => "value", - } - } - end - - - let(:processed) do - positionable.positionally(selector, ops) - end - - it "does not do any replacement" do - expect(processed).to eq(expected) - end - end - end - - context "when the selector has only 1 pair" do - - let(:selector) do - { "_id" => 1 } - end - - let(:processed) do - positionable.positionally(selector, updates) - end - - it "does not do any replacement" do - expect(processed).to eq(updates) - end - end - - context "when the selector has 2 pairs" do - - context "when the second pair has an id" do - - let(:selector) do - { "_id" => 1, "children._id" => 2 } - end - - let(:expected) do - { - "$set" => { - "field" => "value", - "children.$.field" => "value", - "children.$.children.1.children.3.field" => "value" - }, - "$pushAll" => { - "children.$.children.1.children.3.fields" => [ "value", "value" ] - } - } - end - - let(:processed) do - positionable.positionally(selector, updates) - end - - it "replaces the first index with the positional operator" do - expect(processed).to eq(expected) - end - end - - context "when the second pair has no id" do - - let(:selector) do - { "_id" => 1, "children._id" => nil } - end - - let(:expected) do - { - "$set" => { - "field" => "value", - "children.0.field" => "value", - "children.0.children.1.children.3.field" => "value" - }, - "$pushAll" => { - "children.0.children.1.children.3.fields" => [ "value", "value" ] - } - } - end - - let(:processed) do - positionable.positionally(selector, updates) - end - - it "replaces the first index with the positional operator" do - expect(processed).to eq(expected) - end - end - end - - context "when the selector has 3 pairs" do - - let(:selector) do - { "_id" => 1, "children._id" => 2, "children.0.children._id" => 3 } - end - - let(:expected) do - { - "$set" => { - "field" => "value", - "children.$.field" => "value", - "children.0.children.$.children.3.field" => "value" - }, - "$pushAll" => { - "children.0.children.$.children.3.fields" => [ "value", "value" ] - } - } - end - - let(:processed) do - positionable.positionally(selector, updates) - end - - it "replaces the first index with the positional operator" do - expect(processed).to eq(expected) - end - end - - context "when the selector has 4 pairs" do - - let(:selector) do - { - "_id" => 1, - "children._id" => 2, - "children.0.children._id" => 3, - "children.0.children.1.children._id" => 4 - } - end - - let(:expected) do - { - "$set" => { - "field" => "value", - "children.$.field" => "value", - "children.0.children.1.children.$.field" => "value" - }, - "$pushAll" => { - "children.0.children.1.children.$.fields" => [ "value", "value" ] - } - } - end - - let(:processed) do - positionable.positionally(selector, updates) - end - - it "replaces the first index with the positional operator" do - expect(processed).to eq(expected) - end - end - end -end diff --git a/spec/mongoid/relations/embedded/one_spec.rb b/spec/mongoid/relations/embedded/one_spec.rb index 55051a0027..c4baf6db61 100644 --- a/spec/mongoid/relations/embedded/one_spec.rb +++ b/spec/mongoid/relations/embedded/one_spec.rb @@ -963,7 +963,7 @@ class << person end before do - address_two.code = code + person.reload.addresses.first.code = code end it "reloads the correct number" do From 056a70dd91d080d86709ed6e7510700ab133580d Mon Sep 17 00:00:00 2001 From: Jonathan Hyman Date: Tue, 24 Mar 2015 16:13:55 -0400 Subject: [PATCH 02/13] Pull request comments: moves new classes into their own files, style updates. --- lib/mongoid/attributes/processing.rb | 3 +++ spec/app/models/store_as_dup_test1.rb | 5 +++++ spec/app/models/store_as_dup_test2.rb | 5 +++++ spec/mongoid/copyable_spec.rb | 14 ++++++++++++++ 4 files changed, 27 insertions(+) create mode 100644 spec/app/models/store_as_dup_test1.rb create mode 100644 spec/app/models/store_as_dup_test2.rb diff --git a/lib/mongoid/attributes/processing.rb b/lib/mongoid/attributes/processing.rb index e70b9f7662..401d76850f 100644 --- a/lib/mongoid/attributes/processing.rb +++ b/lib/mongoid/attributes/processing.rb @@ -92,6 +92,9 @@ def pending_nested # # @since 2.0.0.rc.7 def process_attribute(name, value) + if !respond_to?("#{name}=", true) && store_as = aliased_fields.invert[name.to_s] + name = store_as + end responds = respond_to?("#{name}=") raise Errors::UnknownAttribute.new(self.class, name) unless responds send("#{name}=", value) diff --git a/spec/app/models/store_as_dup_test1.rb b/spec/app/models/store_as_dup_test1.rb new file mode 100644 index 0000000000..c18c038c93 --- /dev/null +++ b/spec/app/models/store_as_dup_test1.rb @@ -0,0 +1,5 @@ +class StoreAsDupTest1 + include Mongoid::Document + embeds_one :store_as_dup_test2, :store_as => :t + field :name +end diff --git a/spec/app/models/store_as_dup_test2.rb b/spec/app/models/store_as_dup_test2.rb new file mode 100644 index 0000000000..175da72426 --- /dev/null +++ b/spec/app/models/store_as_dup_test2.rb @@ -0,0 +1,5 @@ +class StoreAsDupTest2 + include Mongoid::Document + embedded_in :store_as_dup_test1 + field :name +end diff --git a/spec/mongoid/copyable_spec.rb b/spec/mongoid/copyable_spec.rb index 19f770d209..1b635451b4 100644 --- a/spec/mongoid/copyable_spec.rb +++ b/spec/mongoid/copyable_spec.rb @@ -54,6 +54,20 @@ end end + context "when using store_as" do + + context "and dynamic attributes are not set" do + + it "clones" do + t = StoreAsDupTest1.new(:name => "hi") + t.build_store_as_dup_test2(:name => "there") + t.save + copy = t.send(method) + expect(copy.store_as_dup_test2.name).to eq(t.store_as_dup_test2.name) + end + end + end + context "when cloning a document with multiple languages field" do before do From 0d191cc22f4edc84ac06306490e5afe16955eae9 Mon Sep 17 00:00:00 2001 From: Dianna Hohensee Date: Thu, 5 Nov 2015 11:12:27 -0500 Subject: [PATCH 03/13] MONGOID-3659 Adding a spec to prove this is no longer an issue --- spec/app/models/courier_job.rb | 4 +++ spec/app/models/shipment_address.rb | 2 ++ spec/mongoid/relations/embedded/one_spec.rb | 31 +++++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 spec/app/models/courier_job.rb create mode 100644 spec/app/models/shipment_address.rb diff --git a/spec/app/models/courier_job.rb b/spec/app/models/courier_job.rb new file mode 100644 index 0000000000..a751e8c083 --- /dev/null +++ b/spec/app/models/courier_job.rb @@ -0,0 +1,4 @@ +class CourierJob + include Mongoid::Document + embeds_one :drop_address, as: :addressable, autobuild: true, class_name: "ShipmentAddress" +end diff --git a/spec/app/models/shipment_address.rb b/spec/app/models/shipment_address.rb new file mode 100644 index 0000000000..efddd8d9e8 --- /dev/null +++ b/spec/app/models/shipment_address.rb @@ -0,0 +1,2 @@ +class ShipmentAddress < Address +end diff --git a/spec/mongoid/relations/embedded/one_spec.rb b/spec/mongoid/relations/embedded/one_spec.rb index c4baf6db61..da7f14dc9f 100644 --- a/spec/mongoid/relations/embedded/one_spec.rb +++ b/spec/mongoid/relations/embedded/one_spec.rb @@ -768,6 +768,37 @@ class << person end end + describe "when the relationship is polymorphic" do + + context "when updating an aliased embedded document" do + + context "when the embedded document inherits its relationship" do + + let(:courier_job) do + CourierJob.create + end + + let(:old_child) do + ShipmentAddress.new + end + + let(:new_child) do + ShipmentAddress.new + end + + before do + courier_job.drop_address = old_child + courier_job.update_attribute(:drop_address, new_child) + courier_job.reload + end + + it "the child is embedded correctly" do + expect(courier_job.drop_address).to eq(new_child) + end + end + end + end + describe ".embedded?" do it "returns true" do From 7d6d4d369ca75aa00776014f4ab3256fca35cd36 Mon Sep 17 00:00:00 2001 From: Emily Date: Tue, 26 Apr 2016 22:43:54 +0200 Subject: [PATCH 04/13] MONGOID-4019 Don't include Attributes::Dynamic when cloning docs with unknown attributes --- lib/mongoid/copyable.rb | 14 +++++++++++++- spec/mongoid/copyable_spec.rb | 23 +++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/mongoid/copyable.rb b/lib/mongoid/copyable.rb index abab1b3430..6fad586f51 100644 --- a/lib/mongoid/copyable.rb +++ b/lib/mongoid/copyable.rb @@ -22,7 +22,19 @@ def clone # _id and id field in the document would cause problems with Mongoid # elsewhere. attrs = clone_document.except("_id", "id") - self.class.new(attrs) + dynamic_attrs = {} + attrs.reject! do |attr_name, value| + dynamic_attrs[attr_name] = value unless self.attribute_names.include?(attr_name) + end + self.class.new(attrs).tap do |object| + dynamic_attrs.each do |attr_name, value| + if object.respond_to?("#{attr_name}=") + object.send("#{attr_name}=", value) + else + object.attributes[attr_name] = value + end + end + end end alias :dup :clone diff --git a/spec/mongoid/copyable_spec.rb b/spec/mongoid/copyable_spec.rb index 1b635451b4..ea82ca74ea 100644 --- a/spec/mongoid/copyable_spec.rb +++ b/spec/mongoid/copyable_spec.rb @@ -54,6 +54,29 @@ end end + context "when a document has fields from a legacy schema" do + + let!(:actor) do + Actor.create(name: "test") + end + + before do + Actor.collection.find(_id: actor.id).update_all("$set" => { "this_is_not_a_field" => 1 }) + end + + let(:cloned) do + actor.reload.send(method) + end + + it "sets the legacy attribute" do + expect(cloned.attributes['this_is_not_a_field']).to eq(1) + end + + it "copies the known attriutes" do + expect(cloned.name).to eq('test') + end + end + context "when using store_as" do context "and dynamic attributes are not set" do From f3111ca5b0d9c021f81d4f0cccba90b4c38eb73d Mon Sep 17 00:00:00 2001 From: Eric Pigeon Date: Thu, 23 Jun 2016 16:45:03 -0400 Subject: [PATCH 05/13] Fix cloning with embedded translations when cloning process_localized_fields only works from the parent document causing embedded documents with translations to get set to a hash instead of the proper translation --- lib/mongoid/copyable.rb | 17 ++++++++++++++--- spec/mongoid/copyable_spec.rb | 13 ++++++++++++- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/mongoid/copyable.rb b/lib/mongoid/copyable.rb index 6fad586f51..e12e1249d2 100644 --- a/lib/mongoid/copyable.rb +++ b/lib/mongoid/copyable.rb @@ -52,7 +52,7 @@ def clone # @since 3.0.22 def clone_document attrs = as_document.__deep_copy__ - process_localized_attributes(attrs) + process_localized_attributes(self, attrs) attrs end @@ -67,12 +67,23 @@ def clone_document # @param [ Hash ] attrs The attributes. # # @since 3.0.20 - def process_localized_attributes(attrs) - localized_fields.keys.each do |name| + def process_localized_attributes(klass, attrs) + klass.localized_fields.keys.each do |name| if value = attrs.delete(name) attrs["#{name}_translations"] = value end end + klass.embedded_relations.each do |_, metadata| + next unless attrs.present? && attrs[metadata.key].present? + + if metadata.macro == :embeds_many + attrs[metadata.key].each do |attr| + process_localized_attributes(metadata.klass, attr) + end + else + process_localized_attributes(metadata.klass, attrs[metadata.key]) + end + end end end end diff --git a/spec/mongoid/copyable_spec.rb b/spec/mongoid/copyable_spec.rb index ea82ca74ea..870d94b9e7 100644 --- a/spec/mongoid/copyable_spec.rb +++ b/spec/mongoid/copyable_spec.rb @@ -20,7 +20,7 @@ end let!(:address) do - person.addresses.build(street: "Bond") + person.addresses.build(street: "Bond", name: "Bond") end let!(:name) do @@ -96,6 +96,7 @@ before do I18n.locale = 'pt_BR' person.desc = "descrição" + person.addresses.first.name = "descrição" person.save end @@ -125,6 +126,16 @@ I18n.locale = :fr expect(copy.desc).to be_nil end + + it 'sets embedded translations' do + I18n.locale = 'pt_BR' + expect(copy.addresses.first.name).to eq("descrição") + end + + it 'sets embedded english version' do + I18n.locale = :en + expect(copy.addresses.first.name).to eq("Bond") + end end context "when cloning a loaded document" do From 2613c9b53291c782b2089455bfa9af93810a8f08 Mon Sep 17 00:00:00 2001 From: okoriko Date: Thu, 13 Oct 2016 12:29:04 +0900 Subject: [PATCH 06/13] Add support for clone documents that have nil legacy attributes. --- lib/mongoid/copyable.rb | 5 ++++- spec/mongoid/copyable_spec.rb | 8 +++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/mongoid/copyable.rb b/lib/mongoid/copyable.rb index e12e1249d2..4b34225e4a 100644 --- a/lib/mongoid/copyable.rb +++ b/lib/mongoid/copyable.rb @@ -24,7 +24,10 @@ def clone attrs = clone_document.except("_id", "id") dynamic_attrs = {} attrs.reject! do |attr_name, value| - dynamic_attrs[attr_name] = value unless self.attribute_names.include?(attr_name) + unless self.attribute_names.include?(attr_name) + dynamic_attrs[attr_name] = value + true + end end self.class.new(attrs).tap do |object| dynamic_attrs.each do |attr_name, value| diff --git a/spec/mongoid/copyable_spec.rb b/spec/mongoid/copyable_spec.rb index 870d94b9e7..ea33cf65b4 100644 --- a/spec/mongoid/copyable_spec.rb +++ b/spec/mongoid/copyable_spec.rb @@ -61,7 +61,8 @@ end before do - Actor.collection.find(_id: actor.id).update_all("$set" => { "this_is_not_a_field" => 1 }) + legacy_fields = { "this_is_not_a_field" => 1, "this_legacy_field_is_nil" => nil } + Actor.collection.find(_id: actor.id).update_all("$set" => legacy_fields) end let(:cloned) do @@ -72,6 +73,10 @@ expect(cloned.attributes['this_is_not_a_field']).to eq(1) end + it "contains legacy attribute that are nil" do + expect(cloned.attributes.key?('this_legacy_field_is_nil')).to eq(true) + end + it "copies the known attriutes" do expect(cloned.name).to eq('test') end @@ -94,6 +99,7 @@ context "when cloning a document with multiple languages field" do before do + I18n.enforce_available_locales = false I18n.locale = 'pt_BR' person.desc = "descrição" person.addresses.first.name = "descrição" From a76e485627ce83c353eb57435e3a88265e8334d0 Mon Sep 17 00:00:00 2001 From: Emily Date: Thu, 13 Oct 2016 11:23:11 +0200 Subject: [PATCH 07/13] Use #merge! instead for simplicity --- lib/mongoid/copyable.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/mongoid/copyable.rb b/lib/mongoid/copyable.rb index 4b34225e4a..08058f8e37 100644 --- a/lib/mongoid/copyable.rb +++ b/lib/mongoid/copyable.rb @@ -24,10 +24,7 @@ def clone attrs = clone_document.except("_id", "id") dynamic_attrs = {} attrs.reject! do |attr_name, value| - unless self.attribute_names.include?(attr_name) - dynamic_attrs[attr_name] = value - true - end + dynamic_attrs.merge!(attr_name => value) unless self.attribute_names.include?(attr_name) end self.class.new(attrs).tap do |object| dynamic_attrs.each do |attr_name, value| From b1d3051ec2979e48a790850ccb7dd4f3cc12243b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=CC=81s=CC=8C=20Celizna?= Date: Tue, 12 Sep 2017 10:27:19 +0200 Subject: [PATCH 08/13] fix cloning document with polymorphic embedded documents with multiple language field --- lib/mongoid/copyable.rb | 3 ++- spec/app/models/address_customized.rb | 3 +++ spec/mongoid/copyable_spec.rb | 39 +++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 spec/app/models/address_customized.rb diff --git a/lib/mongoid/copyable.rb b/lib/mongoid/copyable.rb index 08058f8e37..8aee535a73 100644 --- a/lib/mongoid/copyable.rb +++ b/lib/mongoid/copyable.rb @@ -78,7 +78,8 @@ def process_localized_attributes(klass, attrs) if metadata.macro == :embeds_many attrs[metadata.key].each do |attr| - process_localized_attributes(metadata.klass, attr) + klass = attr.fetch('_type', metadata.class_name).constantize + process_localized_attributes(klass, attr) end else process_localized_attributes(metadata.klass, attrs[metadata.key]) diff --git a/spec/app/models/address_customized.rb b/spec/app/models/address_customized.rb new file mode 100644 index 0000000000..490fb49c3f --- /dev/null +++ b/spec/app/models/address_customized.rb @@ -0,0 +1,3 @@ +class AddressCustomized < Address + field :alternative_name, localize: true +end diff --git a/spec/mongoid/copyable_spec.rb b/spec/mongoid/copyable_spec.rb index ea33cf65b4..6e38801aa3 100644 --- a/spec/mongoid/copyable_spec.rb +++ b/spec/mongoid/copyable_spec.rb @@ -144,6 +144,45 @@ end end + context "when cloning a document with polymorphic embedded documents with multiple language field" do + + let!(:address_customized) do + person.addresses.build({ street: "Bond", name: "Bond" }, AddressCustomized) + end + + before do + I18n.enforce_available_locales = false + I18n.locale = 'pt_BR' + person.addresses.each { |address| address.name = "descrição" } + person.addresses.type(AddressCustomized).first.alternative_name = "alternativa" + person.save + end + + after do + I18n.locale = :en + end + + let!(:from_db) do + Person.find(person.id) + end + + let(:copy) do + from_db.send(method) + end + + it 'sets embedded translations' do + I18n.locale = 'pt_BR' + copy.addresses.each do |address| + expect(address.name).to eq("descrição") + end + + copy.addresses.type(AddressCustomized).each do |address| + expect(address.alternative_name).to eq("alternativa") + end + end + + end + context "when cloning a loaded document" do before do From dc721eb0bf0ef475212c38a8a76b7f8422d29bbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=CC=81s=CC=8C=20Celizna?= Date: Tue, 12 Sep 2017 10:35:31 +0200 Subject: [PATCH 09/13] improve naming --- lib/mongoid/copyable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mongoid/copyable.rb b/lib/mongoid/copyable.rb index 8aee535a73..5c0ac554b5 100644 --- a/lib/mongoid/copyable.rb +++ b/lib/mongoid/copyable.rb @@ -78,8 +78,8 @@ def process_localized_attributes(klass, attrs) if metadata.macro == :embeds_many attrs[metadata.key].each do |attr| - klass = attr.fetch('_type', metadata.class_name).constantize - process_localized_attributes(klass, attr) + embedded_klass = attr.fetch('_type', metadata.class_name).constantize + process_localized_attributes(embedded_klass, attr) end else process_localized_attributes(metadata.klass, attrs[metadata.key]) From 30395d082222d81e3199f380677740d6bfcdebc7 Mon Sep 17 00:00:00 2001 From: Alexandre Bini Date: Mon, 16 Jul 2018 16:36:01 -0300 Subject: [PATCH 10/13] Fix cloning documents with deep embedded documents (#4504) --- spec/mongoid/copyable_spec.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/spec/mongoid/copyable_spec.rb b/spec/mongoid/copyable_spec.rb index 6e38801aa3..f65229491a 100644 --- a/spec/mongoid/copyable_spec.rb +++ b/spec/mongoid/copyable_spec.rb @@ -35,6 +35,10 @@ person.build_game(name: "Tron") end + let!(:name_translations) do + person.name.translations.build(language: 'en') + end + context "when the document has an id field in the database" do let!(:band) do @@ -250,14 +254,26 @@ expect(copy.addresses).to eq(person.addresses) end + it "copys deep embeds many documents" do + expect(copy.name.translations).to eq(person.name.translations) + end + it "sets the embedded many documents as new" do expect(copy.addresses.first).to be_new_record end + it "sets the deep embedded many documents as new" do + expect(copy.name.translations.first).to be_new_record + end + it "creates new embeds many instances" do expect(copy.addresses).to_not equal(person.addresses) end + it "creates new deep embeds many instances" do + expect(copy.name.translations).to_not equal(person.name.translations) + end + it "copys embeds one documents" do expect(copy.name).to eq(person.name) end From d5a059eac6ae9b891f293e142a5cf380f9ee6308 Mon Sep 17 00:00:00 2001 From: Oleg Pudeyev <39304720+p-mongo@users.noreply.github.com> Date: Fri, 25 Jan 2019 11:09:39 -0500 Subject: [PATCH 11/13] Fix MONGOID-4679 Can't clone documents with embeds_many and store_as on 7.0.x (#4592) --- spec/app/models/store_as_dup_test3.rb | 7 +++++ spec/app/models/store_as_dup_test4.rb | 7 +++++ spec/mongoid/copyable_spec.rb | 43 +++++++++++++++++++++++---- 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 spec/app/models/store_as_dup_test3.rb create mode 100644 spec/app/models/store_as_dup_test4.rb diff --git a/spec/app/models/store_as_dup_test3.rb b/spec/app/models/store_as_dup_test3.rb new file mode 100644 index 0000000000..ddb20b21da --- /dev/null +++ b/spec/app/models/store_as_dup_test3.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class StoreAsDupTest3 + include Mongoid::Document + embeds_many :store_as_dup_test4s, :store_as => :t + field :name +end diff --git a/spec/app/models/store_as_dup_test4.rb b/spec/app/models/store_as_dup_test4.rb new file mode 100644 index 0000000000..bcd8a0c01f --- /dev/null +++ b/spec/app/models/store_as_dup_test4.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class StoreAsDupTest4 + include Mongoid::Document + embedded_in :store_as_dup_test3 + field :name +end diff --git a/spec/mongoid/copyable_spec.rb b/spec/mongoid/copyable_spec.rb index f65229491a..1673b86aec 100644 --- a/spec/mongoid/copyable_spec.rb +++ b/spec/mongoid/copyable_spec.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true # -*- coding: utf-8 -*- require "spec_helper" @@ -90,12 +91,42 @@ context "and dynamic attributes are not set" do - it "clones" do - t = StoreAsDupTest1.new(:name => "hi") - t.build_store_as_dup_test2(:name => "there") - t.save - copy = t.send(method) - expect(copy.store_as_dup_test2.name).to eq(t.store_as_dup_test2.name) + context 'embeds_one' do + + it "clones" do + t = StoreAsDupTest1.new(:name => "hi") + t.build_store_as_dup_test2(:name => "there") + t.save + copy = t.send(method) + expect(copy.object_id).not_to eq(t.object_id) + expect(copy.store_as_dup_test2.name).to eq(t.store_as_dup_test2.name) + end + end + + context 'embeds_many' do + + it "clones" do + t = StoreAsDupTest3.new(:name => "hi") + t.store_as_dup_test4s << StoreAsDupTest4.new + t.save + copy = t.send(method) + expect(copy.object_id).not_to eq(t.object_id) + expect(copy.store_as_dup_test4s).not_to be_empty + expect(copy.store_as_dup_test4s.first.object_id).not_to eq(t.store_as_dup_test4s.first.object_id) + end + end + + context 'embeds_many' do + + it "clones" do + t = StoreAsDupTest3.new(:name => "hi") + t.store_as_dup_test4s << StoreAsDupTest4.new + t.save + copy = t.send(method) + expect(copy.object_id).not_to eq(t.object_id) + expect(copy.store_as_dup_test4s).not_to be_empty + expect(copy.store_as_dup_test4s.first.object_id).not_to eq(t.store_as_dup_test4s.first.object_id) + end end end end From b0a34be6bdcb2ef05ea4c5d688ebdc5f3a77c31c Mon Sep 17 00:00:00 2001 From: Mike Saffitz Date: Thu, 8 Sep 2022 11:24:27 -0700 Subject: [PATCH 12/13] Ensure that embeds_many with store_as are properly set during clone It's not clear that this properly works in HEAD / latest releases. I searched for a commit that would provide this functionality, but was unable to find one. It's possible that it's there in a spot I didn't see; HEAD varies substantially from our branch. --- lib/mongoid/copyable.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/mongoid/copyable.rb b/lib/mongoid/copyable.rb index 5c0ac554b5..ee0228358d 100644 --- a/lib/mongoid/copyable.rb +++ b/lib/mongoid/copyable.rb @@ -30,6 +30,8 @@ def clone dynamic_attrs.each do |attr_name, value| if object.respond_to?("#{attr_name}=") object.send("#{attr_name}=", value) + elsif (store_as = aliased_fields.invert[attr_name.to_s]) + object.send("#{store_as}=", value) else object.attributes[attr_name] = value end From 6bd09246a30dfeb4edf95fa870bd388e94f2528c Mon Sep 17 00:00:00 2001 From: Ben Crouse Date: Mon, 13 Jul 2015 15:00:00 -0400 Subject: [PATCH 13/13] Fix assigning a localized field when attributes is a BSON::Document --- lib/mongoid/attributes.rb | 3 ++- spec/mongoid/attributes_spec.rb | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/mongoid/attributes.rb b/lib/mongoid/attributes.rb index 73f734fa4a..25f4d8dda8 100644 --- a/lib/mongoid/attributes.rb +++ b/lib/mongoid/attributes.rb @@ -176,7 +176,8 @@ def write_attribute(name, value) attribute_will_change!(access) end if localized - (attributes[access] ||= {}).merge!(typed_value) + attributes[access] ||= {} + attributes[access].merge!(typed_value) else attributes[access] = typed_value end diff --git a/spec/mongoid/attributes_spec.rb b/spec/mongoid/attributes_spec.rb index 3fe0fc2fd7..d77184ff96 100644 --- a/spec/mongoid/attributes_spec.rb +++ b/spec/mongoid/attributes_spec.rb @@ -1207,6 +1207,19 @@ }.to raise_error(Mongoid::Errors::InvalidValue) end end + + context "when attribute is localized and #attributes is a BSON::Document" do + let(:dictionary) { Dictionary.new } + + before do + allow(dictionary).to receive(:attributes).and_return(BSON::Document.new) + end + + it "sets the value for the current locale" do + dictionary.write_attribute(:description, 'foo') + expect(dictionary.description).to eq('foo') + end + end end describe "#typed_value_for" do