mongoid 6.3.0 → 6.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Rakefile +12 -0
  5. data/lib/config/locales/en.yml +21 -0
  6. data/lib/mongoid.rb +2 -2
  7. data/lib/mongoid/clients.rb +2 -0
  8. data/lib/mongoid/clients/sessions.rb +113 -0
  9. data/lib/mongoid/clients/storage_options.rb +1 -0
  10. data/lib/mongoid/contextual/aggregable/mongo.rb +1 -1
  11. data/lib/mongoid/contextual/map_reduce.rb +7 -3
  12. data/lib/mongoid/contextual/memory.rb +7 -2
  13. data/lib/mongoid/contextual/mongo.rb +11 -2
  14. data/lib/mongoid/criteria.rb +1 -0
  15. data/lib/mongoid/criteria/modifiable.rb +12 -2
  16. data/lib/mongoid/criteria/queryable/mergeable.rb +3 -1
  17. data/lib/mongoid/criteria/queryable/selectable.rb +34 -7
  18. data/lib/mongoid/document.rb +4 -4
  19. data/lib/mongoid/errors.rb +1 -0
  20. data/lib/mongoid/errors/invalid_session_use.rb +24 -0
  21. data/lib/mongoid/extensions/big_decimal.rb +1 -1
  22. data/lib/mongoid/extensions/regexp.rb +1 -0
  23. data/lib/mongoid/extensions/string.rb +3 -1
  24. data/lib/mongoid/indexable.rb +4 -4
  25. data/lib/mongoid/matchable.rb +3 -0
  26. data/lib/mongoid/matchable/nor.rb +37 -0
  27. data/lib/mongoid/persistable.rb +1 -1
  28. data/lib/mongoid/persistable/creatable.rb +4 -2
  29. data/lib/mongoid/persistable/deletable.rb +4 -2
  30. data/lib/mongoid/persistable/destroyable.rb +1 -5
  31. data/lib/mongoid/persistable/settable.rb +5 -5
  32. data/lib/mongoid/persistable/updatable.rb +2 -2
  33. data/lib/mongoid/persistable/upsertable.rb +2 -1
  34. data/lib/mongoid/persistence_context.rb +4 -0
  35. data/lib/mongoid/railtie.rb +17 -0
  36. data/lib/mongoid/railties/controller_runtime.rb +86 -0
  37. data/lib/mongoid/relations/embedded/batchable.rb +10 -4
  38. data/lib/mongoid/relations/embedded/many.rb +23 -0
  39. data/lib/mongoid/relations/many.rb +4 -0
  40. data/lib/mongoid/relations/referenced/many.rb +1 -1
  41. data/lib/mongoid/relations/touchable.rb +1 -1
  42. data/lib/mongoid/reloadable.rb +1 -1
  43. data/lib/mongoid/scopable.rb +3 -3
  44. data/lib/mongoid/tasks/database.rb +3 -2
  45. data/lib/mongoid/threaded.rb +74 -0
  46. data/lib/mongoid/version.rb +1 -1
  47. data/lib/rails/generators/mongoid/config/templates/mongoid.yml +4 -0
  48. data/spec/app/models/array_field.rb +7 -0
  49. data/spec/app/models/delegating_patient.rb +16 -0
  50. data/spec/integration/document_spec.rb +22 -0
  51. data/spec/mongoid/attributes/nested_spec.rb +4 -0
  52. data/spec/mongoid/clients/factory_spec.rb +52 -28
  53. data/spec/mongoid/clients/options_spec.rb +30 -15
  54. data/spec/mongoid/clients/sessions_spec.rb +334 -0
  55. data/spec/mongoid/contextual/geo_near_spec.rb +1 -0
  56. data/spec/mongoid/contextual/mongo_spec.rb +40 -2
  57. data/spec/mongoid/criteria/modifiable_spec.rb +59 -10
  58. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +3 -3
  59. data/spec/mongoid/criteria/queryable/selectable_spec.rb +74 -6
  60. data/spec/mongoid/criteria/queryable/selector_spec.rb +2 -2
  61. data/spec/mongoid/criteria/scopable_spec.rb +81 -0
  62. data/spec/mongoid/criteria_spec.rb +4 -1
  63. data/spec/mongoid/document_spec.rb +54 -0
  64. data/spec/mongoid/extensions/big_decimal_spec.rb +9 -9
  65. data/spec/mongoid/extensions/regexp_spec.rb +23 -0
  66. data/spec/mongoid/extensions/string_spec.rb +35 -7
  67. data/spec/mongoid/fields_spec.rb +1 -1
  68. data/spec/mongoid/findable_spec.rb +1 -1
  69. data/spec/mongoid/interceptable_spec.rb +1 -1
  70. data/spec/mongoid/matchable/nor_spec.rb +209 -0
  71. data/spec/mongoid/matchable_spec.rb +26 -1
  72. data/spec/mongoid/persistable/deletable_spec.rb +19 -0
  73. data/spec/mongoid/persistable/destroyable_spec.rb +19 -0
  74. data/spec/mongoid/persistable/incrementable_spec.rb +6 -6
  75. data/spec/mongoid/persistable/settable_spec.rb +35 -1
  76. data/spec/mongoid/persistable_spec.rb +16 -16
  77. data/spec/mongoid/relations/embedded/many_spec.rb +246 -16
  78. data/spec/mongoid/scopable_spec.rb +13 -0
  79. data/spec/mongoid/threaded_spec.rb +68 -0
  80. data/spec/rails/controller_extension/controller_runtime_spec.rb +110 -0
  81. data/spec/spec_helper.rb +79 -0
  82. data/spec/support/cluster_config.rb +158 -0
  83. data/spec/support/constraints.rb +101 -0
  84. data/spec/support/macros.rb +20 -0
  85. data/spec/support/spec_config.rb +42 -0
  86. metadata +471 -443
  87. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 5c7e1fbb3fda280fb0e0f74c312b36b7a73dad79
