activesupport 5.2.5 → 6.0.0

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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +327 -423
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +3 -2
  5. data/lib/active_support/actionable_error.rb +48 -0
  6. data/lib/active_support/backtrace_cleaner.rb +28 -1
  7. data/lib/active_support/cache/file_store.rb +22 -22
  8. data/lib/active_support/cache/mem_cache_store.rb +16 -2
  9. data/lib/active_support/cache/memory_store.rb +7 -2
  10. data/lib/active_support/cache/null_store.rb +5 -0
  11. data/lib/active_support/cache/redis_cache_store.rb +47 -25
  12. data/lib/active_support/cache.rb +45 -23
  13. data/lib/active_support/callbacks.rb +16 -5
  14. data/lib/active_support/concern.rb +24 -1
  15. data/lib/active_support/configurable.rb +7 -11
  16. data/lib/active_support/core_ext/array/access.rb +18 -6
  17. data/lib/active_support/core_ext/array/extract.rb +21 -0
  18. data/lib/active_support/core_ext/array/prepend_and_append.rb +2 -6
  19. data/lib/active_support/core_ext/array.rb +1 -1
  20. data/lib/active_support/core_ext/class/attribute.rb +11 -16
  21. data/lib/active_support/core_ext/class/subclasses.rb +1 -1
  22. data/lib/active_support/core_ext/date/calculations.rb +6 -5
  23. data/lib/active_support/core_ext/date_and_time/calculations.rb +24 -47
  24. data/lib/active_support/core_ext/date_time/calculations.rb +1 -1
  25. data/lib/active_support/core_ext/enumerable.rb +97 -73
  26. data/lib/active_support/core_ext/hash/compact.rb +2 -26
  27. data/lib/active_support/core_ext/hash/deep_transform_values.rb +46 -0
  28. data/lib/active_support/core_ext/hash/except.rb +1 -1
  29. data/lib/active_support/core_ext/hash/keys.rb +0 -29
  30. data/lib/active_support/core_ext/hash/slice.rb +3 -25
  31. data/lib/active_support/core_ext/hash/transform_values.rb +2 -29
  32. data/lib/active_support/core_ext/hash.rb +1 -2
  33. data/lib/active_support/core_ext/integer/multiple.rb +1 -1
  34. data/lib/active_support/core_ext/kernel.rb +0 -1
  35. data/lib/active_support/core_ext/load_error.rb +1 -1
  36. data/lib/active_support/core_ext/module/attribute_accessors.rb +7 -10
  37. data/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +13 -19
  38. data/lib/active_support/core_ext/module/delegation.rb +33 -7
  39. data/lib/active_support/core_ext/module/introspection.rb +37 -13
  40. data/lib/active_support/core_ext/module/reachable.rb +1 -6
  41. data/lib/active_support/core_ext/module/redefine_method.rb +8 -17
  42. data/lib/active_support/core_ext/module.rb +0 -1
  43. data/lib/active_support/core_ext/numeric/conversions.rb +124 -128
  44. data/lib/active_support/core_ext/numeric/inquiry.rb +2 -25
  45. data/lib/active_support/core_ext/numeric.rb +0 -1
  46. data/lib/active_support/core_ext/object/blank.rb +1 -2
  47. data/lib/active_support/core_ext/object/duplicable.rb +7 -114
  48. data/lib/active_support/core_ext/object/json.rb +1 -0
  49. data/lib/active_support/core_ext/object/try.rb +15 -7
  50. data/lib/active_support/core_ext/object/with_options.rb +1 -1
  51. data/lib/active_support/core_ext/range/compare_range.rb +22 -13
  52. data/lib/active_support/core_ext/range/conversions.rb +31 -29
  53. data/lib/active_support/core_ext/range/include_range.rb +6 -0
  54. data/lib/active_support/core_ext/regexp.rb +0 -4
  55. data/lib/active_support/core_ext/securerandom.rb +23 -3
  56. data/lib/active_support/core_ext/string/access.rb +8 -0
  57. data/lib/active_support/core_ext/string/filters.rb +42 -1
  58. data/lib/active_support/core_ext/string/inflections.rb +7 -2
  59. data/lib/active_support/core_ext/string/multibyte.rb +4 -3
  60. data/lib/active_support/core_ext/string/output_safety.rb +61 -5
  61. data/lib/active_support/core_ext/string/strip.rb +3 -1
  62. data/lib/active_support/core_ext/time/calculations.rb +31 -2
  63. data/lib/active_support/core_ext/uri.rb +1 -0
  64. data/lib/active_support/current_attributes.rb +8 -0
  65. data/lib/active_support/dependencies/zeitwerk_integration.rb +110 -0
  66. data/lib/active_support/dependencies.rb +69 -16
  67. data/lib/active_support/deprecation/behaviors.rb +1 -1
  68. data/lib/active_support/deprecation/method_wrappers.rb +8 -20
  69. data/lib/active_support/deprecation/proxy_wrappers.rb +24 -5
  70. data/lib/active_support/deprecation.rb +1 -1
  71. data/lib/active_support/descendants_tracker.rb +56 -9
  72. data/lib/active_support/duration/iso8601_parser.rb +2 -3
  73. data/lib/active_support/duration/iso8601_serializer.rb +3 -4
  74. data/lib/active_support/duration.rb +12 -15
  75. data/lib/active_support/encrypted_configuration.rb +0 -4
  76. data/lib/active_support/encrypted_file.rb +2 -1
  77. data/lib/active_support/evented_file_update_checker.rb +39 -9
  78. data/lib/active_support/execution_wrapper.rb +1 -0
  79. data/lib/active_support/gem_version.rb +3 -3
  80. data/lib/active_support/hash_with_indifferent_access.rb +22 -18
  81. data/lib/active_support/i18n.rb +1 -0
  82. data/lib/active_support/i18n_railtie.rb +9 -1
  83. data/lib/active_support/inflector/inflections.rb +1 -4
  84. data/lib/active_support/inflector/methods.rb +15 -27
  85. data/lib/active_support/inflector/transliterate.rb +47 -18
  86. data/lib/active_support/json/decoding.rb +23 -23
  87. data/lib/active_support/json/encoding.rb +6 -2
  88. data/lib/active_support/key_generator.rb +0 -32
  89. data/lib/active_support/lazy_load_hooks.rb +5 -1
  90. data/lib/active_support/locale/en.rb +31 -0
  91. data/lib/active_support/log_subscriber.rb +31 -8
  92. data/lib/active_support/logger.rb +0 -15
  93. data/lib/active_support/logger_silence.rb +28 -12
  94. data/lib/active_support/logger_thread_safe_level.rb +26 -4
  95. data/lib/active_support/message_encryptor.rb +3 -5
  96. data/lib/active_support/message_verifier.rb +3 -3
  97. data/lib/active_support/multibyte/chars.rb +29 -48
  98. data/lib/active_support/multibyte/unicode.rb +44 -281
  99. data/lib/active_support/notifications/fanout.rb +98 -13
  100. data/lib/active_support/notifications/instrumenter.rb +79 -8
  101. data/lib/active_support/notifications.rb +41 -4
  102. data/lib/active_support/number_helper/number_to_currency_converter.rb +2 -2
  103. data/lib/active_support/number_helper/number_to_delimited_converter.rb +3 -1
  104. data/lib/active_support/number_helper/number_to_human_converter.rb +3 -1
  105. data/lib/active_support/number_helper/number_to_human_size_converter.rb +3 -1
  106. data/lib/active_support/number_helper/number_to_percentage_converter.rb +3 -1
  107. data/lib/active_support/number_helper/number_to_phone_converter.rb +2 -0
  108. data/lib/active_support/number_helper/number_to_rounded_converter.rb +5 -3
  109. data/lib/active_support/number_helper.rb +7 -0
  110. data/lib/active_support/ordered_options.rb +1 -1
  111. data/lib/active_support/parameter_filter.rb +129 -0
  112. data/lib/active_support/rails.rb +0 -6
  113. data/lib/active_support/reloader.rb +4 -5
  114. data/lib/active_support/security_utils.rb +1 -1
  115. data/lib/active_support/subscriber.rb +65 -26
  116. data/lib/active_support/tagged_logging.rb +13 -4
  117. data/lib/active_support/test_case.rb +91 -0
  118. data/lib/active_support/testing/assertions.rb +15 -1
  119. data/lib/active_support/testing/deprecation.rb +0 -1
  120. data/lib/active_support/testing/file_fixtures.rb +2 -0
  121. data/lib/active_support/testing/isolation.rb +2 -2
  122. data/lib/active_support/testing/method_call_assertions.rb +28 -1
  123. data/lib/active_support/testing/parallelization.rb +128 -0
  124. data/lib/active_support/testing/stream.rb +1 -1
  125. data/lib/active_support/testing/time_helpers.rb +7 -7
  126. data/lib/active_support/time_with_zone.rb +15 -5
  127. data/lib/active_support/values/time_zone.rb +12 -7
  128. data/lib/active_support/xml_mini/jdom.rb +2 -2
  129. data/lib/active_support/xml_mini/libxml.rb +2 -2
  130. data/lib/active_support/xml_mini/libxmlsax.rb +4 -4
  131. data/lib/active_support/xml_mini/nokogiri.rb +2 -2
  132. data/lib/active_support/xml_mini/nokogirisax.rb +3 -3
  133. data/lib/active_support/xml_mini/rexml.rb +2 -2
  134. data/lib/active_support/xml_mini.rb +2 -9
  135. data/lib/active_support.rb +2 -1
  136. metadata +34 -9
  137. data/lib/active_support/core_ext/kernel/agnostics.rb +0 -13
  138. data/lib/active_support/values/unicode_tables.dat +0 -0
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "mutex_m"
4
4
  require "concurrent/map"
