mongoid 9.0.2 → 9.0.4

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 (33) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mongoid/attributes/readonly.rb +8 -3
  3. data/lib/mongoid/clients/options.rb +14 -1
  4. data/lib/mongoid/clients/sessions.rb +1 -0
  5. data/lib/mongoid/criteria/queryable/selectable.rb +1 -1
  6. data/lib/mongoid/equality.rb +1 -0
  7. data/lib/mongoid/loadable.rb +72 -8
  8. data/lib/mongoid/matcher.rb +15 -1
  9. data/lib/mongoid/persistence_context.rb +14 -9
  10. data/lib/mongoid/railties/controller_runtime.rb +2 -2
  11. data/lib/mongoid/serializable.rb +7 -7
  12. data/lib/mongoid/threaded.rb +96 -28
  13. data/lib/mongoid/timestamps/timeless.rb +4 -1
  14. data/lib/mongoid/touchable.rb +1 -1
  15. data/lib/mongoid/traversable.rb +25 -2
  16. data/lib/mongoid/validatable/associated.rb +5 -2
  17. data/lib/mongoid/version.rb +1 -1
  18. data/spec/integration/active_job_spec.rb +24 -20
  19. data/spec/integration/app_spec.rb +9 -1
  20. data/spec/mongoid/association/referenced/belongs_to/proxy_spec.rb +4 -0
  21. data/spec/mongoid/attributes/readonly_spec.rb +19 -0
  22. data/spec/mongoid/clients/options_spec.rb +127 -2
  23. data/spec/mongoid/criteria/queryable/selectable_spec.rb +29 -0
  24. data/spec/mongoid/equality_spec.rb +6 -0
  25. data/spec/mongoid/interceptable_spec.rb +12 -0
  26. data/spec/mongoid/interceptable_spec_models.rb +12 -0
  27. data/spec/mongoid/loadable_spec.rb +86 -0
  28. data/spec/mongoid/persistence_context_spec.rb +39 -0
  29. data/spec/mongoid/railties/bson_object_id_serializer_spec.rb +18 -12
  30. data/spec/mongoid/threaded_spec.rb +24 -5
  31. data/spec/mongoid/validatable/associated_spec.rb +14 -4
  32. data/spec/rails/controller_extension/controller_runtime_spec.rb +14 -14
  33. metadata +7 -8
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 558b30b0a39cd36cd9a3e54695f4617a9fcc0d67300637d1299b487b7d911bda
4
- data.tar.gz: eccf9c55bdde17fd38592427e0dd6b5545d3e605b94a4308a422ba332e20c183
3
+ metadata.gz: 3ac7ac8e60ba9d953a5a809d17b7fca7a2501988cdec50d12348af3b7022f615
4
+ data.tar.gz: 27a2bceb132e4f6b33d8be6b70ac8074b8de421c5c1efdb03f3ad2fc97d62364
5
5
  SHA512:
6
- metadata.gz: 420ac9d23abeecb04c19794df124fc80d4f99883e1156d4e3be42e79b4b2e5e5a558c6b650746784024f0477a800def3a2ded537b848d3e9b97fb5e5e220b1f0
7
- data.tar.gz: 40bfdcaf862e3ae7d3d046d84d5f9f5eea4752b5559c869eda16e1bd00cbc9aaae3eef05a7e4bec497915619fb162a248ac8f4be3525068e81f28ae876af1a29
6
+ metadata.gz: 31cea6e77afe05358ff9cdaf5bc861807a93ce9c89e7cfd9209c0f35a2df816fb8d4d595faafbab2665b2dab776f523d0b96eece4b249626f976ec685765d5a5
7
+ data.tar.gz: 1367fb446dae452b73227c099e72ed2f13bd2702434e8050c09f3c1ae2ad97b5346371252ce3cfa3442151353bd8c8ef9a409cd2de75edb7cdf20ff3322107c7
@@ -23,7 +23,7 @@ module Mongoid
23
23
  # @return [ true | false ] If the document is new, or if the field is not
24
24
  # readonly.
25
25
  def attribute_writable?(name)
26
- new_record? || (!readonly_attributes.include?(name) && _loaded?(name))
26
+ new_record? || (!self.class.readonly_attributes.include?(name) && _loaded?(name))
27
27
  end
28
28
 
29
29
  private
@@ -63,12 +63,17 @@ module Mongoid
63
63
  # end
64
64
  #
65
65
  # @param [ Symbol... ] *names The names of the fields.