4
- data.tar.gz: 92dcab3023e9005560efd4209e3eecd26032b259
2
+ SHA256:
3
+ metadata.gz: 6df494597043866e131edd0f3ac733a2b9858819d0ea5e64dd71a876dc0f5c92
4
+ data.tar.gz: 11b00b2c3c74e486c605729487caacbad6b6790ad1168d9f9f25088018187c1d
5
5
  SHA512:
6
- metadata.gz: 44e65ba1446fc7c42e545b38223d4122e798f85d26aeb93103b85c45e7a4abc17e3b7da9f60853a7a38a24b5d4a258338111759d0b22bb4e5f99611e002eaa0f
7
- data.tar.gz: 25b84f09ac4fb6366a0d9782d0865a0a1ae214234fbdbde4ce57146d8bf740823b66f15d4871e4a45b0d3fc5f0557b9f91dd743501713ae6920a430e90bab62e
6
+ metadata.gz: ef53e2257470d0ba736764568da53a4ccef25e525d1f5782c7e364f313be22f0b6c5bbabab354608da0de5887e7f505602936cc3e701fc1487c0969bcae3061e
7
+ data.tar.gz: 2fe686e8ad285385f787079a8a065e4ad7311dc1a034be6bcc722611d976b5b9b3bf421fc9a6fab861dce3d296178138c325998bada913e5b8f8d83298b3ee6c
Binary file
data.tar.gz.sig CHANGED
Binary file
data/Rakefile CHANGED
@@ -33,3 +33,15 @@ RSpec::Core::RakeTask.new('spec:progress') do |spec|
33
33
  end
34
34
 
35
35
  task :default => :spec
