mongoid 9.0.2 → 9.0.4

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