mongoid 8.0.6 → 8.0.8

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: 51995917ca452899edfb1e432aecad879c476f89057c5377e55081a5c2a989ac
4
- data.tar.gz: b01e8f7141b1f37d0ddfc51b5e4fa889f09b85988069af8cc8af7214a00a888c
3
+ metadata.gz: 4d604924530b809dbef3eef08d7e3c52b14e815d674821789a98c590f704b149
4
+ data.tar.gz: bfefc068fa68ed135560cff738dd7641f7df3780d01600a9f936752d5c760b8c
5
5
  SHA512:
6
- metadata.gz: 7a011a2367c77d3e7abdc57a3781f45c97c71451afcaae7a3358a3e0173e8d3bc4ef09ef92d32f8d72c14ca89b5d8f3d9fccdf8b0a7b84ccb9a874329413b561
7
- data.tar.gz: a868fc0217fc98cccfa8b6c3f9c0dccb4425354ddb4f23a1819f52dc539e2754e8dd8a508a804e05bddeeb8be73fe09ff6f027d141a22d3f479c3adc63097fcb
6
+ metadata.gz: 50280304426a7dc55cb2e86d2ce69d295f5b51df6f714dee96e4a5c0b8da00c1ca09ad516d300da6755cf5160699a982868bb46c1afd2da812f952ca8c18e240
7
+ data.tar.gz: 0fccee8bb9add55b0dfaa6454a33c89fe40553ce1a3fe9bef8be36fe4c92a884970d2e2b2a44c32761a198e750351276ef38f27a5e95f7fe11be02686425568b
checksums.yaml.gz.sig CHANGED
Binary file
@@ -178,13 +178,15 @@ module Mongoid
178
178
  #
179
179
  # @return [ Object ] The associated path.
180
180
  def atomic_paths
181
- @atomic_paths ||= begin
182
- if _association
183
- _association.path(self)
184
- else
185
- Atomic::Paths::Root.new(self)
186
- end
187
- end
181
+ return @atomic_paths if @atomic_paths
182
+
183
+ paths = if _association
184
+ _association.path(self)
185
+ else
186
+ Atomic::Paths::Root.new(self)
187
+ end
188
+
189
+ paths.tap { @atomic_paths = paths unless new_record? }
188
190
  end
189
191
 
190
192
  # Get all the attributes that need to be pulled.
@@ -142,6 +142,19 @@ module Mongoid
142
142
  end
143
143
  end
144
144
 
145
+ # When this flag is true, callbacks for embedded documents will not be
146
+ # called. This is the default in 8.x, but will be changed to false in 9.0.
147
+ #
148
+ # Setting this flag to true (as it is in 8.x) may lead to stack
149
+ # overflow errors if there are more than cicrca 1000 embedded
150
+ # documents in the root document's dependencies graph.
151
+ #
152
+ # It is strongly recommended to set this flag to false in 8.x, if you
153
+ # are not using around callbacks for embedded documents.
154
+ #
155
+ # See https://jira.mongodb.org/browse/MONGOID-5658 for more details.
156
+ option :around_callbacks_for_embeds, default: true
157
+
145
158
  # Has Mongoid been configured? This is checking that at least a valid
146
159
  # client config exists.
147
160
  #
@@ -56,7 +56,12 @@ module Mongoid
56
56
  # @return [ Integer ] The number of matches.
57
57
  def count(options = {}, &block)
58
58
  return super(&block) if block_given?
59
- view.count_documents(options)
59
+
60
+ if valid_for_count_documents?
61
+ view.count_documents(options)
62
+ else
63
+ view.count(options)
64
+ end
60
65
  end
61
66
 
62
67
  # Get the estimated number of documents matching the query.
@@ -813,6 +818,24 @@ module Mongoid
813
818
  docs = eager_load(docs)
814
819
  limit ? docs : docs.first
815
820
  end