5
+ require "set"
5
6
 
6
7
  module ActiveSupport
7
8
  module Notifications
@@ -13,7 +14,8 @@ module ActiveSupport
13
14
  include Mutex_m
14
15
 
15
16
  def initialize
16
- @subscribers = []
17
+ @string_subscribers = Hash.new { |h, k| h[k] = [] }
18
+ @other_subscribers = []
17
19
  @listeners_for = Concurrent::Map.new
18
20
  super
19
21
  end
@@ -21,8 +23,13 @@ module ActiveSupport
21
23
  def subscribe(pattern = nil, callable = nil, &block)
22
24
  subscriber = Subscribers.new(pattern, callable || block)
23
25
  synchronize do
24
- @subscribers << subscriber
25
- @listeners_for.clear
26
+ if String === pattern
27
+ @string_subscribers[pattern] << subscriber
28
+ @listeners_for.delete(pattern)
29
+ else
30
+ @other_subscribers << subscriber
31
+ @listeners_for.clear
32
+ end
26
33
  end
27
34
  subscriber
28
35
  end
@@ -31,12 +38,19 @@ module ActiveSupport
31
38
  synchronize do
32
39
  case subscriber_or_name
33
40
  when String
34
- @subscribers.reject! { |s| s.matches?(subscriber_or_name) }
41
+ @string_subscribers[subscriber_or_name].clear
42
+ @listeners_for.delete(subscriber_or_name)
43
+ @other_subscribers.each { |sub| sub.unsubscribe!(subscriber_or_name) }
35
44
  else
