mongoid 7.5.2 → 7.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e67e9eccd518e5c987bbca2d4943a461d7ceaf382abd091842806fb300dffb1e
4
- data.tar.gz: 441f04f551b51c8b5f3300e4158fd7d0b6b0c3406b39dfa073bf1ce11cead324
3
+ metadata.gz: ce7f228fdc033dc1142f46f4771395d36acafc97e0c144599cd063af537c7ae3
4
+ data.tar.gz: 15f8f0efbb076eb03f25f23898cbe27d044e7441e4a7ae4bc8d22f036838bb98
5
5
  SHA512:
6
- metadata.gz: 31d1d76cbe4923b0cb2a54a62d3758183a80ef8c8833448760115d2299153724e8cb2e6160745989357f6a4bbd3e6873380e73f500e13ffb14b307dfa503a3a9
7
- data.tar.gz: 9c3acad387939c0bfe4746102e73e24124597bf311adbd3ce301bfe9a6882a066dadbb67ccb05a470a983c6b0d55ca43cce01f247111cd9d64efb2653614fbd8
6
+ metadata.gz: 91f40b7136ae729e6831c5033c60c0a1e2207cd98d80bf5caaef50844230d6830efe555748571d9bdef62a101f75ffd21d2a7942278f84b68e2a51fc6a8f20df
7
+ data.tar.gz: 3555b90da32865e1faadb9efccc6db0262adea620b4dd473d5dc1ee533b00451039dfaacd982ef5c8f6be4009ccff589119b2f512971a1b5ac509bb7ae4aa96e
checksums.yaml.gz.sig CHANGED
Binary file
data/Rakefile CHANGED
@@ -11,6 +11,15 @@ $: << File.join(ROOT, 'spec/shared/lib')
11
11
  require "rake"
12
12
  require "rspec/core/rake_task"
13
13
  require 'mrss/spec_organizer'
14
+ require 'rubygems/package'
15
+ require 'rubygems/security/policies'
16
+
17
+ def signed_gem?(path_to_gem)
18
+ Gem::Package.new(path_to_gem, Gem::Security::HighSecurity).verify
19
+ true
20
+ rescue Gem::Security::Exception => e
21
+ false
22
+ end
14
23
 
15
24
  $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
16
25
  require "mongoid/version"
@@ -103,3 +112,19 @@ namespace :release do
103
112
  end
104
113
  end
105
114
  end
115
+
116
+ desc 'Verifies that all built gems in pkg/ are valid'
117
+ task :verify do
118
+ gems = Dir['pkg/*.gem']
119
+ if gems.empty?
120
+ puts 'There are no gems in pkg/ to verify'
121
+ else
122
+ gems.each do |gem|
123
+ if signed_gem?(gem)
124
+ puts "#{gem} is signed"
125
+ else
126
+ abort "#{gem} is not signed"
127
+ end
128
+ end
129
+ end
130
+ end
@@ -80,7 +80,8 @@ module Mongoid
80
80
  def batch_replace(docs)
81
81
  if docs.blank?
82
82
  if _assigning? && !empty?
83
- _base.delayed_atomic_sets.clear
83
+ _base.delayed_atomic_sets.delete(path)
84
+ clear_atomic_path_cache
84
85
  _base.add_atomic_unset(first)
85
86
  target_duplicate = _target.dup
86
87
  pre_process_batch_remove(target_duplicate, :delete)
@@ -92,7 +93,8 @@ module Mongoid
92
93
  _base.delayed_atomic_sets.clear unless _assigning?
93
94
  docs = normalize_docs(docs).compact
94
95
  _target.clear and _unscoped.clear
95
- _base.delayed_atomic_unsets.clear
96
+ _base.delayed_atomic_unsets.delete(path)
97
+ clear_atomic_path_cache
96
98
  inserts = execute_batch_set(docs)
97
99
  add_atomic_sets(inserts)
98
100
  end
@@ -234,7 +236,22 @@ module Mongoid
234
236
  #
235
237
  # @return [ String ] The atomic path.
236
238
  def path
