require "spec_helper" describe Mongoid::Contextual::Memory do [ :blank?, :empty? ].each do |method| describe "##{method}" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end context "when there are matching documents" do let(:criteria) do Address.where(street: "hobrecht").tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "returns false" do expect(context.send(method)).to be false end end context "when there are no matching documents" do let(:criteria) do Address.where(street: "pfluger").tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "returns true" do expect(context.send(method)).to be true end end end end describe "#count" do let!(:hobrecht) do Address.new(street: "hobrecht") end let!(:friedel) do Address.new(street: "friedel") end let(:criteria) do Address.where(street: "hobrecht").tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end context "context when no arguments are provided" do it "returns the number of matches" do expect(context.count).to eq(1) end end context "when provided a document" do context "when the document matches" do let(:count) do context.count(hobrecht) end it "returns 1" do expect(count).to eq(1) end end context "when the document does not match" do let(:count) do context.count(friedel) end it "returns 0" do expect(count).to eq(0) end end end context "when provided a block" do context "when the block evals 1 to true" do let(:count) do context.count do |doc| doc.street == "hobrecht" end end it "returns 1" do expect(count).to eq(1) end end context "when the block evals none to true" do let(:count) do context.count do |doc| doc.street == "friedel" end end it "returns 0" do expect(count).to eq(0) end end end end [ :delete, :delete_all ].each do |method| let(:person) do Person.create end let(:hobrecht) do person.addresses.create(street: "hobrecht") end let(:friedel) do person.addresses.create(street: "friedel") end let(:pfluger) do person.addresses.create(street: "pfluger") end describe "##{method}" do context "when embedded a single level" do let(:criteria) do Address.any_in(street: [ "hobrecht", "friedel" ]).tap do |crit| crit.documents = [ hobrecht, friedel, pfluger ] end end let(:context) do described_class.new(criteria) end let!(:deleted) do context.send(method) end it "deletes the first matching document" do expect(hobrecht).to be_destroyed end it "deletes the last matching document" do expect(friedel).to be_destroyed end it "does not delete non matching docs" do expect(pfluger).to_not be_destroyed end it "removes the docs from the relation" do expect(person.addresses).to eq([ pfluger ]) end it "removes the docs from the context" do expect(context.entries).to be_empty end it "persists the changes to the database" do expect(person.reload.addresses).to eq([ pfluger ]) end it "returns the number of deleted documents" do expect(deleted).to eq(2) end end context "when embedded multiple levels" do let!(:home) do hobrecht.locations.create(name: "home") end let!(:work) do hobrecht.locations.create(name: "work") end let(:criteria) do Location.where(name: "work").tap do |crit| crit.documents = [ home, work ] end end let(:context) do described_class.new(criteria) end let!(:deleted) do context.send(method) end it "deletes the first matching document" do expect(work).to be_destroyed end it "does not delete non matching docs" do expect(home).to_not be_destroyed end it "removes the docs from the relation" do expect(person.addresses.first.locations).to eq([ home ]) end it "removes the docs from the context" do expect(context.entries).to be_empty end it "persists the changes to the database" do expect(person.reload.addresses.first.locations).to eq([ home ]) end it "returns the number of deleted documents" do expect(deleted).to eq(1) end end end end [ :destroy, :destroy_all ].each do |method| let(:person) do Person.create end let(:hobrecht) do person.addresses.create(street: "hobrecht") end let(:friedel) do person.addresses.create(street: "friedel") end let(:pfluger) do person.addresses.create(street: "pfluger") end let(:criteria) do Address.any_in(street: [ "hobrecht", "friedel" ]).tap do |crit| crit.documents = [ hobrecht, friedel, pfluger ] end end let(:context) do described_class.new(criteria) end describe "##{method}" do let!(:destroyed) do context.send(method) end it "deletes the first matching document" do expect(hobrecht).to be_destroyed end it "deletes the last matching document" do expect(friedel).to be_destroyed end it "does not delete non matching docs" do expect(pfluger).to_not be_destroyed end it "removes the docs from the relation" do expect(person.addresses).to eq([ pfluger ]) end it "removes the docs from the context" do expect(context.entries).to be_empty end it "persists the changes to the database" do expect(person.reload.addresses).to eq([ pfluger ]) end it "returns the number of destroyed documents" do expect(destroyed).to eq(2) end end end describe "#distinct" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end context "when limiting the result set" do let(:criteria) do Address.where(street: "hobrecht").tap do |crit| crit.documents = [ hobrecht, hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "returns the distinct field values" do expect(context.distinct(:street)).to eq([ "hobrecht" ]) end end context "when not limiting the result set" do let(:criteria) do Address.all.tap do |crit| crit.documents = [ hobrecht, friedel, friedel ] end end let(:context) do described_class.new(criteria) end it "returns the distinct field values" do expect(context.distinct(:street)).to eq([ "hobrecht", "friedel" ]) end end end describe "#each" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end let(:criteria) do Address.where(street: "hobrecht").tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end context "when skip and limit outside of range" do before do context.skip(10).limit(2) end it "contains no documents" do expect(context.map(&:street)).to be_empty end context "when calling next on the enumerator" do it "raises a stop iteration error" do expect { context.each.next }.to raise_error(StopIteration) end end end context "when providing a block" do it "yields mongoid documents to the block" do context.each do |doc| expect(doc).to be_a(Mongoid::Document) end end it "iterates over the matching documents" do context.each do |doc| expect(doc).to eq(hobrecht) end end end context "when no block is provided" do let(:enum) do context.each end it "returns an enumerator" do expect(enum).to be_a(Enumerator) end context "when iterating over the enumerator" do context "when iterating with each" do it "yields mongoid documents to the block" do enum.each do |doc| expect(doc).to be_a(Mongoid::Document) end end end context "when iterating with next" do it "yields mongoid documents" do expect(enum.next).to be_a(Mongoid::Document) end end end end end describe "#exists?" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end context "when there are matching documents" do let(:criteria) do Address.where(street: "hobrecht").tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "returns true" do expect(context).to be_exists end end context "when there are no matching documents" do let(:criteria) do Address.where(street: "pfluger").tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "returns false" do expect(context).to_not be_exists end end end [ :first, :one ].each do |method| describe "##{method}" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end let(:criteria) do Address.where(:street.in => [ "hobrecht", "friedel" ]).tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "returns the first matching document" do expect(context.send(method)).to eq(hobrecht) end end end describe "#initialize" do context "when the criteria has no options" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end let(:criteria) do Address.where(street: "hobrecht").tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "sets the criteria" do expect(context.criteria).to eq(criteria) end it "sets the klass" do expect(context.klass).to eq(Address) end it "sets the matching documents" do expect(context.documents).to eq([ hobrecht ]) end end context "when the criteria skips" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end let(:criteria) do Address.all.skip(1).tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "limits the matching documents" do expect(context).to eq([ friedel ]) end end context "when the criteria limits" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end let(:criteria) do Address.all.limit(1).tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "limits the matching documents" do expect(context).to eq([ hobrecht ]) end end end describe "#last" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end let(:criteria) do Address.where(:street.in => [ "hobrecht", "friedel" ]).tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "returns the last matching document" do expect(context.last).to eq(friedel) end end [ :length, :size ].each do |method| describe "##{method}" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end context "when there are matching documents" do let(:criteria) do Address.where(street: "hobrecht").tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "returns the number of matches" do expect(context.send(method)).to eq(1) end end context "when there are no matching documents" do let(:criteria) do Address.where(street: "pfluger").tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end it "returns zero" do expect(context.send(method)).to eq(0) end end end end describe "#limit" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end let(:pfluger) do Address.new(street: "pfluger") end let(:criteria) do Address.all.tap do |crit| crit.documents = [ hobrecht, friedel, pfluger ] end end let(:context) do described_class.new(criteria) end let!(:limit) do context.limit(2) end it "returns the context" do expect(limit).to eq(context) end context "when asking for all documents" do context "when only a limit exists" do it "only returns the limited documents" do expect(context.entries).to eq([ hobrecht, friedel ]) end end context "when a skip and limit exist" do before do limit.skip(1) end it "applies the skip before the limit" do expect(context.entries).to eq([ friedel, pfluger ]) end end end end describe "#pluck" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end let(:criteria) do Address.all.tap do |crit| crit.documents = [ hobrecht, friedel ] end end let(:context) do described_class.new(criteria) end context "when plucking" do let!(:plucked) do context.pluck(:street) end it "returns the values" do expect(plucked).to eq([ "hobrecht", "friedel" ]) end end context "when plucking a field that doesnt exist" do context "when pluck one field" do let(:plucked) do context.pluck(:foo) end it "returns a empty array" do expect(plucked).to eq([]) end end context "when pluck multiple fields" do let(:plucked) do context.pluck(:foo, :bar) end it "returns a empty array" do expect(plucked).to eq([[], []]) end end end end describe "#skip" do let(:hobrecht) do Address.new(street: "hobrecht") end let(:friedel) do Address.new(street: "friedel") end let(:pfluger) do Address.new(street: "pfluger") end let(:criteria) do Address.all.tap do |crit| crit.documents = [ hobrecht, friedel, pfluger ] end end let(:context) do described_class.new(criteria) end let!(:skip) do context.skip(1) end it "returns the context" do expect(skip).to eq(context) end context "when asking for all documents" do context "when only a skip exists" do it "skips the correct number" do expect(context.entries).to eq([ friedel, pfluger ]) end end context "when a skip and limit exist" do before do skip.limit(1) end it "applies the skip before the limit" do expect(context.entries).to eq([ friedel ]) end end end end describe "#sort" do let(:hobrecht) do Address.new(street: "hobrecht", number: 9, name: "hobrecht") end let(:friedel) do Address.new(street: "friedel", number: 1, name: "friedel") end let(:pfluger) do Address.new(street: "pfluger", number: 5, name: "pfluger") end let(:criteria) do Address.all.tap do |crit| crit.documents = [ hobrecht, friedel, pfluger ] end end let(:context) do described_class.new(criteria) end context "when providing a single field sort" do context "when the sort is ascending" do let!(:sorted) do context.sort(street: 1) end it "sorts the documents" do expect(context.entries).to eq([ friedel, hobrecht, pfluger ]) end it "returns the context" do expect(sorted).to eq(context) end end context "when the sort is descending" do context "when sorting on a string" do let!(:sorted) do context.sort(street: -1) end it "sorts the documents" do expect(context.entries).to eq([ pfluger, hobrecht, friedel ]) end it "returns the context" do expect(sorted).to eq(context) end end context "when sorting on a time" do before do pfluger.move_in = 30.days.ago hobrecht.move_in = 25.days.ago end let!(:sorted) do context.sort(move_in: -1) end it "sorts the documents" do expect(context.entries).to eq([ friedel, hobrecht, pfluger ]) end it "returns the context" do expect(sorted).to eq(context) end end end end context "when providing multiple sort fields" do let(:lenau) do Address.new(street: "lenau", number: 5, name: "lenau") end before do criteria.documents.unshift(lenau) end context "when the sort is ascending" do let!(:sorted) do context.sort(number: 1, street: 1) end it "sorts the documents" do expect(context.entries).to eq([ friedel, lenau, pfluger, hobrecht ]) end it "returns the context" do expect(sorted).to eq(context) end end context "when the sort is descending" do let!(:sorted) do context.sort(number: -1, street: -1) end it "sorts the documents" do expect(context.entries).to eq([ hobrecht, pfluger, lenau, friedel ]) end it "returns the context" do expect(sorted).to eq(context) end end end context "when the field is nil" do let!(:sorted) do context.sort(state: 1) end it "does not sort the documents" do expect(context.entries).to eq([ hobrecht, friedel, pfluger ]) end it "returns the context" do expect(sorted).to eq(context) end end context "with localized field" do let!(:sorted) do context.sort("name.en" => 1) end it "sorts the documents" do expect(context.entries).to eq([ friedel, hobrecht, pfluger ]) end end end describe "#update" do let(:person) do Person.create end let(:hobrecht) do person.addresses.create(street: "hobrecht") end let(:friedel) do person.addresses.create(street: "friedel") end let(:pfluger) do person.addresses.create(street: "pfluger") end context "when the documents are embedded one level" do let(:criteria) do Address.any_in(street: [ "hobrecht", "friedel" ]).tap do |crit| crit.documents = [ hobrecht, friedel, pfluger ] end end let(:context) do described_class.new(criteria) end context "when attributes are provided" do before do context.update(number: 5) end it "updates the first matching document" do expect(hobrecht.number).to eq(5) end it "does not update the last matching document" do expect(friedel.number).to be_nil end it "does not update non matching docs" do expect(pfluger.number).to be_nil end context "when reloading the embedded documents" do it "updates the first matching document" do expect(hobrecht.reload.number).to eq(5) end it "updates the last matching document" do expect(friedel.reload.number).to be_nil end it "does not update non matching docs" do expect(pfluger.reload.number).to be_nil end end end context "when no attributes are provided" do it "returns false" do expect(context.update).to be false end end end context "when the documents are embedded multiple levels" do let!(:home) do hobrecht.locations.create(name: "home") end let!(:work) do hobrecht.locations.create(name: "work") end let(:criteria) do Location.where(name: "work").tap do |crit| crit.documents = [ home, work ] end end let(:context) do described_class.new(criteria) end context "when attributes are provided" do before do context.update(number: 5) end it "updates the first matching document" do expect(work.number).to eq(5) end it "does not update non matching docs" do expect(home.number).to be_nil end context "when reloading the embedded documents" do it "updates the first matching document" do expect(work.reload.number).to eq(5) end it "does not update non matching docs" do expect(home.reload.number).to be_nil end end end context "when no attributes are provided" do it "returns false" do expect(context.update).to be false end end end end describe "#update_all" do let(:person) do Person.create end let(:hobrecht) do person.addresses.create(street: "hobrecht") end let(:friedel) do person.addresses.create(street: "friedel") end let(:pfluger) do person.addresses.create(street: "pfluger") end context "when the documents are empty" do let(:person_two) do Person.create end let(:criteria) do Address.all end let(:context) do described_class.new(criteria) end it "returns false" do expect(context.update_all({})).to be false end end context "when the documents are embedded one level" do let(:criteria) do Address.any_in(street: [ "hobrecht", "friedel" ]).tap do |crit| crit.documents = [ hobrecht, friedel, pfluger ] end end let(:context) do described_class.new(criteria) end context "when providing aliased fields" do before do context.update_all(suite: "10B") end it "updates the first matching document" do expect(hobrecht.suite).to eq("10B") end it "updates the last matching document" do expect(friedel.suite).to eq("10B") end it "does not update non matching docs" do expect(pfluger.suite).to be_nil end end context "when attributes are provided" do before do context.update_all(number: 5) end it "updates the first matching document" do expect(hobrecht.number).to eq(5) end it "updates the last matching document" do expect(friedel.number).to eq(5) end it "does not update non matching docs" do expect(pfluger.number).to be_nil end context "when reloading the embedded documents" do it "updates the first matching document" do expect(hobrecht.reload.number).to eq(5) end it "updates the last matching document" do expect(friedel.reload.number).to eq(5) end it "does not update non matching docs" do expect(pfluger.reload.number).to be_nil end end context "when updating the documents a second time" do before do context.update_all(number: 5) end it "does not error on the update" do expect(hobrecht.number).to eq(5) end end end context "when no attributes are provided" do it "returns false" do expect(context.update_all).to be false end end end context "when the documents are embedded multiple levels" do let!(:home) do hobrecht.locations.create(name: "home") end let!(:work) do hobrecht.locations.create(name: "work") end let(:criteria) do Location.where(name: "work").tap do |crit| crit.documents = [ home, work ] end end let(:context) do described_class.new(criteria) end context "when attributes are provided" do before do context.update_all(number: 5) end it "updates the first matching document" do expect(work.number).to eq(5) end it "does not update non matching docs" do expect(home.number).to be_nil end context "when reloading the embedded documents" do it "updates the first matching document" do expect(work.reload.number).to eq(5) end it "does not update non matching docs" do expect(home.reload.number).to be_nil end end end context "when no attributes are provided" do it "returns false" do expect(context.update_all).to be false end end end end end