mongoid 9.0.1 → 9.0.3

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/lib/config/locales/en.yml +16 -0
  3. data/lib/mongoid/association/accessors.rb +7 -2
  4. data/lib/mongoid/association/nested/one.rb +14 -1
  5. data/lib/mongoid/association/referenced/belongs_to/binding.rb +7 -1
  6. data/lib/mongoid/association/referenced/belongs_to/buildable.rb +1 -1
  7. data/lib/mongoid/association/referenced/belongs_to.rb +15 -0
  8. data/lib/mongoid/association/referenced/has_many.rb +9 -8
  9. data/lib/mongoid/association/referenced/has_one/buildable.rb +3 -8
  10. data/lib/mongoid/association/referenced/with_polymorphic_criteria.rb +41 -0
  11. data/lib/mongoid/attributes/nested.rb +2 -1
  12. data/lib/mongoid/clients/options.rb +14 -1
  13. data/lib/mongoid/clients/sessions.rb +13 -15
  14. data/lib/mongoid/composable.rb +2 -0
  15. data/lib/mongoid/document.rb +2 -0
  16. data/lib/mongoid/errors/unrecognized_model_alias.rb +53 -0
  17. data/lib/mongoid/errors/unrecognized_resolver.rb +27 -0
  18. data/lib/mongoid/errors/unregistered_class.rb +47 -0
  19. data/lib/mongoid/errors.rb +3 -0
  20. data/lib/mongoid/identifiable.rb +28 -0
  21. data/lib/mongoid/matcher.rb +15 -1
  22. data/lib/mongoid/model_resolver.rb +154 -0
  23. data/lib/mongoid/persistence_context.rb +15 -9
  24. data/lib/mongoid/railties/controller_runtime.rb +2 -2
  25. data/lib/mongoid/serializable.rb +7 -7
  26. data/lib/mongoid/threaded.rb +96 -28
  27. data/lib/mongoid/timestamps/timeless.rb +4 -1
  28. data/lib/mongoid/touchable.rb +1 -1
  29. data/lib/mongoid/traversable.rb +11 -2
  30. data/lib/mongoid/validatable/associated.rb +5 -2
  31. data/lib/mongoid/version.rb +1 -1
  32. data/spec/integration/active_job_spec.rb +24 -20
  33. data/spec/integration/app_spec.rb +9 -1
  34. data/spec/integration/associations/belongs_to_spec.rb +129 -0
  35. data/spec/integration/persistence/collection_options_spec.rb +36 -0
  36. data/spec/mongoid/association/embedded/embeds_many_query_spec.rb +4 -0
  37. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +5 -0
  38. data/spec/mongoid/association/referenced/belongs_to_spec.rb +58 -21
  39. data/spec/mongoid/association/referenced/has_many/buildable_spec.rb +4 -0
  40. data/spec/mongoid/attributes/nested_spec.rb +1 -0
  41. data/spec/mongoid/clients/options_spec.rb +127 -2
  42. data/spec/mongoid/clients/transactions_spec.rb +2 -2
  43. data/spec/mongoid/interceptable_spec.rb +12 -0
  44. data/spec/mongoid/interceptable_spec_models.rb +12 -0
  45. data/spec/mongoid/model_resolver_spec.rb +167 -0
  46. data/spec/mongoid/monkey_patches_spec.rb +1 -1
  47. data/spec/mongoid/persistence_context_spec.rb +48 -4
  48. data/spec/mongoid/railties/bson_object_id_serializer_spec.rb +18 -12
  49. data/spec/mongoid/serializable_spec.rb +16 -9
  50. data/spec/mongoid/threaded_spec.rb +24 -5
  51. data/spec/mongoid/validatable/associated_spec.rb +14 -4
  52. data/spec/rails/controller_extension/controller_runtime_spec.rb +14 -14
  53. metadata +14 -4