237
- @path ||= _unscoped.first.atomic_path
239
+ @path ||= if _unscoped.empty?
240
+ Mongoid::Atomic::Paths::Embedded::Many.position_without_document(_base, _association)
241
+ else
242
+ _unscoped.first.atomic_path
243
+ end
244
+ end
245
+
246
+ # Clear the cache for path and atomic_paths. This method is used when
247
+ # the path method is used, and the association has not been set on the
248
+ # document yet, which can cause path and atomic_paths to be calculated
249
+ # incorrectly later.
250
+ #
251
+ # @api private
252
+ def clear_atomic_path_cache
253
+ self.path = nil
254
+ _base.instance_variable_set("@atomic_paths", nil)
238
255
  end
239
256
 
240
257
  # Set the atomic path.
@@ -34,6 +34,25 @@ module Mongoid
34
34
  locator = document.new_record? ? "" : ".#{document._index}"
35
35
  "#{pos}#{"." unless pos.blank?}#{document._association.store_as}#{locator}"
36
36
  end
37
+
38
+ class << self
39
+
40
+ # Get the position of where the document would go for the given
41
+ # association. The use case for this function is when trying to
42
+ # persist an empty list for an embedded association. All of the
43
+ # existing functions for getting the position to store a document
44
+ # require passing in a document to store, which we don't have when
45
+ # trying to store the empty list.
46
+ #
47
+ # @param [ Document ] parent The parent document to store in.
48
+ # @param [ Association ] association The association.
49
+ #
50
+ # @return [ String ] The position string.
51
+ def position_without_document(parent, association)
52
+ pos = parent.atomic_position
53
+ "#{pos}#{"." unless pos.blank?}#{association.store_as}"
54
+ end
55
+ end
37
56
  end
38
57
  end
39
58
  end
@@ -260,16 +260,16 @@ module Mongoid
260
260
  #
261
261
  # @return [ Document ] The first document.
262
262
  def first(limit_or_opts = nil)
263
- limit = limit_or_opts unless limit_or_opts.is_a?(Hash)
263
+ limit, opts = extract_limit_and_opts(limit_or_opts)
264
264
  if cached? && cache_loaded?
265
265
  return limit ? documents.first(limit) : documents.first
266
266
  end
267
267
  try_numbered_cache(:first, limit) do
268
- if limit_or_opts.try(:key?, :id_sort)
268
+ if opts.key?(:id_sort)
269
269
  Mongoid::Warnings.warn_id_sort_deprecated
270
270
  end
271
271
  sorted_view = view
272
- if sort = view.sort || ({ _id: 1 } unless limit_or_opts.try(:fetch, :id_sort) == :none)
272
+ if sort = view.sort || ({ _id: 1 } unless opts[:id_sort] == :none)
273
273
  sorted_view = view.sort(sort)
274
274
  end
275
275
  if raw_docs = sorted_view.limit(limit || 1).to_a
@@ -376,12 +376,12 @@ module Mongoid
376
376
  #
377
377
  # @return [ Document ] The last document.
378
378
  def last(limit_or_opts = nil)
379
- limit = limit_or_opts unless limit_or_opts.is_a?(Hash)
379
+ limit, opts = extract_limit_and_opts(limit_or_opts)
380
380
  if cached? && cache_loaded?
381
381
  return limit ? documents.last(limit) : documents.last
382
382
  end
383
383
  res = try_numbered_cache(:last, limit) do
384
- with_inverse_sorting(limit_or_opts) do
384
+ with_inverse_sorting(opts) do
385
385
  if raw_docs = view.limit(limit || 1).to_a
386
386
  process_raw_docs(raw_docs, limit)
387
387
  end
@@ -612,6 +612,23 @@ module Mongoid
612
612
  end
613
613
  end
614
614
 
615
+ # Extract the limit and opts from the given argument, so that code
616
+ # can operate without having to worry about the current type and
617
+ # state of the argument.
618
+ #
619
+ # @param [ nil | Integer | Hash ] limit_or_opts The value to pull the
620
+ # limit and option hash from.
621
+ #
622
+ # @return [ Array<nil | Integer, Hash> ] A 2-array of the limit and the
623
+ # option hash.
624
+ def extract_limit_and_opts(limit_or_opts)
625
+ case limit_or_opts
626
+ when nil, Integer then [ limit_or_opts, {} ]
627
+ when Hash then [ nil, limit_or_opts ]
628
+ else raise ArgumentError, "expected nil, Integer, or Hash"
629
+ end
630
+ end
631
+
615
632
  # Update the documents for the provided method.
616
633
  #