821
+
822
+ # Queries whether the current context is valid for use with
823
+ # the #count_documents? predicate. A context is valid if it
824
+ # does not include a `$where` operator.
825
+ #
826
+ # @return [ true | false ] whether or not the current context
827
+ # excludes a `$where` operator.
828
+ def valid_for_count_documents?(hash = view.filter)
829
+ # Note that `view.filter` is a BSON::Document, and all keys in a
830
+ # BSON::Document are strings; we don't need to worry about symbol
831
+ # representations of `$where`.
832
+ hash.keys.each do |key|
833
+ return false if key == '$where'
834
+ return false if hash[key].is_a?(Hash) && !valid_for_count_documents?(hash[key])
835
+ end
836
+
837
+ true
838
+ end
816
839
  end
817
840
  end
818
841
  end
@@ -24,10 +24,11 @@ module Mongoid
24
24
  # #=> Mongoid.logger.warn("meow is deprecated and will be removed from Mongoid 8.0 (eat :catnip instead)")
25
25
  #
26
26
  # @param [ Module ] target_module The parent which contains the method.
27
- # @param [ Symbol | Hash<Symbol, [ Symbol | String ]> ] method_descriptors
27
+ # @param [ [ Symbol | Hash<Symbol, [ Symbol | String ]> ]... ] *method_descriptors
28
28
  # The methods to deprecate, with optional replacement instructions.
29
29
  def deprecate(target_module, *method_descriptors)
30
- Mongoid::Deprecation.deprecate_methods(target_module, *method_descriptors)
30
+ @_deprecator ||= Mongoid::Deprecation.new
31
+ @_deprecator.deprecate_methods(target_module, *method_descriptors)
31
32
  end
32
33
  end
33
34
  end
@@ -15,10 +15,10 @@ module Mongoid
15
15
  #
16
16
  # @return Array<Proc> The deprecation behavior.
17
17
  def behavior
