mongoid 9.0.0 → 9.0.1

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