66
+ # @note When a parent class contains readonly attributes and is then
67
+ # inherited by a child class, the child class will inherit the
68
+ # parent's readonly attributes at the time of its creation.
69
+ # Updating the parent does not propagate down to child classes after wards.
66
70
  def attr_readonly(*names)
71
+ self.readonly_attributes = self.readonly_attributes.dup
67
72
  names.each do |name|
68
- readonly_attributes << database_field_name(name)
73
+ self.readonly_attributes << database_field_name(name)
69
74
  end
70
75
  end
71
76
  end
72
77
  end
73
78
  end
74
- end
79
+ end
@@ -86,7 +86,7 @@ module Mongoid
86
86
  else
87
87
  PersistenceContext.get(self) ||
88
88
  PersistenceContext.get(self.class) ||
89
- PersistenceContext.new(self.class, storage_options)
89
+ PersistenceContext.new(self.class, default_storage_options)
90
90
  end
91
91
  end
92
92
 
@@ -112,6 +112,19 @@ module Mongoid
112
112
 
113
113
  private
114
114
 
115
+ def default_storage_options
116
+ # Nothing is overridden, we use either the default storage_options
117
+ # or storage_options defined for the document class.
118
+ return storage_options if Threaded.client_override.nil? && Threaded.database_override.nil?
119
+
120
+ storage_options.tap do |opts|
121
+ # Globally overridden client replaces client defined for the document class.
122
+ opts[:client] = Threaded.client_override unless Threaded.client_override.nil?
123
+ # Globally overridden database replaces database defined for the document class.
124
+ opts[:database] = Threaded.database_override unless Threaded.database_override.nil?
125
+ end
126
+ end
127
+
115
128
  def set_persistence_context(options_or_context)
116
129
  PersistenceContext.set(self, options_or_context)
117
130
  end
@@ -62,6 +62,7 @@ module Mongoid
62
62
  rescue *transactions_not_supported_exceptions
63
63
  raise Mongoid::Errors::TransactionsNotSupported
64
64
  ensure
65
+ Threaded.clear_modified_documents(session)
65
66
  Threaded.clear_session(client: persistence_context.client)
66
67
  end
67
68
 
@@ -553,7 +553,7 @@ module Mongoid
553
553
  # @return [ Selectable ] The new selectable.
554
554
  def not(*criteria)
555
555
  if criteria.empty?
556
- dup.tap { |query| query.negating = true }
556
+ dup.tap { |query| query.negating = !query.negating }
557
557
  else
558
558
  criteria.compact.inject(self.clone) do |c, new_s|
559
559
  if new_s.is_a?(Selectable)
@@ -18,6 +18,7 @@ module Mongoid
18
18
  #
19
19
  # @return [ Integer ] -1, 0, 1.
20
20
  def <=>(other)
21
+ return super unless other.is_a?(Mongoid::Equality)
21
22
  attributes["_id"].to_s <=> other.attributes["_id"].to_s
22
23
  end
23
24
 
@@ -10,6 +10,13 @@ module Mongoid
10
10
  # (See #model_paths.)
11
11
  DEFAULT_MODEL_PATHS = %w( ./app/models ./lib/models ).freeze
12
12
 
