mongoid 6.4.1 → 6.4.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.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +1 -3
  4. data/Rakefile +26 -0
  5. data/lib/mongoid.rb +1 -1
  6. data/lib/mongoid/contextual/map_reduce.rb +1 -1
  7. data/lib/mongoid/criteria/modifiable.rb +12 -2
  8. data/lib/mongoid/criteria/queryable/selectable.rb +34 -7
  9. data/lib/mongoid/document.rb +4 -4
  10. data/lib/mongoid/extensions/big_decimal.rb +1 -1
  11. data/lib/mongoid/extensions/regexp.rb +1 -0
  12. data/lib/mongoid/extensions/string.rb +3 -1
  13. data/lib/mongoid/matchable.rb +3 -0
  14. data/lib/mongoid/matchable/nor.rb +37 -0
  15. data/lib/mongoid/persistable/settable.rb +5 -5
  16. data/lib/mongoid/persistence_context.rb +4 -0
  17. data/lib/mongoid/query_cache.rb +64 -19
  18. data/lib/mongoid/railtie.rb +17 -0
  19. data/lib/mongoid/railties/controller_runtime.rb +86 -0
  20. data/lib/mongoid/scopable.rb +3 -3
  21. data/lib/mongoid/threaded.rb +36 -0
  22. data/lib/mongoid/version.rb +1 -1
  23. data/spec/app/models/array_field.rb +7 -0
  24. data/spec/app/models/delegating_patient.rb +16 -0
  25. data/spec/integration/document_spec.rb +22 -0
  26. data/spec/mongoid/clients/factory_spec.rb +52 -28
  27. data/spec/mongoid/clients/options_spec.rb +30 -15
  28. data/spec/mongoid/clients/sessions_spec.rb +12 -3
  29. data/spec/mongoid/contextual/geo_near_spec.rb +1 -0
  30. data/spec/mongoid/contextual/mongo_spec.rb +2 -2
  31. data/spec/mongoid/criteria/modifiable_spec.rb +59 -10
  32. data/spec/mongoid/criteria/queryable/extensions/big_decimal_spec.rb +3 -3
  33. data/spec/mongoid/criteria/queryable/selectable_spec.rb +42 -3
  34. data/spec/mongoid/criteria/queryable/selector_spec.rb +2 -2
  35. data/spec/mongoid/criteria/scopable_spec.rb +81 -0
  36. data/spec/mongoid/criteria_spec.rb +4 -1
  37. data/spec/mongoid/document_spec.rb +54 -0
  38. data/spec/mongoid/extensions/big_decimal_spec.rb +9 -9
  39. data/spec/mongoid/extensions/regexp_spec.rb +23 -0
  40. data/spec/mongoid/extensions/string_spec.rb +35 -7
  41. data/spec/mongoid/fields_spec.rb +1 -1
  42. data/spec/mongoid/findable_spec.rb +1 -1
  43. data/spec/mongoid/matchable/nor_spec.rb +209 -0
  44. data/spec/mongoid/matchable_spec.rb +26 -1
  45. data/spec/mongoid/persistable/incrementable_spec.rb +6 -6
  46. data/spec/mongoid/persistable/settable_spec.rb +19 -0
  47. data/spec/mongoid/query_cache_spec.rb +87 -18
  48. data/spec/mongoid/scopable_spec.rb +13 -0
  49. data/spec/mongoid/threaded_spec.rb +68 -0
  50. data/spec/rails/controller_extension/controller_runtime_spec.rb +110 -0
  51. data/spec/spec_helper.rb +10 -0
  52. data/spec/support/cluster_config.rb +158 -0
  53. data/spec/support/constraints.rb +101 -0
  54. data/spec/support/macros.rb +20 -0
  55. data/spec/support/session_registry.rb +50 -0
  56. data/spec/support/spec_config.rb +42 -0
  57. metadata +43 -23
  58. metadata.gz.sig +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 4f048aa8781f60c32235fe9d3daee1cfa17e7e58
4
- data.tar.gz: 1fdca10737525f1272fbcde4273452c9d0ea116c
2
+ SHA256:
3
+ metadata.gz: b8c453904dce4061c6cd88c36e21f3514fb778b37976ffd12e985aaf80fc2b98
4
+ data.tar.gz: d10421a146387851daa6a9c27184b48da1cc50d96a946bce0851aa69fd56c9a5
5
5
  SHA512:
