activesupport 7.0.0.alpha2 → 7.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of activesupport might be problematic. Click here for more details.

Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +80 -0
  3. data/lib/active_support/cache/mem_cache_store.rb +9 -5
  4. data/lib/active_support/cache/memory_store.rb +2 -2
  5. data/lib/active_support/cache/redis_cache_store.rb +3 -8
  6. data/lib/active_support/cache/strategy/local_cache.rb +6 -12
  7. data/lib/active_support/callbacks.rb +145 -50
  8. data/lib/active_support/code_generator.rb +65 -0
  9. data/lib/active_support/core_ext/array/conversions.rb +3 -1
  10. data/lib/active_support/core_ext/array/deprecated_conversions.rb +25 -0
  11. data/lib/active_support/core_ext/array.rb +1 -0
  12. data/lib/active_support/core_ext/class/subclasses.rb +4 -2
  13. data/lib/active_support/core_ext/date/calculations.rb +2 -2
  14. data/lib/active_support/core_ext/date/conversions.rb +3 -3
  15. data/lib/active_support/core_ext/date/deprecated_conversions.rb +26 -0
  16. data/lib/active_support/core_ext/date.rb +1 -0
  17. data/lib/active_support/core_ext/date_and_time/compatibility.rb +1 -1
  18. data/lib/active_support/core_ext/date_time/conversions.rb +5 -5
  19. data/lib/active_support/core_ext/date_time/deprecated_conversions.rb +22 -0
  20. data/lib/active_support/core_ext/date_time.rb +1 -0
  21. data/lib/active_support/core_ext/digest/uuid.rb +26 -1
  22. data/lib/active_support/core_ext/module/attribute_accessors.rb +2 -0
  23. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +19 -10
  24. data/lib/active_support/core_ext/numeric/conversions.rb +78 -75
  25. data/lib/active_support/core_ext/numeric/deprecated_conversions.rb +60 -0
  26. data/lib/active_support/core_ext/numeric.rb +1 -0
  27. data/lib/active_support/core_ext/object/with_options.rb +20 -1
  28. data/lib/active_support/core_ext/pathname/existence.rb +21 -0
  29. data/lib/active_support/core_ext/pathname.rb +3 -0
  30. data/lib/active_support/core_ext/range/conversions.rb +8 -8
  31. data/lib/active_support/core_ext/range/deprecated_conversions.rb +26 -0
  32. data/lib/active_support/core_ext/range/include_time_with_zone.rb +4 -25
  33. data/lib/active_support/core_ext/range.rb +1 -1
  34. data/lib/active_support/core_ext/time/calculations.rb +1 -1
  35. data/lib/active_support/core_ext/time/conversions.rb +4 -3
  36. data/lib/active_support/core_ext/time/deprecated_conversions.rb +22 -0
  37. data/lib/active_support/core_ext/time/zones.rb +2 -2
  38. data/lib/active_support/core_ext/time.rb +1 -0
  39. data/lib/active_support/core_ext/uri.rb +3 -13
  40. data/lib/active_support/core_ext.rb +1 -0
  41. data/lib/active_support/current_attributes.rb +26 -25
  42. data/lib/active_support/descendants_tracker.rb +175 -69
  43. data/lib/active_support/error_reporter.rb +117 -0
  44. data/lib/active_support/execution_context/test_helper.rb +13 -0
  45. data/lib/active_support/execution_context.rb +53 -0
  46. data/lib/active_support/execution_wrapper.rb +30 -4
  47. data/lib/active_support/executor/test_helper.rb +7 -0
  48. data/lib/active_support/fork_tracker.rb +18 -9
  49. data/lib/active_support/gem_version.rb +1 -1
  50. data/lib/active_support/html_safe_translation.rb +43 -0
  51. data/lib/active_support/i18n_railtie.rb +1 -1
  52. data/lib/active_support/inflector/inflections.rb +12 -3
  53. data/lib/active_support/inflector/methods.rb +2 -2
  54. data/lib/active_support/isolated_execution_state.rb +56 -0
  55. data/lib/active_support/logger_thread_safe_level.rb +2 -3
  56. data/lib/active_support/message_encryptor.rb +5 -0
  57. data/lib/active_support/multibyte/unicode.rb +0 -12
  58. data/lib/active_support/notifications/fanout.rb +61 -55
  59. data/lib/active_support/notifications/instrumenter.rb +15 -15
  60. data/lib/active_support/notifications.rb +5 -21
  61. data/lib/active_support/option_merger.rb +4 -0
  62. data/lib/active_support/per_thread_registry.rb +4 -0
  63. data/lib/active_support/railtie.rb +38 -11
  64. data/lib/active_support/ruby_features.rb +7 -0
  65. data/lib/active_support/subscriber.rb +2 -18
  66. data/lib/active_support/tagged_logging.rb +1 -1
  67. data/lib/active_support/testing/deprecation.rb +52 -1
  68. data/lib/active_support/testing/isolation.rb +1 -1
  69. data/lib/active_support/time_with_zone.rb +34 -6
  70. data/lib/active_support/values/time_zone.rb +5 -0
  71. data/lib/active_support/xml_mini.rb +3 -3
  72. data/lib/active_support.rb +7 -4
  73. metadata +23 -6