617
634
  # @api private
@@ -676,10 +693,10 @@ module Mongoid
676
693
  # @example Apply the inverse sorting params to the given block
677
694
  # context.with_inverse_sorting
678
695
  def with_inverse_sorting(opts = {})
679
- Mongoid::Warnings.warn_id_sort_deprecated if opts.try(:key?, :id_sort)
696
+ Mongoid::Warnings.warn_id_sort_deprecated if opts.key?(:id_sort)
680
697
 
681
698
  begin
682
- if sort = criteria.options[:sort] || ( { _id: 1 } unless opts.try(:fetch, :id_sort) == :none )
699
+ if sort = criteria.options[:sort] || ( { _id: 1 } unless opts[:id_sort] == :none )
683
700
  @view = view.sort(Hash[sort.map{|k, v| [k, -1*v]}])
684
701
  end
685
702
  yield
@@ -20,7 +20,7 @@ module Mongoid
20
20
  other.each_pair do |key, value|
21
21
  if value.is_a?(Hash) && self[key.to_s].is_a?(Hash)
22
22
  value = self[key.to_s].merge(value) do |_key, old_val, new_val|
23
- case _key
23
+ case _key.to_s
24
24
  when '$in'
25
25
  new_val & old_val
26
26
  when '$nin'
@@ -47,7 +47,7 @@ module Mongoid
47
47
  if value.is_a?(Hash) && selector[field].is_a?(Hash) &&
48
48
  value.keys.all? { |key|
49
49
  key_s = key.to_s
50
- key_s.start_with?('$') && !selector[field].key?(key_s)
50
+ key_s.start_with?('$') && !selector[field].keys.map(&:to_s).include?(key_s)
51
51
  }
52
52
  then
53
53
  # Multiple operators can be combined on the same field by
@@ -47,18 +47,22 @@ module Mongoid
47
47
  self.class.shard_key_fields
48
48
  end
49
49
 
50
- # Returns the selector that would match the current version of this
51
- # document.
50
+ # Returns the selector that would match the defined shard keys. If
51
+ # `prefer_persisted` is false (the default), it uses the current values
52
+ # of the specified shard keys, otherwise, it will try to use whatever value
53
+ # was most recently persisted.
54
+ #
55
+ # @param [ true | false ] prefer_persisted Whether to use the current
56
+ # value of the shard key fields, or to use their most recently persisted
57
+ # values.
52
58
  #
53
59
  # @return [ Hash ] The shard key selector.
54
60
  #
55
61
  # @api private
56
- def shard_key_selector
57
- selector = {}
58
- shard_key_fields.each do |field|
59
- selector[field.to_s] = send(field)
62
+ def shard_key_selector(prefer_persisted: false)
63
+ shard_key_fields.each_with_object({}) do |field, selector|
64
+ selector[field.to_s] = shard_key_field_value(field.to_s, prefer_persisted: prefer_persisted)
60
65
  end
61
- selector
62
66
  end
63
67
 
64
68
  # Returns the selector that would match the existing version of this
@@ -72,11 +76,31 @@ module Mongoid
72
76
  #
73
77
  # @api private
74
78
  def shard_key_selector_in_db
75
- selector = {}
76
- shard_key_fields.each do |field|
77
- selector[field.to_s] = new_record? ? send(field) : attribute_was(field)
79
+ shard_key_selector(prefer_persisted: true)
80
+ end
81
+
82
+ # Returns the value for the named shard key. If the field identifies
83
+ # an embedded document, the key will be parsed and recursively evaluated.
84
+ # If `prefer_persisted` is true, the value last persisted to the database
85
+ # will be returned, regardless of what the current value of the attribute
86
+ # may be.
87
+ #
88
+ # @param [String] field The name of the field to evaluate
89
+ # @param [ true|false ] prefer_persisted Whether or not to prefer the
90
+ # persisted value over the current value.
91
+ #
92
+ # @return [ Object ] The value of the named field.
93
+ #
94
+ # @api private
95
+ def shard_key_field_value(field, prefer_persisted:)
96
+ if field.include?(".")
97
+ relation, remaining = field.split(".", 2)
98
+ send(relation)&.shard_key_field_value(remaining, prefer_persisted: prefer_persisted)
99
+ elsif prefer_persisted && !new_record?
100
+ attribute_was(field)
101
+ else
102
+ send(field)
78
103
  end
