diff --git a/lib/mongoid/association/embedded/embeds_many/proxy.rb b/lib/mongoid/association/embedded/embeds_many/proxy.rb index d154bca61e..a9ba752733 100644 --- a/lib/mongoid/association/embedded/embeds_many/proxy.rb +++ b/lib/mongoid/association/embedded/embeds_many/proxy.rb @@ -227,9 +227,24 @@ def destroy_all(conditions = {}) # @example Are there persisted documents? # person.posts.exists? # - # @return [ true | false ] True is persisted documents exist, false if not. - def exists? - _target.any? { |doc| doc.persisted? } + # @param [ :none | nil | false | Hash | Object ] id_or_conditions + # When :none (the default), returns true if any persisted + # documents exist in the association. When nil or false, this + # will always return false. When a Hash is given, this queries + # the documents in the association for those that match the given + # conditions, and returns true if any match which have been + # persisted. Any other argument is interpreted as an id, and + # queries for the existence of persisted documents in the + # association with a matching _id. + # + # @return [ true | false ] True if persisted documents exist, false if not. + def exists?(id_or_conditions = :none) + case id_or_conditions + when :none then _target.any?(&:persisted?) + when nil, false then false + when Hash then where(id_or_conditions).any?(&:persisted?) + else where(_id: id_or_conditions).any?(&:persisted?) + end end # Finds a document in this association through several different diff --git a/lib/mongoid/association/referenced/has_many/proxy.rb b/lib/mongoid/association/referenced/has_many/proxy.rb index 1fdf1c5b4c..cefb89a6b2 100644 --- a/lib/mongoid/association/referenced/has_many/proxy.rb +++ b/lib/mongoid/association/referenced/has_many/proxy.rb @@ -172,9 +172,18 @@ def each # @example Are there persisted documents? # person.posts.exists? # + # @param [ :none | nil | false | Hash | Object ] id_or_conditions + # When :none (the default), returns true if any persisted + # documents exist in the association. When nil or false, this + # will always return false. When a Hash is given, this queries + # the documents in the association for those that match the given + # conditions, and returns true if any match. Any other argument is + # interpreted as an id, and queries for the existence of documents + # in the association with a matching _id. + # # @return [ true | false ] True is persisted documents exist, false if not. - def exists? - criteria.exists? + def exists?(id_or_conditions = :none) + criteria.exists?(id_or_conditions) end # Find the matching document on the association, either based on id or diff --git a/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb b/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb index b9df5582e8..c39f173fda 100644 --- a/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb +++ b/spec/mongoid/association/embedded/embeds_many/proxy_spec.rb @@ -2310,9 +2310,37 @@ class TrackingIdValidationHistory person.addresses.create!(street: "Bond St") end + let(:address) { person.addresses.first } + it "returns true" do expect(person.addresses.exists?).to be true end + + context 'when given specifying conditions' do + context 'when the record exists in the association' do + it 'returns true by condition' do + expect(person.addresses.exists?(street: 'Bond St')).to be true + end + + it 'returns true by id' do + expect(person.addresses.exists?(address._id)).to be true + end + + it 'returns false when given false' do + expect(person.addresses.exists?(false)).to be false + end + + it 'returns false when given nil' do + expect(person.addresses.exists?(nil)).to be false + end + end + + context 'when the record does not exist in the association' do + it 'returns false' do + expect(person.addresses.exists?(street: 'Garfield Ave')).to be false + end + end + end end context "when no documents exist in the database" do @@ -2324,6 +2352,13 @@ class TrackingIdValidationHistory it "returns false" do expect(person.addresses.exists?).to be false end + + context 'when given specifying conditions' do + it 'returns false' do + expect(person.addresses.exists?(street: 'Hyde Park Dr')).to be false + expect(person.addresses.exists?(street: 'Garfield Ave')).to be false + end + end end end diff --git a/spec/mongoid/association/referenced/has_many/proxy_spec.rb b/spec/mongoid/association/referenced/has_many/proxy_spec.rb index 09a7cb6ead..217dc85308 100644 --- a/spec/mongoid/association/referenced/has_many/proxy_spec.rb +++ b/spec/mongoid/association/referenced/has_many/proxy_spec.rb @@ -2395,6 +2395,42 @@ def initialize(*args) end end end + + context 'when invoked with specifying conditions' do + let(:other_person) { Person.create! } + let(:post) { person.posts.first } + + before do + person.posts.create title: 'bumfuzzle' + other_person.posts.create title: 'bumbershoot' + end + + context 'when the conditions match an associated record' do + it 'detects its existence by condition' do + expect(person.posts.exists?(title: 'bumfuzzle')).to be true + expect(other_person.posts.exists?(title: 'bumbershoot')).to be true + end + + it 'detects its existence by id' do + expect(person.posts.exists?(post._id)).to be true + end + + it 'returns false when given false' do + expect(person.posts.exists?(false)).to be false + end + + it 'returns false when given nil' do + expect(person.posts.exists?(nil)).to be false + end + end + + context 'when the conditions match an unassociated record' do + it 'does not detect its existence' do + expect(person.posts.exists?(title: 'bumbershoot')).to be false + expect(other_person.posts.exists?(title: 'bumfuzzle')).to be false + end + end + end end context "when documents exist in application but not in database" do @@ -2465,6 +2501,12 @@ def initialize(*args) end end end + + context 'when invoked with specifying conditions' do + it 'returns false' do + expect(person.posts.exists?(title: 'hullaballoo')).to be false + end + end end end