36
+
37
+ desc "Generate all documentation"
38
+ task :docs => 'docs:yard'
39
+
40
+ namespace :docs do
41
+ desc "Generate yard documention"
42
+ task :yard do
43
+ out = File.join('yard-docs', Mongoid::VERSION)
44
+ FileUtils.rm_rf(out)
45
+ system "yardoc -o #{out} --title mongoid-#{Mongoid::VERSION}"
46
+ end
47
+ end
@@ -206,6 +206,20 @@ en:
206
206
  \_\_\_\_include Mongoid::Document\n
207
207
  \_\_\_\_scope :inactive, ->{ where(active: false) }\n
208
208
  \_\_end\n\n"
209
+ invalid_session_use:
210
+ message: "A session was attempted to be used with a model whose client cannot use
211
+ that session."
212
+ summary: "Sessions are started via driver clients (Model#mongo_client) and, in most cases, driver
213
+ clients are shared across models. When different models have their own clients, a session cannot
214
+ be obtained via one model and used for operations on another model."
215
+ resolution: "Only execute operations on the model class or instances of the model through which
216
+ the session was created. Otherwise, ensure that all models on which operations are executed
217
+ in the session block share the same driver client. For example, a model may have a different
218
+ client specified in its 'store_in' options.\n\n"
219
+ invalid_session_nesting:
220
+ message: "A session was started while another session was being used."
221
+ summary: "Sessions cannot be nested. Only one session can be used in a thread at once."
222
+ resolution: "Only use one session at a time; sessions cannot be nested."
209
223
  invalid_storage_options:
210
224
  message: "Invalid options passed to %{klass}.store_in: %{options}."
211
225
  summary: "The :store_in macro takes only a hash of parameters with
@@ -452,6 +466,13 @@ en:
452
466
  with the already defined method %{model_name}, or set the
453
467
  configuration option Mongoid.scope_overwrite_exception to false,
454
468
  which is its default. In this case a warning will be logged."
469
+ sessions_not_supported:
470
+ message: "Sessions are not supported by the connected server(s)."
471
+ summary: "A session was attempted to be used with a MongoDB server version
472
+ that doesn't support sessions. Sessions are supported in MongoDB
473
+ server versions 3.6 and higher."
474
+ resolution: "Verify that all servers in your deployment are at least
475
+ version 3.6 or don't attempt to use sessions with older server versions."
455
476
  taken:
456
477
  "is already taken"
457
478
  too_many_nested_attribute_records:
@@ -42,7 +42,7 @@ module Mongoid
42
42
  PLATFORM_DETAILS = "mongoid-#{VERSION}".freeze
43
43
 
44
44
  # The minimum MongoDB version supported.
45
- MONGODB_VERSION = "2.4.0"
45
+ MONGODB_VERSION = "2.6.0"
46
46
 
47
47
  # Sets the Mongoid configuration options. Best used by passing a block.
48
48
  #
@@ -101,5 +101,5 @@ module Mongoid
101
101
  # Mongoid.database = Mongo::Connection.new.db("test")
102
102
  #
103
103
  # @since 1.0.0
104
- delegate(*(Config.public_instance_methods(false) - [ :logger=, :logger ] << { to: Config }))
104
+ delegate(*(Config.public_instance_methods(false) - [ :logger=, :logger ]), to: Config)
105
105
  end
@@ -3,12 +3,14 @@ require "mongoid/clients/factory"
3
3
  require "mongoid/clients/validators"
4
4
  require "mongoid/clients/storage_options"
5
5
  require "mongoid/clients/options"
6
+ require "mongoid/clients/sessions"
6
7
 
7
8
  module Mongoid
8
9
  module Clients
9
10
  extend ActiveSupport::Concern
10
11
  include StorageOptions
11
12
  include Options
13
+ include Sessions
12
14
 
13
15
  class << self
14
16
 