79
- selector
80
104
  end
81
105
 
82
106
  module ClassMethods
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongoid
4
- VERSION = "7.5.2"
4
+ VERSION = "7.5.4"
5
5
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "spec_helper"
4
+ require_relative '../embeds_many_models.rb'
4
5
 
5
6
  describe Mongoid::Association::Embedded::EmbedsMany::Proxy do
6
7
 
@@ -4649,4 +4650,24 @@ describe Mongoid::Association::Embedded::EmbedsMany::Proxy do
4649
4650
  end
4650
4651
  end
4651
4652
  end
4653
+
4654
+ context "when using assign_attributes with an already populated array" do
4655
+ let(:post) { EmmPost.create! }
4656
+
4657
+ before do
4658
+ post.assign_attributes(company_tags: [{id: BSON::ObjectId.new, title: 'a'}],
4659
+ user_tags: [{id: BSON::ObjectId.new, title: 'b'}])
4660
+ post.save!
4661
+ post.reload
4662
+ post.assign_attributes(company_tags: [{id: BSON::ObjectId.new, title: 'c'}],
4663
+ user_tags: [])
4664
+ post.save!
4665
+ post.reload
4666
+ end
4667
+
4668
+ it "has the correct embedded documents" do
4669
+ expect(post.company_tags.length).to eq(1)
4670
+ expect(post.company_tags.first.title).to eq("c")
4671
+ end
4672
+ end
4652
4673
  end
@@ -67,3 +67,124 @@ class EmmOuter
67
67
 
68
68
  field :level, :type => Integer
69
69
  end
70
+
71
+ class EmmCustomerAddress
72
+ include Mongoid::Document
73
+
74
+ embedded_in :addressable, polymorphic: true, inverse_of: :work_address
75
+ end
76
+
77
+ class EmmFriend
78
+ include Mongoid::Document
79
+
80
+ embedded_in :befriendable, polymorphic: true
81
+ end
82
+
83
+ class EmmCustomer
84
+ include Mongoid::Document
85
+
86
+ embeds_one :home_address, class_name: 'EmmCustomerAddress', as: :addressable
87
+ embeds_one :work_address, class_name: 'EmmCustomerAddress', as: :addressable
88
+
89
+ embeds_many :close_friends, class_name: 'EmmFriend', as: :befriendable
90
+ embeds_many :acquaintances, class_name: 'EmmFriend', as: :befriendable
91
+ end
92
+
93
+ class EmmUser
94
+ include Mongoid::Document
95
+ include Mongoid::Timestamps
96
+
97
+ embeds_many :orders, class_name: 'EmmOrder'
98
+ end
99
+
100
+ class EmmOrder
101
+ include Mongoid::Document
102
+
103
+ field :amount, type: Integer
104
+
105
+ embedded_in :user, class_name: 'EmmUser'
106
+ end
107
+
108
+ module EmmSpec
109
+ # There is also a top-level Car class defined.
110
+ class Car
111
+ include Mongoid::Document
112
+
113
+ embeds_many :doors
114
+ end
115
+
116
+ class Door
117
+ include Mongoid::Document
118
+
119
+ embedded_in :car
120
+ end
121
+
122
+ class Tank
123
+ include Mongoid::Document
124
+
125
+ embeds_many :guns
126
+ embeds_many :emm_turrets
127
+ # This association references a model that is not in our module,
128
+ # and it does not define class_name hence Mongoid will not be able to
129
+ # figure out the inverse for this association.
130
+ embeds_many :emm_hatches
131
+
132
+ # class_name is intentionally unqualified, references a class in the
133
+ # same module. Rails permits class_name to be unqualified like this.
134
+ embeds_many :launchers, class_name: 'Launcher'
135
+ end
136
+
137
+ class Gun
138
+ include Mongoid::Document
139
+
140
+ embedded_in :tank
141
+ end
142
+
143
+ class Launcher
144
+ include Mongoid::Document
145
+
146
+ # class_name is intentionally unqualified.
147
+ embedded_in :tank, class_name: 'Tank'
148
+ end
149
+ end
150
+
151
+ # This is intentionally on top level.
152
+ class EmmTurret
153
+ include Mongoid::Document
154
+
155
+ embedded_in :tank, class_name: 'EmmSpec::Tank'
156
+ end
157
+
158
+ # This is intentionally on top level.
159
+ class EmmHatch
160
+ include Mongoid::Document
161
+
162
+ # No :class_name option on this association intentionally.
163
+ embedded_in :tank
164
+ end
165
+
166
+ class EmmPost
167
+ include Mongoid::Document
168
+
169
+ embeds_many :company_tags, class_name: "EmmCompanyTag"
170
+ embeds_many :user_tags, class_name: "EmmUserTag"
171
+ end
172
+
173
+
174
+ class EmmCompanyTag
175
+ include Mongoid::Document
176
+
177
+ field :title, type: String
178
+
179
+ embedded_in :post, class_name: "EmmPost"
180
+ end
181
+
182
+
183
+ class EmmUserTag
184
+ include Mongoid::Document
185
+
186
+ field :title, type: String
187
+
188
+ embedded_in :post, class_name: "EmmPost"
189
+ end
190
+
@@ -1554,9 +1554,9 @@ describe Mongoid::Contextual::Mongo do
1554
1554
  expect(context.send(method)).to eq(depeche_mode)