@@ -4,4 +4,5 @@ require "active_support/core_ext/date/acts_like"
4
4
  require "active_support/core_ext/date/blank"
5
5
  require "active_support/core_ext/date/calculations"
6
6
  require "active_support/core_ext/date/conversions"
7
+ require "active_support/core_ext/date/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
7
8
  require "active_support/core_ext/date/zones"
@@ -15,7 +15,7 @@ module DateAndTime
15
15
 
16
16
  # Change the output of <tt>ActiveSupport::TimeZone.utc_to_local</tt>.
17
17
  #
18
- # When `true`, it returns local times with an UTC offset, with `false` local
18
+ # When `true`, it returns local times with a UTC offset, with `false` local
19
19
  # times are returned as UTC.
20
20
  #
21
21
  # # Given this zone:
@@ -9,14 +9,14 @@ require "active_support/values/time_zone"
9
9
  class DateTime
10
10
  # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats.
11
11
  #
12
- # This method is aliased to <tt>to_s</tt>.
12
+ # This method is aliased to <tt>to_fs</tt>.
13
13
  #
14
14
  # === Examples
15
15
  # datetime = DateTime.civil(2007, 12, 4, 0, 0, 0, 0) # => Tue, 04 Dec 2007 00:00:00 +0000
16
16
  #
17
17
  # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
18
- # datetime.to_s(:db) # => "2007-12-04 00:00:00"
19
- # datetime.to_s(:number) # => "20071204000000"
18
+ # datetime.to_formatted_s(:db) # => "2007-12-04 00:00:00"
19
+ # datetime.to_formatted_s(:number) # => "20071204000000"
20
20
  # datetime.to_formatted_s(:short) # => "04 Dec 00:00"
21
21
  # datetime.to_formatted_s(:long) # => "December 04, 2007 00:00"
22
22
  # datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00"
@@ -39,8 +39,8 @@ class DateTime
39
39
  to_default_s
40
40
  end
41
41
  end
42
+ alias_method :to_fs, :to_formatted_s
42
43
  alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
43
- alias_method :to_s, :to_formatted_s
44
44
 
45
45
  # Returns a formatted string of the offset from UTC, or an alternative
46
46
  # string if the time zone is already UTC.
@@ -54,7 +54,7 @@ class DateTime
54
54
 
55
55
  # Overrides the default inspect method with a human readable one, e.g., "Mon, 21 Feb 2005 14:30:00 +0000".
56
56
  def readable_inspect
57
- to_s(:rfc822)
57
+ to_formatted_s(:rfc822)
58
58
  end
59
59
  alias_method :default_inspect, :inspect
60
60
  alias_method :inspect, :readable_inspect
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ class DateTime
6
+ NOT_SET = Object.new # :nodoc:
7
+ def to_s(format = NOT_SET) # :nodoc:
8
+ if formatter = ::Time::DATE_FORMATS[format]
9
+ ActiveSupport::Deprecation.warn(
10
+ "DateTime#to_s(#{format.inspect}) is deprecated. Please use DateTime#to_formatted_s(#{format.inspect}) instead."
11
+ )
12
+ formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter)
13
+ elsif format == NOT_SET
14
+ to_default_s
15
+ else
16
+ ActiveSupport::Deprecation.warn(
17
+ "DateTime#to_s(#{format.inspect}) is deprecated. Please use DateTime#to_formatted_s(#{format.inspect}) instead."
18
+ )
19
+ to_default_s
20
+ end
21
+ end
22
+ end
@@ -5,3 +5,4 @@ require "active_support/core_ext/date_time/blank"
5
5
  require "active_support/core_ext/date_time/calculations"