@@ -0,0 +1,113 @@
1
+ module Mongoid
2
+ module Clients
3
+
4
+ # Encapsulates behavior for getting a session from the client of a model class or instance,
5
+ # setting the session on the current thread, and yielding to a block.
6
+ # The session will be closed after the block completes or raises an error.
7
+ #
8
+ # @since 6.4.0
9
+ module Sessions
10
+
11
+ # Execute a block within the context of a session.
12
+ #
13
+ # @example Execute some operations in the context of a session.
14
+ # band.with_session(causal_consistency: true) do
15
+ # band.records << Record.create
16
+ # band.name = 'FKA Twigs'
17
+ # band.save
18
+ # band.reload
19
+ # end
20
+ #
21
+ # @param [ Hash ] options The session options. Please see the driver
22
+ # documentation for the available session options.
23
+ #
24
+ # @note You cannot do any operations in the block using models or objects
25
+ # that use a different client; the block will execute all operations
26
+ # in the context of the implicit session and operations on any models using
27
+ # another client will fail. For example, if you set a client using store_in on a
28
+ # particular model and execute an operation on it in the session context block,
29
+ # that operation can't use the block's session and an error will be raised.
30
+ # An error will also be raised if sessions are nested.
31
+ #
32
+ # @raise [ Errors::InvalidSessionUse ] If an operation is attempted on a model using another
33
+ # client from which the session was started or if sessions are nested.
34
+ #
35
+ # @return [ Object ] The result of calling the block.
36
+ #
37
+ # @yieldparam [ Mongo::Session ] The session being used for the block.
38
+ #
39
+ # @since 6.4.0
40
+ def with_session(options = {})
41
+ raise Mongoid::Errors::InvalidSessionUse.new(:invalid_session_nesting) if Threaded.get_session
42
+ session = persistence_context.client.start_session(options)
43
+ Threaded.set_session(session)
44
+ yield(session)
45
+ rescue Mongo::Error::InvalidSession => ex
46
+ if ex.message == Mongo::Session::SESSIONS_NOT_SUPPORTED
47
+ raise Mongoid::Errors::InvalidSessionUse.new(:sessions_not_supported)
48
+ end
49
+ raise Mongoid::Errors::InvalidSessionUse.new(:invalid_session_use)
50
+ ensure
51
+ Threaded.clear_session
52
+ end
53
+
54
+ private
55
+
56
+ def _session
57
+ Threaded.get_session
58
+ end
59
+
60
+ module ClassMethods
61
+
62
+ # Execute a block within the context of a session.
63
+ #
64
+ # @example Execute some operations in the context of a session.
65
+ # Band.with_session(causal_consistency: true) do
66
+ # band = Band.create
67
+ # band.records << Record.new
68
+ # band.save
69
+ # band.reload.records
70
+ # end
71
+ #
72
+ # @param [ Hash ] options The session options. Please see the driver
73
+ # documentation for the available session options.
74
+ #
75
+ # @note You cannot do any operations in the block using models or objects
76
+ # that use a different client; the block will execute all operations
77
+ # in the context of the implicit session and operations on any models using
78
+ # another client will fail. For example, if you set a client using store_in on a
79
+ # particular model and execute an operation on it in the session context block,
80
+ # that operation can't use the block's session and an error will be raised.
81
+ # You also cannot nest sessions.
82
+ #
83
+ # @raise [ Errors::InvalidSessionUse ] If an operation is attempted on a model using another
84
+ # client from which the session was started or if sessions are nested.
85
+ #
86
+ # @return [ Object ] The result of calling the block.
87
+ #
88
+ # @yieldparam [ Mongo::Session ] The session being used for the block.
89
+ #
90
+ # @since 6.4.0
91
+ def with_session(options = {})
92
+ raise Mongoid::Errors::InvalidSessionUse.new(:invalid_session_nesting) if Threaded.get_session
93
+ session = persistence_context.client.start_session(options)
94
+ Threaded.set_session(session)
95
+ yield(session)
96
+ rescue Mongo::Error::InvalidSession => ex
97
+ if ex.message == Mongo::Session::SESSIONS_NOT_SUPPORTED
98
+ raise Mongoid::Errors::InvalidSessionUse.new(:sessions_not_supported)
99
+ end
100
+ raise Mongoid::Errors::InvalidSessionUse.new(:invalid_session_use)
101
+ ensure
102
+ Threaded.clear_session
103
+ end
104
+
105
+ private
106
+
107
+ def _session
108
+ Threaded.get_session
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -61,6 +61,7 @@ module Mongoid
61
61
  # @since 4.0.0