36
- @subscribers.delete(subscriber_or_name)
45
+ pattern = subscriber_or_name.try(:pattern)
46
+ if String === pattern
47
+ @string_subscribers[pattern].delete(subscriber_or_name)
48
+ @listeners_for.delete(pattern)
49
+ else
50
+ @other_subscribers.delete(subscriber_or_name)
51
+ @listeners_for.clear
52
+ end
37
53
  end
38
-
39
- @listeners_for.clear
40
54
  end
41
55
  end
42
56
 
@@ -56,7 +70,8 @@ module ActiveSupport
56
70
  # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
57
71
  @listeners_for[name] || synchronize do
58
72
  # use synchronisation when accessing @subscribers
59
- @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
73
+ @listeners_for[name] ||=
74
+ @string_subscribers[name] + @other_subscribers.select { |s| s.subscribed_to?(name) }
60
75
  end
61
76
  end
62
77
 
@@ -70,12 +85,29 @@ module ActiveSupport
70
85
 
71
86
  module Subscribers # :nodoc:
72
87
  def self.new(pattern, listener)
88
+ subscriber_class = Timed
89
+
73
90
  if listener.respond_to?(:start) && listener.respond_to?(:finish)
74
- subscriber = Evented.new pattern, listener
91
+ subscriber_class = Evented
75
92
  else
