diff options
Diffstat (limited to 'activesupport/lib')
60 files changed, 554 insertions, 171 deletions
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index 16dd733ddb..1796956bd7 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -31,6 +31,9 @@ module ActiveSupport class BacktraceCleaner def initialize @filters, @silencers = [], [] + add_gem_filter + add_gem_silencer + add_stdlib_silencer end # Returns the backtrace after all filters and silencers have been run @@ -82,6 +85,26 @@ module ActiveSupport end private + + FORMATTED_GEMS_PATTERN = /\A[^\/]+ \([\w.]+\) / + + def add_gem_filter + gems_paths = (Gem.path | [Gem.default_dir]).map { |p| Regexp.escape(p) } + return if gems_paths.empty? + + gems_regexp = %r{(#{gems_paths.join('|')})/(bundler/)?gems/([^/]+)-([\w.]+)/(.*)} + gems_result = '\3 (\4) \5'.freeze + add_filter { |line| line.sub(gems_regexp, gems_result) } + end + + def add_gem_silencer + add_silencer { |line| FORMATTED_GEMS_PATTERN.match?(line) } + end + + def add_stdlib_silencer + add_silencer { |line| line.start_with?(RbConfig::CONFIG["rubylibdir"]) } + end + def filter_backtrace(backtrace) @filters.each do |f| backtrace = backtrace.map { |line| f.call(line) } diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index d769e2c8ea..a5d0c52b13 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -229,6 +229,14 @@ module ActiveSupport # ask whether you should force a cache write. Otherwise, it's clearer to # just call <tt>Cache#write</tt>. # + # Setting <tt>skip_nil: true</tt> will not cache nil result: + # + # cache.fetch('foo') { nil } + # cache.fetch('bar', skip_nil: true) { nil } + # cache.exist?('foo') # => true + # cache.exist?('bar') # => false + # + # # Setting <tt>compress: false</tt> disables compression of the cache entry. # # Setting <tt>:expires_in</tt> will set an expiration time on the cache. @@ -686,7 +694,7 @@ module ActiveSupport end def get_entry_value(entry, name, options) - instrument(:fetch_hit, name, options) {} + instrument(:fetch_hit, name, options) { } entry.value end @@ -695,7 +703,7 @@ module ActiveSupport yield(name) end - write(name, result, options) + write(name, result, options) unless result.nil? && options[:skip_nil] result end end diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index a0f44aac0f..53a2b07536 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -26,6 +26,11 @@ module ActiveSupport @cache_path = cache_path.to_s end + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + # Deletes all items from the cache. In this case it deletes all the entries in the specified # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your # config file when using +FileStore+ because everything in that directory will be deleted. @@ -127,15 +132,19 @@ module ActiveSupport hash = Zlib.adler32(fname) hash, dir_1 = hash.divmod(0x1000) dir_2 = hash.modulo(0x1000) - fname_paths = [] # Make sure file name doesn't exceed file system limits. - begin - fname_paths << fname[0, FILENAME_MAX_SIZE] - fname = fname[FILENAME_MAX_SIZE..-1] - end until fname.blank? + if fname.length < FILENAME_MAX_SIZE + fname_paths = fname + else + fname_paths = [] + begin + fname_paths << fname[0, FILENAME_MAX_SIZE] + fname = fname[FILENAME_MAX_SIZE..-1] + end until fname.blank? + end - File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths) + File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, fname_paths) end # Translate a file path into a key. diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 2840781dde..174c784deb 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -47,6 +47,11 @@ module ActiveSupport end end + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + prepend Strategy::LocalCache prepend LocalCacheWithRaw diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 564ac17241..106b616529 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -30,6 +30,11 @@ module ActiveSupport @pruning = false end + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + # Delete all data stored in a given cache store. def clear(options = nil) synchronize do diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb index 1a5983db43..8452a28fd8 100644 --- a/activesupport/lib/active_support/cache/null_store.rb +++ b/activesupport/lib/active_support/cache/null_store.rb @@ -12,6 +12,11 @@ module ActiveSupport class NullStore < Store prepend Strategy::LocalCache + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + def clear(options = nil) end diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index 11c574258f..9a55e49e27 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -66,6 +66,11 @@ module ActiveSupport SCAN_BATCH_SIZE = 1000 private_constant :SCAN_BATCH_SIZE + # Advertise cache versioning support. + def self.supports_cache_versioning? + true + end + # Support raw values in the local cache strategy. module LocalCacheWithRaw # :nodoc: private @@ -258,7 +263,14 @@ module ActiveSupport def increment(name, amount = 1, options = nil) instrument :increment, name, amount: amount do failsafe :increment do - redis.with { |c| c.incrby normalize_key(name, options), amount } + options = merged_options(options) + key = normalize_key(name, options) + + redis.with do |c| + c.incrby(key, amount).tap do + write_key_expiry(c, key, options) + end + end end end end @@ -274,7 +286,14 @@ module ActiveSupport def decrement(name, amount = 1, options = nil) instrument :decrement, name, amount: amount do failsafe :decrement do - redis.with { |c| c.decrby normalize_key(name, options), amount } + options = merged_options(options) + key = normalize_key(name, options) + + redis.with do |c| + c.decrby(key, amount).tap do + write_key_expiry(c, key, options) + end + end end end end @@ -385,6 +404,12 @@ module ActiveSupport end end + def write_key_expiry(client, key, options) + if options[:expires_in] && client.ttl(key).negative? + client.expire key, options[:expires_in].to_i + end + end + # Delete an entry from the cache. def delete_entry(key, options) failsafe :delete_entry, returning: false do diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index a1b841ec3d..487fe79f41 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -497,9 +497,7 @@ module ActiveSupport arg.halted || !@user_conditions.all? { |c| c.call(arg.target, arg.value) } end - def nested - @nested - end + attr_reader :nested def final? !@call_template @@ -578,7 +576,7 @@ module ActiveSupport end protected - def chain; @chain; end + attr_reader :chain private @@ -659,9 +657,17 @@ module ActiveSupport # * <tt>:if</tt> - A symbol or an array of symbols, each naming an instance # method or a proc; the callback will be called only when they all return # a true value. + # + # If a proc is given, its body is evaluated in the context of the + # current object. It can also optionally accept the current object as + # an argument. # * <tt>:unless</tt> - A symbol or an array of symbols, each naming an # instance method or a proc; the callback will be called only when they # all return a false value. + # + # If a proc is given, its body is evaluated in the context of the + # current object. It can also optionally accept the current object as + # an argument. # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the # existing chain rather than appended. def set_callback(name, *filter_list, &block) diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 4d6f7819bb..2610114d8f 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -3,7 +3,6 @@ require "active_support/concern" require "active_support/ordered_options" require "active_support/core_ext/array/extract_options" -require "active_support/core_ext/regexp" module ActiveSupport # Configurable provides a <tt>config</tt> method to store and retrieve diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb index 6d83b76882..a2569c798b 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -3,6 +3,7 @@ require "active_support/core_ext/array/wrap" require "active_support/core_ext/array/access" require "active_support/core_ext/array/conversions" +require "active_support/core_ext/array/extract" require "active_support/core_ext/array/extract_options" require "active_support/core_ext/array/grouping" require "active_support/core_ext/array/prepend_and_append" diff --git a/activesupport/lib/active_support/core_ext/array/extract.rb b/activesupport/lib/active_support/core_ext/array/extract.rb new file mode 100644 index 0000000000..cc5a8a3f88 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/array/extract.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +class Array + # Removes and returns the elements for which the block returns a true value. + # If no block is given, an Enumerator is returned instead. + # + # numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + # odd_numbers = numbers.extract! { |number| number.odd? } # => [1, 3, 5, 7, 9] + # numbers # => [0, 2, 4, 6, 8] + def extract! + return to_enum(:extract!) { size } unless block_given? + + extracted_elements = [] + + reject! do |element| + extracted_elements << element if yield(element) + end + + extracted_elements + end +end diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 75e65337b7..56fb46a88d 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -3,7 +3,7 @@ class Class begin # Test if this Ruby supports each_object against singleton_class - ObjectSpace.each_object(Numeric.singleton_class) {} + ObjectSpace.each_object(Numeric.singleton_class) { } # Returns an array with all classes that are < than its receiver. # 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 de13f00e60..05abd83221 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 @@ -5,13 +5,13 @@ require "active_support/core_ext/object/try" module DateAndTime module Calculations DAYS_INTO_WEEK = { - monday: 0, - tuesday: 1, - wednesday: 2, - thursday: 3, - friday: 4, - saturday: 5, - sunday: 6 + sunday: 0, + monday: 1, + tuesday: 2, + wednesday: 3, + thursday: 4, + friday: 5, + saturday: 6 } WEEKEND_DAYS = [ 6, 0 ] @@ -60,12 +60,12 @@ module DateAndTime !WEEKEND_DAYS.include?(wday) end - # Returns true if the date/time before <tt>date_or_time</tt>. + # Returns true if the date/time falls before <tt>date_or_time</tt>. def before?(date_or_time) self < date_or_time end - # Returns true if the date/time after <tt>date_or_time</tt>. + # Returns true if the date/time falls after <tt>date_or_time</tt>. def after?(date_or_time) self > date_or_time end @@ -263,9 +263,8 @@ module DateAndTime # Week is assumed to start on +start_day+, default is # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. def days_to_week_start(start_day = Date.beginning_of_week) - start_day_number = DAYS_INTO_WEEK[start_day] - current_day_number = wday != 0 ? wday - 1 : 6 - (current_day_number - start_day_number) % 7 + start_day_number = DAYS_INTO_WEEK.fetch(start_day) + (wday - start_day_number) % 7 end # Returns a new date/time representing the start of this week on the given day. @@ -346,8 +345,7 @@ module DateAndTime # today.next_occurring(:monday) # => Mon, 18 Dec 2017 # today.next_occurring(:thursday) # => Thu, 21 Dec 2017 def next_occurring(day_of_week) - current_day_number = wday != 0 ? wday - 1 : 6 - from_now = DAYS_INTO_WEEK.fetch(day_of_week) - current_day_number + from_now = DAYS_INTO_WEEK.fetch(day_of_week) - wday from_now += 7 unless from_now > 0 advance(days: from_now) end @@ -358,8 +356,7 @@ module DateAndTime # today.prev_occurring(:monday) # => Mon, 11 Dec 2017 # today.prev_occurring(:thursday) # => Thu, 07 Dec 2017 def prev_occurring(day_of_week) - current_day_number = wday != 0 ? wday - 1 : 6 - ago = current_day_number - DAYS_INTO_WEEK.fetch(day_of_week) + ago = wday - DAYS_INTO_WEEK.fetch(day_of_week) ago += 7 unless ago > 0 advance(days: -ago) end @@ -374,7 +371,7 @@ module DateAndTime end def days_span(day) - (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 + (DAYS_INTO_WEEK.fetch(day) - DAYS_INTO_WEEK.fetch(Date.beginning_of_week)) % 7 end def copy_time_to(other) diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index edde4f46b9..d87d63f287 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -1,14 +1,21 @@ # frozen_string_literal: true module Enumerable + INDEX_WITH_DEFAULT = Object.new + private_constant :INDEX_WITH_DEFAULT + # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements # when we omit an identity. + # :stopdoc: + # We can't use Refinements here because Refinements with Module which will be prepended # doesn't work well https://bugs.ruby-lang.org/issues/13446 - alias :_original_sum_with_required_identity :sum # :nodoc: + alias :_original_sum_with_required_identity :sum private :_original_sum_with_required_identity + # :startdoc: + # Calculates a sum from the elements. # # payments.sum { |p| p.price * p.tax_rate } @@ -37,10 +44,11 @@ module Enumerable end end - # Convert an enumerable to a hash. + # Convert an enumerable to a hash keying it by the block return value. # # people.index_by(&:login) # # => { "nextangle" => <Person ...>, "chade-" => <Person ...>, ...} + # # people.index_by { |person| "#{person.first_name} #{person.last_name}" } # # => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...} def index_by @@ -53,6 +61,26 @@ module Enumerable end end + # Convert an enumerable to a hash keying it with the enumerable items and with the values returned in the block. + # + # post = Post.new(title: "hey there", body: "what's up?") + # + # %i( title body ).index_with { |attr_name| post.public_send(attr_name) } + # # => { title: "hey there", body: "what's up?" } + def index_with(default = INDEX_WITH_DEFAULT) + if block_given? + result = {} + each { |elem| result[elem] = yield(elem) } + result + elsif default != INDEX_WITH_DEFAULT + result = {} + each { |elem| result[elem] = default } + result + else + to_enum(:index_with) { size if respond_to?(:size) } + end + end + # Returns +true+ if the enumerable has more than 1 element. Functionally # equivalent to <tt>enum.to_a.size > 1</tt>. Can be called with a block too, # much like any?, so <tt>people.many? { |p| p.age > 26 }</tt> returns +true+ diff --git a/activesupport/lib/active_support/core_ext/integer/multiple.rb b/activesupport/lib/active_support/core_ext/integer/multiple.rb index e7606662d3..bd57a909c5 100644 --- a/activesupport/lib/active_support/core_ext/integer/multiple.rb +++ b/activesupport/lib/active_support/core_ext/integer/multiple.rb @@ -7,6 +7,6 @@ class Integer # 6.multiple_of?(5) # => false # 10.multiple_of?(2) # => true def multiple_of?(number) - number != 0 ? self % number == 0 : zero? + number == 0 ? self == 0 : self % number == 0 end end diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index 750f858fcc..6b0dcab905 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -4,6 +4,6 @@ class LoadError # Returns true if the given path name (except perhaps for the ".rb" # extension) is the missing file which caused the exception to be raised. def is_missing?(location) - location.sub(/\.rb$/, "".freeze) == path.sub(/\.rb$/, "".freeze) + location.sub(/\.rb$/, "".freeze) == path.to_s.sub(/\.rb$/, "".freeze) end end diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index 01fee0fb74..281eaa7d5f 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/array/extract_options" -require "active_support/core_ext/regexp" # Extends the module object with class/module and instance accessors for # class/module attributes, just like the native attr* accessors for instance 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 4b9b6ea9bd..b1788a000b 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 @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/array/extract_options" -require "active_support/core_ext/regexp" # Extends the module object with class/module and instance accessors for # class/module attributes, just like the native attr* accessors for instance @@ -137,7 +136,7 @@ class Module # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods. # # class Current - # mattr_accessor :user, instance_accessor: false + # thread_mattr_accessor :user, instance_accessor: false # end # # Current.new.user = "DHH" # => NoMethodError diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 7f42f44efb..3128539112 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "set" -require "active_support/core_ext/regexp" class Module # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 2ca431ab10..f36fef6cc9 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,11 +1,10 @@ # frozen_string_literal: true -require "active_support/core_ext/regexp" require "concurrent/map" class Object # An object is blank if it's false, empty, or a whitespace string. - # For example, +false+, '', ' ', +nil+, [], and {} are all blank. + # For example, +nil+, '', ' ', [], {}, and +false+ are all blank. # # This simplifies # diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index abb461966a..bac6ff9c97 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -75,11 +75,14 @@ class Hash # # This method is also aliased as +to_param+. def to_query(namespace = nil) - collect do |key, value| + query = collect do |key, value| unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? value.to_query(namespace ? "#{namespace}[#{key}]" : key) end - end.compact.sort! * "&" + end.compact + + query.sort! unless namespace.to_s.include?("[]") + query.join("&") end alias_method :to_param, :to_query diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index c874691629..aa6896af32 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -4,19 +4,27 @@ require "delegate" module ActiveSupport module Tryable #:nodoc: - def try(*a, &b) - try!(*a, &b) if a.empty? || respond_to?(a.first) + def try(method_name = nil, *args, &b) + if method_name.nil? && block_given? + if b.arity == 0 + instance_eval(&b) + else + yield self + end + elsif respond_to?(method_name) + public_send(method_name, *args, &b) + end end - def try!(*a, &b) - if a.empty? && block_given? + def try!(method_name = nil, *args, &b) + if method_name.nil? && block_given? if b.arity == 0 instance_eval(&b) else yield self end else - public_send(*a, &b) + public_send(method_name, *args, &b) end end end diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index 2838fd76be..1d46add6e0 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -68,7 +68,7 @@ class Object # You can access these methods using the class name instead: # # class Phone < ActiveRecord::Base - # enum phone_number_type: [home: 0, office: 1, mobile: 2] + # enum phone_number_type: { home: 0, office: 1, mobile: 2 } # # with_options presence: true do # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys } diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb index 4074e91d17..78814fd189 100644 --- a/activesupport/lib/active_support/core_ext/range.rb +++ b/activesupport/lib/active_support/core_ext/range.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "active_support/core_ext/range/conversions" -require "active_support/core_ext/range/include_range" +require "active_support/core_ext/range/compare_range" require "active_support/core_ext/range/include_time_with_zone" require "active_support/core_ext/range/overlaps" require "active_support/core_ext/range/each" diff --git a/activesupport/lib/active_support/core_ext/range/compare_range.rb b/activesupport/lib/active_support/core_ext/range/compare_range.rb new file mode 100644 index 0000000000..6f6d2a27bb --- /dev/null +++ b/activesupport/lib/active_support/core_ext/range/compare_range.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module ActiveSupport + module CompareWithRange + # Extends the default Range#=== to support range comparisons. + # (1..5) === (1..5) # => true + # (1..5) === (2..3) # => true + # (1..5) === (2..6) # => false + # + # The native Range#=== behavior is untouched. + # ('a'..'f') === ('c') # => true + # (5..9) === (11) # => false + def ===(value) + if value.is_a?(::Range) + # 1...10 includes 1..9 but it does not include 1..10. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + super(value.first) && value.last.send(operator, last) + else + super + end + end + + # Extends the default Range#include? to support range comparisons. + # (1..5).include?(1..5) # => true + # (1..5).include?(2..3) # => true + # (1..5).include?(2..6) # => false + # + # The native Range#include? behavior is untouched. + # ('a'..'f').include?('c') # => true + # (5..9).include?(11) # => false + def include?(value) + if value.is_a?(::Range) + # 1...10 includes 1..9 but it does not include 1..10. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + super(value.first) && value.last.send(operator, last) + else + super + end + end + + # Extends the default Range#cover? to support range comparisons. + # (1..5).cover?(1..5) # => true + # (1..5).cover?(2..3) # => true + # (1..5).cover?(2..6) # => false + # + # The native Range#cover? behavior is untouched. + # ('a'..'f').cover?('c') # => true + # (5..9).cover?(11) # => false + def cover?(value) + if value.is_a?(::Range) + # 1...10 covers 1..9 but it does not cover 1..10. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + super(value.first) && value.last.send(operator, last) + else + super + end + end + end +end + +Range.prepend(ActiveSupport::CompareWithRange) diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb index 7ba1011921..2da2c587a3 100644 --- a/activesupport/lib/active_support/core_ext/range/include_range.rb +++ b/activesupport/lib/active_support/core_ext/range/include_range.rb @@ -1,25 +1,9 @@ # frozen_string_literal: true -module ActiveSupport - module IncludeWithRange #:nodoc: - # Extends the default Range#include? to support range comparisons. - # (1..5).include?(1..5) # => true - # (1..5).include?(2..3) # => true - # (1..5).include?(2..6) # => false - # - # The native Range#include? behavior is untouched. - # ('a'..'f').include?('c') # => true - # (5..9).include?(11) # => false - def include?(value) - if value.is_a?(::Range) - # 1...10 includes 1..9 but it does not include 1..10. - operator = exclude_end? && !value.exclude_end? ? :< : :<= - super(value.first) && value.last.send(operator, last) - else - super - end - end - end -end +require "active_support/deprecation" -Range.prepend(ActiveSupport::IncludeWithRange) +ActiveSupport::Deprecation.warn "You have required `active_support/core_ext/range/include_range`. " \ +"This file will be removed in Rails 6.1. You should require `active_support/core_ext/range/compare_range` " \ + "instead." + +require "active_support/core_ext/range/compare_range" diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index f3bdc2977e..d837bb10aa 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -149,9 +149,7 @@ module ActiveSupport #:nodoc: end def [](*args) - if args.size < 2 - super - elsif html_safe? + if html_safe? new_safe_buffer = super if new_safe_buffer diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 0f59558bb5..238a9f0ee6 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -79,6 +79,12 @@ module ActiveSupport #:nodoc: # to allow arbitrary constants to be marked for unloading. mattr_accessor :explicitly_unloadable_constants, default: [] + # The logger used when tracing autoloads. + mattr_accessor :logger + + # If true, trace autoloads with +logger.debug+. + mattr_accessor :verbose, default: false + # The WatchStack keeps a stack of the modules being watched as files are # loaded. If a file in the process of being loaded (parent.rb) triggers the # load of another file (child.rb) the stack will ensure that child.rb @@ -224,6 +230,8 @@ module ActiveSupport #:nodoc: Dependencies.require_or_load(file_name) end + # :doc: + # Interprets a file using <tt>mechanism</tt> and marks its defined # constants as autoloaded. <tt>file_name</tt> can be either a string or # respond to <tt>to_path</tt>. @@ -242,6 +250,8 @@ module ActiveSupport #:nodoc: Dependencies.depend_on(file_name, message) end + # :nodoc: + def load_dependency(file) if Dependencies.load? && Dependencies.constant_watch_stack.watching? Dependencies.new_constants_in(Object) { yield } @@ -341,7 +351,7 @@ module ActiveSupport #:nodoc: end def require_or_load(file_name, const_path = nil) - file_name = $` if file_name =~ /\.rb\z/ + file_name = file_name.chomp(".rb") expanded = File.expand_path(file_name) return if loaded.include?(expanded) @@ -391,7 +401,7 @@ module ActiveSupport #:nodoc: # constant paths which would cause Dependencies to attempt to load this # file. def loadable_constants_for_path(path, bases = autoload_paths) - path = $` if path =~ /\.rb\z/ + path = path.chomp(".rb") expanded_path = File.expand_path(path) paths = [] @@ -412,7 +422,7 @@ module ActiveSupport #:nodoc: # Search for a file in autoload_paths matching the provided suffix. def search_for_file(path_suffix) - path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze) + path_suffix += ".rb" unless path_suffix.ends_with?(".rb") autoload_paths.each do |root| path = File.join(root, path_suffix) @@ -446,6 +456,7 @@ module ActiveSupport #:nodoc: return nil unless base_path = autoloadable_module?(path_suffix) mod = Module.new into.const_set const_name, mod + log("constant #{qualified_name} autoloaded (module autovivified from #{File.join(base_path, path_suffix)})") autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path) autoloaded_constants.uniq! mod @@ -487,7 +498,7 @@ module ActiveSupport #:nodoc: raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!" end - qualified_name = qualified_name_for from_mod, const_name + qualified_name = qualified_name_for(from_mod, const_name) path_suffix = qualified_name.underscore file_path = search_for_file(path_suffix) @@ -500,8 +511,13 @@ module ActiveSupport #:nodoc: raise "Circular dependency detected while autoloading constant #{qualified_name}" else require_or_load(expanded, qualified_name) - raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false) - return from_mod.const_get(const_name) + + if from_mod.const_defined?(const_name, false) + log("constant #{qualified_name} autoloaded from #{expanded}.rb") + return from_mod.const_get(const_name) + else + raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" + end end elsif mod = autoload_module!(from_mod, const_name, qualified_name, path_suffix) return mod @@ -550,6 +566,7 @@ module ActiveSupport #:nodoc: # as the environment will be in an inconsistent state, e.g. other constants # may have already been unloaded and not accessible. def remove_unloadable_constants! + log("removing unloadable constants") autoloaded_constants.each { |const| remove_constant const } autoloaded_constants.clear Reference.clear! @@ -739,6 +756,10 @@ module ActiveSupport #:nodoc: # The constant is no longer reachable, just skip it. end end + + def log(message) + logger.debug("autoloading: #{message}") if logger && verbose + end end end diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 66d6f3225a..725667d139 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -43,7 +43,7 @@ module ActiveSupport deprecation_horizon: deprecation_horizon) }, - silence: ->(message, callstack, deprecation_horizon, gem_name) {}, + silence: ->(message, callstack, deprecation_horizon, gem_name) { }, } # Behavior module allows to determine how to display deprecation messages. @@ -94,6 +94,10 @@ module ActiveSupport private def arity_coerce(behavior) + unless behavior.respond_to?(:call) + raise ArgumentError, "#{behavior.inspect} is not a valid deprecation behavior." + end + if behavior.arity == 4 || behavior.arity == -1 behavior else diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index 5be893d281..81482092fe 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/module/aliasing" require "active_support/core_ext/array/extract_options" module ActiveSupport @@ -54,23 +53,26 @@ module ActiveSupport deprecator = options.delete(:deprecator) || self method_names += options.keys - mod = Module.new do - method_names.each do |method_name| - define_method(method_name) do |*args, &block| - deprecator.deprecation_warning(method_name, options[method_name]) - super(*args, &block) - end + method_names.each do |method_name| + aliased_method, punctuation = method_name.to_s.sub(/([?!=])$/, ""), $1 + with_method = "#{aliased_method}_with_deprecation#{punctuation}" + without_method = "#{aliased_method}_without_deprecation#{punctuation}" - case - when target_module.protected_method_defined?(method_name) - protected method_name - when target_module.private_method_defined?(method_name) - private method_name - end + target_module.send(:define_method, with_method) do |*args, &block| + deprecator.deprecation_warning(method_name, options[method_name]) + send(without_method, *args, &block) end - end - target_module.prepend(mod) + target_module.send(:alias_method, without_method, method_name) + target_module.send(:alias_method, method_name, with_method) + + case + when target_module.protected_method_defined?(without_method) + target_module.send(:protected, method_name) + when target_module.private_method_defined?(without_method) + target_module.send(:private, method_name) + end + end end end end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 896c0d2d8e..56f1e23136 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/regexp" - module ActiveSupport class Deprecation class DeprecationProxy #:nodoc: diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb index 1847eeaa86..414f727705 100644 --- a/activesupport/lib/active_support/duration/iso8601_parser.rb +++ b/activesupport/lib/active_support/duration/iso8601_parser.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "strscan" -require "active_support/core_ext/regexp" module ActiveSupport class Duration diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb index 84ae29c1ec..0fb0e3f3a5 100644 --- a/activesupport/lib/active_support/duration/iso8601_serializer.rb +++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb @@ -16,12 +16,12 @@ module ActiveSupport parts, sign = normalize return "PT0S".freeze if parts.empty? - output = "P".dup + output = +"P" output << "#{parts[:years]}Y" if parts.key?(:years) output << "#{parts[:months]}M" if parts.key?(:months) output << "#{parts[:weeks]}W" if parts.key?(:weeks) output << "#{parts[:days]}D" if parts.key?(:days) - time = "".dup + time = +"" time << "#{parts[:hours]}H" if parts.key?(:hours) time << "#{parts[:minutes]}M" if parts.key?(:minutes) if parts.key?(:seconds) diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index ce8bfbfd8c..584930e413 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support" -require "active_support/file_update_checker" require "active_support/core_ext/array/wrap" # :enddoc: @@ -88,9 +87,21 @@ module I18n when Hash, Array Array.wrap(fallbacks) else # TrueClass - [] + [I18n.default_locale] end + if args.empty? || args.first.is_a?(Hash) + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Using I18n fallbacks with an empty `defaults` sets the defaults to + include the `default_locale`. This behavior will change in Rails 6.1. + If you desire the default locale to be included in the defaults, please + explicitly configure it with `config.i18n.fallbacks.defaults = + [I18n.default_locale]` or `config.i18n.fallbacks = [I18n.default_locale, + {...}]` + MSG + args.unshift I18n.default_locale + end + I18n.fallbacks = I18n::Locale::Fallbacks.new(*args) end diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 7e5dff1d6d..2b86d233e5 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/core_ext/array/prepend_and_append" -require "active_support/core_ext/regexp" require "active_support/i18n" require "active_support/deprecation" diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 339b93b8da..7359de762a 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/inflections" -require "active_support/core_ext/regexp" module ActiveSupport # The Inflector transforms words from singular to plural, class names to table diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 78f7d7ca8d..00edcdd05a 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -59,7 +59,7 @@ module ActiveSupport if secret.blank? raise ArgumentError, "A secret is required to generate an integrity hash " \ "for cookie session data. Set a secret_key_base of at least " \ - "#{SECRET_MIN_LENGTH} characters in via `bin/rails credentials:edit`." + "#{SECRET_MIN_LENGTH} characters by running `rails credentials:edit`." end if secret.length < SECRET_MIN_LENGTH diff --git a/activesupport/lib/active_support/lazy_load_hooks.rb b/activesupport/lib/active_support/lazy_load_hooks.rb index dc8080c469..a6b096a973 100644 --- a/activesupport/lib/active_support/lazy_load_hooks.rb +++ b/activesupport/lib/active_support/lazy_load_hooks.rb @@ -68,7 +68,11 @@ module ActiveSupport if options[:yield] block.call(base) else - base.instance_eval(&block) + if base.is_a?(Module) + base.class_eval(&block) + else + base.instance_eval(&block) + end end end end diff --git a/activesupport/lib/active_support/locale/en.rb b/activesupport/lib/active_support/locale/en.rb index 26c2280c95..a2a7ea7ae1 100644 --- a/activesupport/lib/active_support/locale/en.rb +++ b/activesupport/lib/active_support/locale/en.rb @@ -5,16 +5,19 @@ number: { nth: { ordinals: lambda do |_key, number:, **_options| - abs_number = number.to_i.abs - - if (11..13).cover?(abs_number % 100) - "th" + case number + when 1; "st" + when 2; "nd" + when 3; "rd" + when 4, 5, 6, 7, 8, 9, 10, 11, 12, 13; "th" else - case abs_number % 10 - when 1 then "st" - when 2 then "nd" - when 3 then "rd" - else "th" + num_modulo = number.to_i.abs % 100 + num_modulo %= 10 if num_modulo > 13 + case num_modulo + when 1; "st" + when 2; "nd" + when 3; "rd" + else "th" end end end, diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 8b73270894..404404cad1 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -210,9 +210,7 @@ module ActiveSupport OpenSSL::Cipher.new(@cipher) end - def verifier - @verifier - end + attr_reader :verifier def aead_mode? @aead_mode ||= new_cipher.authenticated? diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 8152b8fd22..499a206f49 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -4,7 +4,6 @@ require "active_support/json" require "active_support/core_ext/string/access" require "active_support/core_ext/string/behavior" require "active_support/core_ext/module/delegation" -require "active_support/core_ext/regexp" module ActiveSupport #:nodoc: module Multibyte #:nodoc: diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 25aab175b4..4e4ca70942 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -70,12 +70,29 @@ module ActiveSupport module Subscribers # :nodoc: def self.new(pattern, listener) + subscriber_class = Timed + if listener.respond_to?(:start) && listener.respond_to?(:finish) - subscriber = Evented.new pattern, listener + subscriber_class = Evented else - subscriber = Timed.new pattern, listener + # Doing all this to detect a block like `proc { |x| }` vs + # `proc { |*x| }` or `proc { |**x| }` + if listener.respond_to?(:parameters) + params = listener.parameters + if params.length == 1 && params.first.first == :opt + subscriber_class = EventObject + end + end end + wrap_all pattern, subscriber_class.new(pattern, listener) + end + + def self.event_object_subscriber(pattern, block) + wrap_all pattern, EventObject.new(pattern, block) + end + + def self.wrap_all(pattern, subscriber) unless pattern AllMessages.new(subscriber) else @@ -130,6 +147,27 @@ module ActiveSupport end end + class EventObject < Evented + def start(name, id, payload) + stack = Thread.current[:_event_stack] ||= [] + event = build_event name, id, payload + event.start! + stack.push event + end + + def finish(name, id, payload) + stack = Thread.current[:_event_stack] + event = stack.pop + event.finish! + @delegate.call event + end + + private + def build_event(name, id, payload) + ActiveSupport::Notifications::Event.new name, nil, nil, id, payload + end + end + class AllMessages # :nodoc: def initialize(delegate) @delegate = delegate diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index e99f5ee688..f8344912bb 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -63,6 +63,42 @@ module ActiveSupport @end = ending @children = [] @duration = nil + @cpu_time_start = nil + @cpu_time_finish = nil + @allocation_count_start = 0 + @allocation_count_finish = 0 + end + + # Record information at the time this event starts + def start! + @time = now + @cpu_time_start = now_cpu + @allocation_count_start = now_allocations + end + + # Record information at the time this event finishes + def finish! + @cpu_time_finish = now_cpu + @end = now + @allocation_count_finish = now_allocations + end + + # Returns the CPU time (in milliseconds) passed since the call to + # +start!+ and the call to +finish!+ + def cpu_time + (@cpu_time_finish - @cpu_time_start) * 1000 + end + + # Returns the idle time time (in milliseconds) passed since the call to + # +start!+ and the call to +finish!+ + def idle_time + duration - cpu_time + end + + # Returns the number of allocations made since the call to +start!+ and + # the call to +finish!+ + def allocations + @allocation_count_finish - @allocation_count_start end # Returns the difference in milliseconds between when the execution of the @@ -88,6 +124,31 @@ module ActiveSupport def parent_of?(event) @children.include? event end + + private + def now + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + + if defined?(Process::CLOCK_PROCESS_CPUTIME_ID) + def now_cpu + Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID) + end + else + def now_cpu + 0 + end + end + + if defined?(JRUBY_VERSION) + def now_allocations + 0 + end + else + def now_allocations + GC.stat :total_allocated_objects + end + end end end end diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 8fd6e932f1..c75ad52b0c 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -85,6 +85,9 @@ module ActiveSupport # number given by <tt>:format</tt>). Accepts the same fields # than <tt>:format</tt>, except <tt>%n</tt> is here the # absolute value of the number. + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). # # ==== Examples # @@ -100,6 +103,8 @@ module ActiveSupport # # => "£1234567890,50" # number_to_currency(1234567890.50, unit: '£', separator: ',', delimiter: '', format: '%n %u') # # => "1234567890,50 £" + # number_to_currency(1234567890.50, strip_insignificant_zeros: true) + # # => "$1,234,567,890.5" def number_to_currency(number, options = {}) NumberToCurrencyConverter.convert(number, options) end diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index 8ad39f7a05..5a4c3d74af 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -79,7 +79,8 @@ module ActiveSupport end def start(name, id, payload) - e = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload) + e = ActiveSupport::Notifications::Event.new(name, nil, nil, id, payload) + e.start! parent = event_stack.last parent << e if parent @@ -87,9 +88,8 @@ module ActiveSupport end def finish(name, id, payload) - finished = Time.now - event = event_stack.pop - event.end = finished + event = event_stack.pop + event.finish! event.payload.merge!(payload) method = name.split(".".freeze).first @@ -97,7 +97,6 @@ module ActiveSupport end private - def event_stack SubscriberQueueRegistry.instance.get_queue(@queue_key) end diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index b069ac94d4..dd72da500c 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -61,8 +61,15 @@ module ActiveSupport end def self.new(logger) - # Ensure we set a default formatter so we aren't extending nil! - logger.formatter ||= ActiveSupport::Logger::SimpleFormatter.new + logger = logger.dup + + if logger.formatter + logger.formatter = logger.formatter.dup + else + # Ensure we set a default formatter so we aren't extending nil! + logger.formatter = ActiveSupport::Logger::SimpleFormatter.new + end + logger.formatter.extend Formatter logger.extend(self) end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index f17743b6db..ef12c6b9b0 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -65,8 +65,8 @@ module ActiveSupport # # parallelize(workers: 2, with: :threads) # - # The threaded parallelization uses Minitest's parallel executor directly. - # The processes parallelization uses a Ruby Drb server. + # The threaded parallelization uses minitest's parallel executor directly. + # The processes parallelization uses a Ruby DRb server. def parallelize(workers: 2, with: :processes) workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index a891ff616d..b27ac7ce99 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -113,11 +113,23 @@ module ActiveSupport # post :create, params: { article: invalid_attributes } # end # + # A lambda can be passed in and evaluated. + # + # assert_no_difference -> { Article.count } do + # post :create, params: { article: invalid_attributes } + # end + # # An error message can be specified. # # assert_no_difference 'Article.count', 'An Article should not be created' do # post :create, params: { article: invalid_attributes } # end + # + # An array of expressions can also be passed in and evaluated. + # + # assert_no_difference [ 'Article.count', -> { Post.count } ] do + # post :create, params: { article: invalid_attributes } + # end def assert_no_difference(expression, message = nil, &block) assert_difference expression, 0, message, &block end @@ -176,7 +188,9 @@ module ActiveSupport assert before != after, error unless to == UNTRACKED - error = "#{expression.inspect} didn't change to #{to}" + error = "#{expression.inspect} didn't change to as expected\n" + error = "#{error}Expected: #{to.inspect}\n" + error = "#{error} Actual: #{after.inspect}" error = "#{message}.\n#{error}" if message assert to === after, error end diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb index f655435729..18d63d2780 100644 --- a/activesupport/lib/active_support/testing/deprecation.rb +++ b/activesupport/lib/active_support/testing/deprecation.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/deprecation" -require "active_support/core_ext/regexp" module ActiveSupport module Testing diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb index c6358002ea..fdc70e1cd3 100644 --- a/activesupport/lib/active_support/testing/method_call_assertions.rb +++ b/activesupport/lib/active_support/testing/method_call_assertions.rb @@ -35,6 +35,35 @@ module ActiveSupport assert_called(object, method_name, message, times: 0, &block) end + # TODO: No need to resort to #send once support for Ruby 2.4 is + # dropped. + def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil) + times_called = 0 + klass.send(:define_method, "stubbed_#{method_name}") do |*| + times_called += 1 + + returns + end + + klass.send(:alias_method, "original_#{method_name}", method_name) + klass.send(:alias_method, method_name, "stubbed_#{method_name}") + + yield + + error = "Expected #{method_name} to be called #{times} times, but was called #{times_called} times" + error = "#{message}.\n#{error}" if message + + assert_equal times, times_called, error + ensure + klass.send(:alias_method, method_name, "original_#{method_name}") + klass.send(:undef_method, "original_#{method_name}") + klass.send(:undef_method, "stubbed_#{method_name}") + end + + def assert_not_called_on_instance_of(klass, method_name, message = nil, &block) + assert_called_on_instance_of(klass, method_name, message, times: 0, &block) + end + def stub_any_instance(klass, instance: klass.new) klass.stub(:new, instance) { yield instance } end diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb index 59c8486f41..beeb470659 100644 --- a/activesupport/lib/active_support/testing/parallelization.rb +++ b/activesupport/lib/active_support/testing/parallelization.rb @@ -26,25 +26,21 @@ module ActiveSupport def pop; @queue.pop; end end - @after_fork_hooks = [] + @@after_fork_hooks = [] def self.after_fork_hook(&blk) - @after_fork_hooks << blk + @@after_fork_hooks << blk end - def self.after_fork_hooks - @after_fork_hooks - end + cattr_reader :after_fork_hooks - @run_cleanup_hooks = [] + @@run_cleanup_hooks = [] def self.run_cleanup_hook(&blk) - @run_cleanup_hooks << blk + @@run_cleanup_hooks << blk end - def self.run_cleanup_hooks - @run_cleanup_hooks - end + cattr_reader :run_cleanup_hooks def initialize(queue_size) @queue_size = queue_size @@ -69,22 +65,24 @@ module ActiveSupport def start @pool = @queue_size.times.map do |worker| fork do - DRb.stop_service + begin + DRb.stop_service - after_fork(worker) + after_fork(worker) - queue = DRbObject.new_with_uri(@url) + queue = DRbObject.new_with_uri(@url) - while job = queue.pop - klass = job[0] - method = job[1] - reporter = job[2] - result = Minitest.run_one_method(klass, method) + while job = queue.pop + klass = job[0] + method = job[1] + reporter = job[2] + result = Minitest.run_one_method(klass, method) - queue.record(reporter, result) + queue.record(reporter, result) + end + ensure + run_cleanup(worker) end - - run_cleanup(worker) end end end diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 801ea2909b..f160e66971 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -158,7 +158,7 @@ module ActiveSupport end # Returns the current time back to its original state, by removing the stubs added by - # +travel+ and +travel_to+. + # +travel+, +travel_to+, and +freeze_time+. # # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel_to Time.zone.local(2004, 11, 24, 01, 04, 44) @@ -168,6 +168,7 @@ module ActiveSupport def travel_back simple_stubs.unstub_all! end + alias_method :unfreeze_time, :travel_back # Calls +travel_to+ with +Time.now+. # diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 7e71318404..fb6956f64f 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -286,8 +286,10 @@ module ActiveSupport alias_method :since, :+ alias_method :in, :+ - # Returns a new TimeWithZone object that represents the difference between - # the current object's time and the +other+ time. + # Subtracts an interval of time and returns a new TimeWithZone object unless + # the other value `acts_like?` time. Then it will return a Float of the difference + # between the two times that represents the difference between the current + # object's time and the +other+ time. # # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00 @@ -302,6 +304,12 @@ module ActiveSupport # # now - 24.hours # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 # now - 1.day # => Sun, 02 Nov 2014 00:26:28 EDT -04:00 + # + # If both the TimeWithZone object and the other value act like Time, a Float + # will be returned. + # + # Time.zone.now - 1.day.ago # => 86399.999967 + # def -(other) if other.acts_like?(:time) to_time - other.to_time diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 5f709c5fd9..fd07a3a6a2 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -265,7 +265,7 @@ module ActiveSupport private def load_country_zones(code) country = TZInfo::Country.get(code) - country.zone_identifiers.map do |tz_id| + country.zone_identifiers.flat_map do |tz_id| if MAPPING.value?(tz_id) MAPPING.inject([]) do |memo, (key, value)| memo << self[key] if value == tz_id @@ -274,7 +274,7 @@ module ActiveSupport else create(tz_id, nil, TZInfo::Timezone.new(tz_id)) end - end.flatten(1).sort! + end.sort! end def zones_map @@ -354,8 +354,13 @@ module ActiveSupport # Time.zone = 'Hawaii' # => "Hawaii" # Time.utc(2000).to_f # => 946684800.0 # Time.zone.at(946684800.0) # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - def at(secs) - Time.at(secs).utc.in_time_zone(self) + # + # A second argument can be supplied to specify sub-second precision. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.at(946684800, 123456.789).nsec # => 123456789 + def at(*args) + Time.at(*args).utc.in_time_zone(self) end # Method for creating new ActiveSupport::TimeWithZone instance in time zone diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index 7f94a64016..59c65db2d5 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -169,7 +169,7 @@ module ActiveSupport # element:: # XML element to be checked. def empty_content?(element) - text = "".dup + text = +"" child_nodes = element.child_nodes (0...child_nodes.length).each do |i| item = child_nodes.item(i) diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index 0b000fea60..2a16932f03 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -55,7 +55,7 @@ module LibXML #:nodoc: if c.element? c.to_hash(node_hash) elsif c.text? || c.cdata? - node_hash[CONTENT_ROOT] ||= "".dup + node_hash[CONTENT_ROOT] ||= +"" node_hash[CONTENT_ROOT] << c.content end end diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb index dcf16e6084..a22a2c9cb7 100644 --- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb +++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb @@ -23,7 +23,7 @@ module ActiveSupport end def on_start_document - @hash = { CONTENT_KEY => "".dup } + @hash = { CONTENT_KEY => +"" } @hash_stack = [@hash] end @@ -33,7 +33,7 @@ module ActiveSupport end def on_start_element(name, attrs = {}) - new_hash = { CONTENT_KEY => "".dup }.merge!(attrs) + new_hash = { CONTENT_KEY => +"" }.merge!(attrs) new_hash[HASH_SIZE_KEY] = new_hash.size + 1 case current_hash[name] diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb index 5ee6fc8159..4762a759d6 100644 --- a/activesupport/lib/active_support/xml_mini/nokogiri.rb +++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb @@ -59,7 +59,7 @@ module ActiveSupport if c.element? c.to_hash(node_hash) elsif c.text? || c.cdata? - node_hash[CONTENT_ROOT] ||= "".dup + node_hash[CONTENT_ROOT] ||= +"" node_hash[CONTENT_ROOT] << c.content end end diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb index b01ed00a14..0bbb4e258a 100644 --- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb +++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb @@ -39,7 +39,7 @@ module ActiveSupport end def start_element(name, attrs = []) - new_hash = { CONTENT_KEY => "".dup }.merge!(Hash[attrs]) + new_hash = { CONTENT_KEY => +"" }.merge!(Hash[attrs]) new_hash[HASH_SIZE_KEY] = new_hash.size + 1 case current_hash[name] diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb index 32458d5b0d..55a155d4ee 100644 --- a/activesupport/lib/active_support/xml_mini/rexml.rb +++ b/activesupport/lib/active_support/xml_mini/rexml.rb @@ -76,7 +76,7 @@ module ActiveSupport hash else # must use value to prevent double-escaping - texts = "".dup + texts = +"" element.texts.each { |t| texts << t.value } merge!(hash, CONTENT_KEY, texts) end |