mongoid 9.0.0 → 9.0.1

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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/Rakefile +44 -21
  4. data/lib/config/locales/en.yml +4 -0
  5. data/lib/mongoid/atomic_update_preparer.rb +7 -6
  6. data/lib/mongoid/config.rb +9 -0
  7. data/lib/mongoid/contextual/aggregable/memory.rb +3 -2
  8. data/lib/mongoid/contextual/aggregable/mongo.rb +5 -2
  9. data/lib/mongoid/criteria/findable.rb +2 -2
  10. data/lib/mongoid/criteria/queryable/extensions/numeric.rb +15 -1
  11. data/lib/mongoid/errors/invalid_around_callback.rb +16 -0
  12. data/lib/mongoid/errors.rb +1 -0
  13. data/lib/mongoid/fields.rb +13 -7
  14. data/lib/mongoid/interceptable.rb +18 -13
  15. data/lib/mongoid/scopable.rb +7 -1
  16. data/lib/mongoid/touchable.rb +1 -7
  17. data/lib/mongoid/version.rb +1 -1
  18. data/spec/mongoid/association_spec.rb +14 -0
  19. data/spec/mongoid/attributes_spec.rb +16 -0
  20. data/spec/mongoid/contextual/aggregable/memory_spec.rb +11 -0
  21. data/spec/mongoid/contextual/aggregable/mongo_spec.rb +11 -0
  22. data/spec/mongoid/contextual/mongo_spec.rb +72 -3
  23. data/spec/mongoid/fields_spec.rb +2 -2
  24. data/spec/mongoid/interceptable_spec.rb +31 -0
  25. data/spec/mongoid/scopable_spec.rb +88 -85
  26. data/spec/mongoid/touchable_spec.rb +75 -0
  27. data/spec/mongoid/touchable_spec_models.rb +16 -0
  28. data/spec/support/models/band.rb +1 -0
  29. data/spec/support/models/lat_lng.rb +6 -0
  30. metadata +8 -80
  31. checksums.yaml.gz.sig +0 -1
  32. data/spec/shared/LICENSE +0 -20
  33. data/spec/shared/bin/get-mongodb-download-url +0 -17
  34. data/spec/shared/bin/s3-copy +0 -45
  35. data/spec/shared/bin/s3-upload +0 -69
  36. data/spec/shared/lib/mrss/child_process_helper.rb +0 -80
  37. data/spec/shared/lib/mrss/cluster_config.rb +0 -231
  38. data/spec/shared/lib/mrss/constraints.rb +0 -378
  39. data/spec/shared/lib/mrss/docker_runner.rb +0 -298
  40. data/spec/shared/lib/mrss/eg_config_utils.rb +0 -51
  41. data/spec/shared/lib/mrss/event_subscriber.rb +0 -210
  42. data/spec/shared/lib/mrss/lite_constraints.rb +0 -238
  43. data/spec/shared/lib/mrss/server_version_registry.rb +0 -113
  44. data/spec/shared/lib/mrss/session_registry.rb +0 -69
  45. data/spec/shared/lib/mrss/session_registry_legacy.rb +0 -60
  46. data/spec/shared/lib/mrss/spec_organizer.rb +0 -179
  47. data/spec/shared/lib/mrss/utils.rb +0 -37
  48. data/spec/shared/share/Dockerfile.erb +0 -281
  49. data/spec/shared/share/haproxy-1.conf +0 -16
  50. data/spec/shared/share/haproxy-2.conf +0 -17
  51. data/spec/shared/shlib/config.sh +0 -27
  52. data/spec/shared/shlib/distro.sh +0 -74
  53. data/spec/shared/shlib/server.sh +0 -417
  54. data/spec/shared/shlib/set_env.sh +0 -146
  55. data.tar.gz.sig +0 -0
  56. metadata.gz.sig +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 38b7f297f31cb9dbf9e703769a789969ef5de77ba9f7b0ebf998c0aec1c76524