76
- subscriber = Timed.new pattern, listener
93
+ # Doing all this to detect a block like `proc { |x| }` vs
94
+ # `proc { |*x| }` or `proc { |**x| }`
95
+ if listener.respond_to?(:parameters)
96
+ params = listener.parameters
97
+ if params.length == 1 && params.first.first == :opt
98
+ subscriber_class = EventObject
99
+ end
100
+ end
77
101
  end
78
102
 
103
+ wrap_all pattern, subscriber_class.new(pattern, listener)
104
+ end
105
+
106
+ def self.event_object_subscriber(pattern, block)
107
+ wrap_all pattern, EventObject.new(pattern, block)
108
+ end
109
+
110
+ def self.wrap_all(pattern, subscriber)
79
111
  unless pattern
80
112
  AllMessages.new(subscriber)
81
113
  else
@@ -83,9 +115,33 @@ module ActiveSupport
83
115
  end
84
116
  end
85
117
 
118
+ class Matcher #:nodoc:
119
+ attr_reader :pattern, :exclusions
120
+
121
+ def self.wrap(pattern)
122
+ return pattern if String === pattern
123
+ new(pattern)
124
+ end
125
+
126
+ def initialize(pattern)
127
+ @pattern = pattern
128
+ @exclusions = Set.new
129
+ end
130
+
131
+ def unsubscribe!(name)
132
+ exclusions << -name if pattern === name
133
+ end
134
+
135
+ def ===(name)
136
+ pattern === name && !exclusions.include?(name)
137
+ end
138
+ end
139
+
86
140
  class Evented #:nodoc:
141
+ attr_reader :pattern
142
+
87
143
  def initialize(pattern, delegate)
88
- @pattern = pattern
144
+ @pattern = Matcher.wrap(pattern)
89
145
  @delegate = delegate
90
146
  @can_publish = delegate.respond_to?(:publish)
91
147
  end
@@ -105,11 +161,15 @@ module ActiveSupport
105
161
  end
106
162
 
107
163
  def subscribed_to?(name)
108
- @pattern === name
164
+ pattern === name
109
165
  end
110
166
 
111
167
  def matches?(name)
112
- @pattern && @pattern === name
168
+ pattern && pattern === name
169
+ end
170
+
171
+ def unsubscribe!(name)
172
+ pattern.unsubscribe!(name)
113
173
  end
114
174
  end
115
175
 
@@ -130,6 +190,27 @@ module ActiveSupport
130
190
  end
131
191
  end
132
192
 
193
+ class EventObject < Evented
194
+ def start(name, id, payload)
195
+ stack = Thread.current[:_event_stack] ||= []
196
+ event = build_event name, id, payload
197
+ event.start!
198
+ stack.push event
199
+ end
200
+
201
+ def finish(name, id, payload)
202
+ stack = Thread.current[:_event_stack]
203
+ event = stack.pop
204
+ event.finish!
205
+ @delegate.call event
206
+ end
207
+
208
+ private
209
+ def build_event(name, id, payload)
210
+ ActiveSupport::Notifications::Event.new name, nil, nil, id, payload
211
+ end
212
+ end
213
+
133
214
  class AllMessages # :nodoc:
134
215
  def initialize(delegate)
135
216
  @delegate = delegate
@@ -151,6 +232,10 @@ module ActiveSupport
151
232
  true
152
233
  end
153
234
 
235
+ def unsubscribe!(*)
236
+ false
237
+ end
238
+
154
239
  alias :matches? :===
155
240
  end
156
241
  end
@@ -13,14 +13,15 @@ module ActiveSupport
13
13
  @notifier = notifier
14
14
  end
15
15
 
16
- # Instrument the given block by measuring the time taken to execute it
17
- # and publish it. Notice that events get sent even if an error occurs
18
- # in the passed-in block.
16
+ # Given a block, instrument it by measuring the time taken to execute
17
+ # and publish it. Without a block, simply send a message via the
18
+ # notifier. Notice that events get sent even if an error occurs in the
19
+ # passed-in block.
19
20
  def instrument(name, payload = {})
20
21
  # some of the listeners might have state
21
22
  listeners_state = start name, payload
22
23
  begin
23
- yield payload
24
+ yield payload if block_given?
24
25
  rescue Exception => e
25
26
  payload[:exception] = [e.class.name, e.message]
26
27
  payload[:exception_object] = e
@@ -52,8 +53,13 @@ module ActiveSupport
52
53
  end