@@ -0,0 +1,154 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Mongoid
6
+ # The default class for resolving model classes based on discriminant keys.
7
+ # Given a key, it will return the corresponding model class, if any. By
8
+ # default, it looks for classes with names that match the given keys, but
9
+ # additional mappings may be provided.
10
+ #
11
+ # It is also possible to instantiate multiple resolvers---and even implement
12
+ # your own---so that different sets of classes can use independent resolution
13
+ # mechanics.
14
+ class ModelResolver
15
+ # The mutex instance used to make the `.instance` method thread-safe.
16
+ #
17
+ # @api private
18
+ INSTANCE_MUTEX = Mutex.new
19
+
20
+ class << self
21
+ extend Forwardable
22
+ def_delegators :instance, :register
23
+
24
+ # Returns the default instance of the ModelResolver.
25
+ #
26
+ # @return [ Mongoid::ModelResolver ] the default ModelResolver instance.
27
+ def instance
28
+ @instance ||= INSTANCE_MUTEX.synchronize { @instance ||= new }
29
+ end
30
+
31
+ # Returns the map of registered resolvers. The default resolver is not
32
+ # included here.
33
+ #
34
+ # @return [ Hash<Symbol => Mongoid::ModelResolver::Interface> ] the hash of
35
+ # resolver instances, mapped by symbol identifier.
36
+ def resolvers
37
+ @resolvers ||= {}
38
+ end
39
+
40
+ # Returns the resolver instance that corresponds to the argument.
41
+ #
42
+ # @param [ nil | true | false Symbol | String | Mongoid::ModelResolver::Interface ] identifier_or_object
43
+ # When nil or false, returns nil. When true or :default, corresponds to the default resolver.
44
+ # When any other symbol or string, corresponds to the registered resolver with that identifier.
45
+ # Otherwise, it must be a resolver instance itself.
46
+ #
47
+ # @raise Mongoid::Errors::UnrecognizedResolver if the given identifier is a
48
+ # symbol or string and it does not match any registered resolver.
49
+ #
50
+ # @return [ Mongoid::ModelResolver::Interface ] the resolver instance corresponding to the
51
+ # given argument.
52
+ def resolver(identifier_or_object = :default)
53
+ case identifier_or_object
54
+ when nil, false then nil
55
+ when true, :default then instance
56
+ when String, Symbol
57
+ resolvers.fetch(identifier_or_object.to_sym) do |key|
58
+ raise Mongoid::Errors::UnrecognizedResolver, key
59
+ end
60
+ else identifier_or_object
61
+ end
62
+ end
63
+
64
+ # Register the given resolver under the given name.
65
+ #
66
+ # @param [ Mongoid::ModelResolver::Interface ] resolver the resolver to register.
67
+ # @param [ String | Symbol ] name the identifier to use to register the resolver.
68
+ def register_resolver(resolver, name)
69
+ resolvers[name.to_sym] = resolver
70
+ self
71
+ end
72
+ end
73
+
74
+ # Instantiates a new ModelResolver instance.
75
+ def initialize
76
+ @key_to_model = {}
77
+ @model_to_keys = {}
78
+ end
79
+
80
+ # Registers the given model class with the given keys. In addition to the given keys, the
81
+ # class name itself will be included as a key to identify the class. Keys are given in priority
82
+ # order, with highest priority keys first and lowest last. The class name, if not given explicitly,
83
+ # is always given lowest priority.
84
+ #
85
+ # If called more than once, newer keys have higher priority than older keys. All duplicate keys will
86
+ # be removed.
87
+ #
88
+ # @param [ Mongoid::Document ] klass the document class to register
89
+ # @param [ Array<String> ] *keys the list of keys to use as an alias (optional)
90
+ def register(klass, *keys)
91
+ default_key = klass.name
92
+
93
+ @model_to_keys[klass] = [ *keys, *@model_to_keys[klass], default_key ].uniq
94
+ @key_to_model[default_key] = klass
95
+
96
+ keys.each do |key|
97
+ @key_to_model[key] = klass
98
+ end
99
+
100
+ self
101
+ end
102
+
103
+ # The `Interface` concern represents the interface that custom resolvers
104
+ # must implement.
105
+ concerning :Interface do
106
+ # Returns the default (highest priority) key for the given record. This is typically
107
+ # the key that will be used when saving a new polymorphic association.
108
+ #
109
+ # @param [ Mongoid::Document ] record the record instance for which to query the default key.
110
+ #
111
+ # @raise Mongoid::Errors::UnregisteredClass if the record's class has not been registered with this resolver.
112
+ #
113
+ # @return [ String ] the default key for the record's class.
114
+ def default_key_for(record)
115
+ keys_for(record).first
116
+ end
117
+
118
+ # Returns the list of all keys for the given record's class, in priority order (with highest
119
+ # priority keys first).
120
+ #
121
+ # @param [ Mongoid::Document] record the record instance for which to query the registered keys.
122
+ #
123
+ # @raise Mongoid::Errors::UnregisteredClass if the record's class has not been registered with this resolver.
124
+ #
125
+ # @return [ Array<String> ] the list of keys that have been registered for the given class.
126
+ def keys_for(record)
127
+ @model_to_keys.fetch(record.class) do |klass|
128
+ # figure out which resolver this is
129
+ resolver = if self == Mongoid::ModelResolver.instance
130
+ :default
131
+ else
132
+ Mongoid::ModelResolver.resolvers.keys.detect { |k| Mongoid::ModelResolver.resolvers[k] == self }
133
+ end
134
+ resolver ||= self # if it hasn't been registered, we'll show it the best we can
135
+ raise Mongoid::Errors::UnregisteredClass.new(klass, resolver)
136
+ end
137
+ end
138
+
139
+ # Returns the document class that has been registered by the given key.
140
+ #
141
+ # @param [ String ] key the key by which to query the corresponding class.
142
+ #
143
+ # @raise Mongoid::Errors::UnrecognizedModelAlias if the given key has not
144
+ # been registered with this resolver.
145
+ #
146
+ # @return [ Class ] the document class that has been registered with the given key.
147
+ def model_for(key)
148
+ @key_to_model.fetch(key) do
149
+ raise Mongoid::Errors::UnrecognizedModelAlias, key
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -25,7 +25,8 @@ module Mongoid
25
25
  # @return [ Array<Symbol> ] The list of extra options besides client options
