aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support')
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb46
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb6
-rw-r--r--activesupport/lib/active_support/dependencies.rb15
-rw-r--r--activesupport/lib/active_support/dependencies/zeitwerk_integration.rb2
-rw-r--r--activesupport/lib/active_support/descendants_tracker.rb6
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb10
-rw-r--r--activesupport/lib/active_support/logger_thread_safe_level.rb3
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb1
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb3
-rw-r--r--activesupport/lib/active_support/parameter_filter.rb7
-rw-r--r--activesupport/lib/active_support/secure_compare_rotator.rb52
11 files changed, 139 insertions, 12 deletions
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 4675c41936..728d332306 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -148,6 +148,41 @@ module Enumerable
map { |element| element[keys.first] }
end
end
+
+ # Returns a new +Array+ without the blank items.
+ # Uses Object#blank? for determining if an item is blank.
+ #
+ # [1, "", nil, 2, " ", [], {}, false, true].compact_blank
+ # # => [1, 2, true]
+ #
+ # Set.new([nil, "", 1, 2])
+ # # => [2, 1] (or [1, 2])
+ #
+ # When called on a +Hash+, returns a new +Hash+ without the blank values.
+ #
+ # { a: "", b: 1, c: nil, d: [], e: false, f: true }.compact_blank
+ # #=> { b: 1, f: true }
+ def compact_blank
+ reject(&:blank?)
+ end
+end
+
+class Hash
+ # Hash#reject has its own definition, so this needs one too.
+ def compact_blank #:nodoc:
+ reject { |_k, v| v.blank? }
+ end
+
+ # Removes all blank values from the +Hash+ in place and returns self.
+ # Uses Object#blank? for determining if a value is blank.
+ #
+ # h = { a: "", b: 1, c: nil, d: [], e: false, f: true }
+ # h.compact_blank!
+ # # => { b: 1, f: true }
+ def compact_blank!
+ # use delete_if rather than reject! because it always returns self even if nothing changed
+ delete_if { |_k, v| v.blank? }
+ end
end
class Range #:nodoc:
@@ -185,4 +220,15 @@ class Array #:nodoc:
super
end
end
+
+ # Removes all blank elements from the +Array+ in place and returns self.
+ # Uses Object#blank? for determining if an item is blank.
+ #
+ # a = [1, "", nil, 2, " ", [], {}, false, true]
+ # a.compact_blank!
+ # # => [1, 2, true]
+ def compact_blank!
+ # use delete_if rather than reject! because it always returns self even if nothing changed
+ delete_if(&:blank?)
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 54271a3970..14d7f0c484 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -276,6 +276,11 @@ class Module
# The delegated method must be public on the target, otherwise it will
# raise +DelegationError+. If you wish to instead return +nil+,
# use the <tt>:allow_nil</tt> option.
+ #
+ # The <tt>marshal_dump</tt> and <tt>_dump</tt> methods are exempt from
+ # delegation due to possible interference when calling
+ # <tt>Marshal.dump(object)</tt>, should the delegation target method
+ # of <tt>object</tt> add or remove instance variables.
def delegate_missing_to(target, allow_nil: nil)
target = target.to_s
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
@@ -285,6 +290,7 @@ class Module
# It may look like an oversight, but we deliberately do not pass
# +include_private+, because they do not get delegated.
+ return false if name == :marshal_dump || name == :_dump
#{target}.respond_to?(name) || super
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 32cb3a53f4..923d6ad228 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -20,6 +20,9 @@ module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
+ UNBOUND_METHOD_MODULE_NAME = Module.instance_method(:name)
+ private_constant :UNBOUND_METHOD_MODULE_NAME
+
mattr_accessor :interlock, default: Interlock.new
# :doc:
@@ -658,7 +661,7 @@ module ActiveSupport #:nodoc:
# Determine if the given constant has been automatically loaded.
def autoloaded?(desc)
- return false if desc.is_a?(Module) && desc.anonymous?
+ return false if desc.is_a?(Module) && real_mod_name(desc).nil?
name = to_constant_name desc
return false unless qualified_const_defined?(name)
autoloaded_constants.include?(name)
@@ -714,7 +717,7 @@ module ActiveSupport #:nodoc:
when String then desc.sub(/^::/, "")
when Symbol then desc.to_s
when Module
- desc.name ||
+ real_mod_name(desc) ||
raise(ArgumentError, "Anonymous modules have no name to be referenced by")
else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}"
end
@@ -788,6 +791,14 @@ module ActiveSupport #:nodoc:
def log(message)
logger.debug("autoloading: #{message}") if logger && verbose
end
+
+ private
+
+ # Returns the original name of a class or module even if `name` has been
+ # overridden.
+ def real_mod_name(mod)
+ UNBOUND_METHOD_MODULE_NAME.bind(mod).call
+ end
end
end
diff --git a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
index 821e3f971e..f75083a05a 100644
--- a/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
+++ b/activesupport/lib/active_support/dependencies/zeitwerk_integration.rb
@@ -28,7 +28,7 @@ module ActiveSupport
end
def autoloaded?(object)
- cpath = object.is_a?(Module) ? object.name : object.to_s
+ cpath = object.is_a?(Module) ? real_mod_name(object) : object.to_s
Rails.autoloaders.main.unloadable_cpath?(cpath)
end
diff --git a/activesupport/lib/active_support/descendants_tracker.rb b/activesupport/lib/active_support/descendants_tracker.rb
index 1dad4f923e..b14842bf67 100644
--- a/activesupport/lib/active_support/descendants_tracker.rb
+++ b/activesupport/lib/active_support/descendants_tracker.rb
@@ -77,15 +77,17 @@ module ActiveSupport
end
def <<(klass)
- cleanup!
@refs << WeakRef.new(klass)
end
def each
- @refs.each do |ref|
+ @refs.reject! do |ref|
yield ref.__getobj__
+ false
rescue WeakRef::RefError
+ true
end
+ self
end
def refs_size
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 5981763f0e..6acf64cb39 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -367,18 +367,20 @@ module ActiveSupport
key.kind_of?(Symbol) ? key.to_s : key
end
- def convert_value(value, options = {}) # :doc:
+ def convert_value(value, for: nil) # :doc:
+ conversion = binding.local_variable_get(:for)
+
if value.is_a? Hash
- if options[:for] == :to_hash
+ if conversion == :to_hash
value.to_hash
else
value.nested_under_indifferent_access
end
elsif value.is_a?(Array)
- if options[:for] != :assignment || value.frozen?
+ if conversion != :assignment || value.frozen?
value = value.dup
end
- value.map! { |e| convert_value(e, options) }
+ value.map! { |e| convert_value(e, for: conversion) }
else
value
end
diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb
index f16c90cfc6..1775a41492 100644
--- a/activesupport/lib/active_support/logger_thread_safe_level.rb
+++ b/activesupport/lib/active_support/logger_thread_safe_level.rb
@@ -3,6 +3,7 @@
require "active_support/concern"
require "active_support/core_ext/module/attribute_accessors"
require "concurrent"
+require "fiber"
module ActiveSupport
module LoggerThreadSafeLevel # :nodoc:
@@ -28,7 +29,7 @@ module ActiveSupport
end
def local_log_id
- Thread.current.__id__
+ Fiber.current.__id__
end
def local_level
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index aa602329ec..dda71b880e 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -218,6 +218,7 @@ module ActiveSupport
def finish(name, id, payload)
stack = Thread.current[:_event_stack]
event = stack.pop
+ event.payload = payload
event.finish!
@delegate.call event
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 7ab39c9bfb..24e1ab313a 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -52,7 +52,8 @@ module ActiveSupport
end
class Event
- attr_reader :name, :time, :end, :transaction_id, :payload, :children
+ attr_reader :name, :time, :end, :transaction_id, :children
+ attr_accessor :payload
def self.clock_gettime_supported? # :nodoc:
defined?(Process::CLOCK_PROCESS_CPUTIME_ID) &&
diff --git a/activesupport/lib/active_support/parameter_filter.rb b/activesupport/lib/active_support/parameter_filter.rb
index 8e5595babf..e1cd7c46c1 100644
--- a/activesupport/lib/active_support/parameter_filter.rb
+++ b/activesupport/lib/active_support/parameter_filter.rb
@@ -109,7 +109,12 @@ module ActiveSupport
elsif value.is_a?(Hash)
value = call(value, parents, original_params)
elsif value.is_a?(Array)
- value = value.map { |v| v.is_a?(Hash) ? call(v, parents, original_params) : v }
+ # If we don't pop the current parent it will be duplicated as we
+ # process each array value.
+ parents.pop if deep_regexps
+ value = value.map { |v| value_for_key(key, v, parents, original_params) }
+ # Restore the parent stack after processing the array.
+ parents.push(key) if deep_regexps
elsif blocks.any?
key = key.dup if key.duplicable?
value = value.dup if value.duplicable?
diff --git a/activesupport/lib/active_support/secure_compare_rotator.rb b/activesupport/lib/active_support/secure_compare_rotator.rb
new file mode 100644
index 0000000000..14a0aee947
--- /dev/null
+++ b/activesupport/lib/active_support/secure_compare_rotator.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require "active_support/security_utils"
+require "active_support/messages/rotator"
+
+module ActiveSupport
+ # The ActiveSupport::SecureCompareRotator is a wrapper around +ActiveSupport::SecurityUtils.secure_compare+
+ # and allows you to rotate a previously defined value to a new one.
+ #
+ # It can be used as follow:
+ #
+ # rotator = ActiveSupport::SecureCompareRotator.new('new_production_value')
+ # rotator.rotate('previous_production_value')
+ # rotator.secure_compare!('previous_production_value')
+ #
+ # One real use case example would be to rotate a basic auth credentials:
+ #
+ # class MyController < ApplicationController
+ # def authenticate_request
+ # rotator = ActiveSupport::SecureComparerotator.new('new_password')
+ # rotator.rotate('old_password')
+ #
+ # authenticate_or_request_with_http_basic do |username, password|
+ # rotator.secure_compare!(password)
+ # rescue ActiveSupport::SecureCompareRotator::InvalidMatch
+ # false
+ # end
+ # end
+ # end
+ class SecureCompareRotator
+ include SecurityUtils
+ prepend Messages::Rotator
+
+ InvalidMatch = Class.new(StandardError)
+
+ def initialize(value, **_options)
+ @value = value
+ end
+
+ def secure_compare!(other_value, on_rotation: @rotation)
+ secure_compare(@value, other_value) ||
+ run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } ||
+ raise(InvalidMatch)
+ end
+
+ private
+
+ def build_rotation(previous_value, _options)
+ self.class.new(previous_value)
+ end
+ end
+end