62
62
  def reset_storage_options!
63
63
  self.storage_options = storage_options_defaults.dup
64
+ PersistenceContext.clear(self)
64
65
  end
65
66
 
66
67
  # Get the default storage options.
@@ -23,7 +23,7 @@ module Mongoid
23
23
  #
24
24
  # @since 3.0.0
25
25
  def aggregates(field)
26
- result = collection.find.aggregate(pipeline(field)).to_a
26
+ result = collection.find.aggregate(pipeline(field), session: _session).to_a
27
27
  if result.empty?
28
28
  { "count" => 0, "sum" => nil, "avg" => nil, "min" => nil, "max" => nil }
29
29
  else
@@ -165,13 +165,13 @@ module Mongoid
165
165
  def raw
166
166
  validate_out!
167
167
  cmd = command
168
- opts = { read: cmd.delete(:read).options } if cmd[:read]
169
- @map_reduce.database.command(cmd, opts || {}).first
168
+ opts = { read: cmd.delete(:read) } if cmd[:read]
169
+ @map_reduce.database.command(cmd, (opts || {}).merge(session: _session)).first
170
170
  end
171
171
  alias :results :raw
172
172
 
173
173
  # Execute the map/reduce, returning the raw output.
174
- # Useful when you don't care about map/reduce's ouptut.
174
+ # Useful when you don't care about map/reduce's output.
175
175
  #
176
176
  # @example Run the map reduce
177
177
  # map_reduce.execute
@@ -249,6 +249,10 @@ module Mongoid
249
249
  def validate_out!
250
250
  raise Errors::NoMapReduceOutput.new({}) unless @map_reduce.out
251
251
  end
252
+
253
+ def _session
254
+ criteria.send(:_session)
255
+ end
252
256
  end
253
257
  end
254
258
  end
@@ -48,7 +48,8 @@ module Mongoid
48
48
  end
49
49
  unless removed.empty?
50
50
  collection.find(selector).update_one(
51
- positionally(selector, "$pullAll" => { path => removed })
51
+ positionally(selector, "$pullAll" => { path => removed }),
52
+ session: _session
52
53
  )
53
54
  end
54
55
  deleted
@@ -303,7 +304,7 @@ module Mongoid
303
304
  updates["$set"].merge!(doc.atomic_updates["$set"] || {})
304
305
  doc.move_changes
305
306
  end
306
- collection.find(selector).update_one(updates) unless updates["$set"].empty?
307
+ collection.find(selector).update_one(updates, session: _session) unless updates["$set"].empty?
307
308
  end
308
309
 
309
310
  # Get the limiting value.
@@ -444,6 +445,10 @@ module Mongoid
444
445
  doc._parent.remove_child(doc)
445
446
  doc.destroyed = true
446
447
  end
448
+
449
+ def _session
450
+ @criteria.send(:_session)
451
+ end
447
452
  end
448
453
  end
449
454
  end
@@ -95,7 +95,8 @@ module Mongoid
95
95
  def destroy
96
96
  each.inject(0) do |count, doc|
97
97
  doc.destroy
98
- count += 1
98
+ count += 1 if acknowledged_write?
99
+ count
99
100
  end
100
101
  end
101
102
  alias :destroy_all :destroy
@@ -340,7 +341,7 @@ module Mongoid
340
341
  @criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache]