26
26
  # that determine the persistence context.
27
27
  EXTRA_OPTIONS = [ :client,
28
- :collection
28
+ :collection,
29
+ :collection_options
29
30
  ].freeze
30
31
 
31
32
  # The full list of valid persistence context options.
@@ -116,12 +117,15 @@ module Mongoid
116
117
  def client
117
118
  @client ||= begin
118
119
  client = Clients.with_name(client_name)
120
+ options = client_options
121
+
119
122
  if database_name_option
120
123
  client = client.use(database_name)
124
+ options = options.except(:database, 'database')
121
125
  end
122
- unless client_options.empty?
123
- client = client.with(client_options)
124
- end
126
+
127
+ client = client.with(options) unless options.empty?
128
+
125
129
  client
126
130
  end
127
131
  end
@@ -281,6 +285,10 @@ module Mongoid
281
285
  # @api private
282
286
  PERSISTENCE_CONTEXT_KEY = :"[mongoid]:persistence_context"
283
287
 
288
+ def context_store
289
+ Threaded.get(PERSISTENCE_CONTEXT_KEY) { {} }
290
+ end
291
+
284
292
  # Get the persistence context for a given object from the thread local
285
293
  # storage.
286
294
  #
@@ -291,8 +299,7 @@ module Mongoid
291
299
  #
292
300
  # @api private
293
301
  def get_context(object)