6
6
  require "active_support/core_ext/date_time/compatibility"
7
7
  require "active_support/core_ext/date_time/conversions"
8
+ require "active_support/core_ext/date_time/deprecated_conversions" unless ENV["RAILS_DISABLE_DEPRECATED_TO_S_CONVERSION"]
@@ -10,13 +10,15 @@ module Digest
10
10
  OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
11
11
  X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" # :nodoc:
12
12
 
13
+ mattr_accessor :use_rfc4122_namespaced_uuids, instance_accessor: false, default: false
14
+
13
15
  # Generates a v5 non-random UUID (Universally Unique IDentifier).
14
16
  #
15
17
  # Using OpenSSL::Digest::MD5 generates version 3 UUIDs; OpenSSL::Digest::SHA1 generates version 5 UUIDs.
16
18
  # uuid_from_hash always generates the same UUID for a given name and namespace combination.
17
19
  #
18
20
  # See RFC 4122 for details of UUID at: https://www.ietf.org/rfc/rfc4122.txt
19
- def self.uuid_from_hash(hash_class, uuid_namespace, name)
21
+ def self.uuid_from_hash(hash_class, namespace, name)
20
22
  if hash_class == Digest::MD5 || hash_class == OpenSSL::Digest::MD5
21
23
  version = 3
22
24
  elsif hash_class == Digest::SHA1 || hash_class == OpenSSL::Digest::SHA1
@@ -25,6 +27,8 @@ module Digest
25
27
  raise ArgumentError, "Expected OpenSSL::Digest::SHA1 or OpenSSL::Digest::MD5, got #{hash_class.name}."
26
28
  end
27
29
 
30
+ uuid_namespace = pack_uuid_namespace(namespace)
31
+
28
32
  hash = hash_class.new
29
33
  hash.update(uuid_namespace)
30
34
  hash.update(name)
@@ -50,5 +54,26 @@ module Digest
50
54
  def self.uuid_v4
51
55
  SecureRandom.uuid
52
56
  end
57
+
58
+ def self.pack_uuid_namespace(namespace)
59
+ if [DNS_NAMESPACE, OID_NAMESPACE, URL_NAMESPACE, X500_NAMESPACE].include?(namespace)
60
+ namespace
61
+ elsif use_rfc4122_namespaced_uuids == true
62
+ match_data = namespace.match(/\A(\h{8})-(\h{4})-(\h{4})-(\h{4})-(\h{4})(\h{8})\z/)
63
+
64
+ raise ArgumentError, "Only UUIDs are valid namespace identifiers" unless match_data.present?
65
+
66
+ match_data.captures.map { |s| s.to_i(16) }.pack("NnnnnN")
67
+ else
68
+ ActiveSupport::Deprecation.warn <<~WARNING.squish
69
+ Providing a namespace ID that is not one of the constants defined on Digest::UUID generates an incorrect UUID value according to RFC 4122.
70
+ To enable the correct behavior, set the Rails.application.config.active_support.use_rfc4122_namespaced_uuids configuration option to true.
71
+ WARNING
72
+
73
+ namespace
74
+ end
75
+ end
76
+
77
+ private_class_method :pack_uuid_namespace
53
78
  end
54
79
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # == Attribute Accessors
4
+ #
3
5
  # Extends the module object with class/module and instance accessors for
4
6
  # class/module attributes, just like the native attr* accessors for instance
5
7
  # attributes.
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # == Attribute Accessors per Thread
4
+ #
3
5
  # Extends the module object with class/module and instance accessors for
4
6
  # class/module attributes, just like the native attr* accessors for instance
5
7
  # attributes, but does so on a per-thread basis.
6
8
  #
7
9
  # So the values are scoped within the Thread.current space under the class name
8
10
  # of the module.
11
+ #
12
+ # Note that it can also be scoped per-fiber if Rails.application.config.active_support.isolation_level
13
+ # is set to `:fiber`
9
14
  class Module
10
15
  # Defines a per-thread class attribute and creates class and instance reader methods.