18
- @behavior ||= Array(->(message, callstack, _deprecation_horizon, _gem_name) {
18
+ @behavior ||= Array(->(*args) {
19
19
  logger = Mongoid.logger
20
- logger.warn(message)
21
- logger.debug(callstack.join("\n ")) if debug
20
+ logger.warn(args[0])
21
+ logger.debug(args[1].join("\n ")) if debug
22
22
  })
23
23
  end
24
24
  end
@@ -43,7 +43,7 @@ module Mongoid
43
43
  real_key = klass.database_field_name(key2)
44
44
 
45
45
  value.delete(key2) if real_key != key2
46
- value[real_key] = (key == "$rename") ? value2.to_s : mongoize_for(key, klass, real_key, value2)
46
+ value[real_key] = value_for(key, klass, real_key, value2)
47
47
  end
48
48
  consolidated[key] ||= {}
49
49
  consolidated[key].update(value)
@@ -185,6 +185,24 @@ module Mongoid
185
185
 
186
186
  private
187
187
 
188
+ # Get the value for the provided operator, klass, key and value.
189
+ #
190
+ # This is necessary for special cases like $rename, $addToSet and $push.
191
+ #
192
+ # @param [ String ] operator The operator.
193
+ # @param [ Class ] klass The model class.
194
+ # @param [ String | Symbol ] key The field key.
195
+ # @param [ Object ] value The original value.
196
+ #
197
+ # @return [ Object ] Value prepared for the provided operator.
198
+ def value_for(operator, klass, key, value)
199
+ case operator
200
+ when "$rename" then value.to_s
201
+ when "$addToSet", "$push" then value.mongoize
202
+ else mongoize_for(operator, klass, operator, value)
203
+ end
204
+ end
205
+
188
206
  # Mongoize for the klass, key and value.
189
207
  #
190
208
  # @api private
@@ -79,7 +79,7 @@ module Mongoid
79
79
  # @example Run only the after save callbacks.
80
80
  # model.run_after_callbacks(:save)
81
81
  #
82
- # @param [ Array<Symbol> ] kinds The events that are occurring.
82
+ # @param [ Symbol... ] *kinds The events that are occurring.
83
83
  #
84
84
  # @return [ Object ] The result of the chain executing.
85
85
  def run_after_callbacks(*kinds)
@@ -96,7 +96,7 @@ module Mongoid
96
96
  # @example Run only the before save callbacks.
97
97
  # model.run_before_callbacks(:save, :create)
98
98
  #
99
- # @param [ Array<Symbol> ] kinds The events that are occurring.
99
+ # @param [ Symbol... ] *kinds The events that are occurring.
100
100
  #
101
101
  # @return [ Object ] The result of the chain executing.
102
102
  def run_before_callbacks(*kinds)
@@ -134,36 +134,126 @@ module Mongoid
134
134
  # Run the callbacks for embedded documents.
135
135
  #
136
136
  # @param [ Symbol ] kind The type of callback to execute.
137
- # @param [ Array<Document> ] children Children to exeute callbacks on. If
137
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
138
138
  # nil, callbacks will be executed on all cascadable children of
139
139
  # the document.
140
140
  #
141
141
  # @api private
142
142
  def _mongoid_run_child_callbacks(kind, children: nil, &block)
143
+ if Mongoid::Config.around_callbacks_for_embeds
144
+ _mongoid_run_child_callbacks_with_around(kind, children: children, &block)
145
+ else
146
+ _mongoid_run_child_callbacks_without_around(kind, children: children, &block)
147
+ end
148
+ end
149
+
150
+ # Execute the callbacks of given kind for embedded documents including
151
+ # around callbacks.
152
+ #
153
+ # @note This method is prone to stack overflow errors if the document
154
+ # has a large number of embedded documents. It is recommended to avoid
155
+ # using around callbacks for embedded documents until a proper solution
156
+ # is implemented.
157
+ #
158
+ # @param [ Symbol ] kind The type of callback to execute.
159
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
160
+ # nil, callbacks will be executed on all cascadable children of
161
+ # the document.
162
+ #
163
+ # @api private
164
+ def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block)
143
165
  child, *tail = (children || cascadable_children(kind))
144
166
  if child.nil?
145
- return block&.call
167
+ block&.call
146
168
  elsif tail.empty?
147
- return child.run_callbacks(child_callback_type(kind, child), &block)
169
+ child.run_callbacks(child_callback_type(kind, child), &block)
148
170
  else
149
- return child.run_callbacks(child_callback_type(kind, child)) do
150
- _mongoid_run_child_callbacks(kind, children: tail, &block)
171
+ child.run_callbacks(child_callback_type(kind, child)) do
172
+ _mongoid_run_child_callbacks_with_around(kind, children: tail, &block)
151
173
  end
152
174
  end
153
175
  end
154
176
 
155
- # This is used to store callbacks to be executed later. A good use case for
156
- # this is delaying the after_find and after_initialize callbacks until the
157
- # associations are set on the document. This can also be used to delay
158
- # applying the defaults on a document.
177
+ # Execute the callbacks of given kind for embedded documents without
178
+ # around callbacks.
179
+ #
180
+ # @param [ Symbol ] kind The type of callback to execute.
181
+ # @param [ Array<Document> ] children Children to execute callbacks on. If
182
+ # nil, callbacks will be executed on all cascadable children of
183
+ # the document.
184
+ #
185
+ # @api private
186
+ def _mongoid_run_child_callbacks_without_around(kind, children: nil, &block)
187
+ children = (children || cascadable_children(kind))
188
+ callback_list = _mongoid_run_child_before_callbacks(kind, children: children)
189
+ return false if callback_list == false
190
+ value = block&.call
191
+ callback_list.each do |_next_sequence, env|
192
+ env.value &&= value
193
+ end
194
+ return false if _mongoid_run_child_after_callbacks(callback_list: callback_list) == false
195
+
196
+ value
197
+ end
198
+
199
+ # Execute the before callbacks of given kind for embedded documents.
200
+ #
201
+ # @param [ Symbol ] kind The type of callback to execute.
202
+ # @param [ Array<Document> ] children Children to execute callbacks on.
203
+ # @param [ Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment> ] callback_list List of
204
+ # pairs of callback sequence and environment. This list will be later used
205
+ # to execute after callbacks in reverse order.
159
206
  #
160
- # @return [ Array<Symbol> ] an array of symbols that represent the pending callbacks.
207
+ # @api private
208
+ def _mongoid_run_child_before_callbacks(kind, children: [], callback_list: [])
209
+ children.each do |child|
210
+ chain = child.__callbacks[child_callback_type(kind, child)]
211
+ env = ActiveSupport::Callbacks::Filters::Environment.new(child, false, nil)
212
+ next_sequence = compile_callbacks(chain)
213
+ unless next_sequence.final?
214
+ Mongoid.logger.warn("Around callbacks are disabled for embedded documents. Skipping around callbacks for #{child.class.name}.")
215
+ Mongoid.logger.warn("To enable around callbacks for embedded documents, set Mongoid::Config.around_callbacks_for_embeds to true.")
216
+ end
217
+ next_sequence.invoke_before(env)
218
+ return false if env.halted
219
+ env.value = !env.halted
220
+ callback_list << [next_sequence, env]
221
+ if (grandchildren = child.send(:cascadable_children, kind))
222
+ _mongoid_run_child_before_callbacks(kind, children: grandchildren, callback_list: callback_list)
223
+ end
224
+ end
225
+ callback_list
226
+ end
227
+
228
+ # Execute the after callbacks.
229
+ #
230
+ # @param [ Array<ActiveSupport::Callbacks::CallbackSequence, ActiveSupport::Callbacks::Filters::Environment> ] callback_list List of
231
+ # pairs of callback sequence and environment.
232
+ def _mongoid_run_child_after_callbacks(callback_list: [])
233
+ callback_list.reverse_each do |next_sequence, env|
234
+ next_sequence.invoke_after(env)
235
+ return false if env.halted
236
+ end
237
+ end
238
+
239
+ # Returns the stored callbacks to be executed later.
240
+ #
241
+ # @return [ Array<Symbol> ] Method symbols of the stored pending callbacks.
161
242
  #
162
243
  # @api private
163
244
  def pending_callbacks
164
245
  @pending_callbacks ||= [].to_set
165
246
  end
166
247
 
248
+ # Stores callbacks to be executed later. A good use case for
249
+ # this is delaying the after_find and after_initialize callbacks until the
250
+ # associations are set on the document. This can also be used to delay
251
+ # applying the defaults on a document.
252
+ #
253
+ # @param [ Array<Symbol> ] value Method symbols of the pending callbacks to store.
254
+ #
255
+ # @return [ Array<Symbol> ] Method symbols of the stored pending callbacks.
256
+ #
167
257
  # @api private
168
258
  def pending_callbacks=(value)
169
259
  @pending_callbacks = value
@@ -298,7 +388,7 @@ module Mongoid
298
388
  end
299
389
  self.class.send :define_method, name do
300
390
  env = ActiveSupport::Callbacks::Filters::Environment.new(self, false, nil)
301
- sequence = chain.compile
391
+ sequence = compile_callbacks(chain)
302
392
  sequence.invoke_before(env)
303
393
  env.value = !env.halted
304
394
  sequence.invoke_after(env)
@@ -308,5 +398,24 @@ module Mongoid
308
398
  end
309
399
  send(name)
310
400
  end
401
+
402
+ # Compile the callback chain.
403
+ #
404
+ # This method hides the differences between ActiveSupport implementations
405
+ # before and after 7.1.
406
+ #
407
+ # @param [ ActiveSupport::Callbacks::CallbackChain ] chain The callback chain.
408
+ # @param [ Symbol | nil ] type The type of callback chain to compile.
409
+ #
410
+ # @return [ ActiveSupport::Callbacks::CallbackSequence ] The compiled callback sequence.
411
+ def compile_callbacks(chain, type = nil)
412
+ if chain.method(:compile).arity == 0
413
+ # ActiveSupport < 7.1
414
+ chain.compile
415
+ else
416
+ # ActiveSupport >= 7.1
417
+ chain.compile(type)
418
+ end
419
+ end
311
420
  end
312
421
  end
@@ -15,32 +15,110 @@ module Mongoid
15
15
  #
16
16
  # validates_associated :name, :addresses
17
17
  # end
18
- class AssociatedValidator < ActiveModel::EachValidator
18
+ class AssociatedValidator < ActiveModel::Validator
19
+ # Required by `validates_with` so that the validator
20
+ # gets added to the correct attributes.
21
+ def attributes
22
+ options[:attributes]
23
+ end
19
24
 
20
- # Validates that the associations provided are either all nil or all
21
- # valid. If neither is true then the appropriate errors will be added to
22
- # the parent document.
25
+ # Checks that the named associations of the given record
26
+ # (`attributes`) are valid. This does NOT load the associations
27
+ # from the database, and will only validate records that are dirty
28
+ # or unpersisted.
23
29
  #
24
- # @example Validate the association.
25
- # validator.validate_each(document, :name, name)
30
+ # If anything is not valid, appropriate errors will be added to
31
+ # the `document` parameter.
32
+ #
33
+ # @param [ Mongoid::Document ] document the document with the
34
+ # associations to validate.
35
+ def validate(document)
36
+ options[:attributes].each do |attr_name|
37
+ validate_association(document, attr_name)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # Validates that the given association provided is either nil,
44
+ # persisted and unchanged, or invalid. Otherwise, the appropriate errors
45
+ # will be added to the parent document.
26
46
  #
27
47
  # @param [ Document ] document The document to validate.
28
48
  # @param [ Symbol ] attribute The association to validate.
29
- # @param [ Object ] value The value of the association.
30
- def validate_each(document, attribute, value)
31
- begin
32
- document.begin_validate
33
- valid = Array.wrap(value).collect do |doc|
34
- if doc.nil? || doc.flagged_for_destroy?
35
- true
49
+ def validate_association(document, attribute)
50
+ # grab the proxy from the instance variable directly; we don't want
51
+ # any loading logic to run; we just want to see if it's already
52
+ # been loaded.
53
+ proxy = document.ivar(attribute)
54
+ return unless proxy
55
+
56
+ # if the variable exists, now we see if it is a proxy, or an actual
57
+ # document. It might be a literal document instead of a proxy if this
58
+ # document was created with a Document instance as a provided attribute,
59
+ # e.g. "Post.new(message: Message.new)".
60
+ target = proxy.respond_to?(:_target) ? proxy._target : proxy
61
+
62
+ # Now, fetch the list of documents from the target. Target may be a
63
+ # single value, or a list of values, and in the case of HasMany,
64
+ # might be a rather complex collection. We need to do this without
65
+ # triggering a load, so it's a bit of a delicate dance.
66
+ list = get_target_documents(target)
67
+
68
+ valid = document.validating do
69
+ # Now, treating the target as an array, look at each element
70
+ # and see if it is valid, but only if it has already been
71
+ # persisted, or changed, and hasn't been flagged for destroy.
72
+ list.all? do |value|
73
+ if value && !value.flagged_for_destroy? && (!value.persisted? || value.changed?)
74
+ value.validated? ? true : value.valid?
36
75
  else
37
- doc.validated? ? true : doc.valid?
76
+ true
38
77
  end
39
- end.all?
40
- ensure
41
- document.exit_validate
78
+ end
79
+ end
80
+
81
+ document.errors.add(attribute, :invalid) unless valid
82
+ end
83
+
84
+ private
85
+
86
+ # Examine the given target object and return an array of
87
+ # documents (possibly empty) that the target represents.
88
+ #
89
+ # @param [ Array | Mongoid::Document | Mongoid::Association::Proxy | HasMany::Enumerable ] target
90
+ # the target object to examine.
91
+ #
92
+ # @return [ Array<Mongoid::Document> ] the list of documents
93
+ def get_target_documents(target)
94
+ if target.respond_to?(:_loaded?)
95
+ get_target_documents_for_has_many(target)
96
+ else
97
+ get_target_documents_for_other(target)
42
98
  end
43
- document.errors.add(attribute, :invalid, **options) unless valid
99
+ end
100
+
101
+ # Returns the list of all currently in-memory values held by
102
+ # the target. The target will not be loaded.
103
+ #
104
+ # @param [ HasMany::Enumerable ] target the target that will
105
+ # be examined for in-memory documents.
106
+ #
107
+ # @return [ Array<Mongoid::Document> ] the in-memory documents
108
+ # held by the target.
109
+ def get_target_documents_for_has_many(target)
110
+ [ *target._loaded.values, *target._added.values ]
111
+ end
112
+
113
+ # Returns the target as an array. If the target represents a single
114
+ # value, it is wrapped in an array.
115
+ #
116
+ # @param [ Array | Mongoid::Document | Mongoid::Association::Proxy ] target
117
+ # the target to return.
118
+ #
119
+ # @return [ Array<Mongoid::Document> ] the target, as an array.
120
+ def get_target_documents_for_other(target)
121
+ Array.wrap(target)
44
122
  end
45
123
  end
46
124
  end
@@ -37,6 +37,14 @@ module Mongoid
37
37
  Threaded.exit_validate(self)
38
38
  end
39
39
 
40
+ # Perform a validation within the associated block.
41
+ def validating
42
+ begin_validate
43
+ yield
44
+ ensure
45
+ exit_validate
46
+ end
47
+
40
48
  # Given the provided options, are we performing validations?
41
49
  #
42
50
  # @example Are we performing validations?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mongoid
4
- VERSION = "8.0.6"
4
+ VERSION = "8.0.8"
5
5
  end
@@ -2,6 +2,28 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
+ module HabtmSpec
6
+ class Page
7
+ include Mongoid::Document
8
+ embeds_many :blocks, class_name: 'HabtmSpec::Block'
9
+ end
10
+
11
+ class Block
12
+ include Mongoid::Document
13
+ embedded_in :page, class_name: 'HabtmSpec::Page'
14
+ end
15
+
16
+ class ImageBlock < Block
17
+ has_and_belongs_to_many :attachments, inverse_of: nil, class_name: 'HabtmSpec::Attachment'
18
+ accepts_nested_attributes_for :attachments
19
+ end
20
+
21
+ class Attachment
22
+ include Mongoid::Document
23
+ field :file, type: String
24
+ end
25
+ end
26
+
5
27
  describe 'has_and_belongs_to_many associations' do
6
28
 
7
29
  context 'when an anonymous class defines a has_and_belongs_to_many association' do
@@ -18,4 +40,22 @@ describe 'has_and_belongs_to_many associations' do
18
40
  expect(klass.new.movies.build).to be_a Movie
19
41
  end
20
42
  end
43
+
44
+ context 'when an embedded has habtm relation' do
45
+ let(:attachment) { HabtmSpec::Attachment.create!(file: 'foo.jpg') }
46
+
47
+ let(:page) { HabtmSpec::Page.create! }
48
+
49
+ let(:image_block) do
50
+ image_block = page.blocks.build({
51
+ _type: 'HabtmSpec::ImageBlock',
52
+ attachment_ids: [ attachment.id.to_s ],
53
+ attachments_attributes: { '1234' => { file: 'bar.jpg', id: attachment.id.to_s } }
54
+ })
55
+ end
56
+
57
+ it 'does not raise on save' do
58
+ expect { image_block.save! }.not_to raise_error
59
+ end
60
+ end
21
61
  end
@@ -447,4 +447,24 @@ describe 'callbacks integration tests' do
447
447
  expect(saved_person.previously_persisted_value).to be_truthy
448
448
  end
449
449
  end
450
+
451
+ context 'cascade callbacks' do
452
+ ruby_version_gte '3.0'
453
+ config_override :around_callbacks_for_embeds, false
454
+
455
+ let(:book) do
456
+ Book.new
457
+ end
458
+
459
+ before do
460
+ 1500.times do
461
+ book.pages.build
462
+ end
463
+ end
464
+
465
+ # https://jira.mongodb.org/browse/MONGOID-5658
466
+ it 'does not raise SystemStackError' do
467
+ expect { book.save! }.not_to raise_error(SystemStackError)
468
+ end
469
+ end
450
470
  end
@@ -608,7 +608,7 @@ describe Mongoid::Config do
608
608
 
609
609
  it 'passes uuid to driver' do
610
610
  Mongo::Client.should receive(:new).with(SpecConfig.instance.addresses,
611
- auto_encryption_options: {
611
+ { auto_encryption_options: {
612
612
  'key_vault_namespace' => 'admin.datakeys',
613
613
  'kms_providers' => {'local' => {'key' => 'z7iYiYKLuYymEWtk4kfny1ESBwwFdA58qMqff96A8ghiOcIK75lJGPUIocku8LOFjQuEgeIP4xlln3s7r93FV9J5sAE7zg8U'}},
614
614
  'schema_map' => {'blog_development.comments' => {
@@ -625,7 +625,7 @@ describe Mongoid::Config do
625
625
  platform: "mongoid-#{Mongoid::VERSION}",
626
626
  wrapping_libraries: [
627
627
  {'name' => 'Mongoid', 'version' => Mongoid::VERSION},
628
- ],
628
+ ]},
629
629
  )
630
630
 
631
631
  client
@@ -158,6 +158,16 @@ describe Mongoid::Contextual::Mongo do
158
158
  end
159
159
  end
160
160
  end
161
+
162
+ context 'when for_js is present' do
163
+ let(:context) do
164
+ Band.for_js('this.name == "Depeche Mode"')
165
+ end
166
+
167
+ it 'counts the expected records' do
168
+ expect(context.count).to eq(1)
169
+ end
170
+ end
161
171
  end
162
172
 
163
173
  describe "#estimated_count" do
@@ -3563,16 +3573,51 @@ describe Mongoid::Contextual::Mongo do
3563
3573
 
3564
3574
  context "when the attributes are in the correct type" do
3565
3575
 
3566
- before do
3567
- context.update_all("$set" => { name: "Smiths" })
3576
+ context "when operation is $set" do
3577
+
3578
+ before do
3579
+ context.update_all("$set" => { name: "Smiths" })
3580
+ end
3581
+
3582
+ it "updates the first matching document" do
3583
+ expect(depeche_mode.reload.name).to eq("Smiths")
3584
+ end
3585
+
3586
+ it "updates the last matching document" do
3587
+ expect(new_order.reload.name).to eq("Smiths")
3588
+ end
3568
3589
  end
3569
3590
 
3570
- it "updates the first matching document" do
3571
- expect(depeche_mode.reload.name).to eq("Smiths")
3591
+ context "when operation is $push" do
3592
+
3593
+ before do
3594
+ depeche_mode.update_attribute(:genres, ["electronic"])
3595
+ new_order.update_attribute(:genres, ["electronic"])
3596
+ context.update_all("$push" => { genres: "pop" })
3597
+ end
3598
+
3599
+ it "updates the first matching document" do
3600
+ expect(depeche_mode.reload.genres).to eq(["electronic", "pop"])
3601
+ end
3602
+
3603
+ it "updates the last matching document" do
3604
+ expect(new_order.reload.genres).to eq(["electronic", "pop"])
3605
+ end
3572
3606
  end
3573
3607
 
3574
- it "updates the last matching document" do
3575
- expect(new_order.reload.name).to eq("Smiths")
3608
+ context "when operation is $addToSet" do
3609
+
3610
+ before do
3611
+ context.update_all("$addToSet" => { genres: "electronic" })
3612
+ end
3613
+
3614
+ it "updates the first matching document" do
3615
+ expect(depeche_mode.reload.genres).to eq(["electronic"])
3616
+ end
3617
+
3618
+ it "updates the last matching document" do
3619
+ expect(new_order.reload.genres).to eq(["electronic"])
3620
+ end
3576
3621
  end
3577
3622
  end
3578
3623