294
- Thread.current[PERSISTENCE_CONTEXT_KEY] ||= {}
295
- Thread.current[PERSISTENCE_CONTEXT_KEY][object.object_id]
302
+ context_store[object.object_id]
296
303
  end
297
304
 
298
305
  # Store persistence context for a given object in the thread local
@@ -304,10 +311,9 @@ module Mongoid
304
311
  # @api private
305
312
  def store_context(object, context)
306
313
  if context.nil?
307
- Thread.current[PERSISTENCE_CONTEXT_KEY]&.delete(object.object_id)
314
+ context_store.delete(object.object_id)
308
315
  else
309
- Thread.current[PERSISTENCE_CONTEXT_KEY] ||= {}
310
- Thread.current[PERSISTENCE_CONTEXT_KEY][object.object_id] = context
316
+ context_store[object.object_id] = context
311
317
  end
312
318
  end
313
319
  end
@@ -78,7 +78,7 @@ module Mongoid
78
78
  #
79
79
  # @return [ Integer ] The runtime value.
80
80
  def self.runtime
81
- Thread.current[VARIABLE_NAME] ||= 0
81
+ Threaded.get(VARIABLE_NAME) { 0 }
82
82
  end
83
83
 
84
84
  # Set the runtime value on the current thread.
@@ -87,7 +87,7 @@ module Mongoid
87
87
  #
88
88
  # @return [ Integer ] The runtime value.
89
89
  def self.runtime= value
90
- Thread.current[VARIABLE_NAME] = value
90
+ Threaded.set(VARIABLE_NAME, value)
91
91
  end
92
92
 
93
93
  # Reset the runtime value to zero the current thread.
@@ -13,13 +13,13 @@ module Mongoid
13
13
  included do
14
14
 
15
15
  class << self
16
- # Note that this intentionally only delegates :include_root_in_json
17
- # and not :include_root_in_json? - delegating the latter produces
18
- # wrong behavior.
19
- # Also note that this intentionally uses the ActiveSupport delegation
20
- # functionality and not the Ruby standard library one.
21
- # See https://jira.mongodb.org/browse/MONGOID-4849.
22
- delegate :include_root_in_json, to: ::Mongoid
16
+ def include_root_in_json
17
+ @include_root_in_json.nil? ? ::Mongoid.include_root_in_json : @include_root_in_json
18
+ end
19
+
20
+ def include_root_in_json=(new_value)
21
+ @include_root_in_json = new_value
22
+ end
23
23
  end
24
24
  end
25
25
 
@@ -18,6 +18,7 @@ module Mongoid
18
18
  CURRENT_SCOPE_KEY = '[mongoid]:current-scope'
19
19
 
20
20
  AUTOSAVES_KEY = '[mongoid]:autosaves'
21
+
21
22
  VALIDATIONS_KEY = '[mongoid]:validations'
22
23
 
23
24
  STACK_KEYS = Hash.new do |hash, key|
@@ -36,6 +37,75 @@ module Mongoid
36
37
 
37
38
  extend self
38
39
 
