activesupport 7.0.4 → 7.1.5.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 +1076 -230
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/active_support/actionable_error.rb +3 -1
- data/lib/active_support/array_inquirer.rb +2 -0
- data/lib/active_support/backtrace_cleaner.rb +30 -5
- data/lib/active_support/benchmarkable.rb +1 -0
- data/lib/active_support/broadcast_logger.rb +251 -0
- data/lib/active_support/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +134 -0
- data/lib/active_support/cache/file_store.rb +37 -10
- data/lib/active_support/cache/mem_cache_store.rb +100 -76
- data/lib/active_support/cache/memory_store.rb +78 -24
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +153 -141
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +29 -14
- data/lib/active_support/cache.rb +333 -253
- data/lib/active_support/callbacks.rb +44 -21
- data/lib/active_support/code_generator.rb +15 -10
- data/lib/active_support/concern.rb +4 -2
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +42 -3
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/configurable.rb +10 -0
- data/lib/active_support/core_ext/array/conversions.rb +2 -1
- data/lib/active_support/core_ext/array.rb +0 -1
- data/lib/active_support/core_ext/class/subclasses.rb +13 -10
- data/lib/active_support/core_ext/date/calculations.rb +15 -0
- data/lib/active_support/core_ext/date/conversions.rb +2 -1
- data/lib/active_support/core_ext/date.rb +0 -1
- data/lib/active_support/core_ext/date_and_time/calculations.rb +10 -0
- data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +6 -2
- data/lib/active_support/core_ext/date_time.rb +0 -1
- data/lib/active_support/core_ext/digest/uuid.rb +1 -10
- data/lib/active_support/core_ext/enumerable.rb +8 -75
- data/lib/active_support/core_ext/erb/util.rb +196 -0
- data/lib/active_support/core_ext/hash/conversions.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +3 -3
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- data/lib/active_support/core_ext/module/attribute_accessors.rb +6 -0
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +34 -16
- data/lib/active_support/core_ext/module/concerning.rb +6 -6
- data/lib/active_support/core_ext/module/delegation.rb +81 -37
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +0 -1
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +2 -0
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/object/deep_dup.rb +16 -0
- data/lib/active_support/core_ext/object/duplicable.rb +25 -16
- data/lib/active_support/core_ext/object/inclusion.rb +13 -5
- data/lib/active_support/core_ext/object/instance_variables.rb +22 -12
- data/lib/active_support/core_ext/object/json.rb +16 -6
- data/lib/active_support/core_ext/object/to_query.rb +0 -2
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +9 -9
- data/lib/active_support/core_ext/object.rb +1 -0
- data/lib/active_support/core_ext/pathname/blank.rb +16 -0
- data/lib/active_support/core_ext/pathname/existence.rb +2 -0
- data/lib/active_support/core_ext/pathname.rb +1 -0
- data/lib/active_support/core_ext/range/conversions.rb +28 -7
- data/lib/active_support/core_ext/range/overlap.rb +40 -0
- data/lib/active_support/core_ext/range.rb +1 -2
- data/lib/active_support/core_ext/securerandom.rb +24 -12
- data/lib/active_support/core_ext/string/filters.rb +20 -14
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +16 -9
- data/lib/active_support/core_ext/string/output_safety.rb +42 -174
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +22 -2
- data/lib/active_support/core_ext/time/conversions.rb +2 -2
- data/lib/active_support/core_ext/time/zones.rb +7 -8
- data/lib/active_support/core_ext/time.rb +0 -1
- data/lib/active_support/current_attributes.rb +15 -6
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/dependencies/autoload.rb +17 -12
- data/lib/active_support/deprecation/behaviors.rb +65 -42
- data/lib/active_support/deprecation/constant_accessor.rb +5 -4
- data/lib/active_support/deprecation/deprecators.rb +104 -0
- data/lib/active_support/deprecation/disallowed.rb +6 -8
- data/lib/active_support/deprecation/instance_delegator.rb +31 -4
- data/lib/active_support/deprecation/method_wrappers.rb +6 -23
- data/lib/active_support/deprecation/proxy_wrappers.rb +37 -22
- data/lib/active_support/deprecation/reporting.rb +43 -26
- data/lib/active_support/deprecation.rb +32 -5
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +104 -132
- data/lib/active_support/duration/iso8601_serializer.rb +0 -2
- data/lib/active_support/duration.rb +2 -1
- data/lib/active_support/encrypted_configuration.rb +63 -11
- data/lib/active_support/encrypted_file.rb +16 -12
- data/lib/active_support/environment_inquirer.rb +22 -2
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +121 -35
- data/lib/active_support/evented_file_update_checker.rb +17 -2
- data/lib/active_support/execution_wrapper.rb +4 -4
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +10 -2
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +35 -17
- data/lib/active_support/html_safe_translation.rb +16 -6
- data/lib/active_support/i18n.rb +1 -1
- data/lib/active_support/i18n_railtie.rb +20 -13
- data/lib/active_support/inflector/inflections.rb +2 -0
- data/lib/active_support/inflector/methods.rb +28 -18
- data/lib/active_support/inflector/transliterate.rb +3 -1
- data/lib/active_support/isolated_execution_state.rb +26 -22
- data/lib/active_support/json/decoding.rb +2 -1
- data/lib/active_support/json/encoding.rb +25 -43
- data/lib/active_support/key_generator.rb +9 -1
- data/lib/active_support/lazy_load_hooks.rb +7 -5
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber.rb +85 -33
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_thread_safe_level.rb +10 -24
- data/lib/active_support/message_encryptor.rb +197 -53
- data/lib/active_support/message_encryptors.rb +141 -0
- data/lib/active_support/message_pack/cache_serializer.rb +23 -0
- data/lib/active_support/message_pack/extensions.rb +292 -0
- data/lib/active_support/message_pack/serializer.rb +63 -0
- data/lib/active_support/message_pack.rb +50 -0
- data/lib/active_support/message_verifier.rb +212 -93
- data/lib/active_support/message_verifiers.rb +135 -0
- data/lib/active_support/messages/codec.rb +65 -0
- data/lib/active_support/messages/metadata.rb +111 -45
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +34 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +2 -0
- data/lib/active_support/multibyte/unicode.rb +9 -37
- data/lib/active_support/notifications/fanout.rb +245 -81
- data/lib/active_support/notifications/instrumenter.rb +87 -22
- data/lib/active_support/notifications.rb +3 -3
- data/lib/active_support/number_helper/number_converter.rb +14 -5
- data/lib/active_support/number_helper/number_to_currency_converter.rb +6 -6
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -3
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
- data/lib/active_support/number_helper.rb +379 -317
- data/lib/active_support/ordered_hash.rb +3 -3
- data/lib/active_support/ordered_options.rb +14 -0
- data/lib/active_support/parameter_filter.rb +103 -84
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/railtie.rb +33 -21
- data/lib/active_support/reloader.rb +12 -4
- data/lib/active_support/rescuable.rb +2 -0
- data/lib/active_support/secure_compare_rotator.rb +16 -9
- data/lib/active_support/string_inquirer.rb +3 -1
- data/lib/active_support/subscriber.rb +9 -27
- data/lib/active_support/syntax_error_proxy.rb +60 -0
- data/lib/active_support/tagged_logging.rb +64 -24
- data/lib/active_support/test_case.rb +153 -6
- data/lib/active_support/testing/assertions.rb +26 -10
- data/lib/active_support/testing/autorun.rb +0 -2
- data/lib/active_support/testing/constant_stubbing.rb +32 -0
- data/lib/active_support/testing/deprecation.rb +25 -25
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +29 -28
- data/lib/active_support/testing/method_call_assertions.rb +21 -8
- data/lib/active_support/testing/parallelize_executor.rb +8 -3
- data/lib/active_support/testing/setup_and_teardown.rb +2 -0
- data/lib/active_support/testing/stream.rb +1 -1
- data/lib/active_support/testing/strict_warnings.rb +39 -0
- data/lib/active_support/testing/time_helpers.rb +37 -15
- data/lib/active_support/time_with_zone.rb +8 -37
- data/lib/active_support/values/time_zone.rb +18 -7
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +3 -10
- data/lib/active_support/xml_mini/nokogiri.rb +1 -1
- data/lib/active_support/xml_mini/nokogirisax.rb +1 -1
- data/lib/active_support/xml_mini/rexml.rb +1 -1
- data/lib/active_support/xml_mini.rb +2 -2
- data/lib/active_support.rb +14 -3
- metadata +148 -19
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -26
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -22
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -26
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -7
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -22
- data/lib/active_support/core_ext/uri.rb +0 -5
- data/lib/active_support/per_thread_registry.rb +0 -65
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Range
|
4
|
+
# Compare two ranges and see if they overlap each other
|
5
|
+
# (1..5).overlap?(4..6) # => true
|
6
|
+
# (1..5).overlap?(7..9) # => false
|
7
|
+
unless Range.method_defined?(:overlap?)
|
8
|
+
def overlap?(other)
|
9
|
+
raise TypeError unless other.is_a? Range
|
10
|
+
|
11
|
+
self_begin = self.begin
|
12
|
+
other_end = other.end
|
13
|
+
other_excl = other.exclude_end?
|
14
|
+
|
15
|
+
return false if _empty_range?(self_begin, other_end, other_excl)
|
16
|
+
|
17
|
+
other_begin = other.begin
|
18
|
+
self_end = self.end
|
19
|
+
self_excl = self.exclude_end?
|
20
|
+
|
21
|
+
return false if _empty_range?(other_begin, self_end, self_excl)
|
22
|
+
return true if self_begin == other_begin
|
23
|
+
|
24
|
+
return false if _empty_range?(self_begin, self_end, self_excl)
|
25
|
+
return false if _empty_range?(other_begin, other_end, other_excl)
|
26
|
+
|
27
|
+
true
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def _empty_range?(b, e, excl)
|
32
|
+
return false if b.nil? || e.nil?
|
33
|
+
|
34
|
+
comp = b <=> e
|
35
|
+
comp.nil? || comp > 0 || (comp == 0 && excl)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
alias :overlaps? :overlap?
|
40
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/range/conversions"
|
4
|
-
require "active_support/core_ext/range/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
|
5
4
|
require "active_support/core_ext/range/compare_range"
|
6
|
-
require "active_support/core_ext/range/
|
5
|
+
require "active_support/core_ext/range/overlap"
|
7
6
|
require "active_support/core_ext/range/each"
|
@@ -16,12 +16,18 @@ module SecureRandom
|
|
16
16
|
#
|
17
17
|
# p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
|
18
18
|
# p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
if RUBY_VERSION >= "3.3"
|
20
|
+
def self.base58(n = 16)
|
21
|
+
SecureRandom.alphanumeric(n, chars: BASE58_ALPHABET)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
def self.base58(n = 16)
|
25
|
+
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
|
26
|
+
idx = byte % 64
|
27
|
+
idx = SecureRandom.random_number(58) if idx >= 58
|
28
|
+
BASE58_ALPHABET[idx]
|
29
|
+
end.join
|
30
|
+
end
|
25
31
|
end
|
26
32
|
|
27
33
|
# SecureRandom.base36 generates a random base36 string in lowercase.
|
@@ -35,11 +41,17 @@ module SecureRandom
|
|
35
41
|
#
|
36
42
|
# p SecureRandom.base36 # => "4kugl2pdqmscqtje"
|
37
43
|
# p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
+
if RUBY_VERSION >= "3.3"
|
45
|
+
def self.base36(n = 16)
|
46
|
+
SecureRandom.alphanumeric(n, chars: BASE36_ALPHABET)
|
47
|
+
end
|
48
|
+
else
|
49
|
+
def self.base36(n = 16)
|
50
|
+
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
|
51
|
+
idx = byte % 64
|
52
|
+
idx = SecureRandom.random_number(36) if idx >= 36
|
53
|
+
BASE36_ALPHABET[idx]
|
54
|
+
end.join
|
55
|
+
end
|
44
56
|
end
|
45
57
|
end
|
@@ -45,7 +45,7 @@ class String
|
|
45
45
|
self
|
46
46
|
end
|
47
47
|
|
48
|
-
# Truncates a given +text+
|
48
|
+
# Truncates a given +text+ to length <tt>truncate_to</tt> if +text+ is longer than <tt>truncate_to</tt>:
|
49
49
|
#
|
50
50
|
# 'Once upon a time in a world far far away'.truncate(27)
|
51
51
|
# # => "Once upon a time in a wo..."
|
@@ -58,16 +58,20 @@ class String
|
|
58
58
|
# 'Once upon a time in a world far far away'.truncate(27, separator: /\s/)
|
59
59
|
# # => "Once upon a time in a..."
|
60
60
|
#
|
61
|
-
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...")
|
62
|
-
#
|
61
|
+
# The last characters will be replaced with the <tt>:omission</tt> string (defaults to "...").
|
62
|
+
# The total length will not exceed <tt>truncate_to</tt> unless both +text+ and <tt>:omission</tt>
|
63
|
+
# are longer than <tt>truncate_to</tt>:
|
63
64
|
#
|
64
65
|
# 'And they found that many people were sleeping better.'.truncate(25, omission: '... (continued)')
|
65
66
|
# # => "And they f... (continued)"
|
66
|
-
|
67
|
-
|
67
|
+
#
|
68
|
+
# 'And they found that many people were sleeping better.'.truncate(4, omission: '... (continued)')
|
69
|
+
# # => "... (continued)"
|
70
|
+
def truncate(truncate_to, options = {})
|
71
|
+
return dup unless length > truncate_to
|
68
72
|
|
69
73
|
omission = options[:omission] || "..."
|
70
|
-
length_with_room_for_omission =
|
74
|
+
length_with_room_for_omission = truncate_to - omission.length
|
71
75
|
stop = \
|
72
76
|
if options[:separator]
|
73
77
|
rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission
|
@@ -78,7 +82,7 @@ class String
|
|
78
82
|
+"#{self[0, stop]}#{omission}"
|
79
83
|
end
|
80
84
|
|
81
|
-
# Truncates +text+ to at most <tt>
|
85
|
+
# Truncates +text+ to at most <tt>truncate_to</tt> bytes in length without
|
82
86
|
# breaking string encoding by splitting multibyte characters or breaking
|
83
87
|
# grapheme clusters ("perceptual characters") by truncating at combining
|
84
88
|
# characters.
|
@@ -91,20 +95,22 @@ class String
|
|
91
95
|
# => "🔪🔪🔪🔪…"
|
92
96
|
#
|
93
97
|
# The truncated text ends with the <tt>:omission</tt> string, defaulting
|
94
|
-
# to "…", for a total length not exceeding <tt>
|
95
|
-
|
98
|
+
# to "…", for a total length not exceeding <tt>truncate_to</tt>.
|
99
|
+
#
|
100
|
+
# Raises +ArgumentError+ when the bytesize of <tt>:omission</tt> exceeds <tt>truncate_to</tt>.
|
101
|
+
def truncate_bytes(truncate_to, omission: "…")
|
96
102
|
omission ||= ""
|
97
103
|
|
98
104
|
case
|
99
|
-
when bytesize <=
|
105
|
+
when bytesize <= truncate_to
|
100
106
|
dup
|
101
|
-
when omission.bytesize >
|
102
|
-
raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{
|
103
|
-
when omission.bytesize ==
|
107
|
+
when omission.bytesize > truncate_to
|
108
|
+
raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_to} bytes"
|
109
|
+
when omission.bytesize == truncate_to
|
104
110
|
omission.dup
|
105
111
|
else
|
106
112
|
self.class.new.tap do |cut|
|
107
|
-
cut_at =
|
113
|
+
cut_at = truncate_to - omission.bytesize
|
108
114
|
|
109
115
|
each_grapheme_cluster do |grapheme|
|
110
116
|
if cut.bytesize + grapheme.bytesize <= cut_at
|
@@ -24,7 +24,7 @@ class String
|
|
24
24
|
#
|
25
25
|
# The second argument, +indent_string+, specifies which indent string to
|
26
26
|
# use. The default is +nil+, which tells the method to make a guess by
|
27
|
-
# peeking at the first indented line, and
|
27
|
+
# peeking at the first indented line, and fall back to a space if there is
|
28
28
|
# none.
|
29
29
|
#
|
30
30
|
# " foo".indent(2) # => " foo"
|
@@ -97,8 +97,6 @@ class String
|
|
97
97
|
# 'active_record/errors'.camelize # => "ActiveRecord::Errors"
|
98
98
|
# 'active_record/errors'.camelize(:lower) # => "activeRecord::Errors"
|
99
99
|
#
|
100
|
-
# +camelize+ is also aliased as +camelcase+.
|
101
|
-
#
|
102
100
|
# See ActiveSupport::Inflector.camelize.
|
103
101
|
def camelize(first_letter = :upper)
|
104
102
|
case first_letter
|
@@ -114,7 +112,7 @@ class String
|
|
114
112
|
|
115
113
|
# Capitalizes all the words and replaces some characters in the string to create
|
116
114
|
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
|
117
|
-
# used in the Rails internals.
|
115
|
+
# used in the \Rails internals.
|
118
116
|
#
|
119
117
|
# The trailing '_id','Id'.. can be kept and capitalized by setting the
|
120
118
|
# optional parameter +keep_id_suffix+ to true.
|
@@ -124,8 +122,6 @@ class String
|
|
124
122
|
# 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
|
125
123
|
# 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id"
|
126
124
|
#
|
127
|
-
# +titleize+ is also aliased as +titlecase+.
|
128
|
-
#
|
129
125
|
# See ActiveSupport::Inflector.titleize.
|
130
126
|
def titleize(keep_id_suffix: false)
|
131
127
|
ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix)
|
@@ -220,7 +216,7 @@ class String
|
|
220
216
|
ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale)
|
221
217
|
end
|
222
218
|
|
223
|
-
# Creates the name of a table like Rails does for models to table names. This method
|
219
|
+
# Creates the name of a table like \Rails does for models to table names. This method
|
224
220
|
# uses the +pluralize+ method on the last word in the string.
|
225
221
|
#
|
226
222
|
# 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
|
@@ -232,7 +228,7 @@ class String
|
|
232
228
|
ActiveSupport::Inflector.tableize(self)
|
233
229
|
end
|
234
230
|
|
235
|
-
# Creates a class name from a plural table name like Rails does for table names to models.
|
231
|
+
# Creates a class name from a plural table name like \Rails does for table names to models.
|
236
232
|
# Note that this returns a string and not a class. (To convert to an actual class
|
237
233
|
# follow +classify+ with +constantize+.)
|
238
234
|
#
|
@@ -244,7 +240,7 @@ class String
|
|
244
240
|
ActiveSupport::Inflector.classify(self)
|
245
241
|
end
|
246
242
|
|
247
|
-
# Capitalizes the first word, turns underscores into spaces, and (by default)strips a
|
243
|
+
# Capitalizes the first word, turns underscores into spaces, and (by default) strips a
|
248
244
|
# trailing '_id' if present.
|
249
245
|
# Like +titleize+, this is meant for creating pretty output.
|
250
246
|
#
|
@@ -267,7 +263,7 @@ class String
|
|
267
263
|
ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix)
|
268
264
|
end
|
269
265
|
|
270
|
-
# Converts
|
266
|
+
# Converts the first character to uppercase.
|
271
267
|
#
|
272
268
|
# 'what a Lovely Day'.upcase_first # => "What a Lovely Day"
|
273
269
|
# 'w'.upcase_first # => "W"
|
@@ -278,6 +274,17 @@ class String
|
|
278
274
|
ActiveSupport::Inflector.upcase_first(self)
|
279
275
|
end
|
280
276
|
|
277
|
+
# Converts the first character to lowercase.
|
278
|
+
#
|
279
|
+
# 'If they enjoyed The Matrix'.downcase_first # => "if they enjoyed The Matrix"
|
280
|
+
# 'I'.downcase_first # => "i"
|
281
|
+
# ''.downcase_first # => ""
|
282
|
+
#
|
283
|
+
# See ActiveSupport::Inflector.downcase_first.
|
284
|
+
def downcase_first
|
285
|
+
ActiveSupport::Inflector.downcase_first(self)
|
286
|
+
end
|
287
|
+
|
281
288
|
# Creates a foreign key name from a class name.
|
282
289
|
# +separate_class_name_and_id_with_underscore+ sets whether
|
283
290
|
# the method should put '_' between the name and 'id'.
|
@@ -1,151 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "erb"
|
4
|
-
require "active_support/core_ext/module/redefine_method"
|
3
|
+
require "active_support/core_ext/erb/util"
|
5
4
|
require "active_support/multibyte/unicode"
|
6
5
|
|
7
|
-
class ERB
|
8
|
-
module Util
|
9
|
-
HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" }
|
10
|
-
JSON_ESCAPE = { "&" => '\u0026', ">" => '\u003e', "<" => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
|
11
|
-
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
|
12
|
-
JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
|
13
|
-
|
14
|
-
# Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name
|
15
|
-
TAG_NAME_START_REGEXP_SET = "@:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \
|
16
|
-
"\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \
|
17
|
-
"\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}"
|
18
|
-
TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}]/
|
19
|
-
TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_START_REGEXP_SET}\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}]/
|
20
|
-
TAG_NAME_REPLACEMENT_CHAR = "_"
|
21
|
-
|
22
|
-
# A utility method for escaping HTML tag characters.
|
23
|
-
# This method is also aliased as <tt>h</tt>.
|
24
|
-
#
|
25
|
-
# puts html_escape('is a > 0 & a < 10?')
|
26
|
-
# # => is a > 0 & a < 10?
|
27
|
-
def html_escape(s)
|
28
|
-
unwrapped_html_escape(s).html_safe
|
29
|
-
end
|
30
|
-
|
31
|
-
silence_redefinition_of_method :h
|
32
|
-
alias h html_escape
|
33
|
-
|
34
|
-
module_function :h
|
35
|
-
|
36
|
-
singleton_class.silence_redefinition_of_method :html_escape
|
37
|
-
module_function :html_escape
|
38
|
-
|
39
|
-
# HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
|
40
|
-
# This method is not for public consumption! Seriously!
|
41
|
-
def unwrapped_html_escape(s) # :nodoc:
|
42
|
-
s = s.to_s
|
43
|
-
if s.html_safe?
|
44
|
-
s
|
45
|
-
else
|
46
|
-
CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
|
47
|
-
end
|
48
|
-
end
|
49
|
-
module_function :unwrapped_html_escape
|
50
|
-
|
51
|
-
# A utility method for escaping HTML without affecting existing escaped entities.
|
52
|
-
#
|
53
|
-
# html_escape_once('1 < 2 & 3')
|
54
|
-
# # => "1 < 2 & 3"
|
55
|
-
#
|
56
|
-
# html_escape_once('<< Accept & Checkout')
|
57
|
-
# # => "<< Accept & Checkout"
|
58
|
-
def html_escape_once(s)
|
59
|
-
result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
|
60
|
-
s.html_safe? ? result.html_safe : result
|
61
|
-
end
|
62
|
-
|
63
|
-
module_function :html_escape_once
|
64
|
-
|
65
|
-
# A utility method for escaping HTML entities in JSON strings. Specifically, the
|
66
|
-
# &, > and < characters are replaced with their equivalent unicode escaped form -
|
67
|
-
# \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
|
68
|
-
# escaped as they are treated as newline characters in some JavaScript engines.
|
69
|
-
# These sequences have identical meaning as the original characters inside the
|
70
|
-
# context of a JSON string, so assuming the input is a valid and well-formed
|
71
|
-
# JSON value, the output will have equivalent meaning when parsed:
|
72
|
-
#
|
73
|
-
# json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
|
74
|
-
# # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
|
75
|
-
#
|
76
|
-
# json_escape(json)
|
77
|
-
# # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
|
78
|
-
#
|
79
|
-
# JSON.parse(json) == JSON.parse(json_escape(json))
|
80
|
-
# # => true
|
81
|
-
#
|
82
|
-
# The intended use case for this method is to escape JSON strings before including
|
83
|
-
# them inside a script tag to avoid XSS vulnerability:
|
84
|
-
#
|
85
|
-
# <script>
|
86
|
-
# var currentUser = <%= raw json_escape(current_user.to_json) %>;
|
87
|
-
# </script>
|
88
|
-
#
|
89
|
-
# It is necessary to +raw+ the result of +json_escape+, so that quotation marks
|
90
|
-
# don't get converted to <tt>"</tt> entities. +json_escape+ doesn't
|
91
|
-
# automatically flag the result as HTML safe, since the raw value is unsafe to
|
92
|
-
# use inside HTML attributes.
|
93
|
-
#
|
94
|
-
# If your JSON is being used downstream for insertion into the DOM, be aware of
|
95
|
-
# whether or not it is being inserted via <tt>html()</tt>. Most jQuery plugins do this.
|
96
|
-
# If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
|
97
|
-
# content returned by your JSON.
|
98
|
-
#
|
99
|
-
# If you need to output JSON elsewhere in your HTML, you can just do something
|
100
|
-
# like this, as any unsafe characters (including quotation marks) will be
|
101
|
-
# automatically escaped for you:
|
102
|
-
#
|
103
|
-
# <div data-user-info="<%= current_user.to_json %>">...</div>
|
104
|
-
#
|
105
|
-
# WARNING: this helper only works with valid JSON. Using this on non-JSON values
|
106
|
-
# will open up serious XSS vulnerabilities. For example, if you replace the
|
107
|
-
# +current_user.to_json+ in the example above with user input instead, the browser
|
108
|
-
# will happily <tt>eval()</tt> that string as JavaScript.
|
109
|
-
#
|
110
|
-
# The escaping performed in this method is identical to those performed in the
|
111
|
-
# Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
|
112
|
-
# set to true. Because this transformation is idempotent, this helper can be
|
113
|
-
# applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
|
114
|
-
#
|
115
|
-
# Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
|
116
|
-
# is enabled, or if you are unsure where your JSON string originated from, it
|
117
|
-
# is recommended that you always apply this helper (other libraries, such as the
|
118
|
-
# JSON gem, do not provide this kind of protection by default; also some gems
|
119
|
-
# might override +to_json+ to bypass Active Support's encoder).
|
120
|
-
def json_escape(s)
|
121
|
-
result = s.to_s.gsub(JSON_ESCAPE_REGEXP, JSON_ESCAPE)
|
122
|
-
s.html_safe? ? result.html_safe : result
|
123
|
-
end
|
124
|
-
|
125
|
-
module_function :json_escape
|
126
|
-
|
127
|
-
# A utility method for escaping XML names of tags and names of attributes.
|
128
|
-
#
|
129
|
-
# xml_name_escape('1 < 2 & 3')
|
130
|
-
# # => "1___2___3"
|
131
|
-
#
|
132
|
-
# It follows the requirements of the specification: https://www.w3.org/TR/REC-xml/#NT-Name
|
133
|
-
def xml_name_escape(name)
|
134
|
-
name = name.to_s
|
135
|
-
return "" if name.blank?
|
136
|
-
|
137
|
-
starting_char = name[0].gsub(TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
|
138
|
-
|
139
|
-
return starting_char if name.size == 1
|
140
|
-
|
141
|
-
following_chars = name[1..-1].gsub(TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
|
142
|
-
|
143
|
-
starting_char + following_chars
|
144
|
-
end
|
145
|
-
module_function :xml_name_escape
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
6
|
class Object
|
150
7
|
def html_safe?
|
151
8
|
false
|
@@ -162,7 +19,7 @@ module ActiveSupport # :nodoc:
|
|
162
19
|
class SafeBuffer < String
|
163
20
|
UNSAFE_STRING_METHODS = %w(
|
164
21
|
capitalize chomp chop delete delete_prefix delete_suffix
|
165
|
-
downcase lstrip next reverse rstrip scrub
|
22
|
+
downcase lstrip next reverse rstrip scrub squeeze strip
|
166
23
|
succ swapcase tr tr_s unicode_normalize upcase
|
167
24
|
)
|
168
25
|
|
@@ -174,7 +31,7 @@ module ActiveSupport # :nodoc:
|
|
174
31
|
# Raised when ActiveSupport::SafeBuffer#safe_concat is called on unsafe buffers.
|
175
32
|
class SafeConcatError < StandardError
|
176
33
|
def initialize
|
177
|
-
super "Could not concatenate to the buffer because it is not
|
34
|
+
super "Could not concatenate to the buffer because it is not HTML safe."
|
178
35
|
end
|
179
36
|
end
|
180
37
|
|
@@ -184,13 +41,26 @@ module ActiveSupport # :nodoc:
|
|
184
41
|
|
185
42
|
return unless new_string
|
186
43
|
|
187
|
-
|
188
|
-
new_safe_buffer.instance_variable_set :@html_safe, true
|
189
|
-
new_safe_buffer
|
44
|
+
string_into_safe_buffer(new_string, true)
|
190
45
|
else
|
191
46
|
to_str[*args]
|
192
47
|
end
|
193
48
|
end
|
49
|
+
alias_method :slice, :[]
|
50
|
+
|
51
|
+
def slice!(*args)
|
52
|
+
new_string = super
|
53
|
+
|
54
|
+
return new_string if !html_safe? || new_string.nil?
|
55
|
+
|
56
|
+
string_into_safe_buffer(new_string, true)
|
57
|
+
end
|
58
|
+
|
59
|
+
def chr
|
60
|
+
return super unless html_safe?
|
61
|
+
|
62
|
+
string_into_safe_buffer(super, true)
|
63
|
+
end
|
194
64
|
|
195
65
|
def safe_concat(value)
|
196
66
|
raise SafeConcatError unless html_safe?
|
@@ -207,7 +77,10 @@ module ActiveSupport # :nodoc:
|
|
207
77
|
@html_safe = other.html_safe?
|
208
78
|
end
|
209
79
|
|
210
|
-
def clone_empty
|
80
|
+
def clone_empty # :nodoc:
|
81
|
+
ActiveSupport.deprecator.warn <<~EOM
|
82
|
+
ActiveSupport::SafeBuffer#clone_empty is deprecated and will be removed in Rails 7.2.
|
83
|
+
EOM
|
211
84
|
self[0, 0]
|
212
85
|
end
|
213
86
|
|
@@ -219,6 +92,10 @@ module ActiveSupport # :nodoc:
|
|
219
92
|
end
|
220
93
|
alias << concat
|
221
94
|
|
95
|
+
def bytesplice(*args, value)
|
96
|
+
super(*args, implicit_html_escape_interpolated_argument(value))
|
97
|
+
end
|
98
|
+
|
222
99
|
def insert(index, value)
|
223
100
|
super(index, implicit_html_escape_interpolated_argument(value))
|
224
101
|
end
|
@@ -231,11 +108,11 @@ module ActiveSupport # :nodoc:
|
|
231
108
|
super(implicit_html_escape_interpolated_argument(value))
|
232
109
|
end
|
233
110
|
|
234
|
-
def []=(
|
235
|
-
if
|
236
|
-
super(
|
111
|
+
def []=(arg1, arg2, arg3 = nil)
|
112
|
+
if arg3
|
113
|
+
super(arg1, arg2, implicit_html_escape_interpolated_argument(arg3))
|
237
114
|
else
|
238
|
-
super(
|
115
|
+
super(arg1, implicit_html_escape_interpolated_argument(arg2))
|
239
116
|
end
|
240
117
|
end
|
241
118
|
|
@@ -243,7 +120,7 @@ module ActiveSupport # :nodoc:
|
|
243
120
|
dup.concat(other)
|
244
121
|
end
|
245
122
|
|
246
|
-
def *(
|
123
|
+
def *(_)
|
247
124
|
new_string = super
|
248
125
|
new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
|
249
126
|
new_safe_buffer.instance_variable_set(:@html_safe, @html_safe)
|
@@ -261,9 +138,9 @@ module ActiveSupport # :nodoc:
|
|
261
138
|
self.class.new(super(escaped_args))
|
262
139
|
end
|
263
140
|
|
264
|
-
|
265
|
-
|
266
|
-
|
141
|
+
attr_reader :html_safe
|
142
|
+
alias_method :html_safe?, :html_safe
|
143
|
+
remove_method :html_safe
|
267
144
|
|
268
145
|
def to_s
|
269
146
|
self
|
@@ -328,22 +205,7 @@ module ActiveSupport # :nodoc:
|
|
328
205
|
if !html_safe? || arg.html_safe?
|
329
206
|
arg
|
330
207
|
else
|
331
|
-
|
332
|
-
arg.to_str
|
333
|
-
rescue NoMethodError => error
|
334
|
-
if error.name == :to_str
|
335
|
-
str = arg.to_s
|
336
|
-
ActiveSupport::Deprecation.warn <<~MSG.squish
|
337
|
-
Implicit conversion of #{arg.class} into String by ActiveSupport::SafeBuffer
|
338
|
-
is deprecated and will be removed in Rails 7.1.
|
339
|
-
You must explicitly cast it to a String.
|
340
|
-
MSG
|
341
|
-
str
|
342
|
-
else
|
343
|
-
raise
|
344
|
-
end
|
345
|
-
end
|
346
|
-
CGI.escapeHTML(arg_string)
|
208
|
+
CGI.escapeHTML(arg.to_str)
|
347
209
|
end
|
348
210
|
end
|
349
211
|
|
@@ -352,6 +214,12 @@ module ActiveSupport # :nodoc:
|
|
352
214
|
rescue ArgumentError
|
353
215
|
# Can't create binding from C level Proc
|
354
216
|
end
|
217
|
+
|
218
|
+
def string_into_safe_buffer(new_string, is_html_safe)
|
219
|
+
new_safe_buffer = new_string.is_a?(SafeBuffer) ? new_string : SafeBuffer.new(new_string)
|
220
|
+
new_safe_buffer.instance_variable_set :@html_safe, is_html_safe
|
221
|
+
new_safe_buffer
|
222
|
+
end
|
355
223
|
end
|
356
224
|
end
|
357
225
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Thread::Backtrace::Location # :nodoc:
|
4
|
+
if defined?(ErrorHighlight) && Gem::Version.new(ErrorHighlight::VERSION) >= Gem::Version.new("0.4.0")
|
5
|
+
def spot(ex)
|
6
|
+
ErrorHighlight.spot(ex, backtrace_location: self)
|
7
|
+
end
|
8
|
+
else
|
9
|
+
def spot(ex)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -160,9 +160,25 @@ class Time
|
|
160
160
|
elsif utc?
|
161
161
|
::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec)
|
162
162
|
elsif zone&.respond_to?(:utc_to_local)
|
163
|
-
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone)
|
163
|
+
new_time = ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, zone)
|
164
|
+
|
165
|
+
# When there are two occurrences of a nominal time due to DST ending,
|
166
|
+
# `Time.new` chooses the first chronological occurrence (the one with a
|
167
|
+
# larger UTC offset). However, for `change`, we want to choose the
|
168
|
+
# occurrence that matches this time's UTC offset.
|
169
|
+
#
|
170
|
+
# If the new time's UTC offset is larger than this time's UTC offset, the
|
171
|
+
# new time might be a first chronological occurrence. So we add the offset
|
172
|
+
# difference to fast-forward the new time, and check if the result has the
|
173
|
+
# desired UTC offset (i.e. is the second chronological occurrence).
|
174
|
+
offset_difference = new_time.utc_offset - utc_offset
|
175
|
+
if offset_difference > 0 && (new_time_2 = new_time + offset_difference).utc_offset == utc_offset
|
176
|
+
new_time_2
|
177
|
+
else
|
178
|
+
new_time
|
179
|
+
end
|
164
180
|
elsif zone
|
165
|
-
::Time.local(
|
181
|
+
::Time.local(new_sec, new_min, new_hour, new_day, new_month, new_year, nil, nil, isdst, nil)
|
166
182
|
else
|
167
183
|
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec, utc_offset)
|
168
184
|
end
|
@@ -179,6 +195,10 @@ class Time
|
|
179
195
|
# Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700
|
180
196
|
# Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700
|
181
197
|
# Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700
|
198
|
+
#
|
199
|
+
# Just like Date#advance, increments are applied in order of time units from
|
200
|
+
# largest to smallest. This order can affect the result around the end of a
|
201
|
+
# month.
|
182
202
|
def advance(options)
|
183
203
|
unless options[:weeks].nil?
|
184
204
|
options[:weeks], partial_weeks = options[:weeks].divmod(1)
|
@@ -54,12 +54,12 @@ class Time
|
|
54
54
|
if formatter = DATE_FORMATS[format]
|
55
55
|
formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
|
56
56
|
else
|
57
|
-
|
58
|
-
to_default_s
|
57
|
+
to_s
|
59
58
|
end
|
60
59
|
end
|
61
60
|
alias_method :to_formatted_s, :to_fs
|
62
61
|
alias_method :to_default_s, :to_s
|
62
|
+
deprecate to_default_s: :to_s, deprecator: ActiveSupport.deprecator
|
63
63
|
|
64
64
|
# Returns a formatted string of the offset from UTC, or an alternative
|
65
65
|
# string if the time zone is already UTC.
|
@@ -19,10 +19,10 @@ class Time
|
|
19
19
|
#
|
20
20
|
# This method accepts any of the following:
|
21
21
|
#
|
22
|
-
# * A Rails TimeZone object.
|
23
|
-
# * An identifier for a Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
|
24
|
-
# * A
|
25
|
-
# * An identifier for a
|
22
|
+
# * A \Rails TimeZone object.
|
23
|
+
# * An identifier for a \Rails TimeZone object (e.g., "Eastern Time (US & Canada)", <tt>-5.hours</tt>).
|
24
|
+
# * A +TZInfo::Timezone+ object.
|
25
|
+
# * An identifier for a +TZInfo::Timezone+ object (e.g., "America/New_York").
|
26
26
|
#
|
27
27
|
# Here's an example of how you might set <tt>Time.zone</tt> on a per request basis and reset it when the request is done.
|
28
28
|
# <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone:
|
@@ -49,10 +49,9 @@ class Time
|
|
49
49
|
# around_action :set_time_zone
|
50
50
|
#
|
51
51
|
# private
|
52
|
-
#
|
53
|
-
#
|
54
|
-
#
|
55
|
-
# end
|
52
|
+
# def set_time_zone
|
53
|
+
# Time.use_zone(current_user.timezone) { yield }
|
54
|
+
# end
|
56
55
|
# end
|
57
56
|
#
|
58
57
|
# NOTE: This won't affect any ActiveSupport::TimeWithZone
|
@@ -4,5 +4,4 @@ require "active_support/core_ext/time/acts_like"
|
|
4
4
|
require "active_support/core_ext/time/calculations"
|
5
5
|
require "active_support/core_ext/time/compatibility"
|
6
6
|
require "active_support/core_ext/time/conversions"
|
7
|
-
require "active_support/core_ext/time/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
|
8
7
|
require "active_support/core_ext/time/zones"
|