11
16
  # The underlying per-thread class variable is set to +nil+, if it is not previously defined.
@@ -14,9 +19,9 @@ class Module
14
19
  # thread_mattr_reader :user
15
20
  # end
16
21
  #
17
- # Current.user # => nil
18
- # Thread.current[:attr_Current_user] = "DHH"
22
+ # Current.user = "DHH"
19
23
  # Current.user # => "DHH"
24
+ # Thread.new { Current.user }.values # => nil
20
25
  #
21
26
  # The attribute name must be a valid method name in Ruby.
22
27
  #
@@ -41,7 +46,8 @@ class Module
41
46
  # to work with inheritance via polymorphism.
42
47
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
43
48
  def self.#{sym}
44
- Thread.current["attr_" + name + "_#{sym}"]
49
+ @__thread_mattr_#{sym} ||= "attr_\#{name}_#{sym}"
50
+ ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}]
45
51
  end
46
52
  EOS
47
53
 
@@ -53,7 +59,7 @@ class Module
53
59
  EOS
54
60
  end
55
61
 
56
- Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil?
62
+ ::ActiveSupport::IsolatedExecutionState["attr_#{name}_#{sym}"] = default unless default.nil?
57
63
  end
58
64
  end
59
65
  alias :thread_cattr_reader :thread_mattr_reader
@@ -84,7 +90,8 @@ class Module
84
90
  # to work with inheritance via polymorphism.
85
91
  class_eval(<<-EOS, __FILE__, __LINE__ + 1)
86
92
  def self.#{sym}=(obj)