40
+ # Queries the thread-local variable with the given name. If a block is
41
+ # given, and the variable does not already exist, the return value of the
42
+ # block will be set as the value of the variable before returning it.
43
+ #
44
+ # It is very important that applications (and espcially Mongoid)
45
+ # use this method instead of Thread#[], since Thread#[] is actually for
46
+ # fiber-local variables, and Mongoid uses Fibers as an implementation
47
+ # detail in some callbacks. Putting thread-local state in a fiber-local
48
+ # store will result in the state being invisible when relevant callbacks are
49
+ # run in a different fiber.
50
+ #
51
+ # Affected callbacks are cascading callbacks on embedded children.
52
+ #
53
+ # @param [ String | Symbol ] key the name of the variable to query
54
+ # @param [ Proc ] default an optional block that must return the default
55
+ # (initial) value of this variable.
56
+ #
57
+ # @return [ Object | nil ] the value of the queried variable, or nil if
58
+ # it is not set and no default was given.
59
+ def get(key, &default)
60
+ result = Thread.current.thread_variable_get(key)
61
+
62
+ if result.nil? && default
63
+ result = yield
64
+ set(key, result)
65
+ end
66
+
67
+ result
68
+ end
69
+
70
+ # Sets a thread-local variable with the given name to the given value.
71
+ # See #get for a discussion of why this method is necessary, and why
72
+ # Thread#[]= should be avoided in cascading callbacks on embedded children.
73
+ #
74
+ # @param [ String | Symbol ] key the name of the variable to set.
75
+ # @param [ Object | nil ] value the value of the variable to set (or `nil`
76
+ # if you wish to unset the variable)
77
+ def set(key, value)
78
+ Thread.current.thread_variable_set(key, value)
79
+ end
80
+
81
+ # Removes the named variable from thread-local storage.
82
+ #
83
+ # @param [ String | Symbol ] key the name of the variable to remove.
84
+ def delete(key)
85
+ set(key, nil)
86
+ end
87
+
88
+ # Queries the presence of a named variable in thread-local storage.
89
+ #
90
+ # @param [ String | Symbol ] key the name of the variable to query.
91
+ #
92
+ # @return [ true | false ] whether the given variable is present or not.
93
+ def has?(key)
94
+ # Here we have a classic example of JRuby not behaving like MRI. In
95
+ # MRI, if you set a thread variable to nil, it removes it from the list
96
+ # and subsequent calls to thread_variable?(key) will return false. Not
97
+ # so with JRuby. Once set, you cannot unset the thread variable.
98
+ #
99
+ # However, because setting a variable to nil is supposed to remove it,
100
+ # we can assume a nil-valued variable doesn't actually exist.
101
+
102
+ # So, instead of this:
103
+ # Thread.current.thread_variable?(key)
104
+
105
+ # We have to do this:
106
+ !get(key).nil?
107
+ end
108
+
39
109
  # Begin entry into a named thread local stack.
40
110
  #
41
111
  # @example Begin entry into the stack.
@@ -55,7 +125,7 @@ module Mongoid
55
125
  #
56
126
  # @return [ String | Symbol ] The override.
57
127
  def database_override
58
- Thread.current[DATABASE_OVERRIDE_KEY]
128
+ get(DATABASE_OVERRIDE_KEY)
59
129
  end
60
130
 
61
131
  # Set the global database override.
@@ -67,7 +137,7 @@ module Mongoid
67
137
  #
68
138
  # @return [ String | Symbol ] The override.
69
139
  def database_override=(name)
70
- Thread.current[DATABASE_OVERRIDE_KEY] = name
140
+ set(DATABASE_OVERRIDE_KEY, name)
71
141
  end
72
142
 
73
143
  # Are in the middle of executing the named stack
@@ -103,7 +173,7 @@ module Mongoid
103
173
  #
104
174
  # @return [ Array ] The stack.
105
175
  def stack(name)
106
- Thread.current[STACK_KEYS[name]] ||= []
176
+ get(STACK_KEYS[name]) { [] }
107
177
  end
108
178
 
109
179
  # Begin autosaving a document on the current thread.
@@ -177,7 +247,7 @@ module Mongoid
177
247
  #
178
248
  # @return [ String | Symbol ] The override.
179
249
  def client_override
180
- Thread.current[CLIENT_OVERRIDE_KEY]
250
+ get(CLIENT_OVERRIDE_KEY)
181
251
  end
182
252
 
183
253
  # Set the global client override.
@@ -189,7 +259,7 @@ module Mongoid
189
259
  #
190
260
  # @return [ String | Symbol ] The override.
191
261
  def client_override=(name)
192
- Thread.current[CLIENT_OVERRIDE_KEY] = name
262
+ set(CLIENT_OVERRIDE_KEY, name)
193
263
  end