53
54
 
54
55
  class Event
55
- attr_reader :name, :time, :transaction_id, :payload, :children
56
- attr_accessor :end
56
+ attr_reader :name, :time, :end, :transaction_id, :payload, :children
57
+
58
+ def self.clock_gettime_supported? # :nodoc:
59
+ defined?(Process::CLOCK_PROCESS_CPUTIME_ID) &&
60
+ !Gem.win_platform?
61
+ end
62
+ private_class_method :clock_gettime_supported?
57
63
 
58
64
  def initialize(name, start, ending, transaction_id, payload)
59
65
  @name = name
@@ -62,7 +68,47 @@ module ActiveSupport
62
68
  @transaction_id = transaction_id
63
69
  @end = ending
64
70
  @children = []
65
- @duration = nil
71
+ @cpu_time_start = 0
72
+ @cpu_time_finish = 0
73
+ @allocation_count_start = 0
74
+ @allocation_count_finish = 0
75
+ end
76
+
77
+ # Record information at the time this event starts
78
+ def start!
79
+ @time = now
80
+ @cpu_time_start = now_cpu
81
+ @allocation_count_start = now_allocations
82
+ end
83
+
84
+ # Record information at the time this event finishes
85
+ def finish!
86
+ @cpu_time_finish = now_cpu
87
+ @end = now
88
+ @allocation_count_finish = now_allocations
89
+ end
90
+
91
+ def end=(ending)
92
+ ActiveSupport::Deprecation.deprecation_warning(:end=, :finish!)
93
+ @end = ending
94
+ end
95
+
96
+ # Returns the CPU time (in milliseconds) passed since the call to
97
+ # +start!+ and the call to +finish!+
98
+ def cpu_time
99
+ (@cpu_time_finish - @cpu_time_start) * 1000
100
+ end
101
+
102
+ # Returns the idle time time (in milliseconds) passed since the call to
103
+ # +start!+ and the call to +finish!+
104
+ def idle_time
105
+ duration - cpu_time
106
+ end
107
+
108
+ # Returns the number of allocations made since the call to +start!+ and
109
+ # the call to +finish!+
110
+ def allocations
111
+ @allocation_count_finish - @allocation_count_start
66
112
  end
67
113
 
68
114
  # Returns the difference in milliseconds between when the execution of the
@@ -78,7 +124,7 @@ module ActiveSupport
78
124
  #
79
125
  # @event.duration # => 1000.138
80
126
  def duration
81
- @duration ||= 1000.0 * (self.end - time)
127
+ 1000.0 * (self.end - time)
82
128
  end
83
129
 
84
130
  def <<(event)
@@ -88,6 +134,31 @@ module ActiveSupport
88
134
  def parent_of?(event)
89
135
  @children.include? event
90
136
  end
137
+
138
+ private
139
+ def now
140
+ Concurrent.monotonic_time
141
+ end
142
+
143
+ if clock_gettime_supported?
144
+ def now_cpu
145
+ Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID)
146
+ end
147
+ else
148
+ def now_cpu
149
+ 0
150
+ end
151
+ end
152
+
153
+ if defined?(JRUBY_VERSION)
154
+ def now_allocations
155
+ 0
156
+ end
157
+ else
158
+ def now_allocations
159
+ GC.stat :total_allocated_objects
160
+ end
161
+ end
91
162
  end
92
163
  end
93
164
  end
@@ -34,7 +34,7 @@ module ActiveSupport
34
34
  # name # => String, name of the event (such as 'render' from above)
35
35
  # start # => Time, when the instrumented block started execution
36
36
  # finish # => Time, when the instrumented block ended execution
37
- # id # => String, unique ID for this notification
37
+ # id # => String, unique ID for the instrumenter that fired the event
38
38
  # payload # => Hash, the payload
39
39
  # end
40
40
  #
@@ -59,7 +59,7 @@ module ActiveSupport
59
59
  # event.payload # => { extra: :information }
60
60
  #
61
61
  # The block in the <tt>subscribe</tt> call gets the name of the event, start
62
- # timestamp, end timestamp, a string with a unique identifier for that event
62
+ # timestamp, end timestamp, a string with a unique identifier for that event's instrumenter
63
63
  # (something like "535801666f04d0298cd6"), and a hash with the payload, in
64
64
  # that order.
65
65
  #
