mongoid 6.4.1 → 6.4.8

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