activesupport 7.0.8.7 → 7.1.0.beta1
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 +722 -314
- 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/builder.rb +1 -1
- data/lib/active_support/cache/coder.rb +153 -0
- data/lib/active_support/cache/entry.rb +128 -0
- data/lib/active_support/cache/file_store.rb +36 -9
- data/lib/active_support/cache/mem_cache_store.rb +84 -68
- data/lib/active_support/cache/memory_store.rb +76 -24
- data/lib/active_support/cache/null_store.rb +6 -0
- data/lib/active_support/cache/redis_cache_store.rb +126 -131
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +20 -8
- data/lib/active_support/cache.rb +304 -246
- data/lib/active_support/callbacks.rb +38 -18
- 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 +1 -0
- 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 -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/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/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/duplicable.rb +15 -24
- 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 +10 -2
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +3 -3
- 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/{overlaps.rb → overlap.rb} +5 -3
- 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/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/dependencies/autoload.rb +17 -12
- data/lib/active_support/deprecation/behaviors.rb +53 -32
- 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 +35 -21
- 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 +8 -3
- 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/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/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 +22 -10
- 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 +78 -33
- data/lib/active_support/logger.rb +1 -1
- data/lib/active_support/logger_thread_safe_level.rb +9 -21
- data/lib/active_support/message_encryptor.rb +197 -53
- data/lib/active_support/message_encryptors.rb +140 -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 +134 -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 +239 -81
- data/lib/active_support/notifications/instrumenter.rb +71 -14
- data/lib/active_support/notifications.rb +1 -1
- data/lib/active_support/number_helper/number_converter.rb +2 -2
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +1 -0
- 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 +49 -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 +25 -9
- 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 +108 -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 +38 -0
- data/lib/active_support/testing/time_helpers.rb +32 -14
- data/lib/active_support/time_with_zone.rb +4 -14
- 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 +13 -3
- metadata +106 -21
- data/lib/active_support/core_ext/array/deprecated_conversions.rb +0 -25
- data/lib/active_support/core_ext/date/deprecated_conversions.rb +0 -40
- data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +0 -60
- data/lib/active_support/core_ext/range/deprecated_conversions.rb +0 -36
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -5
- data/lib/active_support/core_ext/time/deprecated_conversions.rb +0 -73
- 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 = {})
|
@@ -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)
|
@@ -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
|
@@ -28,32 +28,23 @@ class Object
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
unless methods_are_duplicable
|
39
|
-
class Method
|
40
|
-
# Methods are not duplicable:
|
41
|
-
#
|
42
|
-
# method(:puts).duplicable? # => false
|
43
|
-
# method(:puts).dup # => TypeError: allocator undefined for Method
|
44
|
-
def duplicable?
|
45
|
-
false
|
46
|
-
end
|
31
|
+
class Method
|
32
|
+
# Methods are not duplicable:
|
33
|
+
#
|
34
|
+
# method(:puts).duplicable? # => false
|
35
|
+
# method(:puts).dup # => TypeError: allocator undefined for Method
|
36
|
+
def duplicable?
|
37
|
+
false
|
47
38
|
end
|
39
|
+
end
|
48
40
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
41
|
+
class UnboundMethod
|
42
|
+
# Unbound methods are not duplicable:
|
43
|
+
#
|
44
|
+
# method(:puts).unbind.duplicable? # => false
|
45
|
+
# method(:puts).unbind.dup # => TypeError: allocator undefined for UnboundMethod
|
46
|
+
def duplicable?
|
47
|
+
false
|
57
48
|
end
|
58
49
|
end
|
59
50
|
|
@@ -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
|
@@ -12,19 +12,29 @@ class Object
|
|
12
12
|
#
|
13
13
|
# C.new(0, 1).instance_values # => {"x" => 0, "y" => 1}
|
14
14
|
def instance_values
|
15
|
-
|
15
|
+
instance_variables.to_h do |ivar|
|
16
|
+
[ivar[1..-1].freeze, instance_variable_get(ivar)]
|
17
|
+
end
|
16
18
|
end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
20
|
+
if Symbol.method_defined?(:name) # RUBY_VERSION >= "3.0"
|
21
|
+
# Returns an array of instance variable names as strings including "@".
|
22
|
+
#
|
23
|
+
# class C
|
24
|
+
# def initialize(x, y)
|
25
|
+
# @x, @y = x, y
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# C.new(0, 1).instance_variable_names # => ["@y", "@x"]
|
30
|
+
def instance_variable_names
|
31
|
+
instance_variables.map(&:name)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
def instance_variable_names
|
35
|
+
variables = instance_variables
|
36
|
+
variables.map! { |s| s.to_s.freeze }
|
37
|
+
variables
|
38
|
+
end
|
29
39
|
end
|
30
40
|
end
|