87
- Thread.current["attr_" + name + "_#{sym}"] = obj
93
+ @__thread_mattr_#{sym} ||= "attr_\#{name}_#{sym}"
94
+ ::ActiveSupport::IsolatedExecutionState[@__thread_mattr_#{sym}] = obj
88
95
  end
89
96
  EOS
90
97
 
@@ -111,16 +118,18 @@ class Module
111
118
  # Account.user # => "DHH"
112
119
  # Account.new.user # => "DHH"
113
120
  #
121
+ # Unlike `mattr_accessor`, values are *not* shared with subclasses or parent classes.
114
122
  # If a subclass changes the value, the parent class' value is not changed.
115
- # Similarly, if the parent class changes the value, the value of subclasses
116
- # is not changed.
123
+ # If the parent class changes the value, the value of subclasses is not changed.
117
124
  #
118
125
  # class Customer < Account
119
126
  # end
120
127
  #
121
- # Customer.user = "Rafael"
122
- # Customer.user # => "Rafael"
123
- # Account.user # => "DHH"
128
+ # Account.user # => "DHH"
129
+ # Customer.user # => nil
130
+ # Customer.user = "Rafael"
131
+ # Customer.user # => "Rafael"
132
+ # Account.user # => "DHH"
124
133
  #
125
134
  # To omit the instance writer method, pass <tt>instance_writer: false</tt>.
126
135
  # To omit the instance reader method, pass <tt>instance_reader: false</tt>.
@@ -9,6 +9,8 @@ module ActiveSupport
9
9
  # Options are provided for phone numbers, currency, percentage,
10
10
  # precision, positional notation, file size and pretty printing.
11
11
  #
12
+ # This method is aliased to <tt>to_fs</tt>.
13
+ #
12
14
  # ==== Options
13
15
  #
14
16
  # For details on which formats use which options, see ActiveSupport::NumberHelper
@@ -16,102 +18,102 @@ module ActiveSupport
16
18
  # ==== Examples
17
19
  #
18
20
  # Phone Numbers:
19
- # 5551234.to_s(:phone) # => "555-1234"
20
- # 1235551234.to_s(:phone) # => "123-555-1234"
21
- # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234"
22
- # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234"
23
- # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
24
- # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234"
25
- # 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
21
+ # 5551234.to_formatted_s(:phone) # => "555-1234"
22
+ # 1235551234.to_formatted_s(:phone) # => "123-555-1234"
23
+ # 1235551234.to_formatted_s(:phone, area_code: true) # => "(123) 555-1234"
24
+ # 1235551234.to_formatted_s(:phone, delimiter: ' ') # => "123 555 1234"
25
+ # 1235551234.to_formatted_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
26
+ # 1235551234.to_formatted_s(:phone, country_code: 1) # => "+1-123-555-1234"
27
+ # 1235551234.to_formatted_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
26
28
  # # => "+1.123.555.1234 x 1343"
27
29
  #
28
30
  # Currency:
29
- # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
30
- # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
31
- # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
32
- # 1234567890.506.to_s(:currency, round_mode: :down) # => "$1,234,567,890.50"
33
- # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €"
34
- # -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
31
+ # 1234567890.50.to_formatted_s(:currency) # => "$1,234,567,890.50"
32
+ # 1234567890.506.to_formatted_s(:currency) # => "$1,234,567,890.51"
33
+ # 1234567890.506.to_formatted_s(:currency, precision: 3) # => "$1,234,567,890.506"
34
+ # 1234567890.506.to_formatted_s(:currency, round_mode: :down) # => "$1,234,567,890.50"
35
+ # 1234567890.506.to_formatted_s(:currency, locale: :fr) # => "1 234 567 890,51 €"
36
+ # -1234567890.50.to_formatted_s(:currency, negative_format: '(%u%n)')
35
37
  # # => "($1,234,567,890.50)"
36
- # 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '')
38
+ # 1234567890.50.to_formatted_s(:currency, unit: '&pound;', separator: ',', delimiter: '')
37
39
  # # => "&pound;1234567890,50"
38
- # 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
40
+ # 1234567890.50.to_formatted_s(:currency, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
39
41
  # # => "1234567890,50 &pound;"
40
42
  #
41
43
  # Percentage:
42
- # 100.to_s(:percentage) # => "100.000%"
43
- # 100.to_s(:percentage, precision: 0) # => "100%"
44
- # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
45
- # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%"
46
- # 302.24398923423.to_s(:percentage, round_mode: :down) # => "302.243%"
47
- # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%"
48
- # 100.to_s(:percentage, format: '%n %') # => "100.000 %"
44
+ # 100.to_formatted_s(:percentage) # => "100.000%"
45
+ # 100.to_formatted_s(:percentage, precision: 0) # => "100%"
46
+ # 1000.to_formatted_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
47
+ # 302.24398923423.to_formatted_s(:percentage, precision: 5) # => "302.24399%"
48
+ # 302.24398923423.to_formatted_s(:percentage, round_mode: :down) # => "302.243%"
49
+ # 1000.to_formatted_s(:percentage, locale: :fr) # => "1 000,000%"
50
+ # 100.to_formatted_s(:percentage, format: '%n %') # => "100.000 %"
49
51
  #
50
52
  # Delimited:
51
- # 12345678.to_s(:delimited) # => "12,345,678"
52
- # 12345678.05.to_s(:delimited) # => "12,345,678.05"
53
- # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
54
- # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
55
- # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
56
- # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
57
- # 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
53
+ # 12345678.to_formatted_s(:delimited) # => "12,345,678"
54
+ # 12345678.05.to_formatted_s(:delimited) # => "12,345,678.05"
55
+ # 12345678.to_formatted_s(:delimited, delimiter: '.') # => "12.345.678"
56
+ # 12345678.to_formatted_s(:delimited, delimiter: ',') # => "12,345,678"
57
+ # 12345678.05.to_formatted_s(:delimited, separator: ' ') # => "12,345,678 05"
58
+ # 12345678.05.to_formatted_s(:delimited, locale: :fr) # => "12 345 678,05"
59
+ # 98765432.98.to_formatted_s(:delimited, delimiter: ' ', separator: ',')
58
60
  # # => "98 765 432,98"
59
61
  #
60
62
  # Rounded:
61
- # 111.2345.to_s(:rounded) # => "111.235"
62
- # 111.2345.to_s(:rounded, precision: 2) # => "111.23"
63
- # 111.2345.to_s(:rounded, precision: 2, round_mode: :up) # => "111.24"
64
- # 13.to_s(:rounded, precision: 5) # => "13.00000"
65
- # 389.32314.to_s(:rounded, precision: 0) # => "389"
66
- # 111.2345.to_s(:rounded, significant: true) # => "111"
67
- # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
68
- # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
69
- # 111.234.to_s(:rounded, locale: :fr) # => "111,234"
70
- # 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
63
+ # 111.2345.to_formatted_s(:rounded) # => "111.235"
64
+ # 111.2345.to_formatted_s(:rounded, precision: 2) # => "111.23"
65
+ # 111.2345.to_formatted_s(:rounded, precision: 2, round_mode: :up) # => "111.24"
66
+ # 13.to_formatted_s(:rounded, precision: 5) # => "13.00000"
67
+ # 389.32314.to_formatted_s(:rounded, precision: 0) # => "389"
68
+ # 111.2345.to_formatted_s(:rounded, significant: true) # => "111"
69
+ # 111.2345.to_formatted_s(:rounded, precision: 1, significant: true) # => "100"
70
+ # 13.to_formatted_s(:rounded, precision: 5, significant: true) # => "13.000"
71
+ # 111.234.to_formatted_s(:rounded, locale: :fr) # => "111,234"
72
+ # 13.to_formatted_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
71
73
  # # => "13"
72
- # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
73
- # 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
74
+ # 389.32314.to_formatted_s(:rounded, precision: 4, significant: true) # => "389.3"
75
+ # 1111.2345.to_formatted_s(:rounded, precision: 2, separator: ',', delimiter: '.')
74
76
  # # => "1.111,23"
75
77
  #
76
78
  # Human-friendly size in Bytes:
77
- # 123.to_s(:human_size) # => "123 Bytes"
78
- # 1234.to_s(:human_size) # => "1.21 KB"
79
- # 12345.to_s(:human_size) # => "12.1 KB"
80
- # 1234567.to_s(:human_size) # => "1.18 MB"
81
- # 1234567890.to_s(:human_size) # => "1.15 GB"
82
- # 1234567890123.to_s(:human_size) # => "1.12 TB"
83
- # 1234567890123456.to_s(:human_size) # => "1.1 PB"
84
- # 1234567890123456789.to_s(:human_size) # => "1.07 EB"
85
- # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB"
86
- # 1234567.to_s(:human_size, precision: 2, round_mode: :up) # => "1.3 MB"
87
- # 483989.to_s(:human_size, precision: 2) # => "470 KB"
88
- # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
89
- # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
90
- # 524288000.to_s(:human_size, precision: 5) # => "500 MB"
79
+ # 123.to_formatted_s(:human_size) # => "123 Bytes"
80
+ # 1234.to_formatted_s(:human_size) # => "1.21 KB"
81
+ # 12345.to_formatted_s(:human_size) # => "12.1 KB"
82
+ # 1234567.to_formatted_s(:human_size) # => "1.18 MB"
83
+ # 1234567890.to_formatted_s(:human_size) # => "1.15 GB"
84
+ # 1234567890123.to_formatted_s(:human_size) # => "1.12 TB"
85
+ # 1234567890123456.to_formatted_s(:human_size) # => "1.1 PB"
86
+ # 1234567890123456789.to_formatted_s(:human_size) # => "1.07 EB"
87
+ # 1234567.to_formatted_s(:human_size, precision: 2) # => "1.2 MB"
88
+ # 1234567.to_formatted_s(:human_size, precision: 2, round_mode: :up) # => "1.3 MB"
89
+ # 483989.to_formatted_s(:human_size, precision: 2) # => "470 KB"
90
+ # 1234567.to_formatted_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
91
+ # 1234567890123.to_formatted_s(:human_size, precision: 5) # => "1.1228 TB"
92
+ # 524288000.to_formatted_s(:human_size, precision: 5) # => "500 MB"
91
93
  #
92
94
  # Human-friendly format:
93
- # 123.to_s(:human) # => "123"
94
- # 1234.to_s(:human) # => "1.23 Thousand"
95
- # 12345.to_s(:human) # => "12.3 Thousand"
96
- # 1234567.to_s(:human) # => "1.23 Million"
97
- # 1234567890.to_s(:human) # => "1.23 Billion"
98
- # 1234567890123.to_s(:human) # => "1.23 Trillion"
99
- # 1234567890123456.to_s(:human) # => "1.23 Quadrillion"
100
- # 1234567890123456789.to_s(:human) # => "1230 Quadrillion"
101
- # 489939.to_s(:human, precision: 2) # => "490 Thousand"
102
- # 489939.to_s(:human, precision: 2, round_mode: :down) # => "480 Thousand"
103
- # 489939.to_s(:human, precision: 4) # => "489.9 Thousand"
104
- # 1234567.to_s(:human, precision: 4,
105
- # significant: false) # => "1.2346 Million"
106
- # 1234567.to_s(:human, precision: 1,
95
+ # 123.to_formatted_s(:human) # => "123"
96
+ # 1234.to_formatted_s(:human) # => "1.23 Thousand"
97
+ # 12345.to_formatted_s(:human) # => "12.3 Thousand"
98
+ # 1234567.to_formatted_s(:human) # => "1.23 Million"
99
+ # 1234567890.to_formatted_s(:human) # => "1.23 Billion"
100
+ # 1234567890123.to_formatted_s(:human) # => "1.23 Trillion"
101
+ # 1234567890123456.to_formatted_s(:human) # => "1.23 Quadrillion"
102
+ # 1234567890123456789.to_formatted_s(:human) # => "1230 Quadrillion"
103
+ # 489939.to_formatted_s(:human, precision: 2) # => "490 Thousand"
104
+ # 489939.to_formatted_s(:human, precision: 2, round_mode: :down) # => "480 Thousand"
105
+ # 489939.to_formatted_s(:human, precision: 4) # => "489.9 Thousand"
106
+ # 1234567.to_formatted_s(:human, precision: 4,
107
+ # significant: false) # => "1.2346 Million"
108
+ # 1234567.to_formatted_s(:human, precision: 1,
107
109
  # separator: ',',
108
- # significant: false) # => "1,2 Million"
109
- def to_s(format = nil, options = nil)
110
- return super() if format.nil?
110
+ # significant: false) # => "1,2 Million"
111
+ def to_formatted_s(format = nil, options = nil)
112
+ return to_s if format.nil?
111
113
 
112
114
  case format
113
115
  when Integer, String
114
- super(format)
116
+ to_s(format)
115
117
  when :phone
116
118
  ActiveSupport::NumberHelper.number_to_phone(self, options || {})
117
119
  when :currency
@@ -127,11 +129,12 @@ module ActiveSupport
127
129
  when :human_size
128
130
  ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
129
131
  when Symbol
130
- super()
132
+ to_s
131
133
  else
132
- super(format)
134
+ to_s(format)
133
135
  end
134
136
  end
137
+ alias_method :to_fs, :to_formatted_s
135
138
  end
136
139
  end
137
140
 
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module DeprecatedNumericWithFormat # :nodoc:
5
+ def to_s(format = nil, options = nil)
6
+ return super() if format.nil?
7
+
8
+ case format
9
+ when Integer, String
10
+ super(format)
11
+ when :phone
12
+ ActiveSupport::Deprecation.warn(
13
+ "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_formatted_s(#{format.inspect}) instead."
14
+ )
15
+ ActiveSupport::NumberHelper.number_to_phone(self, options || {})
16
+ when :currency
17
+ ActiveSupport::Deprecation.warn(
18
+ "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_formatted_s(#{format.inspect}) instead."
19
+ )
20
+ ActiveSupport::NumberHelper.number_to_currency(self, options || {})
21
+ when :percentage
22
+ ActiveSupport::Deprecation.warn(
23
+ "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_formatted_s(#{format.inspect}) instead."
24
+ )
25
+ ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
26
+ when :delimited
27
+ ActiveSupport::Deprecation.warn(
28
+ "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_formatted_s(#{format.inspect}) instead."
29
+ )
30
+ ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
31
+ when :rounded
32
+ ActiveSupport::Deprecation.warn(
33
+ "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_formatted_s(#{format.inspect}) instead."
34
+ )
35
+ ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
36
+ when :human
37
+ ActiveSupport::Deprecation.warn(
38
+ "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_formatted_s(#{format.inspect}) instead."
39
+ )
40
+ ActiveSupport::NumberHelper.number_to_human(self, options || {})
41
+ when :human_size
42
+ ActiveSupport::Deprecation.warn(
43
+ "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_formatted_s(#{format.inspect}) instead."
44
+ )
45
+ ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
46
+ when Symbol
47
+ ActiveSupport::Deprecation.warn(
48
+ "#{self.class}#to_s(#{format.inspect}) is deprecated. Please use #{self.class}#to_formatted_s(#{format.inspect}) instead."
49
+ )
50
+ super()
51
+ else
52
+ super(format)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ Integer.prepend ActiveSupport::DeprecatedNumericWithFormat
59
+ Float.prepend ActiveSupport::DeprecatedNumericWithFormat
60
+ BigDecimal.prepend ActiveSupport::DeprecatedNumericWithFormat
@@ -3,3 +3,4 @@
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"]
@@ -75,8 +75,27 @@ class Object
75
75
  # end
76
76
  # end
77
77
  #
78
+ # When the block argument is omitted, the decorated Object instance is returned:
79
+ #
80
+ # module MyStyledHelpers
81
+ # def styled
82
+ # with_options style: "color: red;"
83
+ # end
84
+ # end
85
+ #
86
+ # # styled.link_to "I'm red", "/"
87
+ # # #=> <a href="/" style="color: red;">I'm red</a>
88
+ #
89
+ # # styled.button_tag "I'm red too!"
90
+ # # #=> <button style="color: red;">I'm red too!</button>
91
+ #
78
92
  def with_options(options, &block)
79
93
  option_merger = ActiveSupport::OptionMerger.new(self, options)
80
- block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
94
+
95
+ if block
96
+ block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
97
+ else
98
+ option_merger
99
+ end
81
100
  end
82
101
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pathname
4
+ # Returns the receiver if the named file exists otherwise returns +nil+.
5
+ # <tt>pathname.existence</tt> is equivalent to
6
+ #
7
+ # pathname.exists? ? pathname : nil
8
+ #
9
+ # For example, something like
10
+ #
11
+ # content = pathname.read if pathname.exist?
12
+ #
13
+ # becomes
14
+ #
15
+ # content = pathname.existence&.read
16
+ #
17
+ # @return [Pathname]
18
+ def existence
19
+ self if exist?
20
+ end
21
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/pathname/existence"
@@ -7,34 +7,34 @@ module ActiveSupport
7
7
  case start
8
8
  when String then "BETWEEN '#{start}' AND '#{stop}'"
9
9
  else
10
- "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'"
10
+ "BETWEEN '#{start.to_formatted_s(:db)}' AND '#{stop.to_formatted_s(:db)}'"
11
11
  end
12
12
  end
13
13
  }