6
- metadata.gz: 191be89fce8a8d7437070a526b68ab6cd9622235d40ac8d01d22dfc477914bc4eec23a28056fde69c2a5651c36a9d2bfea84765d3dbf1f7b6367de941aa73778
7
- data.tar.gz: 4e6371d1ae1d2fac3ff3c16d0283ac04132a9d09c83d390e8c3ae714a62ed833897f29d6ff0f2b1de9b6ae90b1019b0ae72d68752584792fbe62536db3a2e54a
6
+ metadata.gz: 6c0e672470d68faab3afc70e65524baa771801b46bbbb51b831a5cc709baf6df4ed7f0d81aed2dd5d0c3e3cf6730166da9bc8345b33577298f4438ec6c11fca3
7
+ data.tar.gz: 2008465437ba6eafe50235b22204df40b560b8e3bd27d064e97912c3249f5443af2ad1016c9a545d9d487abc6c1b38835624090a6d9165d06f9c4e676b561a23
Binary file
data.tar.gz.sig CHANGED
@@ -1,3 +1 @@
1
- ���7w\*�����`�+�N3��ZiaU�JL�3��V5'���c��q��4�$��d(�������a�5��7��_".������Qj*X_�֬����u�iPŋ�����f{�D��#��#�L3�j���Հ������ʙH_�&��+q����B"��n
2
-
3
- j�%d�=HcPx���ͺ'�eMP�|���_�R�4�*��ot�-�/iF�";�'3�h�[�4�|`0.�ȃ�ʙ]i�U_cAQ�Zix��g��
1
+ ��9V6����r~��[t�or�;��͞ð|�ƪ@����N��o!+5|�f.e�^�[%�p�eص"�q��$�J\��LO@�zV�[�!IJ�7W���i�:99,>��u.NIh��UT$`��m��Q��x��o�6~��D~H��ƴ���ib �7��fa��3��Yfd6���>!�}�?>0������^���-"c�\\��ݳ��q���:��i��n���;�4�����dW��u�0'Tk�DE m;Bry��
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  require "bundler"
2
+ require "bundler/gem_tasks"
2
3
  Bundler.setup
3
4
 
4
5
  require "rake"
@@ -7,6 +8,9 @@ require "rspec/core/rake_task"
7
8
  $LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
8
9
  require "mongoid/version"
9
10
 
11
+ tasks = Rake.application.instance_variable_get('@tasks')
12
+ tasks['release:do'] = tasks.delete('release')
13
+
10
14
  task :gem => :build
11
15
  task :build do
12
16
  system "gem build mongoid.gemspec"
@@ -33,3 +37,25 @@ RSpec::Core::RakeTask.new('spec:progress') do |spec|
33
37
  end
34
38
 
35
39
  task :default => :spec
40
+
41
+ desc "Generate all documentation"
42
+ task :docs => 'docs:yard'
43
+
44
+ namespace :docs do
45
+ desc "Generate yard documention"
46
+ task :yard do
47
+ out = File.join('yard-docs', Mongoid::VERSION)
48
+ FileUtils.rm_rf(out)
49
+ system "yardoc -o #{out} --title mongoid-#{Mongoid::VERSION}"
50
+ end
51
+ end
52
+
53
+ namespace :release do
54
+ task :check_private_key do
55
+ unless File.exist?('gem-private_key.pem')
56
+ raise "No private key present, cannot release"
57
+ end
58
+ end
59
+ end
60
+
61
+ task :release => ['release:check_private_key', 'release:do']
@@ -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
@@ -165,7 +165,7 @@ module Mongoid
165
165
  def raw
166
166
  validate_out!
167
167
  cmd = command
168
- opts = { read: cmd.delete(:read).options } if cmd[:read]
168
+ opts = { read: cmd.delete(:read) } if cmd[:read]
169
169
  @map_reduce.database.command(cmd, (opts || {}).merge(session: _session)).first
170
170
  end
171
171
  alias :results :raw
@@ -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
@@ -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
@@ -190,11 +190,11 @@ module Mongoid
190
190
  #
191
191
  # @since 5.1.0
192
192
  def as_json(options = nil)
193
- if options && (options[:compact] == true)
194
- super(options).reject! { |k,v| v.nil? }
195
- else
196
- super(options)
193
+ rv = super
194
+ if options && options[:compact]
195
+ rv = rv.compact
197
196
  end
197
+ rv
198
198
  end
199
199
 
200
200
  # Returns an instance of the specified class with the attributes,
@@ -53,7 +53,7 @@ module Mongoid
53
53
  #
54
54
  # @since 3.0.0
55
55
  def demongoize(object)
56
- object && object.numeric? ? ::BigDecimal.new(object.to_s) : nil
56
+ object && object.numeric? ? BigDecimal(object.to_s) : nil
57
57
  end
58
58
 
59
59
  # Mongoize an object of any type to how it's stored in the db as a String.
@@ -17,6 +17,7 @@ module Mongoid
17
17
  #
18
18
  # @since 3.0.0
19
19
  def mongoize(object)
20
+ return nil if object.nil?
20
21
  ::Regexp.new(object)
21
22
  end
22
23
  end
@@ -82,7 +82,9 @@ module Mongoid
82
82
  #
83
83
  # @since 3.0.0
84
84
  def numeric?
85
- true if Float(self) rescue (self =~ /^NaN|\-?Infinity$/)
85
+ !!Float(self)
86
+ rescue ArgumentError
87
+ (self =~ /\A(?:NaN|-?Infinity)\z/) == 0
86
88
  end
87
89
 
88
90
  # Get the string as a getter string.
@@ -11,6 +11,7 @@ require "mongoid/matchable/lte"
11
11
  require "mongoid/matchable/ne"
12
12
  require "mongoid/matchable/nin"
13
13
  require "mongoid/matchable/or"
14
+ require "mongoid/matchable/nor"
14
15
  require "mongoid/matchable/size"
15
16
  require "mongoid/matchable/elem_match"
16
17
  require "mongoid/matchable/regexp"
@@ -40,6 +41,7 @@ module Mongoid
40
41
  "$ne" => Ne,
41
42
  "$nin" => Nin,
42
43
  "$or" => Or,
44
+ "$nor" => Nor,
43
45
  "$size" => Size
44
46
  }.with_indifferent_access.freeze
45
47
 
@@ -124,6 +126,7 @@ module Mongoid
124
126
  case key.to_s
125
127
  when "$or" then Or.new(value, document)
126
128
  when "$and" then And.new(value, document)
129
+ when "$nor" then Nor.new(value, document)
127
130
  else Default.new(extract_attribute(document, key))
128
131
  end
129
132
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ # encoding: utf-8
3
+ module Mongoid
4
+ module Matchable
5
+
6
+ # Defines behavior for handling $nor expressions in embedded documents.
7
+ class Nor < Default
8
+
9
+ # Does the supplied query match the attribute?
10
+ #
11
+ # Note: an empty array as an argument to $nor is prohibited by
12
+ # MongoDB server. Mongoid does allow this and returns false in this case.
13
+ #
14
+ # @example Does this match?
15
+ # matcher._matches?("$nor" => [ { field => value } ])
16
+ #
17
+ # @param [ Array ] conditions The or expression.
18
+ #
19
+ # @return [ true, false ] True if matches, false if not.
20
+ #
21
+ # @since 6.4.2/7.0.2/7.1.0
22
+ def _matches?(conditions)
23
+ if conditions.length == 0
24
+ # MongoDB does not allow $nor array to be empty, but
25
+ # Mongoid accepts an empty array for $or which MongoDB also
26
+ # prohibits
27
+ return false
28
+ end
29
+ conditions.none? do |condition|
30
+ condition.all? do |key, value|
31
+ document._matches?(key => value)
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -25,15 +25,15 @@ module Mongoid
25
25
 
26
26
  field_and_value_hash = hasherizer(field.split('.'), value)
27
27
  field = field_and_value_hash.keys.first.to_s
28
+ value = field_and_value_hash[field]
28
29
 
29
- if fields[field] && fields[field].type == Hash && attributes.key?(field) && !value.empty?
30
+ if fields[field] && fields[field].type == Hash && attributes.key?(field) && Hash === value && !value.empty?
30
31
  merger = proc { |key, v1, v2| Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
31
- value = (attributes[field] || {}).merge(field_and_value_hash[field], &merger)
32
- process_attribute(field.to_s, value)
33
- else
34
- process_attribute(field.to_s, field_and_value_hash[field])
32
+ value = (attributes[field] || {}).merge(value, &merger)
35
33
  end
36
34
 
35
+ process_attribute(field.to_s, value)
36
+
37
37
  unless relations.include?(field.to_s)
38
38
  ops[atomic_attribute_name(field)] = attributes[field]
39
39
  end
@@ -107,6 +107,10 @@ module Mongoid
107
107
  #
108
108
  # @since 6.0.0
109
109
  def client
110
+ client_options = send(:client_options)
111
+ if client_options[:read].is_a?(Symbol)
112
+ client_options = client_options.merge(read: {mode: client_options[:read]})
113
+ end
110
114
  @client ||= (client = Clients.with_name(client_name)
111
115
  client = client.use(database_name) if database_name_option
112
116
  client.with(client_options))
@@ -159,11 +159,14 @@ module Mongoid
159
159
  private
160
160
 
161
161
  def process(result)
162
- @remaining -= result.returned_count if limited?
163
- @cursor_id = result.cursor_id
164
- @coll_name ||= result.namespace.sub("#{database.name}.", '') if result.namespace
165
- documents = result.documents
166
- (@cached_documents ||= []).concat(documents)
162
+ documents = super
163
+
164
+ if @cursor_id.zero? && !@after_first_batch
165
+ @cached_documents ||= []
166
+ @cached_documents.concat(documents)
167
+ end
168
+
169
+ @after_first_batch = true
167
170
  documents
168
171
  end
169
172
  end
@@ -218,20 +221,40 @@ module Mongoid
218
221
  #
219
222
  # @since 5.0.0
220
223
  def each
221
- if system_collection? || !QueryCache.enabled?
224
+ if system_collection? || !QueryCache.enabled? || (respond_to?(:write?, true) && write?)
222
225
  super
223
226
  else
224
- unless cursor = cached_cursor
225
- read_with_retry do
226
- server = server_selector.select_server(cluster)
227
- cursor = CachedCursor.new(view, send_initial_query(server), server)
228
- QueryCache.cache_table[cache_key] = cursor
227
+ @cursor = nil
228
+ unless @cursor = cached_cursor
229
+
230
+ if driver_supports_cursor_sessions?
231
+ session = client.send(:get_session, @options)
232
+ read_with_retry(session, server_selector) do |server|
233
+ result = send_initial_query(server, session)
234
+ @cursor = get_cursor(result, server, session)
235
+ end
236
+ else
237
+ read_with_retry do
238
+ server = server_selector.select_server(cluster)
239
+ result = send_initial_query(server)
240
+ @cursor = get_cursor(result, server)
241
+ end
242
+ end
243
+ end
244
+
245
+ if block_given?
246
+ if limit && limit != -1
247
+ @cursor.to_a[0...limit].each do |doc|
248
+ yield doc
249
+ end
250
+ else
251
+ @cursor.each do |doc|
252
+ yield doc
253
+ end
229
254
  end
255
+ else
256
+ @cursor.to_enum
230
257
  end
231
- cursor.each do |doc|
232
- yield doc
233
- end if block_given?
234
- cursor
235
258
  end
236
259
  end
237
260
 
@@ -241,14 +264,30 @@ module Mongoid
241
264
  if limit
242
265
  key = [ collection.namespace, selector, nil, skip, sort, projection, collation ]
243
266
  cursor = QueryCache.cache_table[key]
244
- if cursor
245
- limited_docs = cursor.to_a[0...limit.abs]
246
- cursor.instance_variable_set(:@cached_documents, limited_docs)
247
- end
248
267
  end
249
268
  cursor || QueryCache.cache_table[cache_key]
250
269
  end
251
270
 
271
+ def get_cursor(result, server, session = nil)
272
+ if result.cursor_id == 0 || result.cursor_id.nil?
273
+ cursor = if session
274
+ CachedCursor.new(view, result, server, session: session)
275
+ else
276
+ CachedCursor.new(view, result, server)
277
+ end
278
+
279
+ QueryCache.cache_table[cache_key] = cursor
280
+ else
281
+ cursor = if session
282
+ Mongo::Cursor.new(view, result, server, session: session)
283
+ else
284
+ Mongo::Cursor.new(view, result, server)
285
+ end
286
+ end
287
+
288
+ cursor
289
+ end
290
+
252
291
  def cache_key
253
292
  [ collection.namespace, selector, limit, skip, sort, projection, collation ]
254
293
  end
@@ -256,6 +295,12 @@ module Mongoid
256
295
  def system_collection?
257
296
  collection.namespace =~ /^system./
258
297
  end
298
+
299
+ def driver_supports_cursor_sessions?
300
+ # Driver versions 2.9 and newer support passing in a session to the
301
+ # cursor object.
302
+ (Mongo::VERSION.split('.').map(&:to_i) <=> [2, 9, 0]) > 0
303
+ end
259
304
  end
260
305
 
261
306
  # Adds behaviour to the query cache for collections.