@@ -67,9 +67,12 @@ module ActiveSupport
67
67
  # have a key <tt>:exception</tt> with an array of two elements as value: a string with
68
68
  # the name of the exception class, and the exception message.
69
69
  # The <tt>:exception_object</tt> key of the payload will have the exception
70
- # itself as the value.
70
+ # itself as the value:
71
71
  #
72
- # As the previous example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
72
+ # event.payload[:exception] # => ["ArgumentError", "Invalid value"]
73
+ # event.payload[:exception_object] # => #<ArgumentError: Invalid value>
74
+ #
75
+ # As the earlier example depicts, the class <tt>ActiveSupport::Notifications::Event</tt>
73
76
  # is able to take the arguments as they come and provide an object-oriented
74
77
  # interface to that data.
75
78
  #
@@ -150,6 +153,15 @@ module ActiveSupport
150
153
  #
151
154
  # ActiveSupport::Notifications.unsubscribe("render")
152
155
  #
156
+ # Subscribers using a regexp or other pattern-matching object will remain subscribed
157
+ # to all events that match their original pattern, unless those events match a string
158
+ # passed to `unsubscribe`:
159
+ #
160
+ # subscriber = ActiveSupport::Notifications.subscribe(/render/) { }
161
+ # ActiveSupport::Notifications.unsubscribe('render_template.action_view')
162
+ # subscriber.matches?('render_template.action_view') # => false
163
+ # subscriber.matches?('render_partial.action_view') # => true
164
+ #
153
165
  # == Default Queue
154
166
  #
155
167
  # Notifications ships with a queue implementation that consumes and publishes events
@@ -171,6 +183,31 @@ module ActiveSupport
171
183
  end
172
184
  end
173
185
 
186
+ # Subscribe to a given event name with the passed +block+.
187
+ #
188
+ # You can subscribe to events by passing a String to match exact event
189
+ # names, or by passing a Regexp to match all events that match a pattern.
190
+ #
191
+ # ActiveSupport::Notifications.subscribe(/render/) do |*args|
192
+ # @event = ActiveSupport::Notifications::Event.new(*args)
193
+ # end
194
+ #
195
+ # The +block+ will receive five parameters with information about the event:
196
+ #
197
+ # ActiveSupport::Notifications.subscribe('render') do |name, start, finish, id, payload|
198
+ # name # => String, name of the event (such as 'render' from above)
199
+ # start # => Time, when the instrumented block started execution
200
+ # finish # => Time, when the instrumented block ended execution
201
+ # id # => String, unique ID for the instrumenter that fired the event
202
+ # payload # => Hash, the payload
203
+ # end
204
+ #
205
+ # If the block passed to the method only takes one parameter,
206
+ # it will yield an event object to the block:
207
+ #
208
+ # ActiveSupport::Notifications.subscribe(/render/) do |event|
209
+ # @event = event
210
+ # end
174
211
  def subscribe(*args, &block)
175
212
  notifier.subscribe(*args, &block)
176
213
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "active_support/core_ext/numeric/inquiry"
3
+ require "active_support/number_helper/number_converter"
4
4
 
5
5
  module ActiveSupport
6
6
  module NumberHelper
@@ -17,7 +17,7 @@ module ActiveSupport
17
17
  end
18
18
 
19
19
  rounded_number = NumberToRoundedConverter.convert(number, options)
20
- format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, options[:unit])
20
+ format.gsub("%n", rounded_number).gsub("%u", options[:unit])
21
21
  end
22
22
 
23
23
  private
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToDelimitedConverter < NumberConverter #:nodoc:
@@ -14,7 +16,7 @@ module ActiveSupport
14
16
  private
15
17
 
16
18
  def parts
17
- left, right = number.to_s.split(".".freeze)
19
+ left, right = number.to_s.split(".")
18
20
  left.gsub!(delimiter_pattern) do |digit_to_delimit|
19
21
  "#{digit_to_delimit}#{options[:delimiter]}"
20
22
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToHumanConverter < NumberConverter # :nodoc:
@@ -25,7 +27,7 @@ module ActiveSupport
25
27
 
26
28
  rounded_number = NumberToRoundedConverter.convert(number, options)
27
29
  unit = determine_unit(units, exponent)
