activesupport 7.0.6 → 7.1.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +967 -263
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -4
- 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 +25 -5
- data/lib/active_support/benchmarkable.rb +1 -0
- data/lib/active_support/broadcast_logger.rb +250 -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 +331 -252
- data/lib/active_support/callbacks.rb +44 -21
- 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/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/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 +3 -70
- 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/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 +40 -11
- 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/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 +11 -3
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +4 -4
- 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 -5
- data/lib/active_support/core_ext/string/output_safety.rb +38 -174
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +18 -2
- data/lib/active_support/core_ext/time/conversions.rb +2 -2
- data/lib/active_support/core_ext/time/zones.rb +4 -4
- 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 +3 -5
- 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 +42 -25
- 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 +30 -9
- 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 +3 -1
- 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 +12 -2
- 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 +24 -12
- 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 +6 -4
- data/lib/active_support/locale/en.yml +2 -0
- data/lib/active_support/log_subscriber.rb +84 -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 +77 -20
- data/lib/active_support/notifications.rb +1 -1
- 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 -318
- 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 +84 -69
- 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 +70 -0
- data/lib/active_support/tagged_logging.rb +60 -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 +1 -1
- 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/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 +6 -35
- data/lib/active_support/values/time_zone.rb +9 -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 +106 -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 -5
- 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,196 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "erb"
|
4
|
+
|
5
|
+
module ActiveSupport
|
6
|
+
module CoreExt
|
7
|
+
module ERBUtil
|
8
|
+
# HTML escapes strings but doesn't wrap them with an ActiveSupport::SafeBuffer.
|
9
|
+
# This method is not for public consumption! Seriously!
|
10
|
+
def html_escape(s) # :nodoc:
|
11
|
+
s = s.to_s
|
12
|
+
if s.html_safe?
|
13
|
+
s
|
14
|
+
else
|
15
|
+
super(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
alias :unwrapped_html_escape :html_escape # :nodoc:
|
19
|
+
|
20
|
+
# A utility method for escaping HTML tag characters.
|
21
|
+
# This method is also aliased as <tt>h</tt>.
|
22
|
+
#
|
23
|
+
# puts html_escape('is a > 0 & a < 10?')
|
24
|
+
# # => is a > 0 & a < 10?
|
25
|
+
def html_escape(s) # rubocop:disable Lint/DuplicateMethods
|
26
|
+
unwrapped_html_escape(s).html_safe
|
27
|
+
end
|
28
|
+
alias h html_escape
|
29
|
+
end
|
30
|
+
|
31
|
+
module ERBUtilPrivate
|
32
|
+
include ERBUtil
|
33
|
+
private :unwrapped_html_escape, :html_escape, :h
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class ERB
|
39
|
+
module Util
|
40
|
+
HTML_ESCAPE = { "&" => "&", ">" => ">", "<" => "<", '"' => """, "'" => "'" }
|
41
|
+
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
|
42
|
+
|
43
|
+
# Following XML requirements: https://www.w3.org/TR/REC-xml/#NT-Name
|
44
|
+
TAG_NAME_START_CODEPOINTS = "@:A-Z_a-z\u{C0}-\u{D6}\u{D8}-\u{F6}\u{F8}-\u{2FF}\u{370}-\u{37D}\u{37F}-\u{1FFF}" \
|
45
|
+
"\u{200C}-\u{200D}\u{2070}-\u{218F}\u{2C00}-\u{2FEF}\u{3001}-\u{D7FF}\u{F900}-\u{FDCF}" \
|
46
|
+
"\u{FDF0}-\u{FFFD}\u{10000}-\u{EFFFF}"
|
47
|
+
INVALID_TAG_NAME_START_REGEXP = /[^#{TAG_NAME_START_CODEPOINTS}]/
|
48
|
+
TAG_NAME_FOLLOWING_CODEPOINTS = "#{TAG_NAME_START_CODEPOINTS}\\-.0-9\u{B7}\u{0300}-\u{036F}\u{203F}-\u{2040}"
|
49
|
+
INVALID_TAG_NAME_FOLLOWING_REGEXP = /[^#{TAG_NAME_FOLLOWING_CODEPOINTS}]/
|
50
|
+
SAFE_XML_TAG_NAME_REGEXP = /\A[#{TAG_NAME_START_CODEPOINTS}][#{TAG_NAME_FOLLOWING_CODEPOINTS}]*\z/
|
51
|
+
TAG_NAME_REPLACEMENT_CHAR = "_"
|
52
|
+
|
53
|
+
prepend ActiveSupport::CoreExt::ERBUtilPrivate
|
54
|
+
singleton_class.prepend ActiveSupport::CoreExt::ERBUtil
|
55
|
+
|
56
|
+
# A utility method for escaping HTML without affecting existing escaped entities.
|
57
|
+
#
|
58
|
+
# html_escape_once('1 < 2 & 3')
|
59
|
+
# # => "1 < 2 & 3"
|
60
|
+
#
|
61
|
+
# html_escape_once('<< Accept & Checkout')
|
62
|
+
# # => "<< Accept & Checkout"
|
63
|
+
def html_escape_once(s)
|
64
|
+
ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE).html_safe
|
65
|
+
end
|
66
|
+
|
67
|
+
module_function :html_escape_once
|
68
|
+
|
69
|
+
# A utility method for escaping HTML entities in JSON strings. Specifically, the
|
70
|
+
# &, > and < characters are replaced with their equivalent unicode escaped form -
|
71
|
+
# \u0026, \u003e, and \u003c. The Unicode sequences \u2028 and \u2029 are also
|
72
|
+
# escaped as they are treated as newline characters in some JavaScript engines.
|
73
|
+
# These sequences have identical meaning as the original characters inside the
|
74
|
+
# context of a JSON string, so assuming the input is a valid and well-formed
|
75
|
+
# JSON value, the output will have equivalent meaning when parsed:
|
76
|
+
#
|
77
|
+
# json = JSON.generate({ name: "</script><script>alert('PWNED!!!')</script>"})
|
78
|
+
# # => "{\"name\":\"</script><script>alert('PWNED!!!')</script>\"}"
|
79
|
+
#
|
80
|
+
# json_escape(json)
|
81
|
+
# # => "{\"name\":\"\\u003C/script\\u003E\\u003Cscript\\u003Ealert('PWNED!!!')\\u003C/script\\u003E\"}"
|
82
|
+
#
|
83
|
+
# JSON.parse(json) == JSON.parse(json_escape(json))
|
84
|
+
# # => true
|
85
|
+
#
|
86
|
+
# The intended use case for this method is to escape JSON strings before including
|
87
|
+
# them inside a script tag to avoid XSS vulnerability:
|
88
|
+
#
|
89
|
+
# <script>
|
90
|
+
# var currentUser = <%= raw json_escape(current_user.to_json) %>;
|
91
|
+
# </script>
|
92
|
+
#
|
93
|
+
# It is necessary to +raw+ the result of +json_escape+, so that quotation marks
|
94
|
+
# don't get converted to <tt>"</tt> entities. +json_escape+ doesn't
|
95
|
+
# automatically flag the result as HTML safe, since the raw value is unsafe to
|
96
|
+
# use inside HTML attributes.
|
97
|
+
#
|
98
|
+
# If your JSON is being used downstream for insertion into the DOM, be aware of
|
99
|
+
# whether or not it is being inserted via <tt>html()</tt>. Most jQuery plugins do this.
|
100
|
+
# If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
|
101
|
+
# content returned by your JSON.
|
102
|
+
#
|
103
|
+
# If you need to output JSON elsewhere in your HTML, you can just do something
|
104
|
+
# like this, as any unsafe characters (including quotation marks) will be
|
105
|
+
# automatically escaped for you:
|
106
|
+
#
|
107
|
+
# <div data-user-info="<%= current_user.to_json %>">...</div>
|
108
|
+
#
|
109
|
+
# WARNING: this helper only works with valid JSON. Using this on non-JSON values
|
110
|
+
# will open up serious XSS vulnerabilities. For example, if you replace the
|
111
|
+
# +current_user.to_json+ in the example above with user input instead, the browser
|
112
|
+
# will happily <tt>eval()</tt> that string as JavaScript.
|
113
|
+
#
|
114
|
+
# The escaping performed in this method is identical to those performed in the
|
115
|
+
# Active Support JSON encoder when +ActiveSupport.escape_html_entities_in_json+ is
|
116
|
+
# set to true. Because this transformation is idempotent, this helper can be
|
117
|
+
# applied even if +ActiveSupport.escape_html_entities_in_json+ is already true.
|
118
|
+
#
|
119
|
+
# Therefore, when you are unsure if +ActiveSupport.escape_html_entities_in_json+
|
120
|
+
# is enabled, or if you are unsure where your JSON string originated from, it
|
121
|
+
# is recommended that you always apply this helper (other libraries, such as the
|
122
|
+
# JSON gem, do not provide this kind of protection by default; also some gems
|
123
|
+
# might override +to_json+ to bypass Active Support's encoder).
|
124
|
+
def json_escape(s)
|
125
|
+
result = s.to_s.dup
|
126
|
+
result.gsub!(">", '\u003e')
|
127
|
+
result.gsub!("<", '\u003c')
|
128
|
+
result.gsub!("&", '\u0026')
|
129
|
+
result.gsub!("\u2028", '\u2028')
|
130
|
+
result.gsub!("\u2029", '\u2029')
|
131
|
+
s.html_safe? ? result.html_safe : result
|
132
|
+
end
|
133
|
+
|
134
|
+
module_function :json_escape
|
135
|
+
|
136
|
+
# A utility method for escaping XML names of tags and names of attributes.
|
137
|
+
#
|
138
|
+
# xml_name_escape('1 < 2 & 3')
|
139
|
+
# # => "1___2___3"
|
140
|
+
#
|
141
|
+
# It follows the requirements of the specification: https://www.w3.org/TR/REC-xml/#NT-Name
|
142
|
+
def xml_name_escape(name)
|
143
|
+
name = name.to_s
|
144
|
+
return "" if name.blank?
|
145
|
+
return name if name.match?(SAFE_XML_TAG_NAME_REGEXP)
|
146
|
+
|
147
|
+
starting_char = name[0]
|
148
|
+
starting_char.gsub!(INVALID_TAG_NAME_START_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
|
149
|
+
|
150
|
+
return starting_char if name.size == 1
|
151
|
+
|
152
|
+
following_chars = name[1..-1]
|
153
|
+
following_chars.gsub!(INVALID_TAG_NAME_FOLLOWING_REGEXP, TAG_NAME_REPLACEMENT_CHAR)
|
154
|
+
|
155
|
+
starting_char << following_chars
|
156
|
+
end
|
157
|
+
module_function :xml_name_escape
|
158
|
+
|
159
|
+
# Tokenizes a line of ERB. This is really just for error reporting and
|
160
|
+
# nobody should use it.
|
161
|
+
def self.tokenize(source) # :nodoc:
|
162
|
+
require "strscan"
|
163
|
+
source = StringScanner.new(source.chomp)
|
164
|
+
tokens = []
|
165
|
+
|
166
|
+
start_re = /<%(?:={1,2}|-|\#|%)?/m
|
167
|
+
finish_re = /(?:[-=])?%>/m
|
168
|
+
|
169
|
+
while !source.eos?
|
170
|
+
pos = source.pos
|
171
|
+
source.scan_until(/(?:#{start_re}|#{finish_re})/)
|
172
|
+
raise NotImplementedError if source.matched.nil?
|
173
|
+
len = source.pos - source.matched.bytesize - pos
|
174
|
+
|
175
|
+
case source.matched
|
176
|
+
when start_re
|
177
|
+
tokens << [:TEXT, source.string[pos, len]] if len > 0
|
178
|
+
tokens << [:OPEN, source.matched]
|
179
|
+
if source.scan(/(.*?)(?=#{finish_re}|\z)/m)
|
180
|
+
tokens << [:CODE, source.matched] unless source.matched.empty?
|
181
|
+
tokens << [:CLOSE, source.scan(finish_re)] unless source.eos?
|
182
|
+
else
|
183
|
+
raise NotImplementedError
|
184
|
+
end
|
185
|
+
when finish_re
|
186
|
+
tokens << [:CODE, source.string[pos, len]] if len > 0
|
187
|
+
tokens << [:CLOSE, source.matched]
|
188
|
+
else
|
189
|
+
raise NotImplementedError, source.matched
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
tokens
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
@@ -68,7 +68,7 @@ class Hash
|
|
68
68
|
#
|
69
69
|
# By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
|
70
70
|
#
|
71
|
-
# The default XML builder is a fresh instance of
|
71
|
+
# The default XML builder is a fresh instance of +Builder::XmlMarkup+. You can
|
72
72
|
# configure your own builder with the <tt>:builder</tt> option. The method also accepts
|
73
73
|
# options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
|
74
74
|
def to_xml(options = {})
|
@@ -1,6 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/deep_mergeable"
|
4
|
+
|
3
5
|
class Hash
|
6
|
+
include ActiveSupport::DeepMergeable
|
7
|
+
|
8
|
+
##
|
9
|
+
# :method: deep_merge
|
10
|
+
# :call-seq: deep_merge(other_hash, &block)
|
11
|
+
#
|
4
12
|
# Returns a new hash with +self+ and +other_hash+ merged recursively.
|
5
13
|
#
|
6
14
|
# h1 = { a: true, b: { c: [1, 2, 3] } }
|
@@ -15,20 +23,20 @@ class Hash
|
|
15
23
|
# h2 = { b: 250, c: { c1: 200 } }
|
16
24
|
# h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val }
|
17
25
|
# # => { a: 100, b: 450, c: { c1: 300 } }
|
18
|
-
|
19
|
-
|
20
|
-
|
26
|
+
#
|
27
|
+
#--
|
28
|
+
# Implemented by ActiveSupport::DeepMergeable#deep_merge.
|
29
|
+
|
30
|
+
##
|
31
|
+
# :method: deep_merge!
|
32
|
+
# :call-seq: deep_merge!(other_hash, &block)
|
33
|
+
#
|
34
|
+
# Same as #deep_merge, but modifies +self+.
|
35
|
+
#
|
36
|
+
#--
|
37
|
+
# Implemented by ActiveSupport::DeepMergeable#deep_merge!.
|
21
38
|
|
22
|
-
|
23
|
-
|
24
|
-
merge!(other_hash) do |key, this_val, other_val|
|
25
|
-
if this_val.is_a?(Hash) && other_val.is_a?(Hash)
|
26
|
-
this_val.deep_merge(other_val, &block)
|
27
|
-
elsif block_given?
|
28
|
-
block.call(key, this_val, other_val)
|
29
|
-
else
|
30
|
-
other_val
|
31
|
-
end
|
32
|
-
end
|
39
|
+
def deep_merge?(other) # :nodoc:
|
40
|
+
other.is_a?(Hash)
|
33
41
|
end
|
34
42
|
end
|
@@ -43,6 +43,7 @@ class Module
|
|
43
43
|
#
|
44
44
|
# module HairColors
|
45
45
|
# mattr_reader :hair_colors, default: [:brown, :black, :blonde, :red]
|
46
|
+
# mattr_reader(:hair_styles) { [:long, :short] }
|
46
47
|
# end
|
47
48
|
#
|
48
49
|
# class Person
|
@@ -50,6 +51,7 @@ class Module
|
|
50
51
|
# end
|
51
52
|
#
|
52
53
|
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
|
54
|
+
# Person.new.hair_styles # => [:long, :short]
|
53
55
|
def mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil, location: nil)
|
54
56
|
raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
|
55
57
|
location ||= caller_locations(1, 1).first
|
@@ -107,6 +109,7 @@ class Module
|
|
107
109
|
#
|
108
110
|
# module HairColors
|
109
111
|
# mattr_writer :hair_colors, default: [:brown, :black, :blonde, :red]
|
112
|
+
# mattr_writer(:hair_styles) { [:long, :short] }
|
110
113
|
# end
|
111
114
|
#
|
112
115
|
# class Person
|
@@ -114,6 +117,7 @@ class Module
|
|
114
117
|
# end
|
115
118
|
#
|
116
119
|
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
|
120
|
+
# Person.class_variable_get("@@hair_styles") # => [:long, :short]
|
117
121
|
def mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil, location: nil)
|
118
122
|
raise TypeError, "module attributes should be defined directly on class, not singleton" if singleton_class?
|
119
123
|
location ||= caller_locations(1, 1).first
|
@@ -192,6 +196,7 @@ class Module
|
|
192
196
|
#
|
193
197
|
# module HairColors
|
194
198
|
# mattr_accessor :hair_colors, default: [:brown, :black, :blonde, :red]
|
199
|
+
# mattr_accessor(:hair_styles) { [:long, :short] }
|
195
200
|
# end
|
196
201
|
#
|
197
202
|
# class Person
|
@@ -199,6 +204,7 @@ class Module
|
|
199
204
|
# end
|
200
205
|
#
|
201
206
|
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
|
207
|
+
# Person.class_variable_get("@@hair_styles") # => [:long, :short]
|
202
208
|
def mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil, &blk)
|
203
209
|
location = caller_locations(1, 1).first
|
204
210
|
mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default, location: location, &blk)
|
@@ -42,14 +42,32 @@ class Module
|
|
42
42
|
syms.each do |sym|
|
43
43
|
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
|
44
44
|
|
45
|
-
# The following generated method concatenates `
|
46
|
-
# to
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
45
|
+
# The following generated method concatenates `object_id` because we want
|
46
|
+
# subclasses to maintain independent values.
|
47
|
+
if default.nil?
|
48
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
49
|
+
def self.#{sym}
|
50
|
+
@__thread_mattr_#{sym} ||= "attr_#{sym}_\#{object_id}"
|
51
|
+
::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}]
|
52
|
+
end
|
53
|
+
EOS
|
54
|
+
else
|
55
|
+
default = default.dup.freeze unless default.frozen?
|
56
|
+
singleton_class.define_method("#{sym}_default_value") { default }
|
57
|
+
|
58
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
59
|
+
def self.#{sym}
|
60
|
+
@__thread_mattr_#{sym} ||= "attr_#{sym}_\#{object_id}"
|
61
|
+
value = ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}]
|
62
|
+
|
63
|
+
if value.nil? && !::ActiveSupport::IsolatedExecutionState.key?(@__thread_mattr_#{sym})
|
64
|
+
::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] = #{sym}_default_value
|
65
|
+
else
|
66
|
+
value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
EOS
|
70
|
+
end
|
53
71
|
|
54
72
|
if instance_reader && instance_accessor
|
55
73
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
@@ -58,8 +76,6 @@ class Module
|
|
58
76
|
end
|
59
77
|
EOS
|
60
78
|
end
|
61
|
-
|
62
|
-
::ActiveSupport::IsolatedExecutionState["attr_#{name}_#{sym}"] = default unless default.nil?
|
63
79
|
end
|
64
80
|
end
|
65
81
|
alias :thread_cattr_reader :thread_mattr_reader
|
@@ -82,15 +98,15 @@ class Module
|
|
82
98
|
# end
|
83
99
|
#
|
84
100
|
# Current.new.user = "DHH" # => NoMethodError
|
85
|
-
def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true
|
101
|
+
def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true) # :nodoc:
|
86
102
|
syms.each do |sym|
|
87
103
|
raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym)
|
88
104
|
|
89
|
-
# The following generated method concatenates `
|
90
|
-
# to
|
105
|
+
# The following generated method concatenates `object_id` because we want
|
106
|
+
# subclasses to maintain independent values.
|
91
107
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
92
108
|
def self.#{sym}=(obj)
|
93
|
-
@__thread_mattr_#{sym} ||= "attr_
|
109
|
+
@__thread_mattr_#{sym} ||= "attr_#{sym}_\#{object_id}"
|
94
110
|
::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] = obj
|
95
111
|
end
|
96
112
|
EOS
|
@@ -102,8 +118,6 @@ class Module
|
|
102
118
|
end
|
103
119
|
EOS
|
104
120
|
end
|
105
|
-
|
106
|
-
public_send("#{sym}=", default) unless default.nil?
|
107
121
|
end
|
108
122
|
end
|
109
123
|
alias :thread_cattr_writer :thread_mattr_writer
|
@@ -149,6 +163,10 @@ class Module
|
|
149
163
|
#
|
150
164
|
# Current.new.user = "DHH" # => NoMethodError
|
151
165
|
# Current.new.user # => NoMethodError
|
166
|
+
#
|
167
|
+
# A default value may be specified using the +:default+ option. Because
|
168
|
+
# multiple threads can access the default value, non-frozen default values
|
169
|
+
# will be <tt>dup</tt>ed and frozen.
|
152
170
|
def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil)
|
153
171
|
thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default)
|
154
172
|
thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor)
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require "active_support/concern"
|
4
4
|
|
5
5
|
class Module
|
6
|
-
#
|
6
|
+
# == Bite-sized separation of concerns
|
7
7
|
#
|
8
8
|
# We often find ourselves with a medium-sized chunk of behavior that we'd
|
9
9
|
# like to extract, but only mix in to a single class.
|
@@ -18,9 +18,9 @@ class Module
|
|
18
18
|
# with a comment, as a least-bad alternative. Using modules in separate files
|
19
19
|
# means tedious sifting to get a big-picture view.
|
20
20
|
#
|
21
|
-
#
|
21
|
+
# == Dissatisfying ways to separate small concerns
|
22
22
|
#
|
23
|
-
#
|
23
|
+
# === Using comments:
|
24
24
|
#
|
25
25
|
# class Todo < ApplicationRecord
|
26
26
|
# # Other todo implementation
|
@@ -37,7 +37,7 @@ class Module
|
|
37
37
|
# end
|
38
38
|
# end
|
39
39
|
#
|
40
|
-
#
|
40
|
+
# === With an inline module:
|
41
41
|
#
|
42
42
|
# Noisy syntax.
|
43
43
|
#
|
@@ -61,7 +61,7 @@ class Module
|
|
61
61
|
# include EventTracking
|
62
62
|
# end
|
63
63
|
#
|
64
|
-
#
|
64
|
+
# === Mix-in noise exiled to its own file:
|
65
65
|
#
|
66
66
|
# Once our chunk of behavior starts pushing the scroll-to-understand-it
|
67
67
|
# boundary, we give in and move it to a separate file. At this size, the
|
@@ -75,7 +75,7 @@ class Module
|
|
75
75
|
# include TodoEventTracking
|
76
76
|
# end
|
77
77
|
#
|
78
|
-
#
|
78
|
+
# == Introducing Module#concerning
|
79
79
|
#
|
80
80
|
# By quieting the mix-in noise, we arrive at a natural, low-ceremony way to
|
81
81
|
# separate bite-sized concerns.
|
@@ -7,9 +7,9 @@ class Module
|
|
7
7
|
# option is not used.
|
8
8
|
class DelegationError < NoMethodError; end
|
9
9
|
|
10
|
-
RUBY_RESERVED_KEYWORDS = %w(alias and BEGIN begin break
|
11
|
-
else elsif END end ensure false for if in module next nil
|
12
|
-
return self super then true undef unless until when while yield)
|
10
|
+
RUBY_RESERVED_KEYWORDS = %w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
|
11
|
+
case class def defined? do else elsif END end ensure false for if in module next nil
|
12
|
+
not or redo rescue retry return self super then true undef unless until when while yield)
|
13
13
|
DELEGATION_RESERVED_KEYWORDS = %w(_ arg args block)
|
14
14
|
DELEGATION_RESERVED_METHOD_NAMES = Set.new(
|
15
15
|
RUBY_RESERVED_KEYWORDS + DELEGATION_RESERVED_KEYWORDS
|
@@ -187,19 +187,49 @@ class Module
|
|
187
187
|
location = caller_locations(1, 1).first
|
188
188
|
file, line = location.path, location.lineno
|
189
189
|
|
190
|
-
|
191
|
-
|
190
|
+
receiver = to.to_s
|
191
|
+
receiver = "self.#{receiver}" if DELEGATION_RESERVED_METHOD_NAMES.include?(receiver)
|
192
192
|
|
193
193
|
method_def = []
|
194
194
|
method_names = []
|
195
195
|
|
196
|
-
|
196
|
+
method_def << "self.private" if private
|
197
|
+
|
198
|
+
methods.each do |method|
|
197
199
|
method_name = prefix ? "#{method_prefix}#{method}" : method
|
198
200
|
method_names << method_name.to_sym
|
199
201
|
|
200
202
|
# Attribute writer methods only accept one argument. Makes sure []=
|
201
203
|
# methods still accept two arguments.
|
202
|
-
definition =
|
204
|
+
definition = \
|
205
|
+
if /[^\]]=\z/.match?(method)
|
206
|
+
"arg"
|
207
|
+
else
|
208
|
+
method_object =
|
209
|
+
begin
|
210
|
+
if to.is_a?(Module)
|
211
|
+
to.method(method)
|
212
|
+
elsif receiver == "self.class"
|
213
|
+
method(method)
|
214
|
+
end
|
215
|
+
rescue NameError
|
216
|
+
# Do nothing. Fall back to `"..."`
|
217
|
+
end
|
218
|
+
|
219
|
+
if method_object
|
220
|
+
parameters = method_object.parameters
|
221
|
+
|
222
|
+
if (parameters.map(&:first) & [:opt, :rest, :keyreq, :key, :keyrest]).any?
|
223
|
+
"..."
|
224
|
+
else
|
225
|
+
defn = parameters.filter_map { |type, arg| arg if type == :req }
|
226
|
+
defn << "&block"
|
227
|
+
defn.join(", ")
|
228
|
+
end
|
229
|
+
else
|
230
|
+
"..."
|
231
|
+
end
|
232
|
+
end
|
203
233
|
|
204
234
|
# The following generated method calls the target exactly once, storing
|
205
235
|
# the returned value in a dummy variable.
|
@@ -213,7 +243,7 @@ class Module
|
|
213
243
|
|
214
244
|
method_def <<
|
215
245
|
"def #{method_name}(#{definition})" <<
|
216
|
-
" _ = #{
|
246
|
+
" _ = #{receiver}" <<
|
217
247
|
" if !_.nil? || nil.respond_to?(:#{method})" <<
|
218
248
|
" _.#{method}(#{definition})" <<
|
219
249
|
" end" <<
|
@@ -224,11 +254,11 @@ class Module
|
|
224
254
|
|
225
255
|
method_def <<
|
226
256
|
"def #{method_name}(#{definition})" <<
|
227
|
-
" _ = #{
|
257
|
+
" _ = #{receiver}" <<
|
228
258
|
" _.#{method}(#{definition})" <<
|
229
259
|
"rescue NoMethodError => e" <<
|
230
260
|
" if _.nil? && e.name == :#{method}" <<
|
231
|
-
%( raise DelegationError, "#{self}##{method_name} delegated to #{
|
261
|
+
%( raise DelegationError, "#{self}##{method_name} delegated to #{receiver}.#{method}, but #{receiver} is nil: \#{self.inspect}") <<
|
232
262
|
" else" <<
|
233
263
|
" raise" <<
|
234
264
|
" end" <<
|
@@ -236,7 +266,6 @@ class Module
|
|
236
266
|
end
|
237
267
|
end
|
238
268
|
module_eval(method_def.join(";"), file, line)
|
239
|
-
private(*method_names) if private
|
240
269
|
method_names
|
241
270
|
end
|
242
271
|
|
@@ -1,17 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Module
|
4
|
-
# deprecate :foo
|
5
|
-
# deprecate bar:
|
6
|
-
# deprecate :foo, :bar, baz: 'warning!', qux: 'gone!'
|
4
|
+
# deprecate :foo, deprecator: MyLib.deprecator
|
5
|
+
# deprecate :foo, bar: "warning!", deprecator: MyLib.deprecator
|
7
6
|
#
|
8
|
-
#
|
9
|
-
#
|
10
|
-
#
|
11
|
-
# deprecate :foo, bar: "warning!", deprecator: MyLib::Deprecator.new
|
12
|
-
#
|
13
|
-
# \Custom deprecators must respond to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt>
|
14
|
-
# method where you can implement your custom warning behavior.
|
7
|
+
# A deprecator is typically an instance of ActiveSupport::Deprecation, but you can also pass any object that responds
|
8
|
+
# to <tt>deprecation_warning(deprecated_method_name, message, caller_backtrace)</tt> where you can implement your
|
9
|
+
# custom warning behavior.
|
15
10
|
#
|
16
11
|
# class MyLib::Deprecator
|
17
12
|
# def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
|
@@ -19,7 +14,15 @@ class Module
|
|
19
14
|
# Kernel.warn message
|
20
15
|
# end
|
21
16
|
# end
|
22
|
-
def deprecate(*method_names)
|
23
|
-
ActiveSupport::Deprecation
|
17
|
+
def deprecate(*method_names, deprecator: nil, **options)
|
18
|
+
if deprecator.is_a?(ActiveSupport::Deprecation)
|
19
|
+
deprecator.deprecate_methods(self, *method_names, **options)
|
20
|
+
elsif deprecator
|
21
|
+
# we just need any instance to call deprecate_methods, but the deprecation will be emitted by deprecator
|
22
|
+
ActiveSupport.deprecator.deprecate_methods(self, *method_names, **options, deprecator: deprecator)
|
23
|
+
else
|
24
|
+
ActiveSupport.deprecator.warn("Module.deprecate without a deprecator is deprecated")
|
25
|
+
ActiveSupport::Deprecation._instance.deprecate_methods(self, *method_names, **options)
|
26
|
+
end
|
24
27
|
end
|
25
28
|
end
|
@@ -7,6 +7,7 @@ class Numeric
|
|
7
7
|
TERABYTE = GIGABYTE * 1024
|
8
8
|
PETABYTE = TERABYTE * 1024
|
9
9
|
EXABYTE = PETABYTE * 1024
|
10
|
+
ZETTABYTE = EXABYTE * 1024
|
10
11
|
|
11
12
|
# Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes
|
12
13
|
#
|
@@ -63,4 +64,12 @@ class Numeric
|
|
63
64
|
self * EXABYTE
|
64
65
|
end
|
65
66
|
alias :exabyte :exabytes
|
67
|
+
|
68
|
+
# Returns the number of bytes equivalent to the zettabytes provided.
|
69
|
+
#
|
70
|
+
# 2.zettabytes # => 2_361_183_241_434_822_606_848
|
71
|
+
def zettabytes
|
72
|
+
self * ZETTABYTE
|
73
|
+
end
|
74
|
+
alias :zettabyte :zettabytes
|
66
75
|
end
|
@@ -5,6 +5,8 @@ require "active_support/number_helper"
|
|
5
5
|
|
6
6
|
module ActiveSupport
|
7
7
|
module NumericWithFormat
|
8
|
+
# \Numeric With Format
|
9
|
+
#
|
8
10
|
# Provides options for converting numbers into formatted strings.
|
9
11
|
# Options are provided for phone numbers, currency, percentage,
|
10
12
|
# precision, positional notation, file size, and pretty printing.
|
@@ -3,4 +3,3 @@
|
|
3
3
|
require "active_support/core_ext/numeric/bytes"
|
4
4
|
require "active_support/core_ext/numeric/time"
|
5
5
|
require "active_support/core_ext/numeric/conversions"
|
6
|
-
require "active_support/core_ext/numeric/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
|
@@ -53,3 +53,19 @@ class Hash
|
|
53
53
|
hash
|
54
54
|
end
|
55
55
|
end
|
56
|
+
|
57
|
+
class Module
|
58
|
+
# Returns a copy of module or class if it's anonymous. If it's
|
59
|
+
# named, returns +self+.
|
60
|
+
#
|
61
|
+
# Object.deep_dup == Object # => true
|
62
|
+
# klass = Class.new
|
63
|
+
# klass.deep_dup == klass # => false
|
64
|
+
def deep_dup
|
65
|
+
if name.nil?
|
66
|
+
super
|
67
|
+
else
|
68
|
+
self
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -1,16 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class Object
|
4
|
-
# Returns true if this object is included in the argument.
|
5
|
-
#
|
4
|
+
# Returns true if this object is included in the argument.
|
5
|
+
#
|
6
|
+
# When argument is a +Range+, +#cover?+ is used to properly handle inclusion
|
7
|
+
# check within open ranges. Otherwise, argument must be any object which responds
|
8
|
+
# to +#include?+. Usage:
|
6
9
|
#
|
7
10
|
# characters = ["Konata", "Kagami", "Tsukasa"]
|
8
11
|
# "Konata".in?(characters) # => true
|
9
12
|
#
|
10
|
-
#
|
11
|
-
# to +#include?+.
|
13
|
+
# For non +Range+ arguments, this will throw an +ArgumentError+ if the argument
|
14
|
+
# doesn't respond to +#include?+.
|
12
15
|
def in?(another_object)
|
13
|
-
another_object
|
16
|
+
case another_object
|
17
|
+
when Range
|
18
|
+
another_object.cover?(self)
|
19
|
+
else
|
20
|
+
another_object.include?(self)
|
21
|
+
end
|
14
22
|
rescue NoMethodError
|
15
23
|
raise ArgumentError.new("The parameter passed to #in? must respond to #include?")
|
16
24
|
end
|