activesupport 8.0.2.1 → 8.1.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +334 -129
- data/README.rdoc +1 -1
- data/lib/active_support/backtrace_cleaner.rb +71 -0
- data/lib/active_support/broadcast_logger.rb +46 -59
- data/lib/active_support/cache/mem_cache_store.rb +25 -27
- data/lib/active_support/cache/redis_cache_store.rb +36 -30
- data/lib/active_support/cache/strategy/local_cache.rb +16 -7
- data/lib/active_support/cache/strategy/local_cache_middleware.rb +7 -7
- data/lib/active_support/cache.rb +70 -6
- data/lib/active_support/callbacks.rb +20 -8
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +8 -62
- data/lib/active_support/concurrency/thread_monitor.rb +55 -0
- data/lib/active_support/configurable.rb +34 -0
- data/lib/active_support/continuous_integration.rb +145 -0
- data/lib/active_support/core_ext/array.rb +7 -7
- data/lib/active_support/core_ext/benchmark.rb +4 -11
- data/lib/active_support/core_ext/big_decimal.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +8 -6
- data/lib/active_support/core_ext/class.rb +2 -2
- data/lib/active_support/core_ext/date.rb +5 -5
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +0 -35
- data/lib/active_support/core_ext/date_time/compatibility.rb +3 -5
- data/lib/active_support/core_ext/date_time/conversions.rb +4 -2
- data/lib/active_support/core_ext/date_time.rb +5 -5
- data/lib/active_support/core_ext/digest.rb +1 -1
- data/lib/active_support/core_ext/enumerable.rb +16 -4
- data/lib/active_support/core_ext/erb/util.rb +3 -3
- data/lib/active_support/core_ext/file.rb +1 -1
- data/lib/active_support/core_ext/hash.rb +8 -8
- data/lib/active_support/core_ext/integer.rb +3 -3
- data/lib/active_support/core_ext/kernel.rb +3 -3
- data/lib/active_support/core_ext/module.rb +11 -11
- data/lib/active_support/core_ext/numeric.rb +3 -3
- data/lib/active_support/core_ext/object/json.rb +8 -1
- data/lib/active_support/core_ext/object/to_query.rb +7 -1
- data/lib/active_support/core_ext/object/try.rb +2 -2
- data/lib/active_support/core_ext/object.rb +13 -13
- data/lib/active_support/core_ext/pathname.rb +2 -2
- data/lib/active_support/core_ext/range/overlap.rb +3 -3
- data/lib/active_support/core_ext/range/sole.rb +17 -0
- data/lib/active_support/core_ext/range.rb +4 -4
- data/lib/active_support/core_ext/string/filters.rb +3 -3
- data/lib/active_support/core_ext/string/multibyte.rb +12 -3
- data/lib/active_support/core_ext/string/output_safety.rb +19 -12
- data/lib/active_support/core_ext/string.rb +13 -13
- data/lib/active_support/core_ext/symbol.rb +1 -1
- data/lib/active_support/core_ext/time/calculations.rb +0 -7
- data/lib/active_support/core_ext/time/compatibility.rb +2 -27
- data/lib/active_support/core_ext/time.rb +5 -5
- data/lib/active_support/core_ext.rb +1 -1
- data/lib/active_support/current_attributes/test_helper.rb +2 -2
- data/lib/active_support/current_attributes.rb +26 -16
- data/lib/active_support/dependencies/interlock.rb +11 -5
- data/lib/active_support/dependencies.rb +6 -1
- data/lib/active_support/deprecation/reporting.rb +4 -2
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/editor.rb +70 -0
- data/lib/active_support/error_reporter.rb +50 -6
- data/lib/active_support/event_reporter/test_helper.rb +32 -0
- data/lib/active_support/event_reporter.rb +592 -0
- data/lib/active_support/evented_file_update_checker.rb +5 -1
- data/lib/active_support/execution_context.rb +64 -7
- data/lib/active_support/file_update_checker.rb +8 -6
- data/lib/active_support/gem_version.rb +3 -3
- data/lib/active_support/gzip.rb +1 -0
- data/lib/active_support/hash_with_indifferent_access.rb +47 -24
- data/lib/active_support/i18n_railtie.rb +2 -2
- data/lib/active_support/inflector/inflections.rb +31 -15
- data/lib/active_support/inflector/transliterate.rb +6 -8
- data/lib/active_support/isolated_execution_state.rb +12 -15
- data/lib/active_support/json/decoding.rb +6 -4
- data/lib/active_support/json/encoding.rb +135 -17
- data/lib/active_support/lazy_load_hooks.rb +1 -1
- data/lib/active_support/log_subscriber.rb +2 -6
- data/lib/active_support/logger_thread_safe_level.rb +6 -3
- data/lib/active_support/message_encryptors.rb +52 -0
- data/lib/active_support/message_pack/extensions.rb +5 -0
- data/lib/active_support/message_verifiers.rb +52 -0
- data/lib/active_support/messages/rotation_coordinator.rb +9 -0
- data/lib/active_support/messages/rotator.rb +5 -0
- data/lib/active_support/multibyte/chars.rb +8 -1
- data/lib/active_support/multibyte.rb +4 -0
- data/lib/active_support/notifications/fanout.rb +64 -42
- data/lib/active_support/notifications/instrumenter.rb +1 -1
- data/lib/active_support/railtie.rb +32 -15
- data/lib/active_support/structured_event_subscriber.rb +99 -0
- data/lib/active_support/subscriber.rb +0 -5
- data/lib/active_support/syntax_error_proxy.rb +3 -0
- data/lib/active_support/test_case.rb +61 -6
- data/lib/active_support/testing/assertions.rb +34 -6
- data/lib/active_support/testing/error_reporter_assertions.rb +18 -1
- data/lib/active_support/testing/event_reporter_assertions.rb +227 -0
- data/lib/active_support/testing/notification_assertions.rb +92 -0
- data/lib/active_support/testing/parallelization/server.rb +15 -2
- data/lib/active_support/testing/parallelization/worker.rb +4 -2
- data/lib/active_support/testing/parallelization.rb +25 -1
- data/lib/active_support/testing/tests_without_assertions.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +7 -3
- data/lib/active_support/time_with_zone.rb +22 -22
- data/lib/active_support/values/time_zone.rb +8 -1
- data/lib/active_support/xml_mini.rb +3 -2
- data/lib/active_support.rb +20 -15
- metadata +25 -17
- data/lib/active_support/core_ext/range/each.rb +0 -24
|
@@ -46,8 +46,11 @@ module ActiveSupport
|
|
|
46
46
|
raise ArgumentError, "A block is required to initialize a FileUpdateChecker"
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
@
|
|
49
|
+
gem_paths = Gem.path
|
|
50
|
+
@files = files.reject { |file| File.expand_path(file).start_with?(*gem_paths) }.freeze
|
|
51
|
+
|
|
52
|
+
@globs = compile_glob(dirs)&.reject { |dir| dir.start_with?(*gem_paths) }
|
|
53
|
+
|
|
51
54
|
@block = block
|
|
52
55
|
|
|
53
56
|
@watched = nil
|
|
@@ -103,7 +106,7 @@ module ActiveSupport
|
|
|
103
106
|
def watched
|
|
104
107
|
@watched || begin
|
|
105
108
|
all = @files.select { |f| File.exist?(f) }
|
|
106
|
-
all.concat(Dir[
|
|
109
|
+
all.concat(Dir[*@globs]) if @globs
|
|
107
110
|
all.tap(&:uniq!)
|
|
108
111
|
end
|
|
109
112
|
end
|
|
@@ -120,7 +123,7 @@ module ActiveSupport
|
|
|
120
123
|
# healthy to consider this edge case because with mtimes in the future
|
|
121
124
|
# reloading is not triggered.
|
|
122
125
|
def max_mtime(paths)
|
|
123
|
-
time_now = Time.
|
|
126
|
+
time_now = Time.at(0, Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond), :nanosecond)
|
|
124
127
|
max_mtime = nil
|
|
125
128
|
|
|
126
129
|
# Time comparisons are performed with #compare_without_coercion because
|
|
@@ -145,10 +148,9 @@ module ActiveSupport
|
|
|
145
148
|
hash.freeze # Freeze so changes aren't accidentally pushed
|
|
146
149
|
return if hash.empty?
|
|
147
150
|
|
|
148
|
-
|
|
151
|
+
hash.map do |key, value|
|
|
149
152
|
"#{escape(key)}/**/*#{compile_ext(value)}"
|
|
150
153
|
end
|
|
151
|
-
"{#{globs.join(",")}}"
|
|
152
154
|
end
|
|
153
155
|
|
|
154
156
|
def escape(key)
|
data/lib/active_support/gzip.rb
CHANGED
|
@@ -68,15 +68,15 @@ module ActiveSupport
|
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def initialize(constructor = nil)
|
|
71
|
-
if constructor.
|
|
71
|
+
if constructor.nil?
|
|
72
|
+
super()
|
|
73
|
+
elsif constructor.respond_to?(:to_hash)
|
|
72
74
|
super()
|
|
73
75
|
update(constructor)
|
|
74
76
|
|
|
75
77
|
hash = constructor.is_a?(Hash) ? constructor : constructor.to_hash
|
|
76
78
|
self.default = hash.default if hash.default
|
|
77
79
|
self.default_proc = hash.default_proc if hash.default_proc
|
|
78
|
-
elsif constructor.nil?
|
|
79
|
-
super()
|
|
80
80
|
else
|
|
81
81
|
super(constructor)
|
|
82
82
|
end
|
|
@@ -95,11 +95,27 @@ module ActiveSupport
|
|
|
95
95
|
# hash[:key] = 'value'
|
|
96
96
|
#
|
|
97
97
|
# This value can be later fetched using either +:key+ or <tt>'key'</tt>.
|
|
98
|
+
#
|
|
99
|
+
# If the value is a Hash or contains one or multiple Hashes, they will be
|
|
100
|
+
# converted to +HashWithIndifferentAccess+.
|
|
98
101
|
def []=(key, value)
|
|
99
102
|
regular_writer(convert_key(key), convert_value(value, conversion: :assignment))
|
|
100
103
|
end
|
|
101
104
|
|
|
102
|
-
|
|
105
|
+
# Assigns a new value to the hash:
|
|
106
|
+
#
|
|
107
|
+
# hash = ActiveSupport::HashWithIndifferentAccess.new
|
|
108
|
+
# hash[:key] = 'value'
|
|
109
|
+
#
|
|
110
|
+
# This value can be later fetched using either +:key+ or <tt>'key'</tt>.
|
|
111
|
+
#
|
|
112
|
+
# If the value is a Hash or contains one or multiple Hashes, they will be
|
|
113
|
+
# converted to +HashWithIndifferentAccess+. unless `convert_value: false`
|
|
114
|
+
# is set.
|
|
115
|
+
def store(key, value, convert_value: true)
|
|
116
|
+
value = convert_value(value, conversion: :assignment) if convert_value
|
|
117
|
+
regular_writer(convert_key(key), value)
|
|
118
|
+
end
|
|
103
119
|
|
|
104
120
|
# Updates the receiver in-place, merging in the hashes passed as arguments:
|
|
105
121
|
#
|
|
@@ -262,9 +278,7 @@ module ActiveSupport
|
|
|
262
278
|
# hash[:a][:c] # => "c"
|
|
263
279
|
# dup[:a][:c] # => "c"
|
|
264
280
|
def dup
|
|
265
|
-
self.class.new(self)
|
|
266
|
-
set_defaults(new_hash)
|
|
267
|
-
end
|
|
281
|
+
copy_defaults(self.class.new(self))
|
|
268
282
|
end
|
|
269
283
|
|
|
270
284
|
# This method has the same semantics of +update+, except it does not
|
|
@@ -281,13 +295,13 @@ module ActiveSupport
|
|
|
281
295
|
# hash['a'] = nil
|
|
282
296
|
# hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
|
|
283
297
|
def reverse_merge(other_hash)
|
|
284
|
-
super(
|
|
298
|
+
super(cast(other_hash))
|
|
285
299
|
end
|
|
286
300
|
alias_method :with_defaults, :reverse_merge
|
|
287
301
|
|
|
288
302
|
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
|
|
289
303
|
def reverse_merge!(other_hash)
|
|
290
|
-
super(
|
|
304
|
+
super(cast(other_hash))
|
|
291
305
|
end
|
|
292
306
|
alias_method :with_defaults!, :reverse_merge!
|
|
293
307
|
|
|
@@ -296,7 +310,7 @@ module ActiveSupport
|
|
|
296
310
|
# h = { "a" => 100, "b" => 200 }
|
|
297
311
|
# h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
|
|
298
312
|
def replace(other_hash)
|
|
299
|
-
super(
|
|
313
|
+
super(cast(other_hash))
|
|
300
314
|
end
|
|
301
315
|
|
|
302
316
|
# Removes the specified key from the hash.
|
|
@@ -338,21 +352,26 @@ module ActiveSupport
|
|
|
338
352
|
NOT_GIVEN = Object.new # :nodoc:
|
|
339
353
|
|
|
340
354
|
def transform_keys(hash = NOT_GIVEN, &block)
|
|
341
|
-
|
|
342
|
-
|
|
355
|
+
if NOT_GIVEN.equal?(hash)
|
|
356
|
+
if block_given?
|
|
357
|
+
self.class.new(super(&block))
|
|
358
|
+
else
|
|
359
|
+
to_enum(:transform_keys)
|
|
360
|
+
end
|
|
361
|
+
else
|
|
362
|
+
self.class.new(super)
|
|
363
|
+
end
|
|
343
364
|
end
|
|
344
365
|
|
|
345
366
|
def transform_keys!(hash = NOT_GIVEN, &block)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
elsif block_given?
|
|
353
|
-
keys.each { |key| self[hash[key] || yield(key)] = delete(key) }
|
|
367
|
+
if NOT_GIVEN.equal?(hash)
|
|
368
|
+
if block_given?
|
|
369
|
+
replace(copy_defaults(transform_keys(&block)))
|
|
370
|
+
else
|
|
371
|
+
return to_enum(:transform_keys!)
|
|
372
|
+
end
|
|
354
373
|
else
|
|
355
|
-
|
|
374
|
+
replace(copy_defaults(transform_keys(hash, &block)))
|
|
356
375
|
end
|
|
357
376
|
|
|
358
377
|
self
|
|
@@ -376,8 +395,7 @@ module ActiveSupport
|
|
|
376
395
|
def to_hash
|
|
377
396
|
copy = Hash[self]
|
|
378
397
|
copy.transform_values! { |v| convert_value_to_hash(v) }
|
|
379
|
-
|
|
380
|
-
copy
|
|
398
|
+
copy_defaults(copy)
|
|
381
399
|
end
|
|
382
400
|
|
|
383
401
|
def to_proc
|
|
@@ -385,6 +403,10 @@ module ActiveSupport
|
|
|
385
403
|
end
|
|
386
404
|
|
|
387
405
|
private
|
|
406
|
+
def cast(other)
|
|
407
|
+
self.class === other ? other : self.class.new(other)
|
|
408
|
+
end
|
|
409
|
+
|
|
388
410
|
def convert_key(key)
|
|
389
411
|
Symbol === key ? key.name : key
|
|
390
412
|
end
|
|
@@ -413,12 +435,13 @@ module ActiveSupport
|
|
|
413
435
|
end
|
|
414
436
|
|
|
415
437
|
|
|
416
|
-
def
|
|
438
|
+
def copy_defaults(target)
|
|
417
439
|
if default_proc
|
|
418
440
|
target.default_proc = default_proc.dup
|
|
419
441
|
else
|
|
420
442
|
target.default = default
|
|
421
443
|
end
|
|
444
|
+
target
|
|
422
445
|
end
|
|
423
446
|
|
|
424
447
|
def update_with_single_argument(other_hash, block)
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "active_support"
|
|
4
4
|
require "active_support/core_ext/array/wrap"
|
|
5
|
+
require "rails/railtie"
|
|
5
6
|
|
|
6
7
|
# :enddoc:
|
|
7
8
|
|
|
@@ -66,8 +67,7 @@ module I18n
|
|
|
66
67
|
|
|
67
68
|
if app.config.reloading_enabled?
|
|
68
69
|
directories = watched_dirs_with_extensions(reloadable_paths)
|
|
69
|
-
|
|
70
|
-
reloader = app.config.file_watcher.new(root_load_paths, directories) do
|
|
70
|
+
reloader = app.config.file_watcher.new(I18n.load_path, directories) do
|
|
71
71
|
I18n.load_path.delete_if { |path| path.to_s.start_with?(Rails.root.to_s) && !File.exist?(path) }
|
|
72
72
|
I18n.load_path |= reloadable_paths.flat_map(&:existent)
|
|
73
73
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "concurrent/map"
|
|
4
|
+
require "active_support/core_ext/module/delegation"
|
|
4
5
|
require "active_support/i18n"
|
|
5
6
|
|
|
6
7
|
module ActiveSupport
|
|
@@ -29,44 +30,59 @@ module ActiveSupport
|
|
|
29
30
|
# before any of the rules that may already have been loaded.
|
|
30
31
|
class Inflections
|
|
31
32
|
@__instance__ = Concurrent::Map.new
|
|
33
|
+
@__en_instance__ = nil
|
|
34
|
+
|
|
35
|
+
class Uncountables # :nodoc:
|
|
36
|
+
include Enumerable
|
|
37
|
+
|
|
38
|
+
delegate :each, :pop, :empty?, :to_s, :==, :to_a, :to_ary, to: :@members
|
|
32
39
|
|
|
33
|
-
class Uncountables < Array
|
|
34
40
|
def initialize
|
|
35
|
-
@
|
|
36
|
-
|
|
41
|
+
@members = []
|
|
42
|
+
@pattern = nil
|
|
37
43
|
end
|
|
38
44
|
|
|
39
45
|
def delete(entry)
|
|
40
|
-
|
|
41
|
-
@
|
|
46
|
+
@members.delete(entry)
|
|
47
|
+
@pattern = nil
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def <<(word)
|
|
51
|
+
word = word.downcase
|
|
52
|
+
@members << word
|
|
53
|
+
@pattern = nil
|
|
54
|
+
self
|
|
42
55
|
end
|
|
43
56
|
|
|
44
|
-
def
|
|
45
|
-
|
|
57
|
+
def flatten
|
|
58
|
+
@members.dup
|
|
46
59
|
end
|
|
47
60
|
|
|
48
61
|
def add(words)
|
|
49
62
|
words = words.flatten.map(&:downcase)
|
|
50
|
-
concat(words)
|
|
51
|
-
@
|
|
63
|
+
@members.concat(words)
|
|
64
|
+
@pattern = nil
|
|
52
65
|
self
|
|
53
66
|
end
|
|
54
67
|
|
|
55
68
|
def uncountable?(str)
|
|
56
|
-
@
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
private
|
|
60
|
-
def to_regex(string)
|
|
61
|
-
/\b#{::Regexp.escape(string)}\Z/i
|
|
69
|
+
if @pattern.nil?
|
|
70
|
+
members_pattern = Regexp.union(@members.map { |w| /#{Regexp.escape(w)}/i })
|
|
71
|
+
@pattern = /\b#{members_pattern}\Z/i
|
|
62
72
|
end
|
|
73
|
+
@pattern.match?(str)
|
|
74
|
+
end
|
|
63
75
|
end
|
|
64
76
|
|
|
65
77
|
def self.instance(locale = :en)
|
|
78
|
+
return @__en_instance__ ||= new if locale == :en
|
|
79
|
+
|
|
66
80
|
@__instance__[locale] ||= new
|
|
67
81
|
end
|
|
68
82
|
|
|
69
83
|
def self.instance_or_fallback(locale)
|
|
84
|
+
return @__en_instance__ ||= new if locale == :en
|
|
85
|
+
|
|
70
86
|
I18n.fallbacks[locale].each do |k|
|
|
71
87
|
return @__instance__[k] if @__instance__.key?(k)
|
|
72
88
|
end
|
|
@@ -128,18 +128,16 @@ module ActiveSupport
|
|
|
128
128
|
parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
|
|
129
129
|
|
|
130
130
|
unless separator.nil? || separator.empty?
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
# No more than one of the separator in a row.
|
|
132
|
+
if separator.length == 1
|
|
133
|
+
parameterized_string.squeeze!(separator)
|
|
134
134
|
else
|
|
135
135
|
re_sep = Regexp.escape(separator)
|
|
136
|
-
|
|
137
|
-
re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
|
|
136
|
+
parameterized_string.gsub!(/#{re_sep}{2,}/, separator)
|
|
138
137
|
end
|
|
139
|
-
# No more than one of the separator in a row.
|
|
140
|
-
parameterized_string.gsub!(re_duplicate_separator, separator)
|
|
141
138
|
# Remove leading/trailing separator.
|
|
142
|
-
parameterized_string.
|
|
139
|
+
parameterized_string.delete_prefix!(separator)
|
|
140
|
+
parameterized_string.delete_suffix!(separator)
|
|
143
141
|
end
|
|
144
142
|
|
|
145
143
|
parameterized_string.downcase! unless preserve_case
|
|
@@ -28,45 +28,42 @@ module ActiveSupport
|
|
|
28
28
|
@isolation_level = level
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def unique_id
|
|
32
|
-
self[:__id__] ||= Object.new
|
|
33
|
-
end
|
|
34
|
-
|
|
35
31
|
def [](key)
|
|
36
|
-
state
|
|
32
|
+
if state = @scope.current.active_support_execution_state
|
|
33
|
+
state[key]
|
|
34
|
+
end
|
|
37
35
|
end
|
|
38
36
|
|
|
39
37
|
def []=(key, value)
|
|
38
|
+
state = (@scope.current.active_support_execution_state ||= {})
|
|
40
39
|
state[key] = value
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
def key?(key)
|
|
44
|
-
|
|
43
|
+
@scope.current.active_support_execution_state&.key?(key)
|
|
45
44
|
end
|
|
46
45
|
|
|
47
46
|
def delete(key)
|
|
48
|
-
|
|
47
|
+
@scope.current.active_support_execution_state&.delete(key)
|
|
49
48
|
end
|
|
50
49
|
|
|
51
50
|
def clear
|
|
52
|
-
|
|
51
|
+
@scope.current.active_support_execution_state&.clear
|
|
53
52
|
end
|
|
54
53
|
|
|
55
54
|
def context
|
|
56
55
|
scope.current
|
|
57
56
|
end
|
|
58
57
|
|
|
59
|
-
def share_with(other)
|
|
58
|
+
def share_with(other, &block)
|
|
60
59
|
# Action Controller streaming spawns a new thread and copy thread locals.
|
|
61
60
|
# We do the same here for backward compatibility, but this is very much a hack
|
|
62
61
|
# and streaming should be rethought.
|
|
63
|
-
context.active_support_execution_state = other.active_support_execution_state.dup
|
|
62
|
+
old_state, context.active_support_execution_state = context.active_support_execution_state, other.active_support_execution_state.dup
|
|
63
|
+
block.call
|
|
64
|
+
ensure
|
|
65
|
+
context.active_support_execution_state = old_state
|
|
64
66
|
end
|
|
65
|
-
|
|
66
|
-
private
|
|
67
|
-
def state
|
|
68
|
-
context.active_support_execution_state ||= {}
|
|
69
|
-
end
|
|
70
67
|
end
|
|
71
68
|
|
|
72
69
|
self.isolation_level = :thread
|
|
@@ -14,13 +14,15 @@ module ActiveSupport
|
|
|
14
14
|
DATETIME_REGEX = /\A(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?)?)\z/
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
|
-
# Parses a JSON string (JavaScript Object Notation) into a
|
|
17
|
+
# Parses a JSON string (JavaScript Object Notation) into a Ruby object.
|
|
18
18
|
# See http://www.json.org for more info.
|
|
19
19
|
#
|
|
20
20
|
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
|
|
21
|
-
# => {"team" => "rails", "players" => "36"}
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
# # => {"team" => "rails", "players" => "36"}
|
|
22
|
+
# ActiveSupport::JSON.decode("2.39")
|
|
23
|
+
# # => 2.39
|
|
24
|
+
def decode(json, options = {})
|
|
25
|
+
data = ::JSON.parse(json, options)
|
|
24
26
|
|
|
25
27
|
if ActiveSupport.parse_json_times
|
|
26
28
|
convert_dates_from(data)
|
|
@@ -8,6 +8,7 @@ module ActiveSupport
|
|
|
8
8
|
delegate :use_standard_json_time_format, :use_standard_json_time_format=,
|
|
9
9
|
:time_precision, :time_precision=,
|
|
10
10
|
:escape_html_entities_in_json, :escape_html_entities_in_json=,
|
|
11
|
+
:escape_js_separators_in_json, :escape_js_separators_in_json=,
|
|
11
12
|
:json_encoder, :json_encoder=,
|
|
12
13
|
to: :'ActiveSupport::JSON::Encoding'
|
|
13
14
|
end
|
|
@@ -20,8 +21,8 @@ module ActiveSupport
|
|
|
20
21
|
# ActiveSupport::JSON.encode({ team: 'rails', players: '36' })
|
|
21
22
|
# # => "{\"team\":\"rails\",\"players\":\"36\"}"
|
|
22
23
|
#
|
|
23
|
-
#
|
|
24
|
-
# U+2028 (Line Separator) and U+2029 (Paragraph Separator):
|
|
24
|
+
# By default, it generates JSON that is safe to include in JavaScript, as
|
|
25
|
+
# it escapes U+2028 (Line Separator) and U+2029 (Paragraph Separator):
|
|
25
26
|
#
|
|
26
27
|
# ActiveSupport::JSON.encode({ key: "\u2028" })
|
|
27
28
|
# # => "{\"key\":\"\\u2028\"}"
|
|
@@ -32,18 +33,45 @@ module ActiveSupport
|
|
|
32
33
|
# ActiveSupport::JSON.encode({ key: "<>&" })
|
|
33
34
|
# # => "{\"key\":\"\\u003c\\u003e\\u0026\"}"
|
|
34
35
|
#
|
|
35
|
-
# This can be changed with the +escape_html_entities+ option, or the
|
|
36
|
+
# This behavior can be changed with the +escape_html_entities+ option, or the
|
|
36
37
|
# global escape_html_entities_in_json configuration option.
|
|
37
38
|
#
|
|
38
39
|
# ActiveSupport::JSON.encode({ key: "<>&" }, escape_html_entities: false)
|
|
39
40
|
# # => "{\"key\":\"<>&\"}"
|
|
41
|
+
#
|
|
42
|
+
# For performance reasons, you can set the +escape+ option to false,
|
|
43
|
+
# which will skip all escaping:
|
|
44
|
+
#
|
|
45
|
+
# ActiveSupport::JSON.encode({ key: "\u2028<>&" }, escape: false)
|
|
46
|
+
# # => "{\"key\":\"\u2028<>&\"}"
|
|
40
47
|
def encode(value, options = nil)
|
|
41
|
-
|
|
48
|
+
if options.nil? || options.empty?
|
|
49
|
+
Encoding.encode_without_options(value)
|
|
50
|
+
elsif options == { escape: false }.freeze
|
|
51
|
+
Encoding.encode_without_escape(value)
|
|
52
|
+
else
|
|
53
|
+
Encoding.json_encoder.new(options).encode(value)
|
|
54
|
+
end
|
|
42
55
|
end
|
|
43
56
|
alias_method :dump, :encode
|
|
44
57
|
end
|
|
45
58
|
|
|
46
59
|
module Encoding # :nodoc:
|
|
60
|
+
U2028 = -"\u2028".b
|
|
61
|
+
U2029 = -"\u2029".b
|
|
62
|
+
|
|
63
|
+
ESCAPED_CHARS = {
|
|
64
|
+
U2028 => '\u2028'.b,
|
|
65
|
+
U2029 => '\u2029'.b,
|
|
66
|
+
">".b => '\u003e'.b,
|
|
67
|
+
"<".b => '\u003c'.b,
|
|
68
|
+
"&".b => '\u0026'.b,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
HTML_ENTITIES_REGEX = Regexp.union(*(ESCAPED_CHARS.keys - [U2028, U2029]))
|
|
72
|
+
FULL_ESCAPE_REGEX = Regexp.union(*ESCAPED_CHARS.keys)
|
|
73
|
+
JS_SEPARATORS_REGEX = Regexp.union(U2028, U2029)
|
|
74
|
+
|
|
47
75
|
class JSONGemEncoder # :nodoc:
|
|
48
76
|
attr_reader :options
|
|
49
77
|
|
|
@@ -58,17 +86,19 @@ module ActiveSupport
|
|
|
58
86
|
end
|
|
59
87
|
json = stringify(jsonify(value))
|
|
60
88
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
return json unless @options.fetch(:escape, true)
|
|
90
|
+
|
|
91
|
+
json.force_encoding(::Encoding::BINARY)
|
|
64
92
|
if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
93
|
+
if Encoding.escape_js_separators_in_json
|
|
94
|
+
json.gsub!(FULL_ESCAPE_REGEX, ESCAPED_CHARS)
|
|
95
|
+
else
|
|
96
|
+
json.gsub!(HTML_ENTITIES_REGEX, ESCAPED_CHARS)
|
|
97
|
+
end
|
|
98
|
+
elsif Encoding.escape_js_separators_in_json
|
|
99
|
+
json.gsub!(JS_SEPARATORS_REGEX, ESCAPED_CHARS)
|
|
68
100
|
end
|
|
69
|
-
json.
|
|
70
|
-
json.gsub!("\u2029", '\u2029')
|
|
71
|
-
json
|
|
101
|
+
json.force_encoding(::Encoding::UTF_8)
|
|
72
102
|
end
|
|
73
103
|
|
|
74
104
|
private
|
|
@@ -101,14 +131,75 @@ module ActiveSupport
|
|
|
101
131
|
when Array
|
|
102
132
|
value.map { |v| jsonify(v) }
|
|
103
133
|
else
|
|
104
|
-
|
|
134
|
+
if defined?(::JSON::Fragment) && ::JSON::Fragment === value
|
|
135
|
+
value
|
|
136
|
+
else
|
|
137
|
+
jsonify value.as_json
|
|
138
|
+
end
|
|
105
139
|
end
|
|
106
140
|
end
|
|
107
141
|
|
|
108
142
|
# Encode a "jsonified" Ruby data structure using the JSON gem
|
|
109
143
|
def stringify(jsonified)
|
|
110
|
-
::JSON.generate(jsonified
|
|
144
|
+
::JSON.generate(jsonified)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# ruby/json 2.14.x yields non-String keys but doesn't let us know it's a key
|
|
149
|
+
if defined?(::JSON::Coder) && Gem::Version.new(::JSON::VERSION) >= Gem::Version.new("2.15.2")
|
|
150
|
+
class JSONGemCoderEncoder # :nodoc:
|
|
151
|
+
JSON_NATIVE_TYPES = [Hash, Array, Float, String, Symbol, Integer, NilClass, TrueClass, FalseClass, ::JSON::Fragment].freeze
|
|
152
|
+
CODER = ::JSON::Coder.new do |value, is_key|
|
|
153
|
+
json_value = value.as_json
|
|
154
|
+
# Keep compatibility by calling to_s on non-String keys
|
|
155
|
+
next json_value.to_s if is_key
|
|
156
|
+
# Handle objects returning self from as_json
|
|
157
|
+
if json_value.equal?(value)
|
|
158
|
+
next ::JSON::Fragment.new(::JSON.generate(json_value))
|
|
159
|
+
end
|
|
160
|
+
# Handle objects not returning JSON-native types from as_json
|
|
161
|
+
count = 5
|
|
162
|
+
until JSON_NATIVE_TYPES.include?(json_value.class)
|
|
163
|
+
raise SystemStackError if count == 0
|
|
164
|
+
json_value = json_value.as_json
|
|
165
|
+
count -= 1
|
|
166
|
+
end
|
|
167
|
+
json_value
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def initialize(options = nil)
|
|
172
|
+
if options
|
|
173
|
+
options = options.dup
|
|
174
|
+
@escape = options.delete(:escape) { true }
|
|
175
|
+
@options = options.freeze
|
|
176
|
+
else
|
|
177
|
+
@escape = true
|
|
178
|
+
@options = {}.freeze
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Encode the given object into a JSON string
|
|
183
|
+
def encode(value)
|
|
184
|
+
value = value.as_json(@options) unless @options.empty?
|
|
185
|
+
|
|
186
|
+
json = CODER.dump(value)
|
|
187
|
+
|
|
188
|
+
return json unless @escape
|
|
189
|
+
|
|
190
|
+
json.force_encoding(::Encoding::BINARY)
|
|
191
|
+
if @options.fetch(:escape_html_entities, Encoding.escape_html_entities_in_json)
|
|
192
|
+
if Encoding.escape_js_separators_in_json
|
|
193
|
+
json.gsub!(FULL_ESCAPE_REGEX, ESCAPED_CHARS)
|
|
194
|
+
else
|
|
195
|
+
json.gsub!(HTML_ENTITIES_REGEX, ESCAPED_CHARS)
|
|
196
|
+
end
|
|
197
|
+
elsif Encoding.escape_js_separators_in_json
|
|
198
|
+
json.gsub!(JS_SEPARATORS_REGEX, ESCAPED_CHARS)
|
|
199
|
+
end
|
|
200
|
+
json.force_encoding(::Encoding::UTF_8)
|
|
111
201
|
end
|
|
202
|
+
end
|
|
112
203
|
end
|
|
113
204
|
|
|
114
205
|
class << self
|
|
@@ -120,18 +211,45 @@ module ActiveSupport
|
|
|
120
211
|
# as a safety measure.
|
|
121
212
|
attr_accessor :escape_html_entities_in_json
|
|
122
213
|
|
|
214
|
+
# If true, encode LINE SEPARATOR (U+2028) and PARAGRAPH SEPARATOR (U+2029)
|
|
215
|
+
# as escaped unicode sequences ('\u2028' and '\u2029').
|
|
216
|
+
# Historically these characters were not valid inside JavaScript strings
|
|
217
|
+
# but that changed in ECMAScript 2019. As such it's no longer a concern in
|
|
218
|
+
# modern browsers: https://caniuse.com/mdn-javascript_builtins_json_json_superset.
|
|
219
|
+
attr_accessor :escape_js_separators_in_json
|
|
220
|
+
|
|
123
221
|
# Sets the precision of encoded time values.
|
|
124
222
|
# Defaults to 3 (equivalent to millisecond precision)
|
|
125
223
|
attr_accessor :time_precision
|
|
126
224
|
|
|
127
225
|
# Sets the encoder used by \Rails to encode Ruby objects into JSON strings
|
|
128
226
|
# in +Object#to_json+ and +ActiveSupport::JSON.encode+.
|
|
129
|
-
|
|
227
|
+
attr_reader :json_encoder
|
|
228
|
+
|
|
229
|
+
def json_encoder=(encoder)
|
|
230
|
+
@json_encoder = encoder
|
|
231
|
+
@encoder_without_options = encoder.new
|
|
232
|
+
@encoder_without_escape = encoder.new(escape: false)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def encode_without_options(value) # :nodoc:
|
|
236
|
+
@encoder_without_options.encode(value)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def encode_without_escape(value) # :nodoc:
|
|
240
|
+
@encoder_without_escape.encode(value)
|
|
241
|
+
end
|
|
130
242
|
end
|
|
131
243
|
|
|
132
244
|
self.use_standard_json_time_format = true
|
|
133
245
|
self.escape_html_entities_in_json = true
|
|
134
|
-
self.
|
|
246
|
+
self.escape_js_separators_in_json = true
|
|
247
|
+
self.json_encoder =
|
|
248
|
+
if defined?(JSONGemCoderEncoder)
|
|
249
|
+
JSONGemCoderEncoder
|
|
250
|
+
else
|
|
251
|
+
JSONGemEncoder
|
|
252
|
+
end
|
|
135
253
|
self.time_precision = 3
|
|
136
254
|
end
|
|
137
255
|
end
|
|
@@ -53,7 +53,7 @@ module ActiveSupport
|
|
|
53
53
|
# loaded. If the component has already loaded, the block is executed
|
|
54
54
|
# immediately.
|
|
55
55
|
#
|
|
56
|
-
# Options
|
|
56
|
+
# ==== Options
|
|
57
57
|
#
|
|
58
58
|
# * <tt>:yield</tt> - Yields the object that run_load_hooks to +block+.
|
|
59
59
|
# * <tt>:run_once</tt> - Given +block+ will run only once.
|
|
@@ -149,12 +149,6 @@ module ActiveSupport
|
|
|
149
149
|
log_exception(event.name, e)
|
|
150
150
|
end
|
|
151
151
|
|
|
152
|
-
def publish_event(event)
|
|
153
|
-
super if logger
|
|
154
|
-
rescue => e
|
|
155
|
-
log_exception(event.name, e)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
152
|
attr_writer :event_levels # :nodoc:
|
|
159
153
|
|
|
160
154
|
private
|
|
@@ -184,6 +178,8 @@ module ActiveSupport
|
|
|
184
178
|
end
|
|
185
179
|
|
|
186
180
|
def log_exception(name, e)
|
|
181
|
+
ActiveSupport.error_reporter.report(e, source: name)
|
|
182
|
+
|
|
187
183
|
if logger
|
|
188
184
|
logger.error "Could not log #{name.inspect} event. #{e.class}: #{e.message} #{e.backtrace}"
|
|
189
185
|
end
|