194
264
 
195
265
  # Get the current Mongoid scope.
@@ -202,12 +272,12 @@ module Mongoid
202
272
  #
203
273
  # @return [ Criteria ] The scope.
204
274
  def current_scope(klass = nil)
205
- if klass && Thread.current[CURRENT_SCOPE_KEY].respond_to?(:keys)
206
- Thread.current[CURRENT_SCOPE_KEY][
207
- Thread.current[CURRENT_SCOPE_KEY].keys.find { |k| k <= klass }
208
- ]
275
+ current_scope = get(CURRENT_SCOPE_KEY)
276
+
277
+ if klass && current_scope.respond_to?(:keys)
278
+ current_scope[current_scope.keys.find { |k| k <= klass }]
209
279
  else
210
- Thread.current[CURRENT_SCOPE_KEY]
280
+ current_scope
211
281
  end
212
282
  end
213
283
 
@@ -220,7 +290,7 @@ module Mongoid
220
290
  #
221
291
  # @return [ Criteria ] The scope.
222
292
  def current_scope=(scope)
223
- Thread.current[CURRENT_SCOPE_KEY] = scope
293
+ set(CURRENT_SCOPE_KEY, scope)
224
294
  end
225
295
 
226
296
  # Set the current Mongoid scope. Safe for multi-model scope chaining.
@@ -236,8 +306,8 @@ module Mongoid
236
306
  if scope.nil?
237
307
  unset_current_scope(klass)
238
308
  else
239
- Thread.current[CURRENT_SCOPE_KEY] ||= {}
240
- Thread.current[CURRENT_SCOPE_KEY][klass] = scope
309
+ current_scope = get(CURRENT_SCOPE_KEY) { {} }
310
+ current_scope[klass] = scope
241
311
  end
242
312
  end
243
313
 
@@ -284,7 +354,7 @@ module Mongoid
284
354
  #
285
355
  # @return [ Hash ] The current autosaves.
286
356
  def autosaves
287
- Thread.current[AUTOSAVES_KEY] ||= {}
357
+ get(AUTOSAVES_KEY) { {} }
288
358
  end
289
359
 
290
360
  # Get all validations on the current thread.
@@ -294,7 +364,7 @@ module Mongoid
294
364
  #
295
365
  # @return [ Hash ] The current validations.
296
366
  def validations
297
- Thread.current[VALIDATIONS_KEY] ||= {}
367
+ get(VALIDATIONS_KEY) { {} }
298
368
  end
299
369
 
300
370
  # Get all autosaves on the current thread for the class.
@@ -376,9 +446,7 @@ module Mongoid
376
446
  # @return [ Set<Mongoid::Document> ] Collection of modified documents before
377
447
  # it was cleared.
378
448
  def clear_modified_documents(session)
379
- modified_documents[session].dup
380
- ensure
381
- modified_documents[session].clear
449
+ modified_documents.delete(session) || []
382
450
  end
383
451
 
384
452
  # Queries whether document callbacks should be executed by default for the
@@ -390,8 +458,8 @@ module Mongoid
390
458
  # @return [ true | false ] Whether or not document callbacks should be
391
459
  # executed by default.
392
460
  def execute_callbacks?
393
- if Thread.current.key?(EXECUTE_CALLBACKS)
394
- Thread.current[EXECUTE_CALLBACKS]
461
+ if has?(EXECUTE_CALLBACKS)
462
+ get(EXECUTE_CALLBACKS)
395
463
  else
396
464
  true
397
465
  end
@@ -404,7 +472,7 @@ module Mongoid
404
472
  # @param flag [ true | false ] Whether or not document callbacks should be
405
473
  # executed by default.
406
474
  def execute_callbacks=(flag)
407
- Thread.current[EXECUTE_CALLBACKS] = flag
475
+ set(EXECUTE_CALLBACKS, flag)
408
476
  end