28
- format.gsub("%n".freeze, rounded_number).gsub("%u".freeze, unit).strip
30
+ format.gsub("%n", rounded_number).gsub("%u", unit).strip
29
31
  end
30
32
 
31
33
  private
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToHumanSizeConverter < NumberConverter #:nodoc:
@@ -22,7 +24,7 @@ module ActiveSupport
22
24
  human_size = number / (base**exponent)
23
25
  number_to_format = NumberToRoundedConverter.convert(human_size, options)
24
26
  end
25
- conversion_format.gsub("%n".freeze, number_to_format).gsub("%u".freeze, unit)
27
+ conversion_format.gsub("%n", number_to_format).gsub("%u", unit)
26
28
  end
27
29
 
28
30
  private
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToPercentageConverter < NumberConverter # :nodoc:
@@ -7,7 +9,7 @@ module ActiveSupport
7
9
 
8
10
  def convert
9
11
  rounded_number = NumberToRoundedConverter.convert(number, options)
10
- options[:format].gsub("%n".freeze, rounded_number)
12
+ options[:format].gsub("%n", rounded_number)
11
13
  end
12
14
  end
13
15
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToPhoneConverter < NumberConverter #:nodoc:
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/number_helper/number_converter"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  class NumberToRoundedConverter < NumberConverter # :nodoc:
@@ -20,9 +22,9 @@ module ActiveSupport
20
22
  formatted_string =
21
23
  if BigDecimal === rounded_number && rounded_number.finite?
22
24
  s = rounded_number.to_s("F")
23
- s << "0".freeze * precision
24
- a, b = s.split(".".freeze, 2)
25
- a << ".".freeze
25
+ s << "0" * precision
26
+ a, b = s.split(".", 2)
27
+ a << "."
26
28
  a << b[0, precision]
27
29
  else
28
30
  "%00.#{precision}f" % rounded_number
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_support/dependencies/autoload"
4
+
3
5
  module ActiveSupport
4
6
  module NumberHelper
5
7
  extend ActiveSupport::Autoload
@@ -85,6 +87,9 @@ module ActiveSupport
85
87
  # number given by <tt>:format</tt>). Accepts the same fields
86
88
  # than <tt>:format</tt>, except <tt>%n</tt> is here the
87
89
  # absolute value of the number.
90
+ # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
91
+ # insignificant zeros after the decimal separator (defaults to
92
+ # +false+).
88
93
  #
89
94
  # ==== Examples
90
95
  #
@@ -100,6 +105,8 @@ module ActiveSupport
100
105
  # # => "&pound;1234567890,50"
101
106
  # number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
102
107
  # # => "1234567890,50 &pound;"
108
+ # number_to_currency(1234567890.50, strip_insignificant_zeros: true)
109
+ # # => "$1,234,567,890.5"
103
110
  def number_to_currency(number, options = {})
104
111
  NumberToCurrencyConverter.convert(number, options)
105
112
  end
@@ -39,7 +39,7 @@ module ActiveSupport
39
39
  end
40
40
 
41
41
  def method_missing(name, *args)
42
- name_string = name.to_s.dup
42
+ name_string = name.to_s
43
43
  if name_string.chomp!("=")
44
44
  self[name_string] = args.first