1555
1555
  end
1556
1556
 
1557
- context 'when calling #last' do
1557
+ context "when calling ##{method}" do
1558
1558
 
1559
- it 'returns the last document, sorted by _id' do
1559
+ it 'returns the requested document, sorted by _id' do
1560
1560
  expect(context.send(method)).to eq(depeche_mode)
1561
1561
  expect(context.last).to eq(rolling_stones)
1562
1562
  end
@@ -1571,7 +1571,7 @@ describe Mongoid::Contextual::Mongo do
1571
1571
  expect(context.send(method, opts)).to eq(depeche_mode)
1572
1572
  end
1573
1573
 
1574
- context 'when calling #last' do
1574
+ context "when calling ##{method}" do
1575
1575
 
1576
1576
  it 'doesn\'t apply a sort on _id' do
1577
1577
  expect(context.send(method, opts)).to eq(depeche_mode)
@@ -1883,6 +1883,16 @@ describe Mongoid::Contextual::Mongo do
1883
1883
  end
1884
1884
  end
1885
1885
 
1886
+ context "when given an empty hash" do
1887
+ let(:context) { described_class.new(criteria) }
1888
+ let(:criteria) { Band.criteria }
1889
+ let(:docs) { context.send(method, {}) }
1890
+
1891
+ it "behaves as if limit is nil" do
1892
+ expect(docs).to eq(depeche_mode)
1893
+ end
1894
+ end
1895
+
1886
1896
  context "when calling #first then #last" do
1887
1897
 
1888
1898
  let(:context) do
@@ -2358,6 +2368,16 @@ describe Mongoid::Contextual::Mongo do
2358
2368
  end
2359
2369
  end
2360
2370
  end
2371
+
2372
+ context "when given an empty hash" do
2373
+ let(:context) { described_class.new(criteria) }
2374
+ let(:criteria) { Band.criteria }
2375
+ let(:docs) { context.last({}) }
2376
+
2377
+ it "behaves as if limit is nil" do
2378
+ expect(docs).to eq(rolling_stones)
2379
+ end
2380
+ end
2361
2381
  end
2362
2382
 
2363
2383
  describe "#initialize" do
@@ -44,7 +44,7 @@ describe Mongoid::Criteria::Queryable::Selector do
44
44
  end
45
45
  end
46
46
 
47
- context "when selector contains a $nin" do
47
+ context "when selector contains a $nin string" do
48
48
 
49
49
  let(:initial) do
50
50
  { "$nin" => ["foo"] }
@@ -72,7 +72,35 @@ describe Mongoid::Criteria::Queryable::Selector do
72
72
  end
73
73
  end
74
74
 
75
- context "when selector contains a $in" do
75
+ context "when selector contains a $nin symbol" do
76
+
77
+ let(:initial) do
78
+ { :$nin => ["foo"] }
79
+ end
80
+
81
+ before do
82
+ selector["field"] = initial
83
+ end
84
+
85
+ context "when merging in a new $nin" do
86
+
87
+ let(:other) do
88
+ { "field" => { :$nin => ["bar"] } }
89
+ end
90
+
91
+ before do
92
+ selector.merge!(other)
93
+ end
94
+
95
+ it "combines the two $nin queries into one" do
96
+ expect(selector).to eq({
97
+ "field" => { :$nin => ["foo", "bar"] }
98
+ })
99
+ end
100
+ end
101
+ end
102
+
103
+ context "when selector contains a $in string" do
76
104
 