13
+ # The default list of glob patterns that match paths to ignore when loading
14
+ # models. Defaults to '*/models/concerns/*', which Rails uses for extensions
15
+ # to models (and which cause errors when loaded out of order).
16
+ #
17
+ # See #ignore_patterns.
18
+ DEFAULT_IGNORE_PATTERNS = %w( */models/concerns/* ).freeze
19
+
13
20
  # Search a list of model paths to get every model and require it, so
14
21
  # that indexing and inheritance work in both development and production
15
22
  # with the same results.
@@ -24,17 +31,47 @@ module Mongoid
24
31
  # for model files. These must either be absolute paths, or relative to
25
32
  # the current working directory.
26
33
  def load_models(paths = model_paths)
27
- paths.each do |path|
28
- if preload_models.resizable?
29
- files = preload_models.map { |model| "#{path}/#{model.underscore}.rb" }
34
+ files = files_under_paths(paths)
35
+
36
+ files.sort.each do |file|
37
+ load_model(file)
38
+ end
39
+
40
+ nil
41
+ end
42
+
43
+ # Given a list of paths, return all ruby files under that path (or, if
44
+ # `preload_models` is a list of model names, returns only the files for
45
+ # those named models).
46
+ #
47
+ # @param [ Array<String> ] paths the list of paths to search
48
+ #
49
+ # @return [ Array<String> ] the normalized file names, suitable for loading
50
+ # via `require_dependency` or `require`.
51
+ def files_under_paths(paths)
52
+ paths.flat_map { |path| files_under_path(path) }
53
+ end
54
+
55
+ # Given a single path, returns all ruby files under that path (or, if
56
+ # `preload_models` is a list of model names, returns only the files for
57
+ # those named models).
58
+ #
59
+ # @param [ String ] path the path to search
60
+ #
61
+ # @return [ Array<String> ] the normalized file names, suitable for loading
62
+ # via `require_dependency` or `require`.
63
+ def files_under_path(path)
64
+ files = if preload_models.resizable?
65
+ preload_models.
66
+ map { |model| "#{path}/#{model.underscore}.rb" }.
67
+ select { |file_name| File.exists?(file_name) }
30
68
  else
31
- files = Dir.glob("#{path}/**/*.rb")
69
+ Dir.glob("#{path}/**/*.rb").
70
+ reject { |file_name| ignored?(file_name) }
32
71
  end
33
72
 
34
- files.sort.each do |file|
35
- load_model(file.gsub(/^#{path}\// , "").gsub(/\.rb$/, ""))
36
- end
37
- end
73
+ # strip the path and the suffix from each entry
74
+ files.map { |file| file.gsub(/^#{path}\// , "").gsub(/\.rb$/, "") }
38
75
  end
39
76
 
40
77
  # A convenience method for loading a model's file. If Rails'
@@ -71,6 +108,14 @@ module Mongoid
71
108
  DEFAULT_MODEL_PATHS
72
109
  end
73
110
 
111
+ # Returns the array of glob patterns that determine whether a given
112
+ # path should be ignored by the model loader.
113
+ #
114
+ # @return [ Array<String> ] the array of ignore patterns
115
+ def ignore_patterns
116
+ @ignore_patterns ||= DEFAULT_IGNORE_PATTERNS.dup
117
+ end
118
+
74
119
  # Sets the model paths to the given array of paths. These are the paths
75
120
  # where the application's model definitions are located.
76
121
  #
@@ -78,6 +123,25 @@ module Mongoid
78
123
  def model_paths=(paths)
79
124
  @model_paths = paths
80
125
  end
126
+
127
+ # Sets the ignore patterns to the given array of patterns. These are glob
128
+ # patterns that determine whether a given path should be ignored by the
129
+ # model loader or not.
130
+ #
131
+ # @param [ Array<String> ] patterns The list of glob patterns
132
+ def ignore_patterns=(patterns)
133
+ @ignore_patterns = patterns
134
+ end
135
+
136
+ # Returns true if the given file path matches any of the ignore patterns.
137
+ #
138
+ # @param [ String ] file_path The file path to consider
139
+ #
140
+ # @return [ true | false ] whether or not the given file path should be
141
+ # ignored.
142
+ def ignored?(file_path)
143
+ ignore_patterns.any? { |pattern| File.fnmatch?(pattern, file_path) }
144
+ end
81
145
  end
82
146
 
83
147
  end
@@ -39,11 +39,25 @@ module Mongoid
39
39
  # from and behaves identically to association traversal for the purposes
40
40
  # of, for example, subsequent array element retrieval.
41
41
  #
42
- # @param [ Document | Hash ] document The document to extract from.
42
+ # @param [ Document | Hash | String ] document The document to extract from.
43
43
  # @param [ String ] key The key path to extract.
44
44
  #
45
45
  # @return [ Object | Array ] Field value or values.
46
46
  module_function def extract_attribute(document, key)
47
+ # The matcher system will wind up sending atomic values to this as well,
48
+ # when attepting to match more complex types. If anything other than a
49
+ # Document or a Hash is given, we'll short-circuit the logic and just
50
+ # return an empty array.
51
+ return [] unless document.is_a?(Hash) || document.is_a?(Document)
52
+
53
+ # Performance optimization; if the key does not include a '.' character,
54
+ # it must reference an immediate attribute of the document.
55
+ unless key.include?('.')
56
+ hash = document.respond_to?(:attributes) ? document.attributes : document
57
+ key = find_exact_key(hash, key)
58
+ return key ? [ hash[key] ] : []
59
+ end
60
+
47
61
  if document.respond_to?(:as_attributes, true)
48
62
  # If a document has hash fields, as_attributes would keep those fields
49
63
  # as Hash instances which do not offer indifferent access.
@@ -117,12 +117,15 @@ module Mongoid
117
117
  def client
118
118
  @client ||= begin
119
119
  client = Clients.with_name(client_name)
120
+ options = client_options
121
+
120
122
  if database_name_option
121
123
  client = client.use(database_name)
124
+ options = options.except(:database, 'database')
122
125
  end
123
- unless client_options.empty?
124
- client = client.with(client_options)
125
- end
126
+
127
+ client = client.with(options) unless options.empty?
128
+
126
129
  client
127
130
  end
128
131
  end
@@ -135,7 +138,7 @@ module Mongoid
135
138
  # @return [ Symbol ] The client name for this persistence
136
139
  # context.
137
140
  def client_name
138
- @client_name ||= options[:client] ||
141
+ @client_name ||= __evaluate__(options[:client]) ||
139
142
  Threaded.client_override ||
140
143
  __evaluate__(storage_options[:client])
141
144
  end
@@ -282,6 +285,10 @@ module Mongoid
282
285
  # @api private
283
286
  PERSISTENCE_CONTEXT_KEY = :"[mongoid]:persistence_context"
284
287
 
288
+ def context_store
289
+ Threaded.get(PERSISTENCE_CONTEXT_KEY) { {} }
290
+ end
291
+
285
292
  # Get the persistence context for a given object from the thread local
286
293
  # storage.
287
294
  #
@@ -292,8 +299,7 @@ module Mongoid
292
299
  #
293
300
  # @api private
294
301
  def get_context(object)
295
- Thread.current[PERSISTENCE_CONTEXT_KEY] ||= {}
296
- Thread.current[PERSISTENCE_CONTEXT_KEY][object.object_id]
302
+ context_store[object.object_id]
297
303
  end
298
304
 
299
305
  # Store persistence context for a given object in the thread local
@@ -305,10 +311,9 @@ module Mongoid
305
311
  # @api private
306
312
  def store_context(object, context)
307
313
  if context.nil?
308
- Thread.current[PERSISTENCE_CONTEXT_KEY]&.delete(object.object_id)
314
+ context_store.delete(object.object_id)
309
315
  else
310
- Thread.current[PERSISTENCE_CONTEXT_KEY] ||= {}
311
- Thread.current[PERSISTENCE_CONTEXT_KEY][object.object_id] = context
316
+ context_store[object.object_id] = context
312
317
  end
313
318
  end
314
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
@@ -8,6 +8,29 @@ module Mongoid
8
8
  # around traversing the document graph.
9
9
  module Traversable
10
10
  extend ActiveSupport::Concern
11
+ # This code is extracted from ActiveSupport so that we do not depend on
12
+ # their private API that may change at any time.
13
+ # This code should be reviewed and maybe removed when implementing
14
+ # https://jira.mongodb.org/browse/MONGOID-5832
15
+ class << self
16
+ # @api private
17
+ def __redefine(owner, name, value)
18
+ if owner.singleton_class?
19
+ owner.redefine_method(name) { value }
20
+ owner.send(:public, name)
21
+ end
22
+ owner.redefine_singleton_method(name) { value }
23
+ owner.singleton_class.send(:public, name)
24
+ owner.redefine_singleton_method("#{name}=") do |new_value|
25
+ if owner.equal?(self)
26
+ value = new_value
27
+ else
28
+ ::Mongoid::Traversable.redefine(self, name, new_value)
29
+ end
30
+ end
31
+ owner.singleton_class.send(:public, "#{name}=")
32
+ end
33
+ end
11
34
 
12
35
  # Class-level methods for the Traversable behavior.
13
36
  module ClassMethods
@@ -105,7 +128,7 @@ module Mongoid
105
128
  if value
106
129
  Mongoid::Fields::Validators::Macro.validate_field_name(self, value)
107
130
  value = value.to_s
108
- super
131
+ ::Mongoid::Traversable.__redefine(self, 'discriminator_key', value)
109
132
  else
110
133
  # When discriminator key is set to nil, replace the class's definition
111
134
  # of the discriminator key reader (provided by class_attribute earlier)
@@ -119,7 +142,7 @@ module Mongoid
119
142
  # an existing field.
120
143
  # This condition also checks if the class has any descendants, because
121
144
  # if it doesn't then it doesn't need a discriminator key.
122
- return unless !fields.key?(discriminator_key) && !descendants.empty?
145
+ return if fields.key?(discriminator_key) || descendants.empty?
123
146
 
124
147
  default_proc = -> { self.class.discriminator_value }
125
148
  field(discriminator_key, default: default_proc, type: String)