45
45
  else
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/duplicable"
4
+ require "active_support/core_ext/array/extract"
5
+
6
+ module ActiveSupport
7
+ # +ParameterFilter+ allows you to specify keys for sensitive data from
8
+ # hash-like object and replace corresponding value. Filtering only certain
9
+ # sub-keys from a hash is possible by using the dot notation:
10
+ # 'credit_card.number'. If a proc is given, each key and value of a hash and
11
+ # all sub-hashes are passed to it, where the value or the key can be replaced
12
+ # using String#replace or similar methods.
13
+ #
14
+ # ActiveSupport::ParameterFilter.new([:password])
15
+ # => replaces the value to all keys matching /password/i with "[FILTERED]"
16
+ #
17
+ # ActiveSupport::ParameterFilter.new([:foo, "bar"])
18
+ # => replaces the value to all keys matching /foo|bar/i with "[FILTERED]"
19
+ #
20
+ # ActiveSupport::ParameterFilter.new(["credit_card.code"])
21
+ # => replaces { credit_card: {code: "xxxx"} } with "[FILTERED]", does not
22
+ # change { file: { code: "xxxx"} }
23
+ #
24
+ # ActiveSupport::ParameterFilter.new([-> (k, v) do
25
+ # v.reverse! if k =~ /secret/i
26
+ # end])
27
+ # => reverses the value to all keys matching /secret/i
28
+ class ParameterFilter
29
+ FILTERED = "[FILTERED]" # :nodoc:
30
+
31
+ # Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+.
32
+ # Other types of filters are treated as +String+ using +to_s+.
33
+ # For +Proc+ filters, key, value, and optional original hash is passed to block arguments.
34
+ #
35
+ # ==== Options
36
+ #
37
+ # * <tt>:mask</tt> - A replaced object when filtered. Defaults to +"[FILTERED]"+
38
+ def initialize(filters = [], mask: FILTERED)
39
+ @filters = filters
40
+ @mask = mask
41
+ end
42
+
43
+ # Mask value of +params+ if key matches one of filters.
44
+ def filter(params)
45
+ compiled_filter.call(params)
46
+ end
47
+
48
+ # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated.
49
+ def filter_param(key, value)
50
+ @filters.empty? ? value : compiled_filter.value_for_key(key, value)
51
+ end
52
+
53
+ private
54
+
55
+ def compiled_filter
56
+ @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask)
57
+ end
58
+
59
+ class CompiledFilter # :nodoc:
60
+ def self.compile(filters, mask:)
61
+ return lambda { |params| params.dup } if filters.empty?
62
+
63
+ strings, regexps, blocks = [], [], []
64
+
65
+ filters.each do |item|
66
+ case item
67
+ when Proc
68
+ blocks << item
69
+ when Regexp
70
+ regexps << item
71
+ else
72
+ strings << Regexp.escape(item.to_s)
73
+ end
74
+ end
75
+
76
+ deep_regexps = regexps.extract! { |r| r.to_s.include?("\\.") }
77
+ deep_strings = strings.extract! { |s| s.include?("\\.") }
78
+
79
+ regexps << Regexp.new(strings.join("|"), true) unless strings.empty?
80
+ deep_regexps << Regexp.new(deep_strings.join("|"), true) unless deep_strings.empty?
81
+
82
+ new regexps, deep_regexps, blocks, mask: mask
83
+ end
84
+
85
+ attr_reader :regexps, :deep_regexps, :blocks
86
+
87
+ def initialize(regexps, deep_regexps, blocks, mask:)
88
+ @regexps = regexps
89
+ @deep_regexps = deep_regexps.any? ? deep_regexps : nil
90
+ @blocks = blocks
91
+ @mask = mask
92
+ end
93
+
94
+ def call(params, parents = [], original_params = params)
95
+ filtered_params = params.class.new
96
+
97
+ params.each do |key, value|
98
+ filtered_params[key] = value_for_key(key, value, parents, original_params)
99
+ end
100
+
101
+ filtered_params
102
+ end
103
+
104
+ def value_for_key(key, value, parents = [], original_params = nil)
105
+ parents.push(key) if deep_regexps
106
+ if regexps.any? { |r| r.match?(key) }
107
+ value = @mask
108
+ elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) }
109
+ value = @mask
110
+ elsif value.is_a?(Hash)
111
+ value = call(value, parents, original_params)
112
+ elsif value.is_a?(Array)
113
+ # If we don't pop the current parent it will be duplicated as we
114
+ # process each array value.
115
+ parents.pop if deep_regexps
116
+ value = value.map { |v| value_for_key(key, v, parents, original_params) }
117
+ # Restore the parent stack after processing the array.
118
+ parents.push(key) if deep_regexps
119
+ elsif blocks.any?
120
+ key = key.dup if key.duplicable?
121
+ value = value.dup if value.duplicable?
122
+ blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
123
+ end
124
+ parents.pop if deep_regexps
125
+ value
126
+ end
127
+ end
128
+ end
129
+ end
@@ -27,9 +27,3 @@ require "active_support/core_ext/module/delegation"
27
27
 
28
28
  # Defines ActiveSupport::Deprecation.
29
29
  require "active_support/deprecation"
30
-
31
- # Defines Regexp#match?.
32
- #
33
- # This should be removed when Rails needs Ruby 2.4 or later, and the require
34
- # added where other Regexp extensions are being used (easy to grep).
35
- require "active_support/core_ext/regexp"