409
477
 
410
478
  # Returns the thread store of sessions.
@@ -413,7 +481,7 @@ module Mongoid
413
481
  #
414
482
  # @api private
415
483
  def sessions
416
- Thread.current[SESSIONS_KEY] ||= {}.compare_by_identity
484
+ get(SESSIONS_KEY) { {}.compare_by_identity }
417
485
  end
418
486
 
419
487
  # Returns the thread store of modified documents.
@@ -423,9 +491,7 @@ module Mongoid
423
491
  #
424
492
  # @api private
425
493
  def modified_documents
426
- Thread.current[MODIFIED_DOCUMENTS_KEY] ||= Hash.new do |h, k|
427
- h[k] = Set.new
428
- end
494
+ get(MODIFIED_DOCUMENTS_KEY) { Hash.new { |h, k| h[k] = Set.new } }
429
495
  end
430
496
 
431
497
  private
@@ -435,10 +501,12 @@ module Mongoid
435
501
  #
436
502
  # @param klass [ Class ] the class to remove from the current scope.
437
503
  def unset_current_scope(klass)
438
- return unless Thread.current[CURRENT_SCOPE_KEY]
504
+ return unless has?(CURRENT_SCOPE_KEY)
505
+
506
+ scope = get(CURRENT_SCOPE_KEY)
507
+ scope.delete(klass)
439
508
 
440
- Thread.current[CURRENT_SCOPE_KEY].delete(klass)
441
- Thread.current[CURRENT_SCOPE_KEY] = nil if Thread.current[CURRENT_SCOPE_KEY].empty?
509
+ delete(CURRENT_SCOPE_KEY) if scope.empty?
442
510
  end
443
511
  end
444
512
  end
@@ -46,6 +46,9 @@ module Mongoid
46
46
  class << self
47
47
  extend Forwardable
48
48
 
49
+ # The key to use to store the timeless table
50
+ TIMELESS_TABLE_KEY = '[mongoid]:timeless'
51
+
49
52
  # Returns the in-memory thread cache of classes
50
53
  # for which to skip timestamping.
51
54
  #
@@ -53,7 +56,7 @@ module Mongoid
53
56
  #
54
57
  # @api private
55
58
  def timeless_table
56
- Thread.current['[mongoid]:timeless'] ||= Hash.new
59
+ Threaded.get(TIMELESS_TABLE_KEY) { Hash.new }
57
60
  end
58
61
 
59
62
  def_delegators :timeless_table, :[]=, :[]
@@ -195,7 +195,7 @@ module Mongoid
195
195
  # @return [ Hash ] The hash that contains touch callback suppression
196
196
  # statuses
197
197
  def touch_callback_statuses
198
- Thread.current[SUPPRESS_TOUCH_CALLBACKS_KEY] ||= {}
198
+ Threaded.get(SUPPRESS_TOUCH_CALLBACKS_KEY) { {} }
199
199
  end
200
200
 
201
201
  # Define the method that will get called for touching belongs_to
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'mongoid/fields/validators/macro'
4
+ require 'mongoid/model_resolver'
4
5
 
5
6
  module Mongoid
6
7
  # Mixin module included in Mongoid::Document to provide behavior
@@ -32,6 +33,10 @@ module Mongoid
32
33
  # rubocop:disable Metrics/AbcSize
33
34
  def inherited(subclass)
34
35
  super
36
+
37
+ # Register the new subclass with the resolver subsystem
38
+ Mongoid::ModelResolver.register(subclass)
39
+
35
40
  @_type = nil
36
41
  subclass.aliased_fields = aliased_fields.dup
37
42
  subclass.localized_fields = localized_fields.dup
@@ -100,7 +105,11 @@ module Mongoid
100
105
  if value
101
106
  Mongoid::Fields::Validators::Macro.validate_field_name(self, value)
102
107
  value = value.to_s