341
342
  @collection = @klass.collection
342
343
  criteria.send(:merge_type_selection)
343
- @view = collection.find(criteria.selector)
344
+ @view = collection.find(criteria.selector, session: _session)
344
345
  apply_options
345
346
  end
346
347
 
@@ -704,6 +705,14 @@ module Mongoid
704
705
  yield(doc)
705
706
  documents.push(doc) if cacheable?
706
707
  end
708
+
709
+ def _session
710
+ @criteria.send(:_session)
711
+ end
712
+
713
+ def acknowledged_write?
714
+ collection.write_concern.nil? || collection.write_concern.acknowledged?
715
+ end
707
716
  end
708
717
  end
709
718
  end
@@ -28,6 +28,7 @@ module Mongoid
28
28
  include Scopable
29
29
  include Clients::Options
30
30
  include Options
31
+ include Clients::Sessions
31
32
 
32
33
  # Static array used to check with method missing - we only need to ever
33
34
  # instantiate once.
@@ -3,6 +3,10 @@ module Mongoid
3
3
  class Criteria
4
4
  module Modifiable
5
5
 
6
+ # @attribute [r] create_attrs Additional attributes to add to the Document upon creation.
7
+ # @api private
8
+ attr_reader :create_attrs
9
+
6
10
  # Build a document given the selector and return it.
7
11
  # Complex criteria, such as $in and $or operations will get ignored.
8
12
  #
@@ -57,6 +61,9 @@ module Mongoid
57
61
 
58
62
  # Define attributes with which new documents will be created.
59
63
  #
64
+ # Note that if `find_or_create_by` is called after this in a method chain, the attributes in
65
+ # the query will override those from this method.
66
+ #
60
67
  # @example Define attributes to be used when a new document is created.
61
68
  # Person.create_with(job: 'Engineer').find_or_create_by(employer: 'MongoDB')
62
69
  #
@@ -64,7 +71,9 @@ module Mongoid
64
71
  #
65
72
  # @since 5.1.0
66
73
  def create_with(attrs = {})
67
- where(selector.merge(attrs))
74
+ tap do
75
+ (@create_attrs ||= {}).merge!(attrs)
76
+ end
68
77
  end
69
78
 
70
79
  # Find the first +Document+ given the conditions, or creates a new document
@@ -172,7 +181,8 @@ module Mongoid
172
181
  #
173
182
  # @since 3.0.0
174
183
  def create_document(method, attrs = nil, &block)
175
- attributes = selector.reduce(attrs ? attrs.dup : {}) do |hash, (key, value)|
184
+ attrs = (create_attrs || {}).merge(attrs || {})
185
+ attributes = selector.reduce(attrs) do |hash, (key, value)|
176
186
  unless invalid_key?(hash, key) || invalid_embedded_doc?(value)
177
187
  hash[key] = value
178
188
  end
@@ -259,7 +259,9 @@ module Mongoid
259
259
  def prepare(field, operator, value)
260
260
  unless operator =~ /exists|type|size/
261
261
  value = value.__expand_complex__
262
- serializer = serializers[field]
262
+ field = field.to_s
263
+ name = aliases[field] || field
264
+ serializer = serializers[name]
263
265
  value = serializer ? serializer.evolve(value) : value
264
266
  end
265
267
  selection = { operator => value }
@@ -24,7 +24,7 @@ module Mongoid
24
24
  # @since 2.0.0
25
25
  POLYGON = "Polygon"
26
26
 
27
- # @attribute [rw] negating If the next spression is negated.
27
+ # @attribute [rw] negating If the next expression is negated.
28
28
  # @attribute [rw] selector The query selector.
29
29
  attr_accessor :negating, :selector
30
30
 
@@ -134,13 +134,21 @@ module Mongoid
134
134
  ::Boolean.evolve(value)
135
135
  end
136
136
 
