mongoid 7.5.2 → 7.5.4

Sign up to get free protection for your applications and to get access to all the features.
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 }