activesupport 6.0.6.1 → 7.1.3.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +865 -438
- data/MIT-LICENSE +1 -1
- data/README.rdoc +6 -6
- data/lib/active_support/actionable_error.rb +4 -2
- data/lib/active_support/array_inquirer.rb +4 -2
- data/lib/active_support/backtrace_cleaner.rb +30 -10
- data/lib/active_support/benchmarkable.rb +4 -3
- 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 +53 -20
- data/lib/active_support/cache/mem_cache_store.rb +208 -63
- data/lib/active_support/cache/memory_store.rb +120 -38
- data/lib/active_support/cache/null_store.rb +16 -2
- data/lib/active_support/cache/redis_cache_store.rb +201 -208
- data/lib/active_support/cache/serializer_with_fallback.rb +175 -0
- data/lib/active_support/cache/strategy/local_cache.rb +73 -66
- data/lib/active_support/cache.rb +539 -261
- data/lib/active_support/callbacks.rb +273 -142
- data/lib/active_support/code_generator.rb +65 -0
- data/lib/active_support/concern.rb +53 -7
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +44 -7
- data/lib/active_support/concurrency/null_lock.rb +13 -0
- data/lib/active_support/concurrency/share_lock.rb +2 -2
- data/lib/active_support/configurable.rb +19 -6
- data/lib/active_support/configuration_file.rb +51 -0
- data/lib/active_support/core_ext/array/access.rb +1 -5
- data/lib/active_support/core_ext/array/conversions.rb +15 -13
- data/lib/active_support/core_ext/array/grouping.rb +6 -6
- data/lib/active_support/core_ext/array/inquiry.rb +2 -2
- data/lib/active_support/core_ext/benchmark.rb +2 -2
- data/lib/active_support/core_ext/big_decimal/conversions.rb +1 -1
- data/lib/active_support/core_ext/class/attribute.rb +34 -44
- data/lib/active_support/core_ext/class/subclasses.rb +19 -29
- data/lib/active_support/core_ext/date/blank.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +24 -9
- data/lib/active_support/core_ext/date/conversions.rb +18 -16
- data/lib/active_support/core_ext/date_and_time/calculations.rb +27 -4
- data/lib/active_support/core_ext/date_and_time/compatibility.rb +15 -0
- data/lib/active_support/core_ext/date_time/blank.rb +1 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +4 -0
- data/lib/active_support/core_ext/date_time/conversions.rb +19 -15
- data/lib/active_support/core_ext/digest/uuid.rb +30 -13
- data/lib/active_support/core_ext/enumerable.rb +146 -72
- data/lib/active_support/core_ext/erb/util.rb +196 -0
- data/lib/active_support/core_ext/file/atomic.rb +3 -1
- data/lib/active_support/core_ext/hash/conversions.rb +3 -4
- data/lib/active_support/core_ext/hash/deep_merge.rb +22 -14
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +4 -4
- data/lib/active_support/core_ext/hash/indifferent_access.rb +3 -3
- data/lib/active_support/core_ext/hash/keys.rb +5 -5
- data/lib/active_support/core_ext/hash/slice.rb +3 -2
- data/lib/active_support/core_ext/integer/inflections.rb +12 -12
- data/lib/active_support/core_ext/kernel/reporting.rb +4 -4
- data/lib/active_support/core_ext/kernel/singleton_class.rb +1 -1
- data/lib/active_support/core_ext/load_error.rb +1 -1
- data/lib/active_support/core_ext/module/attr_internal.rb +2 -2
- data/lib/active_support/core_ext/module/attribute_accessors.rb +31 -29
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +51 -20
- data/lib/active_support/core_ext/module/concerning.rb +14 -8
- data/lib/active_support/core_ext/module/delegation.rb +75 -42
- data/lib/active_support/core_ext/module/deprecation.rb +15 -12
- data/lib/active_support/core_ext/module/introspection.rb +1 -26
- data/lib/active_support/core_ext/name_error.rb +23 -2
- data/lib/active_support/core_ext/numeric/bytes.rb +9 -0
- data/lib/active_support/core_ext/numeric/conversions.rb +82 -73
- data/lib/active_support/core_ext/object/acts_like.rb +29 -5
- data/lib/active_support/core_ext/object/blank.rb +2 -2
- data/lib/active_support/core_ext/object/deep_dup.rb +17 -1
- data/lib/active_support/core_ext/object/duplicable.rb +15 -4
- 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 +52 -28
- data/lib/active_support/core_ext/object/to_query.rb +2 -4
- data/lib/active_support/core_ext/object/try.rb +20 -20
- data/lib/active_support/core_ext/object/with.rb +44 -0
- data/lib/active_support/core_ext/object/with_options.rb +25 -6
- 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 +23 -0
- data/lib/active_support/core_ext/pathname.rb +4 -0
- data/lib/active_support/core_ext/range/compare_range.rb +6 -25
- data/lib/active_support/core_ext/range/conversions.rb +34 -13
- data/lib/active_support/core_ext/range/each.rb +1 -1
- 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/regexp.rb +8 -1
- data/lib/active_support/core_ext/securerandom.rb +25 -13
- data/lib/active_support/core_ext/string/access.rb +5 -24
- data/lib/active_support/core_ext/string/conversions.rb +3 -2
- data/lib/active_support/core_ext/string/filters.rb +21 -15
- data/lib/active_support/core_ext/string/indent.rb +1 -1
- data/lib/active_support/core_ext/string/inflections.rb +51 -10
- data/lib/active_support/core_ext/string/inquiry.rb +2 -1
- data/lib/active_support/core_ext/string/multibyte.rb +2 -2
- data/lib/active_support/core_ext/string/output_safety.rb +85 -194
- data/lib/active_support/core_ext/string/starts_ends_with.rb +2 -2
- data/lib/active_support/core_ext/symbol/starts_ends_with.rb +6 -0
- data/lib/active_support/core_ext/symbol.rb +3 -0
- data/lib/active_support/core_ext/thread/backtrace/location.rb +12 -0
- data/lib/active_support/core_ext/time/calculations.rb +46 -8
- data/lib/active_support/core_ext/time/conversions.rb +16 -13
- data/lib/active_support/core_ext/time/zones.rb +12 -28
- data/lib/active_support/core_ext.rb +2 -1
- data/lib/active_support/current_attributes/test_helper.rb +13 -0
- data/lib/active_support/current_attributes.rb +54 -22
- data/lib/active_support/deep_mergeable.rb +53 -0
- data/lib/active_support/dependencies/autoload.rb +17 -12
- data/lib/active_support/dependencies/interlock.rb +10 -18
- data/lib/active_support/dependencies/require_dependency.rb +28 -0
- data/lib/active_support/dependencies.rb +58 -769
- data/lib/active_support/deprecation/behaviors.rb +77 -38
- 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 +54 -0
- data/lib/active_support/deprecation/instance_delegator.rb +31 -5
- data/lib/active_support/deprecation/method_wrappers.rb +12 -28
- data/lib/active_support/deprecation/proxy_wrappers.rb +40 -25
- data/lib/active_support/deprecation/reporting.rb +76 -16
- data/lib/active_support/deprecation.rb +36 -4
- data/lib/active_support/deprecator.rb +7 -0
- data/lib/active_support/descendants_tracker.rb +150 -68
- data/lib/active_support/digest.rb +5 -3
- data/lib/active_support/duration/iso8601_parser.rb +3 -3
- data/lib/active_support/duration/iso8601_serializer.rb +24 -12
- data/lib/active_support/duration.rb +136 -56
- data/lib/active_support/encrypted_configuration.rb +72 -9
- data/lib/active_support/encrypted_file.rb +46 -13
- data/lib/active_support/environment_inquirer.rb +40 -0
- data/lib/active_support/error_reporter/test_helper.rb +15 -0
- data/lib/active_support/error_reporter.rb +203 -0
- data/lib/active_support/evented_file_update_checker.rb +86 -137
- data/lib/active_support/execution_context/test_helper.rb +13 -0
- data/lib/active_support/execution_context.rb +53 -0
- data/lib/active_support/execution_wrapper.rb +31 -12
- data/lib/active_support/executor/test_helper.rb +7 -0
- data/lib/active_support/file_update_checker.rb +4 -2
- data/lib/active_support/fork_tracker.rb +79 -0
- data/lib/active_support/gem_version.rb +5 -5
- data/lib/active_support/gzip.rb +2 -0
- data/lib/active_support/hash_with_indifferent_access.rb +86 -42
- data/lib/active_support/html_safe_translation.rb +53 -0
- data/lib/active_support/i18n.rb +2 -1
- data/lib/active_support/i18n_railtie.rb +29 -27
- data/lib/active_support/inflector/inflections.rb +26 -9
- data/lib/active_support/inflector/methods.rb +54 -64
- data/lib/active_support/inflector/transliterate.rb +7 -5
- data/lib/active_support/isolated_execution_state.rb +76 -0
- data/lib/active_support/json/decoding.rb +6 -5
- data/lib/active_support/json/encoding.rb +31 -45
- data/lib/active_support/key_generator.rb +32 -7
- data/lib/active_support/lazy_load_hooks.rb +33 -7
- data/lib/active_support/locale/en.yml +10 -4
- data/lib/active_support/log_subscriber/test_helper.rb +2 -2
- data/lib/active_support/log_subscriber.rb +101 -32
- data/lib/active_support/logger.rb +9 -60
- data/lib/active_support/logger_silence.rb +2 -26
- data/lib/active_support/logger_thread_safe_level.rb +24 -25
- data/lib/active_support/message_encryptor.rb +205 -58
- 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 +237 -86
- 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 +112 -46
- data/lib/active_support/messages/rotation_configuration.rb +2 -1
- data/lib/active_support/messages/rotation_coordinator.rb +93 -0
- data/lib/active_support/messages/rotator.rb +35 -32
- data/lib/active_support/messages/serializer_with_fallback.rb +158 -0
- data/lib/active_support/multibyte/chars.rb +15 -52
- data/lib/active_support/multibyte/unicode.rb +8 -122
- data/lib/active_support/multibyte.rb +1 -1
- data/lib/active_support/notifications/fanout.rb +310 -105
- data/lib/active_support/notifications/instrumenter.rb +113 -48
- data/lib/active_support/notifications.rb +56 -29
- data/lib/active_support/number_helper/number_converter.rb +15 -8
- data/lib/active_support/number_helper/number_to_currency_converter.rb +11 -6
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_converter.rb +1 -1
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +5 -5
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +9 -5
- data/lib/active_support/number_helper/rounding_helper.rb +12 -32
- data/lib/active_support/number_helper.rb +379 -304
- data/lib/active_support/option_merger.rb +11 -18
- data/lib/active_support/ordered_hash.rb +4 -4
- data/lib/active_support/ordered_options.rb +23 -3
- data/lib/active_support/parameter_filter.rb +104 -75
- data/lib/active_support/proxy_object.rb +2 -0
- data/lib/active_support/rails.rb +1 -4
- data/lib/active_support/railtie.rb +90 -6
- data/lib/active_support/reloader.rb +12 -4
- data/lib/active_support/rescuable.rb +18 -16
- data/lib/active_support/ruby_features.rb +7 -0
- data/lib/active_support/secure_compare_rotator.rb +58 -0
- data/lib/active_support/security_utils.rb +19 -12
- data/lib/active_support/string_inquirer.rb +5 -3
- data/lib/active_support/subscriber.rb +23 -47
- data/lib/active_support/syntax_error_proxy.rb +70 -0
- data/lib/active_support/tagged_logging.rb +84 -23
- data/lib/active_support/test_case.rb +166 -27
- data/lib/active_support/testing/assertions.rb +73 -20
- 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 +53 -2
- data/lib/active_support/testing/error_reporter_assertions.rb +107 -0
- data/lib/active_support/testing/isolation.rb +30 -29
- data/lib/active_support/testing/method_call_assertions.rb +24 -11
- data/lib/active_support/testing/parallelization/server.rb +82 -0
- data/lib/active_support/testing/parallelization/worker.rb +103 -0
- data/lib/active_support/testing/parallelization.rb +16 -95
- data/lib/active_support/testing/parallelize_executor.rb +81 -0
- data/lib/active_support/testing/stream.rb +4 -6
- data/lib/active_support/testing/strict_warnings.rb +39 -0
- data/lib/active_support/testing/tagged_logging.rb +1 -1
- data/lib/active_support/testing/time_helpers.rb +89 -19
- data/lib/active_support/time_with_zone.rb +105 -70
- data/lib/active_support/values/time_zone.rb +59 -26
- data/lib/active_support/version.rb +1 -1
- data/lib/active_support/xml_mini/jdom.rb +4 -11
- data/lib/active_support/xml_mini/libxml.rb +5 -5
- data/lib/active_support/xml_mini/libxmlsax.rb +1 -1
- data/lib/active_support/xml_mini/nokogiri.rb +5 -5
- data/lib/active_support/xml_mini/nokogirisax.rb +2 -2
- data/lib/active_support/xml_mini/rexml.rb +9 -2
- data/lib/active_support/xml_mini.rb +7 -6
- data/lib/active_support.rb +40 -1
- metadata +127 -40
- data/lib/active_support/core_ext/array/prepend_and_append.rb +0 -5
- data/lib/active_support/core_ext/hash/compact.rb +0 -5
- data/lib/active_support/core_ext/hash/transform_values.rb +0 -5
- data/lib/active_support/core_ext/marshal.rb +0 -24
- data/lib/active_support/core_ext/module/reachable.rb +0 -6
- data/lib/active_support/core_ext/numeric/inquiry.rb +0 -5
- data/lib/active_support/core_ext/range/include_range.rb +0 -9
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +0 -23
- data/lib/active_support/core_ext/range/overlaps.rb +0 -10
- data/lib/active_support/core_ext/uri.rb +0 -25
- data/lib/active_support/dependencies/zeitwerk_integration.rb +0 -117
- data/lib/active_support/per_thread_registry.rb +0 -60
@@ -1,50 +1,48 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
module ActiveSupport
|
4
|
+
module EnumerableCoreExt # :nodoc:
|
5
|
+
module Constants
|
6
|
+
private
|
7
|
+
def const_missing(name)
|
8
|
+
if name == :SoleItemExpectedError
|
9
|
+
::ActiveSupport::EnumerableCoreExt::SoleItemExpectedError
|
10
|
+
else
|
11
|
+
super
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
11
17
|
|
12
|
-
|
13
|
-
# doesn't
|
14
|
-
|
15
|
-
|
18
|
+
module Enumerable
|
19
|
+
# Error generated by +sole+ when called on an enumerable that doesn't have
|
20
|
+
# exactly one item.
|
21
|
+
class SoleItemExpectedError < StandardError; end
|
16
22
|
|
17
|
-
# :
|
23
|
+
# HACK: For performance reasons, Enumerable shouldn't have any constants of its own.
|
24
|
+
# So we move SoleItemExpectedError into ActiveSupport::EnumerableCoreExt.
|
25
|
+
ActiveSupport::EnumerableCoreExt::SoleItemExpectedError = remove_const(:SoleItemExpectedError)
|
26
|
+
singleton_class.prepend(ActiveSupport::EnumerableCoreExt::Constants)
|
18
27
|
|
19
|
-
# Calculates
|
20
|
-
#
|
21
|
-
# payments.sum { |p| p.price * p.tax_rate }
|
22
|
-
# payments.sum(&:price)
|
28
|
+
# Calculates the minimum from the extracted elements.
|
23
29
|
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
#
|
31
|
-
# ['foo', 'bar'].sum # => "foobar"
|
32
|
-
# [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5]
|
33
|
-
#
|
34
|
-
# The default sum of an empty list is zero. You can override this default:
|
30
|
+
# payments = [Payment.new(5), Payment.new(15), Payment.new(10)]
|
31
|
+
# payments.minimum(:price) # => 5
|
32
|
+
def minimum(key)
|
33
|
+
map(&key).min
|
34
|
+
end
|
35
|
+
|
36
|
+
# Calculates the maximum from the extracted elements.
|
35
37
|
#
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
elsif block_given?
|
41
|
-
map(&block).sum(identity)
|
42
|
-
else
|
43
|
-
inject(:+) || 0
|
44
|
-
end
|
38
|
+
# payments = [Payment.new(5), Payment.new(15), Payment.new(10)]
|
39
|
+
# payments.maximum(:price) # => 15
|
40
|
+
def maximum(key)
|
41
|
+
map(&key).max
|
45
42
|
end
|
46
43
|
|
47
|
-
# Convert an enumerable to a hash
|
44
|
+
# Convert an enumerable to a hash, using the block result as the key and the
|
45
|
+
# element as the value.
|
48
46
|
#
|
49
47
|
# people.index_by(&:login)
|
50
48
|
# # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...}
|
@@ -61,23 +59,30 @@ module Enumerable
|
|
61
59
|
end
|
62
60
|
end
|
63
61
|
|
64
|
-
# Convert an enumerable to a hash
|
62
|
+
# Convert an enumerable to a hash, using the element as the key and the block
|
63
|
+
# result as the value.
|
65
64
|
#
|
66
65
|
# post = Post.new(title: "hey there", body: "what's up?")
|
67
66
|
#
|
68
67
|
# %i( title body ).index_with { |attr_name| post.public_send(attr_name) }
|
69
68
|
# # => { title: "hey there", body: "what's up?" }
|
70
|
-
|
69
|
+
#
|
70
|
+
# If an argument is passed instead of a block, it will be used as the value
|
71
|
+
# for all elements:
|
72
|
+
#
|
73
|
+
# %i( created_at updated_at ).index_with(Time.now)
|
74
|
+
# # => { created_at: 2020-03-09 22:31:47, updated_at: 2020-03-09 22:31:47 }
|
75
|
+
def index_with(default = (no_default = true))
|
71
76
|
if block_given?
|
72
77
|
result = {}
|
73
78
|
each { |elem| result[elem] = yield(elem) }
|
74
79
|
result
|
75
|
-
elsif
|
80
|
+
elsif no_default
|
81
|
+
to_enum(:index_with) { size if respond_to?(:size) }
|
82
|
+
else
|
76
83
|
result = {}
|
77
84
|
each { |elem| result[elem] = default }
|
78
85
|
result
|
79
|
-
else
|
80
|
-
to_enum(:index_with) { size if respond_to?(:size) }
|
81
86
|
end
|
82
87
|
end
|
83
88
|
|
@@ -88,8 +93,8 @@ module Enumerable
|
|
88
93
|
def many?
|
89
94
|
cnt = 0
|
90
95
|
if block_given?
|
91
|
-
any? do |
|
92
|
-
cnt += 1 if yield
|
96
|
+
any? do |*args|
|
97
|
+
cnt += 1 if yield(*args)
|
93
98
|
cnt > 1
|
94
99
|
end
|
95
100
|
else
|
@@ -128,13 +133,9 @@ module Enumerable
|
|
128
133
|
elements.flatten!(1)
|
129
134
|
reject { |element| elements.include?(element) }
|
130
135
|
end
|
136
|
+
alias :without :excluding
|
131
137
|
|
132
|
-
#
|
133
|
-
def without(*elements)
|
134
|
-
excluding(*elements)
|
135
|
-
end
|
136
|
-
|
137
|
-
# Convert an enumerable to an array based on the given key.
|
138
|
+
# Extract the given key from each element in the enumerable.
|
138
139
|
#
|
139
140
|
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
|
140
141
|
# # => ["David", "Rafael", "Aaron"]
|
@@ -145,44 +146,117 @@ module Enumerable
|
|
145
146
|
if keys.many?
|
146
147
|
map { |element| keys.map { |key| element[key] } }
|
147
148
|
else
|
148
|
-
|
149
|
+
key = keys.first
|
150
|
+
map { |element| element[key] }
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Extract the given key from the first element in the enumerable.
|
155
|
+
#
|
156
|
+
# [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pick(:name)
|
157
|
+
# # => "David"
|
158
|
+
#
|
159
|
+
# [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pick(:id, :name)
|
160
|
+
# # => [1, "David"]
|
161
|
+
def pick(*keys)
|
162
|
+
return if none?
|
163
|
+
|
164
|
+
if keys.many?
|
165
|
+
keys.map { |key| first[key] }
|
166
|
+
else
|
167
|
+
first[keys.first]
|
149
168
|
end
|
150
169
|
end
|
170
|
+
|
171
|
+
# Returns a new +Array+ without the blank items.
|
172
|
+
# Uses Object#blank? for determining if an item is blank.
|
173
|
+
#
|
174
|
+
# [1, "", nil, 2, " ", [], {}, false, true].compact_blank
|
175
|
+
# # => [1, 2, true]
|
176
|
+
#
|
177
|
+
# Set.new([nil, "", 1, false]).compact_blank
|
178
|
+
# # => [1]
|
179
|
+
#
|
180
|
+
# When called on a +Hash+, returns a new +Hash+ without the blank values.
|
181
|
+
#
|
182
|
+
# { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
|
183
|
+
# # => { b: 1, f: true }
|
184
|
+
def compact_blank
|
185
|
+
reject(&:blank?)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Returns a new +Array+ where the order has been set to that provided in the +series+, based on the +key+ of the
|
189
|
+
# objects in the original enumerable.
|
190
|
+
#
|
191
|
+
# [ Person.find(5), Person.find(3), Person.find(1) ].in_order_of(:id, [ 1, 5, 3 ])
|
192
|
+
# # => [ Person.find(1), Person.find(5), Person.find(3) ]
|
193
|
+
#
|
194
|
+
# If the +series+ include keys that have no corresponding element in the Enumerable, these are ignored.
|
195
|
+
# If the Enumerable has additional elements that aren't named in the +series+, these are not included in the result.
|
196
|
+
def in_order_of(key, series)
|
197
|
+
group_by(&key).values_at(*series).flatten(1).compact
|
198
|
+
end
|
199
|
+
|
200
|
+
# Returns the sole item in the enumerable. If there are no items, or more
|
201
|
+
# than one item, raises +Enumerable::SoleItemExpectedError+.
|
202
|
+
#
|
203
|
+
# ["x"].sole # => "x"
|
204
|
+
# Set.new.sole # => Enumerable::SoleItemExpectedError: no item found
|
205
|
+
# { a: 1, b: 2 }.sole # => Enumerable::SoleItemExpectedError: multiple items found
|
206
|
+
def sole
|
207
|
+
case count
|
208
|
+
when 1 then return first # rubocop:disable Style/RedundantReturn
|
209
|
+
when 0 then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "no item found"
|
210
|
+
when 2.. then raise ActiveSupport::EnumerableCoreExt::SoleItemExpectedError, "multiple items found"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
class Hash
|
216
|
+
# Hash#reject has its own definition, so this needs one too.
|
217
|
+
def compact_blank # :nodoc:
|
218
|
+
reject { |_k, v| v.blank? }
|
219
|
+
end
|
220
|
+
|
221
|
+
# Removes all blank values from the +Hash+ in place and returns self.
|
222
|
+
# Uses Object#blank? for determining if a value is blank.
|
223
|
+
#
|
224
|
+
# h = { a: "", b: 1, c: nil, d: [], e: false, f: true }
|
225
|
+
# h.compact_blank!
|
226
|
+
# # => { b: 1, f: true }
|
227
|
+
def compact_blank!
|
228
|
+
# use delete_if rather than reject! because it always returns self even if nothing changed
|
229
|
+
delete_if { |_k, v| v.blank? }
|
230
|
+
end
|
151
231
|
end
|
152
232
|
|
153
|
-
class Range
|
233
|
+
class Range # :nodoc:
|
154
234
|
# Optimize range sum to use arithmetic progression if a block is not given and
|
155
235
|
# we have a range of numeric values.
|
156
|
-
def sum(
|
236
|
+
def sum(initial_value = 0)
|
157
237
|
if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
|
158
238
|
super
|
159
239
|
else
|
160
240
|
actual_last = exclude_end? ? (last - 1) : last
|
161
241
|
if actual_last >= first
|
162
|
-
sum =
|
242
|
+
sum = initial_value || 0
|
163
243
|
sum + (actual_last - first + 1) * (actual_last + first) / 2
|
164
244
|
else
|
165
|
-
|
245
|
+
initial_value || 0
|
166
246
|
end
|
167
247
|
end
|
168
248
|
end
|
169
249
|
end
|
170
250
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
if init.is_a?(Numeric) || first.is_a?(Numeric)
|
182
|
-
init ||= 0
|
183
|
-
orig_sum(init, &block)
|
184
|
-
else
|
185
|
-
super
|
186
|
-
end
|
251
|
+
class Array # :nodoc:
|
252
|
+
# Removes all blank elements from the +Array+ in place and returns self.
|
253
|
+
# Uses Object#blank? for determining if an item is blank.
|
254
|
+
#
|
255
|
+
# a = [1, "", nil, 2, " ", [], {}, false, true]
|
256
|
+
# a.compact_blank!
|
257
|
+
# # => [1, 2, true]
|
258
|
+
def compact_blank!
|
259
|
+
# use delete_if rather than reject! because it always returns self even if nothing changed
|
260
|
+
delete_if(&:blank?)
|
187
261
|
end
|
188
262
|
end
|
@@ -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
|
@@ -53,7 +53,7 @@ class File
|
|
53
53
|
end
|
54
54
|
|
55
55
|
# Private utility method.
|
56
|
-
def self.probe_stat_in(dir)
|
56
|
+
def self.probe_stat_in(dir) # :nodoc:
|
57
57
|
basename = [
|
58
58
|
".permissions_check",
|
59
59
|
Thread.current.object_id,
|
@@ -64,6 +64,8 @@ class File
|
|
64
64
|
file_name = join(dir, basename)
|
65
65
|
FileUtils.touch(file_name)
|
66
66
|
stat(file_name)
|
67
|
+
rescue Errno::ENOENT
|
68
|
+
file_name = nil
|
67
69
|
ensure
|
68
70
|
FileUtils.rm_f(file_name) if file_name
|
69
71
|
end
|
@@ -1,10 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "active_support/xml_mini"
|
4
|
-
require "active_support/time"
|
5
3
|
require "active_support/core_ext/object/blank"
|
6
4
|
require "active_support/core_ext/object/to_param"
|
7
5
|
require "active_support/core_ext/object/to_query"
|
6
|
+
require "active_support/core_ext/object/try"
|
8
7
|
require "active_support/core_ext/array/wrap"
|
9
8
|
require "active_support/core_ext/hash/reverse_merge"
|
10
9
|
require "active_support/core_ext/string/inflections"
|
@@ -69,7 +68,7 @@ class Hash
|
|
69
68
|
#
|
70
69
|
# By default the root node is "hash", but that's configurable via the <tt>:root</tt> option.
|
71
70
|
#
|
72
|
-
# The default XML builder is a fresh instance of
|
71
|
+
# The default XML builder is a fresh instance of +Builder::XmlMarkup+. You can
|
73
72
|
# configure your own builder with the <tt>:builder</tt> option. The method also accepts
|
74
73
|
# options like <tt>:dasherize</tt> and friends, they are forwarded to the builder.
|
75
74
|
def to_xml(options = {})
|
@@ -208,7 +207,7 @@ module ActiveSupport
|
|
208
207
|
elsif become_empty_string?(value)
|
209
208
|
""
|
210
209
|
elsif become_hash?(value)
|
211
|
-
xml_value =
|
210
|
+
xml_value = value.transform_values { |v| deep_to_h(v) }
|
212
211
|
|
213
212
|
# Turn { files: { file: #<StringIO> } } into { files: #<StringIO> } so it is compatible with
|
214
213
|
# how multipart uploaded files from HTML appear
|
@@ -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
|
@@ -5,10 +5,10 @@ class Hash
|
|
5
5
|
# This includes the values from the root hash and from all
|
6
6
|
# nested hashes and arrays.
|
7
7
|
#
|
8
|
-
#
|
8
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
9
9
|
#
|
10
|
-
#
|
11
|
-
#
|
10
|
+
# hash.deep_transform_values{ |value| value.to_s.upcase }
|
11
|
+
# # => {person: {name: "ROB", age: "28"}}
|
12
12
|
def deep_transform_values(&block)
|
13
13
|
_deep_transform_values_in_object(self, &block)
|
14
14
|
end
|
@@ -21,7 +21,7 @@ class Hash
|
|
21
21
|
end
|
22
22
|
|
23
23
|
private
|
24
|
-
#
|
24
|
+
# Support methods for deep transforming nested hashes and arrays.
|
25
25
|
def _deep_transform_values_in_object(object, &block)
|
26
26
|
case object
|
27
27
|
when Hash
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require "active_support/hash_with_indifferent_access"
|
4
4
|
|
5
5
|
class Hash
|
6
|
-
# Returns an
|
6
|
+
# Returns an ActiveSupport::HashWithIndifferentAccess out of its receiver:
|
7
7
|
#
|
8
8
|
# { a: 1 }.with_indifferent_access['a'] # => 1
|
9
9
|
def with_indifferent_access
|
@@ -13,8 +13,8 @@ class Hash
|
|
13
13
|
# Called when object is nested under an object that receives
|
14
14
|
# #with_indifferent_access. This method will be called on the current object
|
15
15
|
# by the enclosing object and is aliased to #with_indifferent_access by
|
16
|
-
# default. Subclasses of Hash may
|
17
|
-
# converting to an
|
16
|
+
# default. Subclasses of Hash may override this method to return +self+ if
|
17
|
+
# converting to an ActiveSupport::HashWithIndifferentAccess would not be
|
18
18
|
# desirable.
|
19
19
|
#
|
20
20
|
# b = { b: 1 }
|
@@ -58,10 +58,10 @@ class Hash
|
|
58
58
|
# This includes the keys from the root hash and from all
|
59
59
|
# nested hashes and arrays.
|
60
60
|
#
|
61
|
-
#
|
61
|
+
# hash = { person: { name: 'Rob', age: '28' } }
|
62
62
|
#
|
63
|
-
#
|
64
|
-
#
|
63
|
+
# hash.deep_transform_keys{ |key| key.to_s.upcase }
|
64
|
+
# # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}}
|
65
65
|
def deep_transform_keys(&block)
|
66
66
|
_deep_transform_keys_in_object(self, &block)
|
67
67
|
end
|
@@ -112,11 +112,11 @@ class Hash
|
|
112
112
|
end
|
113
113
|
|
114
114
|
private
|
115
|
-
#
|
115
|
+
# Support methods for deep transforming nested hashes and arrays.
|
116
116
|
def _deep_transform_keys_in_object(object, &block)
|
117
117
|
case object
|
118
118
|
when Hash
|
119
|
-
object.each_with_object(
|
119
|
+
object.each_with_object(self.class.new) do |(key, value), result|
|
120
120
|
result[yield(key)] = _deep_transform_keys_in_object(value, &block)
|
121
121
|
end
|
122
122
|
when Array
|
@@ -18,8 +18,9 @@ class Hash
|
|
18
18
|
|
19
19
|
# Removes and returns the key/value pairs matching the given keys.
|
20
20
|
#
|
21
|
-
# { a: 1, b: 2, c: 3, d: 4 }
|
22
|
-
#
|
21
|
+
# hash = { a: 1, b: 2, c: 3, d: 4 }
|
22
|
+
# hash.extract!(:a, :b) # => {:a=>1, :b=>2}
|
23
|
+
# hash # => {:c=>3, :d=>4}
|
23
24
|
def extract!(*keys)
|
24
25
|
keys.each_with_object(self.class.new) { |key, result| result[key] = delete(key) if has_key?(key) }
|
25
26
|
end
|