From 83c49766f8fa3b72655cff1631dc1243cab0430f Mon Sep 17 00:00:00 2001 From: Reid Morrison Date: Thu, 27 Sep 2018 16:41:26 -0400 Subject: [PATCH] Support a dynamic collection name --- lib/mongoid/clients/options.rb | 31 +++++++++++++++++----- lib/mongoid/contextual/mongo.rb | 2 +- lib/mongoid/factory.rb | 7 ++++- spec/mongoid/clients/options_spec.rb | 38 ++++++++++++++++++++++++++- spec/mongoid/contextual/mongo_spec.rb | 14 ++++++++++ spec/mongoid/factory_spec.rb | 27 +++++++++++++++++++ 6 files changed, 109 insertions(+), 10 deletions(-) diff --git a/lib/mongoid/clients/options.rb b/lib/mongoid/clients/options.rb index 94f53dcd9c..7f6c186932 100644 --- a/lib/mongoid/clients/options.rb +++ b/lib/mongoid/clients/options.rb @@ -21,6 +21,12 @@ module Options # # @since 6.0.0 def with(options_or_context, &block) + # Only changing the collection name does not require the overhead of an entire new persistence context. + if options_or_context.is_a?(Hash) && (options_or_context.size == 1) && options_or_context.key?(:collection) + self.collection_name = options_or_context[:collection] + return block_given? ? yield(self) : self + end + original_cluster = persistence_context.cluster set_persistence_context(options_or_context) yield self @@ -29,11 +35,15 @@ def with(options_or_context, &block) end def collection(parent = nil) - persistence_context.collection(parent) + @collection_name ? mongo_client[@collection_name] : persistence_context.collection(parent) end def collection_name - persistence_context.collection_name + @collection_name || persistence_context.collection_name + end + + def collection_name=(collection_name) + @collection_name = collection_name.nil? ? nil : collection_name.to_sym end def mongo_client @@ -93,11 +103,18 @@ def mongo_client # # @since 6.0.0 def with(options, &block) - original_cluster = persistence_context.cluster - PersistenceContext.set(self, options) - yield self - ensure - PersistenceContext.clear(self, original_cluster) + # Support changing just the collection name, when not used with a block + if !block_given? && options.is_a?(Hash) && (options.size == 1) && options.key?(:collection) + return all.with(options, &block) + end + + begin + original_cluster = persistence_context.cluster + PersistenceContext.set(self, options) + yield self + ensure + PersistenceContext.clear(self, original_cluster) + end end def persistence_context diff --git a/lib/mongoid/contextual/mongo.rb b/lib/mongoid/contextual/mongo.rb index dacf25de08..3fdcaaa206 100644 --- a/lib/mongoid/contextual/mongo.rb +++ b/lib/mongoid/contextual/mongo.rb @@ -342,7 +342,7 @@ def map(field = nil, &block) # @since 3.0.0 def initialize(criteria) @criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache] - @collection = @klass.collection + @collection = criteria.collection criteria.send(:merge_type_selection) @view = collection.find(criteria.selector, session: _session) apply_options diff --git a/lib/mongoid/factory.rb b/lib/mongoid/factory.rb index 0cb6416124..2cf22e3dac 100644 --- a/lib/mongoid/factory.rb +++ b/lib/mongoid/factory.rb @@ -47,6 +47,9 @@ def from_db(klass, attributes = nil, criteria = nil) if criteria && criteria.association && criteria.parent_document obj.set_relation(criteria.association.inverse, criteria.parent_document) end + # Use the collection name from the criteria so that any changes to this model + # are saved back to the collection from which it was retrieved. + obj.collection_name = criteria.collection_name if criteria obj else camelized = type.camelize @@ -63,7 +66,9 @@ def from_db(klass, attributes = nil, criteria = nil) raise Errors::UnknownModel.new(camelized, type) end - constantized.instantiate(attributes, selected_fields) + obj = constantized.instantiate(attributes, selected_fields) + obj.collection_name = criteria.collection_name if criteria + obj end end end diff --git a/spec/mongoid/clients/options_spec.rb b/spec/mongoid/clients/options_spec.rb index 7bf654ddee..193135d009 100644 --- a/spec/mongoid/clients/options_spec.rb +++ b/spec/mongoid/clients/options_spec.rb @@ -74,6 +74,32 @@ end end + context 'when not passing a block' do + + let(:collection_name) do + Minim.with(options).collection_name + end + + let(:persistence_context) do + Minim.with(options).persistence_context + end + + let(:options) { { collection: 'another-collection' } } + + it 'uses the collection' do + expect(collection_name).to eq(options[:collection].to_sym) + end + + it 'does not change the context' do + expect(Minim.persistence_context).to eq(persistence_context) + end + + it 'does not include the collection option in the client options' do + expect(persistence_context.client.options[:collection]).to be_nil + expect(persistence_context.client.options['collection']).to be_nil + end + end + context 'when passing a block', if: testing_locally? do let!(:connections_before) do @@ -184,6 +210,10 @@ it 'uses that collection' do expect(persistence_context.collection.name).to eq(options[:collection]) end + + it 'uses that collection when no block is supplied' do + expect(Minim.with(options).collection_name).to eq(options[:collection].to_sym) + end end context 'when returning a criteria' do @@ -437,8 +467,14 @@ { collection: 'other' } end + let(:collection_name) do + test_model.with(options) do |object| + object.collection_name + end + end + it 'uses that collection' do - expect(persistence_context.collection.name).to eq(options[:collection]) + expect(collection_name).to eq(options[:collection].to_sym) end end diff --git a/spec/mongoid/contextual/mongo_spec.rb b/spec/mongoid/contextual/mongo_spec.rb index 28e10f9c4d..3ed9ebd7d1 100644 --- a/spec/mongoid/contextual/mongo_spec.rb +++ b/spec/mongoid/contextual/mongo_spec.rb @@ -1363,6 +1363,20 @@ it "sets the view selector" do expect(context.view.selector).to eq({ "name" => "Depeche Mode" }) end + + it "sets the collection" do + expect(context.collection).to eq(Band.collection) + end + + context "with a different collection name" do + let(:criteria) do + Band.with(collection: 'other').where(name: "Depeche Mode").no_timeout + end + + it "sets the collection" do + expect(context.collection.name).to eq("other") + end + end end [ :length, :size ].each do |method| diff --git a/spec/mongoid/factory_spec.rb b/spec/mongoid/factory_spec.rb index 4b71ae333e..68e0a5f38e 100644 --- a/spec/mongoid/factory_spec.rb +++ b/spec/mongoid/factory_spec.rb @@ -236,5 +236,32 @@ def self.instantiate(*args) end end + + context "when built from within the context of a criteria" do + + let(:criteria) do + Person.with(collection: 'other').where(title: "Sir") + end + + let(:attributes) do + { "_type" => "Person", "title" => "Sir" } + end + + let(:document) do + described_class.from_db(Person, attributes, criteria) + end + + it "generates based on the type" do + expect(document).to be_a(Person) + end + + it "sets the attributes" do + expect(document.title).to eq("Sir") + end + + it "retains the collection name" do + expect(document.collection_name).to eq(:other) + end + end end end