103
- super
108
+ if defined?(::ActiveSupport::ClassAttribute)
109
+ ::ActiveSupport::ClassAttribute.redefine(self, 'discriminator_key', value)
110
+ else
111
+ super
112
+ end
104
113
  else
105
114
  # When discriminator key is set to nil, replace the class's definition
106
115
  # of the discriminator key reader (provided by class_attribute earlier)
@@ -114,7 +123,7 @@ module Mongoid
114
123
  # an existing field.
115
124
  # This condition also checks if the class has any descendants, because
116
125
  # if it doesn't then it doesn't need a discriminator key.
117
- return unless !fields.key?(discriminator_key) && !descendants.empty?
126
+ return if fields.key?(discriminator_key) || descendants.empty?
118
127
 
119
128
  default_proc = -> { self.class.discriminator_value }
120
129
  field(discriminator_key, default: default_proc, type: String)
@@ -70,13 +70,16 @@ module Mongoid
70
70
  # Now, treating the target as an array, look at each element
71
71
  # and see if it is valid, but only if it has already been
72
72
  # persisted, or changed, and hasn't been flagged for destroy.
73
- list.all? do |value|
73
+ #
74
+ # use map.all? instead of just all?, because all? will do short-circuit
75
+ # evaluation and terminate on the first failed validation.
76
+ list.map do |value|
74
77
  if value && !value.flagged_for_destroy? && (!value.persisted? || value.changed?)
75
78
  value.validated? ? true : value.valid?
76
79
  else
77
80
  true
78
81
  end
79
- end
82
+ end.all?
80
83
  end
81
84
 
82
85
  document.errors.add(attribute, :invalid) unless valid
@@ -2,5 +2,5 @@
2
2
  # rubocop:todo all
3
3
 
4
4
  module Mongoid
5
- VERSION = "9.0.1"
5
+ VERSION = "9.0.3"
6
6
  end
@@ -2,31 +2,35 @@
2
2
  # rubocop:todo all
3
3
 
4
4
  require 'spec_helper'
5
- require 'active_job'
6
- require 'mongoid/railties/bson_object_id_serializer'
5
+ begin
6
+ require 'active_job'
7
+ require 'mongoid/railties/bson_object_id_serializer'
7
8
 
8
- describe 'ActiveJob Serialization' do
9
- skip unless defined?(ActiveJob)
9
+ describe 'ActiveJob Serialization' do
10
+ skip unless defined?(ActiveJob)
10
11
 
11
- class TestBsonObjectIdSerializerJob < ActiveJob::Base
12
- def perform(*args)
13
- args
12
+ class TestBsonObjectIdSerializerJob < ActiveJob::Base
13
+ def perform(*args)
14
+ args
15
+ end
14
16
  end
15
- end
16
17
 
17
- let(:band) do
18
- Band.create!
19
- end
18
+ let(:band) do
19
+ Band.create!
20
+ end
20
21
 
21
- before do
22
- ActiveJob::Serializers.add_serializers(
23
- [::Mongoid::Railties::ActiveJobSerializers::BsonObjectIdSerializer]
24
- )
25
- end
22
+ before do
23
+ ActiveJob::Serializers.add_serializers(
24
+ [::Mongoid::Railties::ActiveJobSerializers::BsonObjectIdSerializer]
25
+ )
26
+ end
26
27
 
27
- it 'serializes and deserializes BSON::ObjectId' do
28
- expect do
29
- TestBsonObjectIdSerializerJob.perform_later(band.id)
30
- end.not_to raise_error
28
+ it 'serializes and deserializes BSON::ObjectId' do
29
+ expect do
30
+ TestBsonObjectIdSerializerJob.perform_later(band.id)
31
+ end.not_to raise_error
32
+ end
31
33
  end
34
+ rescue LoadError
35
+ RSpec.context.skip 'This test requires active_job'
32
36
  end