Skip to content

Commit e9674a2

Browse files
authored
MONGOID-5100 allow selector arguments for .exists? (#5466)
* MONGOID-5100 allow selector arguments for .exists? * MONGOID-5100 add examples to the docstrings * MONGOID-5100 add docs and release notes * Empty-Commit * MONGOID-5100 allow false to be passed * MONGOID-5100 update docs and docstrings * MONGOID-5100 remove false as a param * MONGOID-5100 put back nil/false * MONGOID-5100 fix memory tests and fix limit == 0
1 parent f2cf42f commit e9674a2

File tree

8 files changed

+354
-61
lines changed

8 files changed

+354
-61
lines changed

docs/reference/queries.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,11 +1243,22 @@ Mongoid also has some helpful methods on criteria.
12431243
*Determine if any matching documents exist. Will return true if there
12441244
are 1 or more.*
12451245

1246+
``#exists?`` *now takes a number of argument types:*
1247+
1248+
- ``Hash``: *A hash of conditions.*
1249+
- ``Object``: *An _id to search for.*
1250+
- ``false``/``nil``: *Always returns false.*
1251+
12461252
-
12471253
.. code-block:: ruby
12481254

12491255
Band.exists?
12501256
Band.where(name: "Photek").exists?
1257+
Band.exists?(name: "Photek")
1258+
Band.exists?(BSON::ObjectId('6320d96a3282a48cfce9e72c'))
1259+
Band.exists?('6320d96a3282a48cfce9e72c')
1260+
Band.exists?(false)
1261+
Band.exists?(nil)
12511262

12521263
* - ``Criteria#fifth``
12531264

docs/release-notes/mongoid-8.1.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,3 +287,16 @@ raised when destroying or deleting, and not on saving or updating. See the
287287
section on :ref:`Read-only Documents <readonly-documents>` for more details.
288288

289289

290+
Added method parameters to ``#exists?``
291+
---------------------------------------
292+
293+
Mongoid 8.1 introduces method paramters to the ``Contextual#exists?`` method.
294+
An _id, a hash of conditions, or ``false``/``nil`` can now be included:
295+
296+
.. code:: ruby
297+
298+
Band.exists?
299+
Band.exists?(name: "The Rolling Stones")
300+
Band.exists?(BSON::ObjectId('6320d96a3282a48cfce9e72c'))
301+
Band.exists?(false) # always false
302+
Band.exists?(nil) # always false

lib/mongoid/contextual/memory.rb

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,24 @@ def each
110110
# @example Do any documents exist for the context.
111111
# context.exists?
112112
#
113+
# @example Do any documents exist for given _id.
114+
# context.exists?(BSON::ObjectId(...))
115+
#
116+
# @example Do any documents exist for given conditions.
117+
# context.exists?(name: "...")
118+
#
119+
# @param [ Hash | Object | false ] id_or_conditions an _id to
120+
# search for, a hash of conditions, nil or false.
121+
#
113122
# @return [ true | false ] If the count is more than zero.
114-
def exists?
115-
any?
123+
# Always false if passed nil or false.
124+
def exists?(id_or_conditions = :none)
125+
case id_or_conditions
126+
when :none then any?
127+
when nil, false then false
128+
when Hash then Memory.new(criteria.where(id_or_conditions)).exists?
129+
else Memory.new(criteria.where(_id: id_or_conditions)).exists?
130+
end
116131
end
117132

118133
# Get the first document in the database for the criteria's selector.

lib/mongoid/contextual/mongo.rb

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,12 +161,28 @@ def each(&block)
161161
# @example Do any documents exist for the context.
162162
# context.exists?
163163
#
164+
# @example Do any documents exist for given _id.
165+
# context.exists?(BSON::ObjectId(...))
166+
#
167+
# @example Do any documents exist for given conditions.
168+
# context.exists?(name: "...")
169+
#
164170
# @note We don't use count here since Mongo does not use counted
165171
# b-tree indexes.
166172
#
173+
# @param [ Hash | Object | false ] id_or_conditions an _id to
174+
# search for, a hash of conditions, nil or false.
175+
#
167176
# @return [ true | false ] If the count is more than zero.
168-
def exists?
169-
!!(view.projection(_id: 1).limit(1).first)
177+
# Always false if passed nil or false.
178+
def exists?(id_or_conditions = :none)
179+
return false if self.view.limit == 0
180+
case id_or_conditions
181+
when :none then !!(view.projection(_id: 1).limit(1).first)
182+
when nil, false then false
183+
when Hash then Mongo.new(criteria.where(id_or_conditions)).exists?
184+
else Mongo.new(criteria.where(_id: id_or_conditions)).exists?
185+
end
170186
end
171187

172188
# Run an explain on the criteria.

lib/mongoid/contextual/none.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,17 @@ def each
8080
# @example Do any documents exist in the null context.
8181
# context.exists?
8282
#
83+
# @example Do any documents exist for given _id.
84+
# context.exists?(BSON::ObjectId(...))
85+
#
86+
# @example Do any documents exist for given conditions.
87+
# context.exists?(name: "...")
88+
#
89+
# @param [ Hash | Object | false ] id_or_conditions an _id to
90+
# search for, a hash of conditions, nil or false.
91+
#
8392
# @return [ false ] Always false.
84-
def exists?; false; end
93+
def exists?(id_or_conditions = :none); false; end
8594

8695
# Pluck the field values in null context.
8796
#

lib/mongoid/findable.rb

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,19 @@ def empty?
101101
# @example Do any documents exist for the conditions?
102102
# Person.exists?
103103
#
104+
# @example Do any documents exist for given _id.
105+
# Person.exists?(BSON::ObjectId(...))
106+
#
107+
# @example Do any documents exist for given conditions.
108+
# Person.exists?(name: "...")
109+
#
110+
# @param [ Hash | Object | false ] id_or_conditions an _id to
111+
# search for, a hash of conditions, nil or false.
112+
#
104113
# @return [ true | false ] If any documents exist for the conditions.
105-
def exists?
106-
with_default_scope.exists?
114+
# Always false if passed nil or false.
115+
def exists?(id_or_conditions = :none)
116+
with_default_scope.exists?(id_or_conditions)
107117
end
108118

109119
# Finds a +Document+ or multiple documents by their _id values.

spec/mongoid/contextual/memory_spec.rb

Lines changed: 130 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -766,52 +766,158 @@
766766
Address.new(street: "friedel")
767767
end
768768

769-
context "when there are matching documents" do
769+
let(:criteria) do
770+
Address.where(street: "hobrecht").tap do |crit|
771+
crit.documents = [ hobrecht, friedel ]
772+
end
773+
end
770774

771-
let(:criteria) do
772-
Address.where(street: "hobrecht").tap do |crit|
773-
crit.documents = [ hobrecht, friedel ]
775+
context "when not passing options" do
776+
777+
context "when there are matching documents" do
778+
779+
let(:context) do
780+
described_class.new(criteria)
781+
end
782+
783+
it "returns true" do
784+
expect(context).to be_exists
774785
end
775786
end
776787

777-
let(:context) do
778-
described_class.new(criteria)
788+
context "when there are no matching documents" do
789+
790+
let(:criteria) do
791+
Address.where(street: "pfluger").tap do |crit|
792+
crit.documents = [ hobrecht, friedel ]
793+
end
794+
end
795+
796+
let(:context) do
797+
described_class.new(criteria)
798+
end
799+
800+
it "returns false" do
801+
expect(context).to_not be_exists
802+
end
779803
end
780804

781-
it "returns true" do
782-
expect(context).to be_exists
805+
context 'when there is a collation on the criteria' do
806+
807+
let(:criteria) do
808+
Address.where(street: "pfluger").tap do |crit|
809+
crit.documents = [ hobrecht, friedel ]
810+
end.collation(locale: 'en_US', strength: 2)
811+
end
812+
813+
it "raises an exception" do
814+
expect {
815+
context
816+
}.to raise_exception(Mongoid::Errors::InMemoryCollationNotSupported)
817+
end
783818
end
784819
end
785820

786-
context "when there are no matching documents" do
821+
context "when passing an _id" do
787822

788-
let(:criteria) do
789-
Address.where(street: "pfluger").tap do |crit|
790-
crit.documents = [ hobrecht, friedel ]
823+
context "when its of type BSON::ObjectId" do
824+
825+
context "when calling it on an empty criteria" do
826+
827+
it "returns true" do
828+
expect(criteria.exists?(hobrecht._id)).to be true
829+
end
830+
end
831+
832+
context "when calling it on a criteria that includes the object" do
833+
834+
it "returns true" do
835+
expect(criteria.where(street: hobrecht.street).exists?(hobrecht._id)).to be true
836+
end
837+
end
838+
839+
context "when calling it on a criteria that does not include the object" do
840+
841+
it "returns false" do
842+
expect(criteria.where(street: "bogus").exists?(hobrecht._id)).to be false
843+
end
844+
end
845+
846+
context "when the id does not exist" do
847+
848+
it "returns false" do
849+
expect(criteria.exists?(BSON::ObjectId.new)).to be false
850+
end
791851
end
792852
end
793853

794-
let(:context) do
795-
described_class.new(criteria)
854+
context "when its of type String" do
855+
856+
context "when the id exists" do
857+
858+
it "returns true" do
859+
expect(criteria.exists?(hobrecht._id.to_s)).to be true
860+
end
861+
end
862+
863+
context "when the id does not exist" do
864+
865+
it "returns false" do
866+
expect(criteria.exists?(BSON::ObjectId.new.to_s)).to be false
867+
end
868+
end
869+
end
870+
end
871+
872+
context "when passing a hash" do
873+
874+
context "when calling it on an empty criteria" do
875+
876+
it "returns true" do
877+
expect(criteria.exists?(street: hobrecht.street)).to be true
878+
end
879+
end
880+
881+
context "when calling it on a criteria that includes the object" do
882+
883+
it "returns true" do
884+
expect(criteria.where(_id: hobrecht._id).exists?(street: hobrecht.street)).to be true
885+
end
886+
end
887+
888+
context "when calling it on a criteria that does not include the object" do
889+
890+
it "returns false" do
891+
expect(criteria.where(_id: BSON::ObjectId.new).exists?(street: hobrecht.street)).to be false
892+
end
796893
end
797894

895+
context "when the conditions don't match" do
896+
897+
it "returns false" do
898+
expect(criteria.exists?(street: "bogus")).to be false
899+
end
900+
end
901+
end
902+
903+
context "when passing false" do
904+
798905
it "returns false" do
799-
expect(context).to_not be_exists
906+
expect(criteria.exists?(false)).to be false
800907
end
801908
end
802909

803-
context 'when there is a collation on the criteria' do
910+
context "when passing nil" do
804911

805-
let(:criteria) do
806-
Address.where(street: "pfluger").tap do |crit|
807-
crit.documents = [ hobrecht, friedel ]
808-
end.collation(locale: 'en_US', strength: 2)
912+
it "returns false" do
913+
expect(criteria.exists?(nil)).to be false
809914
end
915+
end
810916

811-
it "raises an exception" do
812-
expect {
813-
context
814-
}.to raise_exception(Mongoid::Errors::InMemoryCollationNotSupported)
917+
context "when the limit is 0" do
918+
919+
it "returns false" do
920+
expect(criteria.limit(0).exists?).to be false
815921
end
816922
end
817923
end

0 commit comments

Comments
 (0)