4
- data.tar.gz: 0aeb2382db008ef20fe7635546da3c13df040933b31005c859747d8cd16247f9
3
+ metadata.gz: f756ccb6822c09519cb0e833a2d3bd2edf5b5a0abf3f3c29205a756682e5d954
4
+ data.tar.gz: 7d44016955b7e2e90d2dc07b2142cc6c79bacfb83831705f26fdafc773e17e7c
5
5
  SHA512:
6
- metadata.gz: de6b4d0dbf469baaa5900cbd968a82d74d3eaf1f2b52cc002ddbb37f1531a343c0b5e48a54b47e554a81a1389c7daf43653aa9338653fa7eb7c171d309dd2990
7
- data.tar.gz: 1202e0a37c944780c9b9b7ffca7a89736df81d7dd8757bfd0c2c2ec6723e7ee1ebc088feeca7479a1103fc3290ff679282bc5241cf67335505e56e08a0fee1d6
6
+ metadata.gz: e69bc7c2b6031330346265b5c6cdc8d11909fd1204e32727845e3220c6f105dde407006635f31491df9857597711f610a688818dfaa3bc32f27525e2574ce29d
7
+ data.tar.gz: 259996128cd10553af4972c6b40f5a8ac537158eeaa1cd012bbc9e6d90d67ad745def51fbd47bafaffe9330324967695e45787732446b305d5bdd13338b94075
data/README.md CHANGED
@@ -14,6 +14,8 @@ Mongoid has [extensive user documentation](https://www.mongodb.com/docs/mongoid/
14
14
  Mongoid is built on top of the MongoDB Ruby driver which has
15
15
  [its own user documentation](https://www.mongodb.com/docs/ruby-driver/current/).
16
16
 
17
+ High-level Mongoid documentation including tutorials and the reference that were in the docs folder can now be found at the docs-mongoid repository, [here](https://github.com/mongodb/docs-mongoid).
18
+
17
19
  Compatibility
18
20
  -------------
19
21
 
data/Rakefile CHANGED
@@ -2,7 +2,6 @@
2
2
  # rubocop:todo all
3
3
 
4
4
  require "bundler"
5
- require "bundler/gem_tasks"
6
5
  Bundler.setup
7
6
 
8
7
  ROOT = File.expand_path(File.join(File.dirname(__FILE__)))
@@ -11,25 +10,53 @@ $: << File.join(ROOT, 'spec/shared/lib')
11
10
 
12
11
  require "rake"
13
12
  require "rspec/core/rake_task"
14
- require 'mrss/spec_organizer'
15
13
 
16
- $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
17
- require "mongoid/version"
18
-
19
- tasks = Rake.application.instance_variable_get('@tasks')
20
- tasks['release:do'] = tasks.delete('release')
21
-
22
- task :gem => :build
14
+ # stands in for the Bundler-provided `build` task, which builds the
15
+ # gem for this project. Our release process builds the gems in a
16
+ # particular way, in a GitHub action. This task is just to help remind
17
+ # developers of that fact.
23
18
  task :build do
24
- system "gem build mongoid.gemspec"
19
+ abort <<~WARNING
20
+ `rake build` does nothing in this project. The gem must be built via
21
+ the `Mongoid Release` action on GitHub, which is triggered manually when
22
+ a new release is ready.
23
+ WARNING
25
24
  end
26
25
 
27
- task :install => :build do
28
- system "sudo gem install mongoid-#{Mongoid::VERSION}.gem"
26
+ # `rake version` is used by the deployment system so get the release version
27
+ # of the product beng deployed. It must do nothing more than just print the
28
+ # product version number.
29
+ #
30
+ # See the mongodb-labs/driver-github-tools/ruby/publish Github action.
31
+ desc "Print the current value of Mongoid::VERSION"
32
+ task :version do
33
+ require 'mongoid/version'
34
+
35
+ puts Mongoid::VERSION
29
36
  end
30
37
 
38
+ # overrides the default Bundler-provided `release` task, which also
39
+ # builds the gem. Our release process assumes the gem has already
40
+ # been built (and signed via GPG), so we just need `rake release` to
41
+ # push the gem to rubygems.
31
42
  task :release do
32
- raise "Please use ./release.sh to release"
43
+ require 'mongoid/version'
44
+
45
+ if ENV['GITHUB_ACTION'].nil?
46
+ abort <<~WARNING
47
+ `rake release` must be invoked from the `Mongoid Release` GitHub action,
48
+ and must not be invoked locally. This ensures the gem is properly signed
49
+ and distributed by the appropriate user.
50
+
51
+ Note that it is the `rubygems/release-gem@v1` step in the `Mongoid Release`
52
+ action that invokes this task. Do not rename or remove this task, or the
53
+ release-gem step will fail. Reimplement this task with caution.
54
+
55
+ mongoid-#{Mongoid::VERSION}.gem was NOT pushed to RubyGems.
56
+ WARNING
57
+ end
58
+
59
+ system 'gem', 'push', "mongoid-#{Mongoid::VERSION}.gem"
33
60
  end
34
61
 
35
62
  RSpec::Core::RakeTask.new("spec") do |spec|
@@ -96,6 +123,8 @@ RUN_PRIORITY = %i(
96
123
  )
97
124
 
98
125
  def spec_organizer
126
+ require 'mrss/spec_organizer'
127
+
99
128
  Mrss::SpecOrganizer.new(
100
129
  root: ROOT,
101
130
  classifiers: CLASSIFIERS,
@@ -131,16 +160,10 @@ task :docs => 'docs:yard'
131
160
  namespace :docs do
132
161
  desc "Generate yard documentation"
133
162
  task :yard do
163
+ require "mongoid/version"
164
+
134
165
  out = File.join('yard-docs', Mongoid::VERSION)
135
166
  FileUtils.rm_rf(out)
136
167
  system "yardoc -o #{out} --title mongoid-#{Mongoid::VERSION}"
137
168
  end
138
169
  end
139
-
140
- namespace :release do
141
- task :check_private_key do
142
- unless File.exist?('gem-private_key.pem')
143
- raise "No private key present, cannot release"
144
- end
145
- end
146
- end
@@ -140,6 +140,10 @@ en:
140
140
  A collation option is only supported if the query is executed on a MongoDB server
141
141
  with version >= 3.4."
142
142
  resolution: "Remove the collation option from the query."
143
+ invalid_around_callback:
144
+ message: "An around callback must contain a yield in its definition."
145
+ summary: "The block needs to be yielded to for around callbacks to function as intended."
146
+ resolution: "Ensure there is a yield statement inside the body of the around callback."
143
147
  invalid_async_query_executor:
144
148
  message: "Invalid async_query_executor option: %{executor}."
145
149
  summary: "A invalid async query executor was specified.
@@ -25,7 +25,7 @@ module Mongoid
25
25
  if key.to_s.start_with?('$')
26
26
  (atomic_updates[key] ||= {}).update(prepare_operation(klass, key, value))
27
27
  else
28
- (atomic_updates['$set'] ||= {})[key] = mongoize_for(key, klass, key, value)
28
+ (atomic_updates['$set'] ||= {})[key] = mongoize_for('$set', klass, key, value)
29
29
  end
30
30
  end
31
31
  end
@@ -43,24 +43,25 @@ module Mongoid
43
43
  def prepare_operation(klass, key, value)
44
44
  value.each_with_object({}) do |(key2, value2), hash|
45
45
  key2 = klass.database_field_name(key2)
46
- hash[key2] = value_for(key, klass, value2)
46
+ hash[key2] = value_for(key, klass, key2, value2)
47
47
  end
48
48
  end
49
49
 
50
50
  # Get the value for the provided operator, klass, key and value.
51
51
  #
52
- # This is necessary for special cases like $rename, $addToSet and $push.
52
+ # This is necessary for special cases like $rename, $addToSet, $push, $pull and $pop.
53
53
  #
54
54
  # @param [ String ] operator The operator.
55
55
  # @param [ Class ] klass The model class.
56
+ # @param [ String | Symbol ] key The field key.
56
57
  # @param [ Object ] value The original value.
57
58
  #
58
59
  # @return [ Object ] Value prepared for the provided operator.
59
- def value_for(operator, klass, value)
60
+ def value_for(operator, klass, key, value)
60
61
  case operator
61
62
  when '$rename' then value.to_s
62
- when '$addToSet', '$push' then value.mongoize
63
- else mongoize_for(operator, klass, operator, value)
63
+ when '$addToSet', '$push', '$pull', '$pop' then value.mongoize
64
+ else mongoize_for(operator, klass, key, value)
64
65
  end
65
66
  end
66
67
 
@@ -171,6 +171,15 @@ module Mongoid
171
171
  # See https://jira.mongodb.org/browse/MONGOID-5658 for more details.
172
172
  option :around_callbacks_for_embeds, default: false
173
173
 
174
+ # When this flag is false, named scopes cannot unset a default scope.
175
+ # This is the traditional (and default) behavior in Mongoid 9 and earlier.
176
+ #
177
+ # Setting this flag to true will allow named scopes to unset the default
178
+ # scope. This will be the default in Mongoid 10.
179
+ #
180
+ # See https://jira.mongodb.org/browse/MONGOID-5785 for more details.
181
+ option :allow_scopes_to_unset_default_scope, default: false
182
+
174
183
  # Returns the Config singleton, for use in the configure DSL.
175
184
  #
176
185
  # @return [ self ] The Config singleton.
@@ -90,11 +90,12 @@ module Mongoid
90
90
  # @example Get the sum for the provided block.
91
91
  # aggregable.sum(&:likes)
92
92
  #
93
- # @param [ Symbol ] field The field to sum.
93
+ # @param [ Symbol | Numeric ] field The field to sum, or the intial
94
+ # value of the sum when a block is given.
94
95
  #
95
96
  # @return [ Numeric ] The sum value.
96
97
  def sum(field = nil)
97
- return super() if block_given?
98
+ return super(field || 0) if block_given?
98
99
 
99
100
  aggregate_by(field, :sum) || 0
100
101
  end
@@ -96,11 +96,14 @@ module Mongoid
96
96
  # @example Get the sum for the provided block.
97
97
  # aggregable.sum(&:likes)
98
98
  #
99
- # @param [ Symbol ] field The field to sum.
99
+ # @param [ Symbol | Numeric ] field The field to sum, or the initial
100
+ # value of the sum when a block is given.
100
101
  #
101
102
  # @return [ Float ] The sum value.
102
103
  def sum(field = nil)
103
- block_given? ? super() : aggregates(field)["sum"] || 0
104
+ return super(field || 0) if block_given?
105
+
106
+ aggregates(field)["sum"] || 0
104
107
  end
105
108
 
106
109
  private
@@ -169,12 +169,12 @@ module Mongoid
169
169
  args.size > 1 || !args.first.is_a?(Hash) && args.first.resizable?
170
170
  end
171
171
 
172
- # Convenience method of raising an invalid options error.
172
+ # Convenience method of raising an invalid find error.
173
173
  #
174
174
  # @example Raise the error.
175
175
  # criteria.raise_invalid
176
176
  #
177
- # @raise [ Errors::InvalidOptions ] The error.
177
+ # @raise [ Errors::InvalidFind ] The error.
178
178
  def raise_invalid
179
179
  raise Errors::InvalidFind.new
180
180
  end
@@ -44,7 +44,21 @@ module Mongoid
44
44
  #
45
45
  # @return [ Object ] The converted number.
46
46
  def __numeric__(object)
47
- object.to_s.match?(/\A[-+]?[0-9]*[0-9.]0*\z/) ? object.to_i : Float(object)
47
+ str = object.to_s
48
+ raise ArgumentError if str.empty?
49
+
50
+ # These requirements seem a bit odd, but they're explicitly specified in the tests,
51
+ # so we're obligated to keep them, for now. (This code was rewritten from a one-line
52
+ # regex, due to security concerns with a polynomial regex being used on uncontrolled
53
+ # data).
54
+
55
+ str = str.chop if str.end_with?('.')
56
+ return 0 if str.empty?
57
+
58
+ result = Integer(str) rescue Float(object)
59
+
60
+ integer = result.to_i
61
+ integer == result ? integer : result
48
62
  end
49
63
 
50
64
  # Evolve the object to an integer.
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mongoid
4
+ module Errors
5
+ # This error is raised when an around callback is
6
+ # defined by the user without a yield
7
+ class InvalidAroundCallback < MongoidError
8
+ # Create the new error.
9
+ #
10
+ # @api private
11
+ def initialize
12
+ super(compose_message('invalid_around_callback'))
13
+ end
14
+ end
15
+ end
16
+ end
@@ -13,6 +13,7 @@ require "mongoid/errors/empty_config_file"
13
13
  require "mongoid/errors/immutable_attribute"
14
14
  require "mongoid/errors/in_memory_collation_not_supported"
15
15
  require "mongoid/errors/invalid_auto_encryption_configuration"
16
+ require "mongoid/errors/invalid_around_callback"
16
17
  require "mongoid/errors/invalid_async_query_executor"
17
18
  require "mongoid/errors/invalid_collection"
18
19
  require "mongoid/errors/invalid_config_file"
@@ -49,6 +49,11 @@ module Mongoid
49
49
  # @api private
50
50
  INVALID_BSON_CLASSES = [ BSON::Decimal128, BSON::Int32, BSON::Int64 ].freeze
51
51
 
52
+ # The suffix for generated translated fields.
53
+ #
54
+ # @api private
55
+ TRANSLATIONS_SFX = '_translations'
56
+
52
57
  module ClassMethods
53
58
  # Returns the list of id fields for this model class, as both strings
54
59
  # and symbols.
@@ -101,8 +106,8 @@ module Mongoid
101
106
  ar.each_with_index do |fn, i|
102
107
  key = fn
103
108
  unless klass.fields.key?(fn) || klass.relations.key?(fn)
104
- if tr = fn.match(/(.*)_translations\z/)&.captures&.first
105
- key = tr
109
+ if fn.end_with?(TRANSLATIONS_SFX)
110
+ key = fn.delete_suffix(TRANSLATIONS_SFX)
106
111
  else
107
112
  key = fn
108
113
  end
@@ -408,7 +413,8 @@ module Mongoid
408
413
  #
409
414
  # @api private
410
415
  def database_field_name(name, relations, aliased_fields, aliased_associations)
411
- return nil unless name.present?
416
+ return "" unless name.present?
417
+
412
418
  key = name.to_s
413
419
  segment, remaining = key.split('.', 2)
414
420
 
@@ -725,11 +731,11 @@ module Mongoid
725
731
  # @api private
726
732
  def create_translations_getter(name, meth)
727
733
  generated_methods.module_eval do
728
- re_define_method("#{meth}_translations") do
734
+ re_define_method("#{meth}#{TRANSLATIONS_SFX}") do
729
735
  attributes[name] ||= {}
730
736
  attributes[name].with_indifferent_access
731
737
  end
732
- alias_method :"#{meth}_t", :"#{meth}_translations"
738
+ alias_method :"#{meth}_t", :"#{meth}#{TRANSLATIONS_SFX}"
733
739
  end
734
740
  end
735
741
 
@@ -745,14 +751,14 @@ module Mongoid
745
751
  # @api private
746
752
  def create_translations_setter(name, meth, field)
747
753
  generated_methods.module_eval do
748
- re_define_method("#{meth}_translations=") do |value|
754
+ re_define_method("#{meth}#{TRANSLATIONS_SFX}=") do |value|
749
755
  attribute_will_change!(name)
750
756
  value&.transform_values! do |_value|
751
757
  field.type.mongoize(_value)
752
758
  end
753
759
  attributes[name] = value
754
760
  end
755
- alias_method :"#{meth}_t=", :"#{meth}_translations="
761
+ alias_method :"#{meth}_t=", :"#{meth}#{TRANSLATIONS_SFX}="
756
762
  end
757
763
  end
758
764
 
@@ -161,11 +161,6 @@ module Mongoid
161
161
  # Execute the callbacks of given kind for embedded documents including
162
162
  # around callbacks.
163
163
  #
164
- # @note This method is prone to stack overflow errors if the document
165
- # has a large number of embedded documents. It is recommended to avoid
166
- # using around callbacks for embedded documents until a proper solution
167
- # is implemented.
168
- #
169
164
  # @param [ Symbol ] kind The type of callback to execute.
170
165
  # @param [ Array<Document> ] children Children to execute callbacks on. If
171
166
  # nil, callbacks will be executed on all cascadable children of
@@ -173,17 +168,27 @@ module Mongoid
173
168
  #
174
169
  # @api private
175
170
  def _mongoid_run_child_callbacks_with_around(kind, children: nil, &block)
176
- child, *tail = (children || cascadable_children(kind))
171
+ children = (children || cascadable_children(kind))
177
172
  with_children = !Mongoid::Config.prevent_multiple_calls_of_embedded_callbacks
178
- if child.nil?
179
- block&.call
180
- elsif tail.empty?
181
- child.run_callbacks(child_callback_type(kind, child), with_children: with_children, &block)
182
- else
183
- child.run_callbacks(child_callback_type(kind, child), with_children: with_children) do
184
- _mongoid_run_child_callbacks_with_around(kind, children: tail, &block)
173
+
174
+ return block&.call if children.empty?
175
+
176
+ fibers = children.map do |child|
177
+ Fiber.new do
178
+ child.run_callbacks(child_callback_type(kind, child), with_children: with_children) do
179
+ Fiber.yield
180
+ end
185
181
  end
186
182
  end
183
+
184
+ fibers.each do |fiber|
185
+ fiber.resume
186
+ raise Mongoid::Errors::InvalidAroundCallback unless fiber.alive?
187
+ end
188
+
189
+ block&.call
190
+
191
+ fibers.reverse.each(&:resume)
187
192
  end
188
193
 
189
194
  # Execute the callbacks of given kind for embedded documents without
@@ -294,7 +294,13 @@ module Mongoid
294
294
  scope = instance_exec(*args, **kwargs, &scoping[:scope])
295
295
  extension = scoping[:extension]
296
296
  to_merge = scope || queryable
297
- criteria = to_merge.empty_and_chainable? ? to_merge : with_default_scope.merge(to_merge)
297
+
298
+ criteria = if Mongoid.allow_scopes_to_unset_default_scope
299
+ to_merge
300
+ else
301
+ to_merge.empty_and_chainable? ? to_merge : with_default_scope.merge(to_merge)
302
+ end
303
+
298
304
  criteria.extend(extension)
299
305
  criteria
300
306
  end
@@ -78,7 +78,7 @@ module Mongoid
78
78
  field = database_field_name(field)
79
79
 
80
80
  write_attribute(:updated_at, now) if respond_to?("updated_at=")
81
- write_attribute(field, now) if field
81
+ write_attribute(field, now) if field.present?
82
82
 
83
83
  touches = _extract_touches_from_atomic_sets(field) || {}
84
84
  touches.merge!(_parent._gather_touch_updates(now) || {}) if _touchable_parent?
@@ -212,12 +212,6 @@ module Mongoid
212
212
  #
213
213
  # @return [ Symbol ] The method name.
214
214
  def define_relation_touch_method(name, association)
215
- relation_classes = if association.polymorphic?
216
- association.send(:inverse_association_classes)
217
- else
218
- [ association.relation_class ]
219
- end
220
-
221
215
  method_name = "touch_#{name}_after_create_or_destroy"
222
216
  association.inverse_class.class_eval do
223
217
  define_method(method_name) do
@@ -2,5 +2,5 @@
2
2
  # rubocop:todo all
3
3
 
4
4
  module Mongoid
5
- VERSION = "9.0.0"
5
+ VERSION = "9.0.1"
6
6
  end
@@ -15,6 +15,20 @@ describe Mongoid::Association do
15
15
  )
16
16
  end
17
17
 
18
+ context "when class_name references an unknown class" do
19
+ context "when loading" do
20
+ it "does not raise an exception" do
21
+ expect do
22
+ class AssocationSpecModel
23
+ include Mongoid::Document
24
+
25
+ embedded_in :parent, class_name: 'SomethingBogusThatDoesNotExistYet'
26
+ end
27
+ end.not_to raise_exception
28
+ end
29
+ end
30
+ end
31
+
18
32
  describe "#embedded?" do
19
33
 
20
34
  let(:person) do
@@ -289,6 +289,22 @@ describe Mongoid::Attributes do
289
289
  end
290
290
  end
291
291
 
292
+ context "when given nil" do
293
+
294
+ it "returns nil" do
295
+ expect(person[nil]).to be nil
296
+ end
297
+
298
+ end
299
+
300
+ context "when given an empty string" do
301
+
302
+ it "returns nil" do
303
+ expect(person[""]).to be nil
304
+ end
305
+
306
+ end
307
+
292
308
  context "when the field was not explicitly defined" do
293
309
 
294
310
  context "when excluding with only and the field was not excluded" do
@@ -570,5 +570,16 @@ describe Mongoid::Contextual::Aggregable::Memory do
570
570
  expect(sum).to eq(1500)
571
571
  end
572
572
  end
573
+
574
+ context "when provided a block with initial value" do
575
+
576
+ let(:sum) do
577
+ context.sum(500, &:likes)
578
+ end
579
+
580
+ it "returns the sum for the provided block starting from initial value" do
581
+ expect(sum).to eq(2000)
582
+ end
583
+ end
573
584
  end
574
585
  end
@@ -515,6 +515,17 @@ describe Mongoid::Contextual::Aggregable::Mongo do
515
515
  expect(sum).to eq(1500)
516
516
  end
517
517
  end
518
+
519
+ context "when provided a block with initial value" do
520
+
521
+ let(:sum) do
522
+ context.sum(500, &:likes)
523
+ end
524
+
525
+ it "returns the sum for the provided block starting from initial value" do
526
+ expect(sum).to eq(2000)
527
+ end
528
+ end
518
529
  end
519
530
  end
520
531
 
@@ -3640,6 +3640,75 @@ describe Mongoid::Contextual::Mongo do
3640
3640
  expect(new_order.reload.genres).to eq(["electronic"])
3641
3641
  end
3642
3642
  end
3643
+
3644
+ context "when operation is $pull" do
3645
+ context "when pulling single element" do
3646
+
3647
+ before do
3648
+ depeche_mode.update_attribute(:genres, ["electronic", "pop"])
3649
+ new_order.update_attribute(:genres, ["electronic", "pop"])
3650
+ context.update_all("$pull" => { genres: "electronic" })
3651
+ end
3652
+
3653
+ it "updates the first matching document" do
3654
+ expect(depeche_mode.reload.genres).to eq(["pop"])
3655
+ end
3656
+
3657
+ it "updates the last matching document" do
3658
+ expect(new_order.reload.genres).to eq(["pop"])
3659
+ end
3660
+ end
3661
+
3662
+ context "when pulling based on condition" do
3663
+ before do
3664
+ depeche_mode.update_attribute(:genres, ["electronic", "pop", "dance"])
3665
+ new_order.update_attribute(:genres, ["electronic", "pop", "dance"])
3666
+ context.update_all("$pull" => { genres: { '$in' => ["electronic", "pop"] } })
3667
+ end
3668
+
3669
+ it "updates the first matching document" do
3670
+ expect(depeche_mode.reload.genres).to eq(["dance"])
3671
+ end
3672
+
3673
+ it "updates the last matching document" do
3674
+ expect(new_order.reload.genres).to eq(["dance"])
3675
+ end
3676
+ end
3677
+ end
3678
+
3679
+ context "when operation is $pop" do
3680
+
3681
+ before do
3682
+ depeche_mode.update_attribute(:genres, ["pop", "electronic"])
3683
+ end
3684
+
3685
+ it "removes first element in array" do
3686
+ context.update_all("$pop" => { genres: -1 })
3687
+ expect(depeche_mode.reload.genres).to eq(["electronic"])
3688
+ end
3689
+
3690
+ it "removes last element in array" do
3691
+ context.update_all("$pop" => { genres: 1 })
3692
+ expect(depeche_mode.reload.genres).to eq(["pop"])
3693
+ end
3694
+ end
3695
+
3696
+ context "when operation is $pullAll" do
3697
+
3698
+ before do
3699
+ depeche_mode.update_attribute(:genres, ["pop", "electronic", "dance", "pop" ])
3700
+ new_order.update_attribute(:genres, ["electronic", "pop", "electronic", "dance"])
3701
+ context.update_all("$pullAll" => { genres: ["pop", "electronic"] })
3702
+ end
3703
+
3704
+ it "updates the first matching document" do
3705
+ expect(depeche_mode.reload.genres).to eq(["dance"])
3706
+ end
3707
+
3708
+ it "updates the last matching document" do
3709
+ expect(new_order.reload.genres).to eq(["dance"])
3710
+ end
3711
+ end
3643
3712
  end
3644
3713
 
3645
3714
  context 'when using aliased field names' do
@@ -3659,15 +3728,15 @@ describe Mongoid::Contextual::Mongo do
3659
3728
  context "when the attributes must be mongoized" do
3660
3729
 
3661
3730
  before do
3662
- context.update_all("$set" => { member_count: "1" })
3731
+ context.update_all("$set" => { location: LatLng.new(52.30, 13.25) })
3663
3732
  end
3664
3733
 
3665
3734
  it "updates the first matching document" do
3666
- expect(depeche_mode.reload.member_count).to eq(1)
3735
+ expect(depeche_mode.reload.location).to eq(LatLng.new(52.30, 13.25))
3667
3736
  end
3668
3737
 
3669
3738
  it "updates the last matching document" do
3670
- expect(new_order.reload.member_count).to eq(1)
3739
+ expect(new_order.reload.location).to eq(LatLng.new(52.30, 13.25))
3671
3740
  end
3672
3741
  end
3673
3742
  end
@@ -1871,12 +1871,12 @@ describe Mongoid::Fields do
1871
1871
 
1872
1872
  context 'given nil' do
1873
1873
  subject { Person.database_field_name(nil) }
1874
- it { is_expected.to eq nil }
1874
+ it { is_expected.to eq '' }
1875
1875
  end
1876
1876
 
1877
1877
  context 'given an empty String' do
1878
1878
  subject { Person.database_field_name('') }
1879
- it { is_expected.to eq nil }
1879
+ it { is_expected.to eq '' }
1880
1880
  end
1881
1881
 
1882
1882
  context 'given a String' do