14
14
 
15
15
  # Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
16
16
  #
17
+ # This method is aliased to <tt>to_fs</tt>.
18
+ #
17
19
  # range = (1..100) # => 1..100
18
20
  #
19
21
  # range.to_s # => "1..100"
20
- # range.to_s(:db) # => "BETWEEN '1' AND '100'"
22
+ # range.to_formatted_s(:db) # => "BETWEEN '1' AND '100'"
21
23
  #
22
24
  # == Adding your own range formats to to_s
23
25
  # You can add your own formats to the Range::RANGE_FORMATS hash.
24
26
  # Use the format name as the hash key and a Proc instance.
25
27
  #
26
28
  # # 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
+ # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_formatted_s(:db)} and #{stop.to_formatted_s(:db)}" }
30
+ def to_formatted_s(format = :default)
29
31
  if formatter = RANGE_FORMATS[format]
30
32
  formatter.call(first, last)
31
33
  else
32
- super()
34
+ to_s
33
35
  end
34
36
  end
35
-
36
- alias_method :to_default_s, :to_s
37
- alias_method :to_formatted_s, :to_s
37
+ alias_method :to_fs, :to_formatted_s
38
38
  end
39
39
  end
40
40
 
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveSupport
4
+ module DeprecatedRangeWithFormat # :nodoc:
5
+ NOT_SET = Object.new # :nodoc:
6
+ def to_s(format = NOT_SET)
7
+ if formatter = RangeWithFormat::RANGE_FORMATS[format]
8
+ ActiveSupport::Deprecation.warn(
9
+ "Range#to_s(#{format.inspect}) is deprecated. Please use Range#to_formatted_s(#{format.inspect}) instead."
10
+ )
11
+ formatter.call(first, last)
12
+ elsif format == NOT_SET
13
+ super()
14
+ else
15
+ ActiveSupport::Deprecation.warn(
16
+ "Range#to_s(#{format.inspect}) is deprecated. Please use Range#to_formatted_s(#{format.inspect}) instead."
17
+ )
18
+ super()
19
+ end
20
+ end
21
+ alias_method :to_default_s, :to_s
22
+ deprecate :to_default_s
23
+ end
24
+ end
25
+
26
+ Range.prepend(ActiveSupport::DeprecatedRangeWithFormat)