diff options
Diffstat (limited to 'activesupport/lib')
37 files changed, 330 insertions, 224 deletions
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index f55e821e10..6273012808 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -16,7 +16,7 @@ module ActiveSupport # # bc = ActiveSupport::BacktraceCleaner.new # bc.add_filter { |line| line.gsub(Rails.root.to_s, '') } # strip the Rails.root prefix - # bc.add_silencer { |line| line =~ /puma|rubygems/ } # skip any lines from puma or rubygems + # bc.add_silencer { |line| /puma|rubygems/.match?(line) } # skip any lines from puma or rubygems # bc.clean(exception.backtrace) # perform the cleanup # # To reconfigure an existing BacktraceCleaner (like the default one in Rails) @@ -65,7 +65,7 @@ module ActiveSupport # for a given line, it will be excluded from the clean backtrace. # # # Will reject all lines that include the word "puma", like "/gems/puma/server.rb" or "/app/my_puma_server/rb" - # backtrace_cleaner.add_silencer { |line| line =~ /puma/ } + # backtrace_cleaner.add_silencer { |line| /puma/.match?(line) } def add_silencer(&block) @silencers << block end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index e055135bb4..a42553ae3a 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -7,6 +7,7 @@ require "active_support/core_ext/module/attribute_accessors" require "active_support/core_ext/numeric/bytes" require "active_support/core_ext/numeric/time" require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/try" require "active_support/core_ext/string/inflections" module ActiveSupport @@ -678,18 +679,15 @@ module ActiveSupport end def instrument(operation, key, options = nil) - log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } + if logger && logger.debug? && !silence? + logger.debug "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" + end payload = { key: key } payload.merge!(options) if options.is_a?(Hash) ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload) { yield(payload) } end - def log - return unless logger && logger.debug? && !silence? - logger.debug(yield) - end - def handle_expired_entry(entry, key, options) if entry && entry.expired? race_ttl = options[:race_condition_ttl].to_i diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 39b32fc7f6..8e80946fbb 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/object/duplicable" require "active_support/core_ext/string/inflections" require "active_support/per_thread_registry" @@ -75,7 +74,10 @@ module ActiveSupport end def fetch_entry(key, options = nil) # :nodoc: - @data.fetch(key) { @data[key] = yield } + entry = @data.fetch(key) { @data[key] = yield } + dup_entry = entry.dup + dup_entry&.dup_value! + dup_entry end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 11746e0537..daf98c9528 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -7,7 +7,6 @@ require "active_support/core_ext/class/attribute" require "active_support/core_ext/kernel/reporting" require "active_support/core_ext/kernel/singleton_class" require "active_support/core_ext/string/filters" -require "active_support/deprecation" require "thread" module ActiveSupport diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb index c7a2378e41..27cb47eb6e 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "active_support/core_ext/object/try" +require "active_support/core_ext/date_time/conversions" module DateAndTime module Calculations 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/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 5b48254646..cc42647879 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require "active_support/xml_mini" -require "active_support/time" require "active_support/core_ext/object/blank" require "active_support/core_ext/object/to_param" require "active_support/core_ext/object/to_query" +require "active_support/core_ext/object/try" require "active_support/core_ext/array/wrap" require "active_support/core_ext/hash/reverse_merge" require "active_support/core_ext/string/inflections" diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb index 0c72cd7b47..5ff0e34d82 100644 --- a/activesupport/lib/active_support/core_ext/marshal.rb +++ b/activesupport/lib/active_support/core_ext/marshal.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/core_ext/string/inflections" + module ActiveSupport module MarshalWithAutoloading # :nodoc: def load(source, proc = nil) diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb index a6e87aeb68..ea4034303e 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb @@ -33,7 +33,7 @@ class Module # end # # Current.new.user # => NoMethodError - def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true) # :nodoc: + def thread_mattr_reader(*syms, instance_reader: true, instance_accessor: true, default: nil) # :nodoc: syms.each do |sym| raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) @@ -52,6 +52,8 @@ class Module end EOS end + + Thread.current["attr_" + name + "_#{sym}"] = default unless default.nil? end end alias :thread_cattr_reader :thread_mattr_reader @@ -74,7 +76,7 @@ class Module # end # # Current.new.user = "DHH" # => NoMethodError - def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true) # :nodoc: + def thread_mattr_writer(*syms, instance_writer: true, instance_accessor: true, default: nil) # :nodoc: syms.each do |sym| raise NameError.new("invalid attribute name: #{sym}") unless /^[_A-Za-z]\w*$/.match?(sym) @@ -93,6 +95,8 @@ class Module end EOS end + + public_send("#{sym}=", default) unless default.nil? end end alias :thread_cattr_writer :thread_mattr_writer @@ -136,8 +140,8 @@ class Module # # Current.new.user = "DHH" # => NoMethodError # Current.new.user # => NoMethodError - def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true) - thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor) + def thread_mattr_accessor(*syms, instance_reader: true, instance_writer: true, instance_accessor: true, default: nil) + thread_mattr_reader(*syms, instance_reader: instance_reader, instance_accessor: instance_accessor, default: default) thread_mattr_writer(*syms, instance_writer: instance_writer, instance_accessor: instance_accessor) end alias :thread_cattr_accessor :thread_mattr_accessor 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/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb index 6d37cd9dfd..c51aec0e03 100644 --- a/activesupport/lib/active_support/core_ext/name_error.rb +++ b/activesupport/lib/active_support/core_ext/name_error.rb @@ -15,7 +15,7 @@ class NameError # We should use original_message message instead. message = respond_to?(:original_message) ? original_message : self.message - if /undefined local variable or method/ !~ message + unless /undefined local variable or method/.match?(message) $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message end end diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index c78ee6bbfc..3ebcdca02b 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -28,96 +28,6 @@ class Object end end -class NilClass - begin - nil.dup - rescue TypeError - - # +nil+ is not duplicable: - # - # nil.duplicable? # => false - # nil.dup # => TypeError: can't dup NilClass - def duplicable? - false - end - end -end - -class FalseClass - begin - false.dup - rescue TypeError - - # +false+ is not duplicable: - # - # false.duplicable? # => false - # false.dup # => TypeError: can't dup FalseClass - def duplicable? - false - end - end -end - -class TrueClass - begin - true.dup - rescue TypeError - - # +true+ is not duplicable: - # - # true.duplicable? # => false - # true.dup # => TypeError: can't dup TrueClass - def duplicable? - false - end - end -end - -class Symbol - begin - :symbol.dup - - # Some symbols couldn't be duped in Ruby 2.4.0 only, due to a bug. - # This feature check catches any regression. - "symbol_from_string".to_sym.dup - rescue TypeError - - # Symbols are not duplicable: - # - # :my_symbol.duplicable? # => false - # :my_symbol.dup # => TypeError: can't dup Symbol - def duplicable? - false - end - end -end - -class Numeric - begin - 1.dup - rescue TypeError - - # Numbers are not duplicable: - # - # 3.duplicable? # => false - # 3.dup # => TypeError: can't dup Integer - def duplicable? - false - end - end -end - -require "bigdecimal" -class BigDecimal - # BigDecimals are duplicable: - # - # BigDecimal("1.2").duplicable? # => true - # BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> - def duplicable? - true - end -end - class Method # Methods are not duplicable: # @@ -128,32 +38,12 @@ class Method end end -class Complex - begin - Complex(1).dup - rescue TypeError - - # Complexes are not duplicable: - # - # Complex(1).duplicable? # => false - # Complex(1).dup # => TypeError: can't copy Complex - def duplicable? - false - end - end -end - -class Rational - begin - Rational(1).dup - rescue TypeError - - # Rationals are not duplicable: - # - # Rational(1).duplicable? # => false - # Rational(1).dup # => TypeError: can't copy Rational - def duplicable? - false - end +class UnboundMethod + # Unbound methods are not duplicable: + # + # method(:puts).unbind.duplicable? # => false + # method(:puts).unbind.dup # => TypeError: allocator undefined for UnboundMethod + def duplicable? + false end end diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 4ca24028b0..f6a14c08bc 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -44,7 +44,7 @@ class String # str.from(0).to(-1) # => "hello" # str.from(1).to(-2) # => "ell" def from(position) - self[position..-1] + self[position, length] end # Returns a substring from the beginning of the string to the given position. @@ -61,7 +61,8 @@ class String # str.from(0).to(-1) # => "hello" # str.from(1).to(-2) # => "ell" def to(position) - self[0..position] + position += size if position < 0 + self[0, position + 1] || +"" end # Returns the first character. If a limit is supplied, returns a substring @@ -75,17 +76,7 @@ class String # str.first(0) # => "" # str.first(6) # => "hello" def first(limit = 1) - ActiveSupport::Deprecation.warn( - "Calling String#first with a negative integer limit " \ - "will raise an ArgumentError in Rails 6.1." - ) if limit < 0 - if limit == 0 - "" - elsif limit >= size - dup - else - to(limit - 1) - end + self[0, limit] || raise(ArgumentError, "negative limit") end # Returns the last character of the string. If a limit is supplied, returns a substring @@ -99,16 +90,6 @@ class String # str.last(0) # => "" # str.last(6) # => "hello" def last(limit = 1) - ActiveSupport::Deprecation.warn( - "Calling String#last with a negative integer limit " \ - "will raise an ArgumentError in Rails 6.1." - ) if limit < 0 - if limit == 0 - "" - elsif limit >= size - dup - else - from(-limit) - end + self[[length - limit, 0].max, limit] || raise(ArgumentError, "negative limit") end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 5dc47b20c6..6d71a67a46 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: @@ -201,6 +204,11 @@ module ActiveSupport #:nodoc: end end + def self.include_into(base) + base.include(self) + append_features(base) + end + def const_missing(const_name) from_mod = anonymous? ? guess_for_anonymous(const_name) : self Dependencies.load_missing_constant(from_mod, const_name) @@ -230,6 +238,21 @@ module ActiveSupport #:nodoc: base.class_eval do define_method(:load, Kernel.instance_method(:load)) private :load + + define_method(:require, Kernel.instance_method(:require)) + private :require + end + end + + def self.include_into(base) + base.include(self) + + if base.instance_method(:load).owner == base + base.remove_method(:load) + end + + if base.instance_method(:require).owner == base + base.remove_method(:require) end end @@ -325,9 +348,9 @@ module ActiveSupport #:nodoc: end def hook! - Object.class_eval { include Loadable } - Module.class_eval { include ModuleConstMissing } - Exception.class_eval { include Blamable } + Loadable.include_into(Object) + ModuleConstMissing.include_into(Module) + Exception.include(Blamable) end def unhook! @@ -638,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) @@ -694,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 @@ -768,6 +791,13 @@ 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..2cf55713bd 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 @@ -42,6 +42,17 @@ module ActiveSupport end end + module RequireDependency + def require_dependency(filename) + filename = filename.to_path if filename.respond_to?(:to_path) + if abspath = ActiveSupport::Dependencies.search_for_file(filename) + require abspath + else + require filename + end + end + end + module Inflector def self.camelize(basename, _abspath) basename.camelize @@ -90,7 +101,7 @@ module ActiveSupport def decorate_dependencies Dependencies.unhook! Dependencies.singleton_class.prepend(Decorations) - Object.class_eval { alias_method :require_dependency, :require } + Object.prepend(RequireDependency) end end end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 56f1e23136..d7d3c30b97 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -120,9 +120,16 @@ module ActiveSupport # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. # (Backtrace information…) # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] - class DeprecatedConstantProxy < DeprecationProxy + class DeprecatedConstantProxy < Module + def self.new(*args, &block) + object = args.first + + return object unless object + super + end + def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.") - require "active_support/inflector/methods" + Kernel.require "active_support/inflector/methods" @old_const = old_const @new_const = new_const @@ -130,6 +137,14 @@ module ActiveSupport @message = message end + instance_methods.each { |m| undef_method m unless /^__|^object_id$/.match?(m) } + + # Don't give a deprecation warning on inspect since test/unit and error + # logs rely on it for diagnostics. + def inspect + target.inspect + end + # Returns the class of the new constant. # # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) @@ -144,8 +159,14 @@ module ActiveSupport ActiveSupport::Inflector.constantize(@new_const.to_s) end - def warn(callstack, called, args) - @deprecator.warn(@message, callstack) + def const_missing(name) + @deprecator.warn(@message, caller_locations) + target.const_get(name) + end + + def method_missing(called, *args, &block) + @deprecator.warn(@message, caller_locations) + target.__send__(called, *args, &block) end end 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/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 5b29a13894..efee74a1df 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -2,7 +2,6 @@ require "concurrent/map" require "active_support/i18n" -require "active_support/deprecation" module ActiveSupport module Inflector diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 18f3f53879..33a17a7741 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require "active_support/inflections" +require "active_support/core_ext/object/blank" module ActiveSupport # The Inflector transforms words from singular to plural, class names to table @@ -269,32 +270,36 @@ module ActiveSupport # NameError is raised when the name is not in CamelCase or the constant is # unknown. def constantize(camel_cased_word) - names = camel_cased_word.split("::") - - # Trigger a built-in NameError exception including the ill-formed constant in the message. - Object.const_get(camel_cased_word) if names.empty? - - # Remove the first blank element in case of '::ClassName' notation. - names.shift if names.size > 1 && names.first.empty? - - names.inject(Object) do |constant, name| - if constant == Object - constant.const_get(name) - else - candidate = constant.const_get(name) - next candidate if constant.const_defined?(name, false) - next candidate unless Object.const_defined?(name) - - # Go down the ancestors to check if it is owned directly. The check - # stops when we reach Object or the end of ancestors tree. - constant = constant.ancestors.inject(constant) do |const, ancestor| - break const if ancestor == Object - break ancestor if ancestor.const_defined?(name, false) - const + if camel_cased_word.blank? || !camel_cased_word.include?("::") + Object.const_get(camel_cased_word) + else + names = camel_cased_word.split("::") + + # Trigger a built-in NameError exception including the ill-formed constant in the message. + Object.const_get(camel_cased_word) if names.empty? + + # Remove the first blank element in case of '::ClassName' notation. + names.shift if names.size > 1 && names.first.empty? + + names.inject(Object) do |constant, name| + if constant == Object + constant.const_get(name) + else + candidate = constant.const_get(name) + next candidate if constant.const_defined?(name, false) + next candidate unless Object.const_defined?(name) + + # Go down the ancestors to check if it is owned directly. The check + # stops when we reach Object or the end of ancestors tree. + constant = constant.ancestors.inject(constant) do |const, ancestor| + break const if ancestor == Object + break ancestor if ancestor.const_defined?(name, false) + const + end + + # owner is in Object, so raise + constant.const_get(name, false) end - - # owner is in Object, so raise - constant.const_get(name, false) end end end @@ -371,7 +376,7 @@ module ActiveSupport last = parts.pop - parts.reverse.inject(last) do |acc, part| + parts.reverse!.inject(last) do |acc, part| part.empty? ? acc : "#{part}(::#{acc})?" end end diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index ec6e9ccb59..1899a1212d 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -56,14 +56,40 @@ module ActiveSupport # # transliterate('Jürgen', locale: :de) # # => "Juergen" + # + # Transliteration is restricted to UTF-8, US-ASCII and GB18030 strings + # Other encodings will raise an ArgumentError. def transliterate(string, replacement = "?", locale: nil) + string = string.dup if string.frozen? raise ArgumentError, "Can only transliterate strings. Received #{string.class.name}" unless string.is_a?(String) - I18n.transliterate( + allowed_encodings = [Encoding::UTF_8, Encoding::US_ASCII, Encoding::GB18030] + raise ArgumentError, "Can not transliterate strings with #{string.encoding} encoding" unless allowed_encodings.include?(string.encoding) + + input_encoding = string.encoding + + # US-ASCII is a subset of UTF-8 so we'll force encoding as UTF-8 if + # US-ASCII is given. This way we can let tidy_bytes handle the string + # in the same way as we do for UTF-8 + string.force_encoding(Encoding::UTF_8) if string.encoding == Encoding::US_ASCII + + # GB18030 is Unicode compatible but is not a direct mapping so needs to be + # transcoded. Using invalid/undef :replace will result in loss of data in + # the event of invalid characters, but since tidy_bytes will replace + # invalid/undef with a "?" we're safe to do the same beforehand + string.encode!(Encoding::UTF_8, invalid: :replace, undef: :replace) if string.encoding == Encoding::GB18030 + + transliterated = I18n.transliterate( ActiveSupport::Multibyte::Unicode.tidy_bytes(string).unicode_normalize(:nfc), replacement: replacement, locale: locale ) + + # Restore the string encoding of the input if it was not UTF-8. + # Apply invalid/undef :replace as tidy_bytes does + transliterated.encode!(input_encoding, invalid: :replace, undef: :replace) if input_encoding != transliterated.encoding + + transliterated end # Replaces special characters in a string so that it may be used as part of @@ -91,7 +117,7 @@ module ActiveSupport # If the optional parameter +locale+ is specified, # the word will be parameterized as a word of that language. # By default, this parameter is set to <tt>nil</tt> and it will use - # the configured <tt>I18n.locale<tt>. + # the configured <tt>I18n.locale</tt>. def parameterize(string, separator: "-", preserve_case: false, locale: nil) # Replace accented chars with their ASCII equivalents. parameterized_string = transliterate(string, locale: locale) diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index db991c7a32..8b9dd1fffe 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -29,6 +29,9 @@ module ActiveSupport # subscriber, the line above should be called after your # <tt>ActiveRecord::LogSubscriber</tt> definition. # + # A logger also needs to be set with <tt>ActiveRecord::LogSubscriber.logger=</tt>. + # This is assigned automatically in a Rails environment. + # # After configured, whenever a <tt>"sql.active_record"</tt> notification is published, # it will properly dispatch the event # (<tt>ActiveSupport::Notifications::Event</tt>) to the sql method. diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb index b8555c887b..2576bee4ff 100644 --- a/activesupport/lib/active_support/logger.rb +++ b/activesupport/lib/active_support/logger.rb @@ -14,7 +14,7 @@ module ActiveSupport # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT) # # => true def self.logger_outputs_to?(logger, *sources) - logdev = logger.instance_variable_get("@logdev") + logdev = logger.instance_variable_get(:@logdev) logger_source = logdev.dev if logdev.respond_to?(:dev) sources.any? { |source| source == logger_source } 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/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index c4a4afe95f..a5dc1181d8 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -178,8 +178,8 @@ module ActiveSupport # Generates a signed message for the provided value. # - # The message is signed with the +MessageVerifier+'s secret. Without knowing - # the secret, the original value cannot be extracted from the message. + # The message is signed with the +MessageVerifier+'s secret. + # Returns Base64-encoded message joined with the generated signature. # # verifier = ActiveSupport::MessageVerifier.new 's3Krit' # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772" diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 2ba3936cae..868cb70423 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -48,7 +48,7 @@ module ActiveSupport #:nodoc: alias to_s wrapped_string alias to_str wrapped_string - delegate :<=>, :=~, :acts_like_string?, to: :wrapped_string + delegate :<=>, :=~, :match?, :acts_like_string?, to: :wrapped_string # Creates a new Chars instance by wrapping _string_. def initialize(string) diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index aa602329ec..b0f30d2995 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -3,6 +3,7 @@ require "mutex_m" require "concurrent/map" require "set" +require "active_support/core_ext/object/try" module ActiveSupport module Notifications @@ -218,6 +219,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/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 8f3d04aa5c..0c87114c0d 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "active_support/dependencies/autoload" - module ActiveSupport module NumberHelper extend ActiveSupport::Autoload diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb index ab9ca727f6..8e54b11be6 100644 --- a/activesupport/lib/active_support/option_merger.rb +++ b/activesupport/lib/active_support/option_merger.rb @@ -5,7 +5,7 @@ require "active_support/core_ext/hash/deep_merge" module ActiveSupport class OptionMerger #:nodoc: instance_methods.each do |method| - undef_method(method) if method !~ /^(__|instance_eval|class|object_id)/ + undef_method(method) unless method.match?(/^(__|instance_eval|class|object_id)/) end def initialize(context, options) diff --git a/activesupport/lib/active_support/parameter_filter.rb b/activesupport/lib/active_support/parameter_filter.rb index 8e5595babf..f4c4f2d2fb 100644 --- a/activesupport/lib/active_support/parameter_filter.rb +++ b/activesupport/lib/active_support/parameter_filter.rb @@ -22,7 +22,7 @@ module ActiveSupport # change { file: { code: "xxxx"} } # # ActiveSupport::ParameterFilter.new([-> (k, v) do - # v.reverse! if k =~ /secret/i + # v.reverse! if /secret/i.match?(k) # end]) # => reverses the value to all keys matching /secret/i class ParameterFilter @@ -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/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index eb92fb4371..bf5a3b97ae 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -40,7 +40,7 @@ module ActiveSupport # If the class has an initializer, it must accept no arguments. module PerThreadRegistry def self.extended(object) - object.instance_variable_set "@per_thread_registry_key", object.name.freeze + object.instance_variable_set :@per_thread_registry_key, object.name.freeze end def instance diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb index 8b727a69ec..30857f04d8 100644 --- a/activesupport/lib/active_support/rails.rb +++ b/activesupport/lib/active_support/rails.rb @@ -13,9 +13,6 @@ # Defines Object#blank? and Object#present?. require "active_support/core_ext/object/blank" -# Rails own autoload, eager_load, etc. -require "active_support/dependencies/autoload" - # Support for ClassMethods and the included macro. require "active_support/concern" 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..269703c34a --- /dev/null +++ b/activesupport/lib/active_support/secure_compare_rotator.rb @@ -0,0 +1,51 @@ +# 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: @on_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 diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index d8a86d997e..75a7ca24c2 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -32,15 +32,18 @@ module ActiveSupport def push_tags(*tags) tags.flatten.reject(&:blank?).tap do |new_tags| + @tags_text = nil current_tags.concat new_tags end end def pop_tags(size = 1) + @tags_text = nil current_tags.pop size end def clear_tags! + @tags_text = nil current_tags.clear end @@ -51,11 +54,13 @@ module ActiveSupport end def tags_text - tags = current_tags - if tags.one? - "[#{tags[0]}] " - elsif tags.any? - tags.collect { |tag| "[#{tag}] " }.join + @tags_text ||= begin + tags = current_tags + if tags.one? + "[#{tags[0]}] " + elsif tags.any? + tags.collect { |tag| "[#{tag}] " }.join + end end end end diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb index f50a5e0554..96518a4a58 100644 --- a/activesupport/lib/active_support/testing/parallelization.rb +++ b/activesupport/lib/active_support/testing/parallelization.rb @@ -72,7 +72,11 @@ module ActiveSupport def start @pool = @queue_size.times.map do |worker| + title = "Rails test worker #{worker}" + fork do + Process.setproctitle("#{title} - (starting)") + DRb.stop_service begin @@ -85,6 +89,9 @@ module ActiveSupport klass = job[0] method = job[1] reporter = job[2] + + Process.setproctitle("#{title} - #{klass}##{method}") + result = klass.with_info_handler reporter do Minitest.run_one_method(klass, method) end @@ -99,8 +106,12 @@ module ActiveSupport end queue.record(reporter, result) end + + Process.setproctitle("#{title} - (idle)") end ensure + Process.setproctitle("#{title} - (stopping)") + run_cleanup(worker) end end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index d9e033e23b..90830b89bd 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -334,6 +334,13 @@ module ActiveSupport re === name || re === MAPPING[name] end + # Compare #name and TZInfo identifier to a supplied regexp, returning +true+ + # if a match is found. + def match?(re) + (re == name) || (re == MAPPING[name]) || + ((Regexp === re) && (re.match?(name) || re.match?(MAPPING[name]))) + end + # Returns a textual representation of this time zone. def to_s "(GMT#{formatted_offset}) #{name}" |