137
- # Add a $geoIntersects or $geoWithin selection. Symbol operators must be used as shown in
138
- # the examples to expand the criteria.
137
+ # Add a $geoIntersects or $geoWithin selection. Symbol operators must
138
+ # be used as shown in the examples to expand the criteria.
139
139
  #
140
140
  # @note The only valid geometry shapes for a $geoIntersects are:
141
141
  # :intersects_line, :intersects_point, and :intersects_polygon.
142
142
  #
143
- # @note The only valid geometry shape for a $geoWithin is :within_polygon
143
+ # @note The only valid options for a $geoWithin query are the geometry
144
+ # shape :within_polygon and the operator :within_box.
145
+ #
146
+ # @note The :within_box operator for the $geoWithin query expects the
147
+ # lower left (south west) coordinate pair as the first argument and
148
+ # the upper right (north east) as the second argument.
149
+ # Important: When latitude and longitude are passed, longitude is
150
+ # expected as the first element of the coordinate pair.
151
+ # Source: https://docs.mongodb.com/manual/reference/operator/query/box/
144
152
  #
145
153
  # @example Add a geo intersect criterion for a line.
146
154
  # query.geo_spacial(:location.intersects_line => [[ 1, 10 ], [ 2, 10 ]])
@@ -154,6 +162,9 @@ module Mongoid
154
162
  # @example Add a geo within criterion for a polygon.
155
163
  # query.geo_spacial(:location.within_polygon => [[ 1, 10 ], [ 2, 10 ], [ 1, 10 ]])
156
164
  #
165
+ # @example Add a geo within criterion for a box.
166
+ # query.geo_spacial(:location.within_box => [[ 1, 10 ], [ 2, 10 ])
167
+ #
157
168
  # @param [ Hash ] criterion The criterion.
158
169
  #
159
170
  # @return [ Selectable ] The cloned selectable.
@@ -174,6 +185,7 @@ module Mongoid
174
185
  key :within_polygon, :override, "$geoWithin", "$geometry" do |value|
175
186
  { "type" => POLYGON, "coordinates" => value }
176
187
  end
188
+ key :within_box, :override, "$geoWithin", "$box"
177
189
 
178
190
  # Add the $gt criterion to the selector.
179
191
  #
@@ -501,6 +513,11 @@ module Mongoid
501
513
  # @example Construct a text search selector with options.
502
514
  # selectable.text_search("testing", :$language => "fr")
503
515
  #
516
+ # @note Per https://docs.mongodb.com/manual/reference/operator/query/text/
517
+ # it is not currently possible to supply multiple text search
518
+ # conditions in a query. Mongoid will build such a query but the
519
+ # server will return an error when trying to execute it.
520
+ #
504
521
  # @param [ String, Symbol ] terms A string of terms that MongoDB parses
505
522
  # and uses to query the text index.
506
523
  # @param [ Hash ] opts Text search options. See MongoDB documentation
@@ -512,9 +529,19 @@ module Mongoid
512
529
  def text_search(terms, opts = nil)
513
530
  clone.tap do |query|
514
531
  if terms
515
- criterion = { :$text => { :$search => terms } }
516
- criterion[:$text].merge!(opts) if opts
517
- query.selector = criterion
532
+ criterion = {'$text' => { '$search' => terms }}
533
+ criterion['$text'].merge!(opts) if opts
534
+ if query.selector['$text']
535
+ # Per https://docs.mongodb.com/manual/reference/operator/query/text/
536
+ # multiple $text expressions are not currently supported by
537
+ # MongoDB server, but build the query correctly instead of
538
+ # overwriting previous text search condition with the currently
539
+ # given one.
540
+ Mongoid.logger.warn('Multiple $text expressions per query are not currently supported by the server')
541
+ query.selector = {'$and' => [query.selector]}.merge(criterion)
542
+ else
543
+ query.selector = query.selector.merge(criterion)
544
+ end
518
545
  end
519
546
  end
520
547
  end