activesupport 5.2.0 → 6.0.3.2
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.
Potentially problematic release.
This version of activesupport might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +479 -330
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -3
- data/lib/active_support.rb +2 -1
- data/lib/active_support/actionable_error.rb +48 -0
- data/lib/active_support/backtrace_cleaner.rb +27 -1
- data/lib/active_support/cache.rb +104 -84
- data/lib/active_support/cache/file_store.rb +29 -30
- data/lib/active_support/cache/mem_cache_store.rb +14 -19
- data/lib/active_support/cache/memory_store.rb +15 -9
- data/lib/active_support/cache/null_store.rb +8 -3
- data/lib/active_support/cache/redis_cache_store.rb +73 -34
- data/lib/active_support/cache/strategy/local_cache.rb +23 -23
- data/lib/active_support/callbacks.rb +16 -8
- data/lib/active_support/concern.rb +31 -4
- data/lib/active_support/concurrency/load_interlock_aware_monitor.rb +18 -0
- data/lib/active_support/concurrency/share_lock.rb +0 -1
- data/lib/active_support/configurable.rb +7 -11
- data/lib/active_support/core_ext/array.rb +1 -1
- data/lib/active_support/core_ext/array/access.rb +18 -6
- data/lib/active_support/core_ext/array/conversions.rb +5 -5
- data/lib/active_support/core_ext/array/extract.rb +21 -0
- data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
- data/lib/active_support/core_ext/class/attribute.rb +11 -16
- data/lib/active_support/core_ext/class/subclasses.rb +1 -1
- data/lib/active_support/core_ext/date/calculations.rb +6 -5
- data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
- data/lib/active_support/core_ext/date_and_time/zones.rb +0 -1
- data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
- data/lib/active_support/core_ext/date_time/conversions.rb +0 -1
- data/lib/active_support/core_ext/digest.rb +3 -0
- data/lib/active_support/core_ext/enumerable.rb +97 -68
- data/lib/active_support/core_ext/file/atomic.rb +1 -1
- data/lib/active_support/core_ext/hash.rb +1 -2
- data/lib/active_support/core_ext/hash/compact.rb +2 -26
- data/lib/active_support/core_ext/hash/conversions.rb +1 -1
- data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
- data/lib/active_support/core_ext/hash/except.rb +1 -1
- data/lib/active_support/core_ext/hash/keys.rb +0 -29
- data/lib/active_support/core_ext/hash/slice.rb +3 -25
- data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
- data/lib/active_support/core_ext/integer/multiple.rb +1 -1
- data/lib/active_support/core_ext/kernel.rb +0 -1
- data/lib/active_support/core_ext/load_error.rb +1 -1
- data/lib/active_support/core_ext/module.rb +0 -1
- data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
- data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
- data/lib/active_support/core_ext/module/delegation.rb +41 -8
- data/lib/active_support/core_ext/module/introspection.rb +38 -13
- data/lib/active_support/core_ext/module/reachable.rb +1 -6
- data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
- data/lib/active_support/core_ext/numeric.rb +0 -1
- data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
- data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
- data/lib/active_support/core_ext/object/blank.rb +1 -2
- data/lib/active_support/core_ext/object/duplicable.rb +7 -114
- data/lib/active_support/core_ext/object/json.rb +1 -0
- data/lib/active_support/core_ext/object/to_query.rb +5 -2
- data/lib/active_support/core_ext/object/try.rb +17 -7
- data/lib/active_support/core_ext/object/with_options.rb +1 -1
- data/lib/active_support/core_ext/range.rb +1 -1
- data/lib/active_support/core_ext/range/compare_range.rb +76 -0
- data/lib/active_support/core_ext/range/conversions.rb +31 -29
- data/lib/active_support/core_ext/range/each.rb +0 -1
- data/lib/active_support/core_ext/range/include_range.rb +6 -22
- data/lib/active_support/core_ext/range/include_time_with_zone.rb +2 -2
- data/lib/active_support/core_ext/regexp.rb +0 -4
- data/lib/active_support/core_ext/securerandom.rb +23 -3
- data/lib/active_support/core_ext/string/access.rb +8 -0
- data/lib/active_support/core_ext/string/filters.rb +42 -1
- data/lib/active_support/core_ext/string/inflections.rb +7 -2
- data/lib/active_support/core_ext/string/multibyte.rb +4 -3
- data/lib/active_support/core_ext/string/output_safety.rb +63 -6
- data/lib/active_support/core_ext/string/strip.rb +3 -1
- data/lib/active_support/core_ext/time/calculations.rb +31 -2
- data/lib/active_support/core_ext/uri.rb +2 -4
- data/lib/active_support/current_attributes.rb +8 -0
- data/lib/active_support/dependencies.rb +77 -18
- data/lib/active_support/dependencies/zeitwerk_integration.rb +117 -0
- data/lib/active_support/deprecation.rb +1 -1
- data/lib/active_support/deprecation/behaviors.rb +5 -1
- data/lib/active_support/deprecation/method_wrappers.rb +20 -13
- data/lib/active_support/deprecation/proxy_wrappers.rb +28 -5
- data/lib/active_support/deprecation/reporting.rb +1 -1
- data/lib/active_support/descendants_tracker.rb +55 -9
- data/lib/active_support/duration.rb +19 -16
- data/lib/active_support/duration/iso8601_parser.rb +2 -4
- data/lib/active_support/duration/iso8601_serializer.rb +3 -5
- data/lib/active_support/encrypted_configuration.rb +1 -5
- data/lib/active_support/encrypted_file.rb +4 -3
- data/lib/active_support/evented_file_update_checker.rb +39 -10
- data/lib/active_support/execution_wrapper.rb +1 -0
- data/lib/active_support/file_update_checker.rb +0 -1
- data/lib/active_support/gem_version.rb +4 -4
- data/lib/active_support/hash_with_indifferent_access.rb +36 -18
- data/lib/active_support/i18n.rb +1 -0
- data/lib/active_support/i18n_railtie.rb +18 -2
- data/lib/active_support/inflector/inflections.rb +1 -5
- data/lib/active_support/inflector/methods.rb +18 -29
- data/lib/active_support/inflector/transliterate.rb +47 -18
- data/lib/active_support/json/decoding.rb +23 -24
- data/lib/active_support/json/encoding.rb +6 -2
- data/lib/active_support/key_generator.rb +0 -32
- data/lib/active_support/lazy_load_hooks.rb +5 -2
- data/lib/active_support/locale/en.rb +33 -0
- data/lib/active_support/log_subscriber.rb +31 -9
- data/lib/active_support/logger.rb +1 -16
- data/lib/active_support/logger_silence.rb +28 -12
- data/lib/active_support/logger_thread_safe_level.rb +28 -5
- data/lib/active_support/message_encryptor.rb +4 -6
- data/lib/active_support/message_verifier.rb +5 -5
- data/lib/active_support/messages/metadata.rb +3 -2
- data/lib/active_support/messages/rotator.rb +4 -4
- data/lib/active_support/multibyte/chars.rb +29 -49
- data/lib/active_support/multibyte/unicode.rb +44 -282
- data/lib/active_support/notifications.rb +41 -4
- data/lib/active_support/notifications/fanout.rb +100 -15
- data/lib/active_support/notifications/instrumenter.rb +80 -9
- data/lib/active_support/number_helper.rb +11 -0
- data/lib/active_support/number_helper/number_converter.rb +4 -5
- data/lib/active_support/number_helper/number_to_currency_converter.rb +9 -10
- data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_human_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -2
- data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
- data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -1
- data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -4
- data/lib/active_support/option_merger.rb +21 -3
- data/lib/active_support/ordered_hash.rb +1 -1
- data/lib/active_support/ordered_options.rb +5 -1
- data/lib/active_support/parameter_filter.rb +128 -0
- data/lib/active_support/rails.rb +0 -6
- data/lib/active_support/reloader.rb +4 -5
- data/lib/active_support/security_utils.rb +1 -1
- data/lib/active_support/string_inquirer.rb +0 -1
- data/lib/active_support/subscriber.rb +65 -22
- data/lib/active_support/tagged_logging.rb +13 -4
- data/lib/active_support/test_case.rb +92 -1
- data/lib/active_support/testing/assertions.rb +15 -1
- data/lib/active_support/testing/deprecation.rb +0 -1
- data/lib/active_support/testing/file_fixtures.rb +2 -0
- data/lib/active_support/testing/isolation.rb +2 -2
- data/lib/active_support/testing/method_call_assertions.rb +28 -1
- data/lib/active_support/testing/parallelization.rb +134 -0
- data/lib/active_support/testing/setup_and_teardown.rb +5 -9
- data/lib/active_support/testing/stream.rb +1 -2
- data/lib/active_support/testing/time_helpers.rb +7 -9
- data/lib/active_support/time_with_zone.rb +15 -5
- data/lib/active_support/values/time_zone.rb +14 -8
- data/lib/active_support/xml_mini.rb +2 -10
- data/lib/active_support/xml_mini/jdom.rb +2 -3
- data/lib/active_support/xml_mini/libxml.rb +2 -2
- data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
- data/lib/active_support/xml_mini/nokogiri.rb +2 -2
- data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
- data/lib/active_support/xml_mini/rexml.rb +2 -2
- metadata +42 -13
- data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
- data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -14,6 +14,7 @@ require "active_support/core_ext/time/conversions"
|
|
14
14
|
require "active_support/core_ext/date_time/conversions"
|
15
15
|
require "active_support/core_ext/date/conversions"
|
16
16
|
|
17
|
+
#--
|
17
18
|
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
|
18
19
|
# their default behavior. That said, we need to define the basic to_json method in all of them,
|
19
20
|
# otherwise they will always use to_json gem implementation, which is backwards incompatible in
|
@@ -75,11 +75,14 @@ class Hash
|
|
75
75
|
#
|
76
76
|
# This method is also aliased as +to_param+.
|
77
77
|
def to_query(namespace = nil)
|
78
|
-
collect do |key, value|
|
78
|
+
query = collect do |key, value|
|
79
79
|
unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
|
80
80
|
value.to_query(namespace ? "#{namespace}[#{key}]" : key)
|
81
81
|
end
|
82
|
-
end.compact
|
82
|
+
end.compact
|
83
|
+
|
84
|
+
query.sort! unless namespace.to_s.include?("[]")
|
85
|
+
query.join("&")
|
83
86
|
end
|
84
87
|
|
85
88
|
alias_method :to_param, :to_query
|
@@ -4,21 +4,31 @@ require "delegate"
|
|
4
4
|
|
5
5
|
module ActiveSupport
|
6
6
|
module Tryable #:nodoc:
|
7
|
-
def try(*
|
8
|
-
|
7
|
+
def try(method_name = nil, *args, &b)
|
8
|
+
if method_name.nil? && block_given?
|
9
|
+
if b.arity == 0
|
10
|
+
instance_eval(&b)
|
11
|
+
else
|
12
|
+
yield self
|
13
|
+
end
|
14
|
+
elsif respond_to?(method_name)
|
15
|
+
public_send(method_name, *args, &b)
|
16
|
+
end
|
9
17
|
end
|
18
|
+
ruby2_keywords(:try) if respond_to?(:ruby2_keywords, true)
|
10
19
|
|
11
|
-
def try!(*
|
12
|
-
if
|
20
|
+
def try!(method_name = nil, *args, &b)
|
21
|
+
if method_name.nil? && block_given?
|
13
22
|
if b.arity == 0
|
14
23
|
instance_eval(&b)
|
15
24
|
else
|
16
25
|
yield self
|
17
26
|
end
|
18
27
|
else
|
19
|
-
public_send(*
|
28
|
+
public_send(method_name, *args, &b)
|
20
29
|
end
|
21
30
|
end
|
31
|
+
ruby2_keywords(:try!) if respond_to?(:ruby2_keywords, true)
|
22
32
|
end
|
23
33
|
end
|
24
34
|
|
@@ -135,14 +145,14 @@ class NilClass
|
|
135
145
|
#
|
136
146
|
# With +try+
|
137
147
|
# @person.try(:children).try(:first).try(:name)
|
138
|
-
def try(*args)
|
148
|
+
def try(method_name = nil, *args)
|
139
149
|
nil
|
140
150
|
end
|
141
151
|
|
142
152
|
# Calling +try!+ on +nil+ always returns +nil+.
|
143
153
|
#
|
144
154
|
# nil.try!(:name) # => nil
|
145
|
-
def try!(*args)
|
155
|
+
def try!(method_name = nil, *args)
|
146
156
|
nil
|
147
157
|
end
|
148
158
|
end
|
@@ -68,7 +68,7 @@ class Object
|
|
68
68
|
# You can access these methods using the class name instead:
|
69
69
|
#
|
70
70
|
# class Phone < ActiveRecord::Base
|
71
|
-
# enum phone_number_type:
|
71
|
+
# enum phone_number_type: { home: 0, office: 1, mobile: 2 }
|
72
72
|
#
|
73
73
|
# with_options presence: true do
|
74
74
|
# validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys }
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support/core_ext/range/conversions"
|
4
|
-
require "active_support/core_ext/range/
|
4
|
+
require "active_support/core_ext/range/compare_range"
|
5
5
|
require "active_support/core_ext/range/include_time_with_zone"
|
6
6
|
require "active_support/core_ext/range/overlaps"
|
7
7
|
require "active_support/core_ext/range/each"
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveSupport
|
4
|
+
module CompareWithRange
|
5
|
+
# Extends the default Range#=== to support range comparisons.
|
6
|
+
# (1..5) === (1..5) # => true
|
7
|
+
# (1..5) === (2..3) # => true
|
8
|
+
# (1..5) === (1...6) # => true
|
9
|
+
# (1..5) === (2..6) # => false
|
10
|
+
#
|
11
|
+
# The native Range#=== behavior is untouched.
|
12
|
+
# ('a'..'f') === ('c') # => true
|
13
|
+
# (5..9) === (11) # => false
|
14
|
+
#
|
15
|
+
# The given range must be fully bounded, with both start and end.
|
16
|
+
def ===(value)
|
17
|
+
if value.is_a?(::Range)
|
18
|
+
# 1...10 includes 1..9 but it does not include 1..10.
|
19
|
+
# 1..10 includes 1...11 but it does not include 1...12.
|
20
|
+
operator = exclude_end? && !value.exclude_end? ? :< : :<=
|
21
|
+
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
|
22
|
+
super(value.first) && (self.end.nil? || value_max.send(operator, last))
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Extends the default Range#include? to support range comparisons.
|
29
|
+
# (1..5).include?(1..5) # => true
|
30
|
+
# (1..5).include?(2..3) # => true
|
31
|
+
# (1..5).include?(1...6) # => true
|
32
|
+
# (1..5).include?(2..6) # => false
|
33
|
+
#
|
34
|
+
# The native Range#include? behavior is untouched.
|
35
|
+
# ('a'..'f').include?('c') # => true
|
36
|
+
# (5..9).include?(11) # => false
|
37
|
+
#
|
38
|
+
# The given range must be fully bounded, with both start and end.
|
39
|
+
def include?(value)
|
40
|
+
if value.is_a?(::Range)
|
41
|
+
# 1...10 includes 1..9 but it does not include 1..10.
|
42
|
+
# 1..10 includes 1...11 but it does not include 1...12.
|
43
|
+
operator = exclude_end? && !value.exclude_end? ? :< : :<=
|
44
|
+
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
|
45
|
+
super(value.first) && (self.end.nil? || value_max.send(operator, last))
|
46
|
+
else
|
47
|
+
super
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Extends the default Range#cover? to support range comparisons.
|
52
|
+
# (1..5).cover?(1..5) # => true
|
53
|
+
# (1..5).cover?(2..3) # => true
|
54
|
+
# (1..5).cover?(1...6) # => true
|
55
|
+
# (1..5).cover?(2..6) # => false
|
56
|
+
#
|
57
|
+
# The native Range#cover? behavior is untouched.
|
58
|
+
# ('a'..'f').cover?('c') # => true
|
59
|
+
# (5..9).cover?(11) # => false
|
60
|
+
#
|
61
|
+
# The given range must be fully bounded, with both start and end.
|
62
|
+
def cover?(value)
|
63
|
+
if value.is_a?(::Range)
|
64
|
+
# 1...10 covers 1..9 but it does not cover 1..10.
|
65
|
+
# 1..10 covers 1...11 but it does not cover 1...12.
|
66
|
+
operator = exclude_end? && !value.exclude_end? ? :< : :<=
|
67
|
+
value_max = !exclude_end? && value.exclude_end? ? value.max : value.last
|
68
|
+
super(value.first) && (self.end.nil? || value_max.send(operator, last))
|
69
|
+
else
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
Range.prepend(ActiveSupport::CompareWithRange)
|
@@ -1,39 +1,41 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
module ActiveSupport
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
module ActiveSupport
|
4
|
+
module RangeWithFormat
|
5
|
+
RANGE_FORMATS = {
|
6
|
+
db: -> (start, stop) do
|
7
|
+
case start
|
8
|
+
when String then "BETWEEN '#{start}' AND '#{stop}'"
|
9
|
+
else
|
10
|
+
"BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
}
|
14
|
+
|
15
|
+
# Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
|
16
|
+
#
|
17
|
+
# range = (1..100) # => 1..100
|
18
|
+
#
|
19
|
+
# range.to_s # => "1..100"
|
20
|
+
# range.to_s(:db) # => "BETWEEN '1' AND '100'"
|
21
|
+
#
|
22
|
+
# == Adding your own range formats to to_s
|
23
|
+
# You can add your own formats to the Range::RANGE_FORMATS hash.
|
24
|
+
# Use the format name as the hash key and a Proc instance.
|
25
|
+
#
|
26
|
+
# # config/initializers/range_formats.rb
|
27
|
+
# Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
|
28
|
+
def to_s(format = :default)
|
29
|
+
if formatter = RANGE_FORMATS[format]
|
30
|
+
formatter.call(first, last)
|
8
31
|
else
|
9
|
-
|
32
|
+
super()
|
10
33
|
end
|
11
34
|
end
|
12
|
-
}
|
13
35
|
|
14
|
-
|
15
|
-
|
16
|
-
# range = (1..100) # => 1..100
|
17
|
-
#
|
18
|
-
# range.to_s # => "1..100"
|
19
|
-
# range.to_s(:db) # => "BETWEEN '1' AND '100'"
|
20
|
-
#
|
21
|
-
# == Adding your own range formats to to_s
|
22
|
-
# You can add your own formats to the Range::RANGE_FORMATS hash.
|
23
|
-
# Use the format name as the hash key and a Proc instance.
|
24
|
-
#
|
25
|
-
# # config/initializers/range_formats.rb
|
26
|
-
# Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
|
27
|
-
def to_s(format = :default)
|
28
|
-
if formatter = RANGE_FORMATS[format]
|
29
|
-
formatter.call(first, last)
|
30
|
-
else
|
31
|
-
super()
|
32
|
-
end
|
36
|
+
alias_method :to_default_s, :to_s
|
37
|
+
alias_method :to_formatted_s, :to_s
|
33
38
|
end
|
34
|
-
|
35
|
-
alias_method :to_default_s, :to_s
|
36
|
-
alias_method :to_formatted_s, :to_s
|
37
39
|
end
|
38
40
|
|
39
41
|
Range.prepend(ActiveSupport::RangeWithFormat)
|
@@ -1,25 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
module IncludeWithRange #:nodoc:
|
5
|
-
# Extends the default Range#include? to support range comparisons.
|
6
|
-
# (1..5).include?(1..5) # => true
|
7
|
-
# (1..5).include?(2..3) # => true
|
8
|
-
# (1..5).include?(2..6) # => false
|
9
|
-
#
|
10
|
-
# The native Range#include? behavior is untouched.
|
11
|
-
# ('a'..'f').include?('c') # => true
|
12
|
-
# (5..9).include?(11) # => false
|
13
|
-
def include?(value)
|
14
|
-
if value.is_a?(::Range)
|
15
|
-
# 1...10 includes 1..9 but it does not include 1..10.
|
16
|
-
operator = exclude_end? && !value.exclude_end? ? :< : :<=
|
17
|
-
super(value.first) && value.last.send(operator, last)
|
18
|
-
else
|
19
|
-
super
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
end
|
3
|
+
require "active_support/deprecation"
|
24
4
|
|
25
|
-
|
5
|
+
ActiveSupport::Deprecation.warn "You have required `active_support/core_ext/range/include_range`. " \
|
6
|
+
"This file will be removed in Rails 6.1. You should require `active_support/core_ext/range/compare_range` " \
|
7
|
+
"instead."
|
8
|
+
|
9
|
+
require "active_support/core_ext/range/compare_range"
|
@@ -9,9 +9,9 @@ module ActiveSupport
|
|
9
9
|
# (1.hour.ago..1.hour.from_now).include?(Time.current) # => true
|
10
10
|
#
|
11
11
|
def include?(value)
|
12
|
-
if
|
12
|
+
if self.begin.is_a?(TimeWithZone)
|
13
13
|
cover?(value)
|
14
|
-
elsif
|
14
|
+
elsif self.end.is_a?(TimeWithZone)
|
15
15
|
cover?(value)
|
16
16
|
else
|
17
17
|
super
|
@@ -4,17 +4,18 @@ require "securerandom"
|
|
4
4
|
|
5
5
|
module SecureRandom
|
6
6
|
BASE58_ALPHABET = ("0".."9").to_a + ("A".."Z").to_a + ("a".."z").to_a - ["0", "O", "I", "l"]
|
7
|
+
BASE36_ALPHABET = ("0".."9").to_a + ("a".."z").to_a
|
8
|
+
|
7
9
|
# SecureRandom.base58 generates a random base58 string.
|
8
10
|
#
|
9
|
-
# The argument _n_ specifies the length
|
11
|
+
# The argument _n_ specifies the length of the random string to be generated.
|
10
12
|
#
|
11
13
|
# If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
|
12
14
|
#
|
13
|
-
# The result may contain alphanumeric characters except 0, O, I and l
|
15
|
+
# The result may contain alphanumeric characters except 0, O, I and l.
|
14
16
|
#
|
15
17
|
# p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
|
16
18
|
# p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
|
17
|
-
#
|
18
19
|
def self.base58(n = 16)
|
19
20
|
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
|
20
21
|
idx = byte % 64
|
@@ -22,4 +23,23 @@ module SecureRandom
|
|
22
23
|
BASE58_ALPHABET[idx]
|
23
24
|
end.join
|
24
25
|
end
|
26
|
+
|
27
|
+
# SecureRandom.base36 generates a random base36 string in lowercase.
|
28
|
+
#
|
29
|
+
# The argument _n_ specifies the length of the random string to be generated.
|
30
|
+
#
|
31
|
+
# If _n_ is not specified or is +nil+, 16 is assumed. It may be larger in the future.
|
32
|
+
# This method can be used over +base58+ if a deterministic case key is necessary.
|
33
|
+
#
|
34
|
+
# The result will contain alphanumeric characters in lowercase.
|
35
|
+
#
|
36
|
+
# p SecureRandom.base36 # => "4kugl2pdqmscqtje"
|
37
|
+
# p SecureRandom.base36(24) # => "77tmhrhjfvfdwodq8w7ev2m7"
|
38
|
+
def self.base36(n = 16)
|
39
|
+
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
|
40
|
+
idx = byte % 64
|
41
|
+
idx = SecureRandom.random_number(36) if idx >= 36
|
42
|
+
BASE36_ALPHABET[idx]
|
43
|
+
end.join
|
44
|
+
end
|
25
45
|
end
|
@@ -75,6 +75,10 @@ class String
|
|
75
75
|
# str.first(0) # => ""
|
76
76
|
# str.first(6) # => "hello"
|
77
77
|
def first(limit = 1)
|
78
|
+
ActiveSupport::Deprecation.warn(
|
79
|
+
"Calling String#first with a negative integer limit " \
|
80
|
+
"will raise an ArgumentError in Rails 6.1."
|
81
|
+
) if limit < 0
|
78
82
|
if limit == 0
|
79
83
|
""
|
80
84
|
elsif limit >= size
|
@@ -95,6 +99,10 @@ class String
|
|
95
99
|
# str.last(0) # => ""
|
96
100
|
# str.last(6) # => "hello"
|
97
101
|
def last(limit = 1)
|
102
|
+
ActiveSupport::Deprecation.warn(
|
103
|
+
"Calling String#last with a negative integer limit " \
|
104
|
+
"will raise an ArgumentError in Rails 6.1."
|
105
|
+
) if limit < 0
|
98
106
|
if limit == 0
|
99
107
|
""
|
100
108
|
elsif limit >= size
|
@@ -75,7 +75,48 @@ class String
|
|
75
75
|
length_with_room_for_omission
|
76
76
|
end
|
77
77
|
|
78
|
-
"#{self[0, stop]}#{omission}"
|
78
|
+
+"#{self[0, stop]}#{omission}"
|
79
|
+
end
|
80
|
+
|
81
|
+
# Truncates +text+ to at most <tt>bytesize</tt> bytes in length without
|
82
|
+
# breaking string encoding by splitting multibyte characters or breaking
|
83
|
+
# grapheme clusters ("perceptual characters") by truncating at combining
|
84
|
+
# characters.
|
85
|
+
#
|
86
|
+
# >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size
|
87
|
+
# => 20
|
88
|
+
# >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize
|
89
|
+
# => 80
|
90
|
+
# >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20)
|
91
|
+
# => "🔪🔪🔪🔪…"
|
92
|
+
#
|
93
|
+
# The truncated text ends with the <tt>:omission</tt> string, defaulting
|
94
|
+
# to "…", for a total length not exceeding <tt>bytesize</tt>.
|
95
|
+
def truncate_bytes(truncate_at, omission: "…")
|
96
|
+
omission ||= ""
|
97
|
+
|
98
|
+
case
|
99
|
+
when bytesize <= truncate_at
|
100
|
+
dup
|
101
|
+
when omission.bytesize > truncate_at
|
102
|
+
raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes"
|
103
|
+
when omission.bytesize == truncate_at
|
104
|
+
omission.dup
|
105
|
+
else
|
106
|
+
self.class.new.tap do |cut|
|
107
|
+
cut_at = truncate_at - omission.bytesize
|
108
|
+
|
109
|
+
scan(/\X/) do |grapheme|
|
110
|
+
if cut.bytesize + grapheme.bytesize <= cut_at
|
111
|
+
cut << grapheme
|
112
|
+
else
|
113
|
+
break
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
cut << omission
|
118
|
+
end
|
119
|
+
end
|
79
120
|
end
|
80
121
|
|
81
122
|
# Truncates a given +text+ after a given number of words (<tt>words_count</tt>):
|
@@ -162,6 +162,11 @@ class String
|
|
162
162
|
|
163
163
|
# Replaces special characters in a string so that it may be used as part of a 'pretty' URL.
|
164
164
|
#
|
165
|
+
# If the optional parameter +locale+ is specified,
|
166
|
+
# the word will be parameterized as a word of that language.
|
167
|
+
# By default, this parameter is set to <tt>nil</tt> and it will use
|
168
|
+
# the configured <tt>I18n.locale</tt>.
|
169
|
+
#
|
165
170
|
# class Person
|
166
171
|
# def to_param
|
167
172
|
# "#{id}-#{name.parameterize}"
|
@@ -187,8 +192,8 @@ class String
|
|
187
192
|
#
|
188
193
|
# <%= link_to(@person.name, person_path) %>
|
189
194
|
# # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a>
|
190
|
-
def parameterize(separator: "-", preserve_case: false)
|
191
|
-
ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case)
|
195
|
+
def parameterize(separator: "-", preserve_case: false, locale: nil)
|
196
|
+
ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case, locale: locale)
|
192
197
|
end
|
193
198
|
|
194
199
|
# Creates the name of a table like Rails does for models to table names. This method
|