77
105
  let(:initial) do
78
106
  { "$in" => [1, 2] }
@@ -117,6 +145,51 @@ describe Mongoid::Criteria::Queryable::Selector do
117
145
  end
118
146
  end
119
147
 
148
+ context "when selector contains a $in symbol" do
149
+
150
+ let(:initial) do
151
+ { :$in => [1, 2] }
152
+ end
153
+
154
+ before do
155
+ selector["field"] = initial
156
+ end
157
+
158
+ context "when merging in a new $in with an intersecting value" do
159
+
160
+ let(:other) do
161
+ { "field" => { :$in => [1] } }
162
+ end
163
+
164
+ before do
165
+ selector.merge!(other)
166
+ end
167
+
168
+ it "intersects the $in values" do
169
+ expect(selector).to eq({
170
+ "field" => { :$in => [1] }
171
+ })
172
+ end
173
+ end
174
+
175
+ context "when merging in a new $in with no intersecting values" do
176
+
177
+ let(:other) do
178
+ { "field" => { :$in => [3] } }
179
+ end
180
+
181
+ before do
182
+ selector.merge!(other)
183
+ end
184
+
185
+ it "intersects the $in values" do
186
+ expect(selector).to eq({
187
+ "field" => { :$in => [] }
188
+ })
189
+ end
190
+ end
191
+ end
192
+
120
193
  context "when selector is not nested" do
121
194
 
122
195
  before do
@@ -210,7 +210,79 @@ describe Mongoid::Criteria::Queryable::Storable do
210
210
  }
211
211
  end
212
212
  end
213
+
214
+ context 'when value is a hash combine values with different operator keys' do
215
+ let(:base) do
216
+ query.add_field_expression('foo', {'$in' => ['bar']})
217
+ end
218
+
219
+ let(:modified) do
220
+ base.add_field_expression('foo', {'$nin' => ['zoom']})
221
+ end
222
+
223
+ it 'combines the conditions using $and' do
224
+ modified.selector.should == {
225
+ 'foo' => {
226
+ '$in' => ['bar'],
227
+ '$nin' => ['zoom']
228
+ }
229
+ }
230
+ end
231
+ end
232
+
233
+ context 'when value is a hash with symbol operator key combine values with different operator keys' do
234
+ let(:base) do
235
+ query.add_field_expression('foo', {:$in => ['bar']})
236
+ end
237
+
238
+ let(:modified) do
239
+ base.add_field_expression('foo', {:$nin => ['zoom']})
240
+ end
241
+
242
+ it 'combines the conditions using $and' do
243
+ modified.selector.should == {
244
+ 'foo' => {
245
+ :$in => ['bar'],
246
+ :$nin => ['zoom']
247
+ }
248
+ }
249
+ end
250
+ end
251
+
252
+ context 'when value is a hash add values with same operator keys using $and' do
253
+ let(:base) do
254
+ query.add_field_expression('foo', {'$in' => ['bar']})
255
+ end
256
+
257
+ let(:modified) do
258
+ base.add_field_expression('foo', {'$in' => ['zoom']})
259
+ end
260
+
261
+ it 'adds the new condition using $and' do
262
+ modified.selector.should == {
263
+ 'foo' => {'$in' => ['bar']},
264
+ '$and' => ['foo' => {'$in' => ['zoom']}]
265
+ }
266
+ end
267
+ end
268
+
269
+ context 'when value is a hash with symbol operator key add values with same operator keys using $and' do
270
+ let(:base) do
271
+ query.add_field_expression('foo', {:$in => ['bar']})
272
+ end
273
+
274
+ let(:modified) do
275
+ base.add_field_expression('foo', {:$in => ['zoom']})
276
+ end
277
+
278
+ it 'adds the new condition using $and' do
279
+ modified.selector.should == {
280
+ 'foo' => {:$in => ['bar']},
281
+ '$and' => ['foo' => {:$in => ['zoom']}]
282
+ }
283
+ end
213
284
  end
285
+ end
214
286
 
215
287
  describe '#add_operator_expression' do
216
288
  let(:query_method) { :add_operator_expression }