diff options
Diffstat (limited to 'activesupport/lib/active_support')
166 files changed, 1997 insertions, 1272 deletions
diff --git a/activesupport/lib/active_support/all.rb b/activesupport/lib/active_support/all.rb index 6638b25419..4adf446af8 100644 --- a/activesupport/lib/active_support/all.rb +++ b/activesupport/lib/active_support/all.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true require "active_support" -require_relative "time" -require_relative "core_ext" +require "active_support/time" +require "active_support/core_ext" diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb index 5573f6750e..f481d68198 100644 --- a/activesupport/lib/active_support/benchmarkable.rb +++ b/activesupport/lib/active_support/benchmarkable.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "core_ext/benchmark" -require_relative "core_ext/hash/keys" +require "active_support/core_ext/benchmark" +require "active_support/core_ext/hash/keys" module ActiveSupport module Benchmarkable diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 49d8965cb1..d769e2c8ea 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -1,21 +1,22 @@ # frozen_string_literal: true require "zlib" -require_relative "core_ext/array/extract_options" -require_relative "core_ext/array/wrap" -require_relative "core_ext/module/attribute_accessors" -require_relative "core_ext/numeric/bytes" -require_relative "core_ext/numeric/time" -require_relative "core_ext/object/to_param" -require_relative "core_ext/string/inflections" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/array/wrap" +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/string/inflections" module ActiveSupport # See ActiveSupport::Cache::Store for documentation. module Cache - autoload :FileStore, "active_support/cache/file_store" - autoload :MemoryStore, "active_support/cache/memory_store" - autoload :MemCacheStore, "active_support/cache/mem_cache_store" - autoload :NullStore, "active_support/cache/null_store" + autoload :FileStore, "active_support/cache/file_store" + autoload :MemoryStore, "active_support/cache/memory_store" + autoload :MemCacheStore, "active_support/cache/mem_cache_store" + autoload :NullStore, "active_support/cache/null_store" + autoload :RedisCacheStore, "active_support/cache/redis_cache_store" # These options mean something to all cache implementations. Individual cache # implementations may support additional options. @@ -148,18 +149,34 @@ module ActiveSupport # cache.namespace = -> { @last_mod_time } # Set the namespace to a variable # @last_mod_time = Time.now # Invalidate the entire cache by changing namespace # - # Caches can also store values in a compressed format to save space and - # reduce time spent sending data. Since there is overhead, values must be - # large enough to warrant compression. To turn on compression either pass - # <tt>compress: true</tt> in the initializer or as an option to +fetch+ - # or +write+. To specify the threshold at which to compress values, set the - # <tt>:compress_threshold</tt> option. The default threshold is 16K. + # Cached data larger than 1kB are compressed by default. To turn off + # compression, pass <tt>compress: false</tt> to the initializer or to + # individual +fetch+ or +write+ method calls. The 1kB compression + # threshold is configurable with the <tt>:compress_threshold</tt> option, + # specified in bytes. class Store cattr_accessor :logger, instance_writer: true attr_reader :silence, :options alias :silence? :silence + class << self + private + def retrieve_pool_options(options) + {}.tap do |pool_options| + pool_options[:size] = options.delete(:pool_size) if options[:pool_size] + pool_options[:timeout] = options.delete(:pool_timeout) if options[:pool_timeout] + end + end + + def ensure_connection_pool_added! + require "connection_pool" + rescue LoadError => e + $stderr.puts "You don't have connection_pool installed in your application. Please add it to your Gemfile and run bundle install" + raise e + end + end + # Creates a new cache. The options will be passed to any write method calls # except for <tt>:namespace</tt> which can be used to set the global # namespace for the cache. @@ -212,8 +229,7 @@ module ActiveSupport # ask whether you should force a cache write. Otherwise, it's clearer to # just call <tt>Cache#write</tt>. # - # Setting <tt>:compress</tt> will store a large cache entry set by the call - # in a compressed format. + # Setting <tt>compress: false</tt> disables compression of the cache entry. # # Setting <tt>:expires_in</tt> will set an expiration time on the cache. # All caches support auto-expiring content after a specified number of @@ -317,8 +333,9 @@ module ActiveSupport # the cache with the given key, then that data is returned. Otherwise, # +nil+ is returned. # - # Note, if data was written with the <tt>:expires_in<tt> or <tt>:version</tt> options, - # both of these conditions are applied before the data is returned. + # Note, if data was written with the <tt>:expires_in</tt> or + # <tt>:version</tt> options, both of these conditions are applied before + # the data is returned. # # Options are passed to the underlying cache implementation. def read(name, options = nil) @@ -358,23 +375,11 @@ module ActiveSupport options = names.extract_options! options = merged_options(options) - results = {} - names.each do |name| - key = normalize_key(name, options) - version = normalize_version(name, options) - entry = read_entry(key, options) - - if entry - if entry.expired? - delete_entry(key, options) - elsif entry.mismatched?(version) - # Skip mismatched versions - else - results[name] = entry.value - end + instrument :read_multi, names, options do |payload| + read_multi_entries(names, options).tap do |results| + payload[:hits] = results.keys end end - results end # Cache Storage API to write multiple values at once. @@ -415,14 +420,19 @@ module ActiveSupport options = names.extract_options! options = merged_options(options) - read_multi(*names, options).tap do |results| - writes = {} + instrument :read_multi, names, options do |payload| + read_multi_entries(names, options).tap do |results| + payload[:hits] = results.keys + payload[:super_operation] = :fetch_multi - (names - results.keys).each do |name| - results[name] = writes[name] = yield(name) - end + writes = {} + + (names - results.keys).each do |name| + results[name] = writes[name] = yield(name) + end - write_multi writes, options + write_multi writes, options + end end end @@ -539,6 +549,28 @@ module ActiveSupport raise NotImplementedError.new end + # Reads multiple entries from the cache implementation. Subclasses MAY + # implement this method. + def read_multi_entries(names, options) + results = {} + names.each do |name| + key = normalize_key(name, options) + version = normalize_version(name, options) + entry = read_entry(key, options) + + if entry + if entry.expired? + delete_entry(key, options) + elsif entry.mismatched?(version) + # Skip mismatched versions + else + results[name] = entry.value + end + end + end + results + end + # Writes multiple entries to the cache implementation. Subclasses MAY # implement this method. def write_multi_entries(hash, options) @@ -562,14 +594,34 @@ module ActiveSupport end end - # Prefixes a key with the namespace. Namespace and key will be delimited - # with a colon. - def normalize_key(key, options) - key = expanded_key(key) - namespace = options[:namespace] if options - prefix = namespace.is_a?(Proc) ? namespace.call : namespace - key = "#{prefix}:#{key}" if prefix - key + # Expands and namespaces the cache key. May be overridden by + # cache stores to do additional normalization. + def normalize_key(key, options = nil) + namespace_key expanded_key(key), options + end + + # Prefix the key with a namespace string: + # + # namespace_key 'foo', namespace: 'cache' + # # => 'cache:foo' + # + # With a namespace block: + # + # namespace_key 'foo', namespace: -> { 'cache' } + # # => 'cache:foo' + def namespace_key(key, options = nil) + options = merged_options(options) + namespace = options[:namespace] + + if namespace.respond_to?(:call) + namespace = namespace.call + end + + if namespace + "#{namespace}:#{key}" + else + key + end end # Expands key to be a consistent string value. Invokes +cache_key+ if @@ -658,22 +710,17 @@ module ActiveSupport class Entry # :nodoc: attr_reader :version - DEFAULT_COMPRESS_LIMIT = 16.kilobytes + DEFAULT_COMPRESS_LIMIT = 1.kilobyte # Creates a new cache entry for the specified value. Options supported are - # +:compress+, +:compress_threshold+, and +:expires_in+. - def initialize(value, options = {}) - if should_compress?(value, options) - @value = compress(value) - @compressed = true - else - @value = value - end - - @version = options[:version] + # +:compress+, +:compress_threshold+, +:version+ and +:expires_in+. + def initialize(value, compress: true, compress_threshold: DEFAULT_COMPRESS_LIMIT, version: nil, expires_in: nil, **) + @value = value + @version = version @created_at = Time.now.to_f - @expires_in = options[:expires_in] - @expires_in = @expires_in.to_f if @expires_in + @expires_in = expires_in && expires_in.to_f + + compress!(compress_threshold) if compress end def value @@ -705,17 +752,13 @@ module ActiveSupport # Returns the size of the cached value. This could be less than # <tt>value.size</tt> if the data is compressed. def size - if defined?(@s) - @s + case value + when NilClass + 0 + when String + @value.bytesize else - case value - when NilClass - 0 - when String - @value.bytesize - else - @s = Marshal.dump(@value).bytesize - end + @s ||= Marshal.dump(@value).bytesize end end @@ -732,23 +775,30 @@ module ActiveSupport end private - def should_compress?(value, options) - if value && options[:compress] - compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT - serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize - - return true if serialized_value_size >= compress_threshold + def compress!(compress_threshold) + case @value + when nil, true, false, Numeric + uncompressed_size = 0 + when String + uncompressed_size = @value.bytesize + else + serialized = Marshal.dump(@value) + uncompressed_size = serialized.bytesize end - false - end + if uncompressed_size >= compress_threshold + serialized ||= Marshal.dump(@value) + compressed = Zlib::Deflate.deflate(serialized) - def compressed? - defined?(@compressed) ? @compressed : false + if compressed.bytesize < uncompressed_size + @value = compressed + @compressed = true + end + end end - def compress(value) - Zlib::Deflate.deflate(Marshal.dump(value)) + def compressed? + defined?(@compressed) end def uncompress(value) diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 28df2c066e..a0f44aac0f 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative "../core_ext/marshal" -require_relative "../core_ext/file/atomic" -require_relative "../core_ext/string/conversions" +require "active_support/core_ext/marshal" +require "active_support/core_ext/file/atomic" +require "active_support/core_ext/string/conversions" require "uri/common" module ActiveSupport @@ -39,9 +39,8 @@ module ActiveSupport def cleanup(options = nil) options = merged_options(options) search_dir(cache_path) do |fname| - key = file_path_key(fname) - entry = read_entry(key, options) - delete_entry(key, options) if entry && entry.expired? + entry = read_entry(fname, options) + delete_entry(fname, options) if entry && entry.expired? end end @@ -122,7 +121,7 @@ module ActiveSupport fname = URI.encode_www_form_component(key) if fname.size > FILEPATH_MAX_SIZE - fname = Digest::MD5.hexdigest(key) + fname = ActiveSupport::Digest.hexdigest(key) end hash = Zlib.adler32(fname) diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index d7bd914722..2840781dde 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -7,9 +7,8 @@ rescue LoadError => e raise e end -require "digest/md5" -require_relative "../core_ext/marshal" -require_relative "../core_ext/array/extract_options" +require "active_support/core_ext/marshal" +require "active_support/core_ext/array/extract_options" module ActiveSupport module Cache @@ -64,7 +63,14 @@ module ActiveSupport addresses = addresses.flatten options = addresses.extract_options! addresses = ["localhost:11211"] if addresses.empty? - Dalli::Client.new(addresses, options) + pool_options = retrieve_pool_options(options) + + if pool_options.empty? + Dalli::Client.new(addresses, options) + else + ensure_connection_pool_added! + ConnectionPool.new(pool_options) { Dalli::Client.new(addresses, options.merge(threadsafe: false)) } + end end # Creates a new MemCacheStore object, with the given memcached server @@ -92,28 +98,6 @@ module ActiveSupport end end - # Reads multiple values from the cache using a single call to the - # servers for all keys. Options can be passed in the last argument. - def read_multi(*names) - options = names.extract_options! - options = merged_options(options) - - keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }] - - raw_values = @data.get_multi(keys_to_names.keys) - values = {} - - raw_values.each do |key, value| - entry = deserialize_entry(value) - - unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options)) - values[keys_to_names[key]] = entry.value - end - end - - values - end - # Increment a cached value. This method uses the memcached incr atomic # operator and can only be used on values written with the :raw option. # Calling it on a value not stored with :raw will initialize that value @@ -122,7 +106,7 @@ module ActiveSupport options = merged_options(options) instrument(:increment, name, amount: amount) do rescue_error_with nil do - @data.incr(normalize_key(name, options), amount) + @data.with { |c| c.incr(normalize_key(name, options), amount, options[:expires_in]) } end end end @@ -135,7 +119,7 @@ module ActiveSupport options = merged_options(options) instrument(:decrement, name, amount: amount) do rescue_error_with nil do - @data.decr(normalize_key(name, options), amount) + @data.with { |c| c.decr(normalize_key(name, options), amount, options[:expires_in]) } end end end @@ -143,18 +127,18 @@ module ActiveSupport # Clear the entire cache on all memcached servers. This method should # be used with care when shared cache is being used. def clear(options = nil) - rescue_error_with(nil) { @data.flush_all } + rescue_error_with(nil) { @data.with { |c| c.flush_all } } end # Get the statistics from the memcached servers. def stats - @data.stats + @data.with { |c| c.stats } end private # Read an entry from the cache. def read_entry(key, options) - rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) } + rescue_error_with(nil) { deserialize_entry(@data.with { |c| c.get(key, options) }) } end # Write an entry to the cache. @@ -167,13 +151,31 @@ module ActiveSupport expires_in += 5.minutes end rescue_error_with false do - @data.send(method, key, value, expires_in, options) + @data.with { |c| c.send(method, key, value, expires_in, options) } + end + end + + # Reads multiple entries from the cache implementation. + def read_multi_entries(names, options) + keys_to_names = Hash[names.map { |name| [normalize_key(name, options), name] }] + + raw_values = @data.with { |c| c.get_multi(keys_to_names.keys) } + values = {} + + raw_values.each do |key, value| + entry = deserialize_entry(value) + + unless entry.expired? || entry.mismatched?(normalize_version(keys_to_names[key], options)) + values[keys_to_names[key]] = entry.value + end end + + values end # Delete an entry from the cache. def delete_entry(key, options) - rescue_error_with(false) { @data.delete(key) } + rescue_error_with(false) { @data.with { |c| c.delete(key) } } end # Memcache keys are binaries. So we need to force their encoding to binary @@ -183,7 +185,7 @@ module ActiveSupport key = super.dup key = key.force_encoding(Encoding::ASCII_8BIT) key = key.gsub(ESCAPE_KEY_CHARS) { |match| "%#{match.getbyte(0).to_s(16).upcase}" } - key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250 + key = "#{key[0, 213]}:md5:#{ActiveSupport::Digest.hexdigest(key)}" if key.size > 250 key end diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb new file mode 100644 index 0000000000..11c574258f --- /dev/null +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -0,0 +1,460 @@ +# frozen_string_literal: true + +begin + gem "redis", ">= 4.0.1" + require "redis" + require "redis/distributed" +rescue LoadError + warn "The Redis cache store requires the redis gem, version 4.0.1 or later. Please add it to your Gemfile: `gem \"redis\", \"~> 4.0\"`" + raise +end + +# Prefer the hiredis driver but don't require it. +begin + require "redis/connection/hiredis" +rescue LoadError +end + +require "digest/sha2" +require "active_support/core_ext/marshal" + +module ActiveSupport + module Cache + module ConnectionPoolLike + def with + yield self + end + end + + ::Redis.include(ConnectionPoolLike) + ::Redis::Distributed.include(ConnectionPoolLike) + + # Redis cache store. + # + # Deployment note: Take care to use a *dedicated Redis cache* rather + # than pointing this at your existing Redis server. It won't cope well + # with mixed usage patterns and it won't expire cache entries by default. + # + # Redis cache server setup guide: https://redis.io/topics/lru-cache + # + # * Supports vanilla Redis, hiredis, and Redis::Distributed. + # * Supports Memcached-like sharding across Redises with Redis::Distributed. + # * Fault tolerant. If the Redis server is unavailable, no exceptions are + # raised. Cache fetches are all misses and writes are dropped. + # * Local cache. Hot in-memory primary cache within block/middleware scope. + # * +read_multi+ and +write_multi+ support for Redis mget/mset. Use Redis::Distributed + # 4.0.1+ for distributed mget support. + # * +delete_matched+ support for Redis KEYS globs. + class RedisCacheStore < Store + # Keys are truncated with their own SHA2 digest if they exceed 1kB + MAX_KEY_BYTESIZE = 1024 + + DEFAULT_REDIS_OPTIONS = { + connect_timeout: 20, + read_timeout: 1, + write_timeout: 1, + reconnect_attempts: 0, + } + + DEFAULT_ERROR_HANDLER = -> (method:, returning:, exception:) do + if logger + logger.error { "RedisCacheStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" } + end + end + + # The maximum number of entries to receive per SCAN call. + SCAN_BATCH_SIZE = 1000 + private_constant :SCAN_BATCH_SIZE + + # Support raw values in the local cache strategy. + module LocalCacheWithRaw # :nodoc: + private + def read_entry(key, options) + entry = super + if options[:raw] && local_cache && entry + entry = deserialize_entry(entry.value) + end + entry + end + + def write_entry(key, entry, options) + if options[:raw] && local_cache + raw_entry = Entry.new(serialize_entry(entry, raw: true)) + raw_entry.expires_at = entry.expires_at + super(key, raw_entry, options) + else + super + end + end + + def write_multi_entries(entries, options) + if options[:raw] && local_cache + raw_entries = entries.map do |key, entry| + raw_entry = Entry.new(serialize_entry(entry, raw: true)) + raw_entry.expires_at = entry.expires_at + end.to_h + + super(raw_entries, options) + else + super + end + end + end + + prepend Strategy::LocalCache + prepend LocalCacheWithRaw + + class << self + # Factory method to create a new Redis instance. + # + # Handles four options: :redis block, :redis instance, single :url + # string, and multiple :url strings. + # + # Option Class Result + # :redis Proc -> options[:redis].call + # :redis Object -> options[:redis] + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + def build_redis(redis: nil, url: nil, **redis_options) #:nodoc: + urls = Array(url) + + if redis.is_a?(Proc) + redis.call + elsif redis + redis + elsif urls.size > 1 + build_redis_distributed_client urls: urls, **redis_options + else + build_redis_client url: urls.first, **redis_options + end + end + + private + def build_redis_distributed_client(urls:, **redis_options) + ::Redis::Distributed.new([], DEFAULT_REDIS_OPTIONS.merge(redis_options)).tap do |dist| + urls.each { |u| dist.add_node url: u } + end + end + + def build_redis_client(url:, **redis_options) + ::Redis.new DEFAULT_REDIS_OPTIONS.merge(redis_options.merge(url: url)) + end + end + + attr_reader :redis_options + attr_reader :max_key_bytesize + + # Creates a new Redis cache store. + # + # Handles three options: block provided to instantiate, single URL + # provided, and multiple URLs provided. + # + # :redis Proc -> options[:redis].call + # :url String -> Redis.new(url: …) + # :url Array -> Redis::Distributed.new([{ url: … }, { url: … }, …]) + # + # No namespace is set by default. Provide one if the Redis cache + # server is shared with other apps: <tt>namespace: 'myapp-cache'</tt>. + # + # Compression is enabled by default with a 1kB threshold, so cached + # values larger than 1kB are automatically compressed. Disable by + # passing <tt>compress: false</tt> or change the threshold by passing + # <tt>compress_threshold: 4.kilobytes</tt>. + # + # No expiry is set on cache entries by default. Redis is expected to + # be configured with an eviction policy that automatically deletes + # least-recently or -frequently used keys when it reaches max memory. + # See https://redis.io/topics/lru-cache for cache server setup. + # + # Race condition TTL is not set by default. This can be used to avoid + # "thundering herd" cache writes when hot cache entries are expired. + # See <tt>ActiveSupport::Cache::Store#fetch</tt> for more. + def initialize(namespace: nil, compress: true, compress_threshold: 1.kilobyte, expires_in: nil, race_condition_ttl: nil, error_handler: DEFAULT_ERROR_HANDLER, **redis_options) + @redis_options = redis_options + + @max_key_bytesize = MAX_KEY_BYTESIZE + @error_handler = error_handler + + super namespace: namespace, + compress: compress, compress_threshold: compress_threshold, + expires_in: expires_in, race_condition_ttl: race_condition_ttl + end + + def redis + @redis ||= begin + pool_options = self.class.send(:retrieve_pool_options, redis_options) + + if pool_options.any? + self.class.send(:ensure_connection_pool_added!) + ::ConnectionPool.new(pool_options) { self.class.build_redis(**redis_options) } + else + self.class.build_redis(**redis_options) + end + end + end + + def inspect + instance = @redis || @redis_options + "<##{self.class} options=#{options.inspect} redis=#{instance.inspect}>" + end + + # Cache Store API implementation. + # + # Read multiple values at once. Returns a hash of requested keys -> + # fetched values. + def read_multi(*names) + if mget_capable? + instrument(:read_multi, names, options) do |payload| + read_multi_mget(*names).tap do |results| + payload[:hits] = results.keys + end + end + else + super + end + end + + # Cache Store API implementation. + # + # Supports Redis KEYS glob patterns: + # + # h?llo matches hello, hallo and hxllo + # h*llo matches hllo and heeeello + # h[ae]llo matches hello and hallo, but not hillo + # h[^e]llo matches hallo, hbllo, ... but not hello + # h[a-b]llo matches hallo and hbllo + # + # Use \ to escape special characters if you want to match them verbatim. + # + # See https://redis.io/commands/KEYS for more. + # + # Failsafe: Raises errors. + def delete_matched(matcher, options = nil) + instrument :delete_matched, matcher do + unless String === matcher + raise ArgumentError, "Only Redis glob strings are supported: #{matcher.inspect}" + end + redis.with do |c| + pattern = namespace_key(matcher, options) + cursor = "0" + # Fetch keys in batches using SCAN to avoid blocking the Redis server. + begin + cursor, keys = c.scan(cursor, match: pattern, count: SCAN_BATCH_SIZE) + c.del(*keys) unless keys.empty? + end until cursor == "0" + end + end + end + + # Cache Store API implementation. + # + # Increment a cached value. This method uses the Redis incr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + 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 } + end + end + end + + # Cache Store API implementation. + # + # Decrement a cached value. This method uses the Redis decr atomic + # operator and can only be used on values written with the :raw option. + # Calling it on a value not stored with :raw will initialize that value + # to zero. + # + # Failsafe: Raises errors. + 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 } + end + end + end + + # Cache Store API implementation. + # + # Removes expired entries. Handled natively by Redis least-recently-/ + # least-frequently-used expiry, so manual cleanup is not supported. + def cleanup(options = nil) + super + end + + # Clear the entire cache on all Redis servers. Safe to use on + # shared servers if the cache is namespaced. + # + # Failsafe: Raises errors. + def clear(options = nil) + failsafe :clear do + if namespace = merged_options(options)[:namespace] + delete_matched "*", namespace: namespace + else + redis.with { |c| c.flushdb } + end + end + end + + def mget_capable? #:nodoc: + set_redis_capabilities unless defined? @mget_capable + @mget_capable + end + + def mset_capable? #:nodoc: + set_redis_capabilities unless defined? @mset_capable + @mset_capable + end + + private + def set_redis_capabilities + case redis + when Redis::Distributed + @mget_capable = true + @mset_capable = false + else + @mget_capable = true + @mset_capable = true + end + end + + # Store provider interface: + # Read an entry from the cache. + def read_entry(key, options = nil) + failsafe :read_entry do + deserialize_entry redis.with { |c| c.get(key) } + end + end + + def read_multi_entries(names, _options) + if mget_capable? + read_multi_mget(*names) + else + super + end + end + + def read_multi_mget(*names) + options = names.extract_options! + options = merged_options(options) + + keys = names.map { |name| normalize_key(name, options) } + + values = failsafe(:read_multi_mget, returning: {}) do + redis.with { |c| c.mget(*keys) } + end + + names.zip(values).each_with_object({}) do |(name, value), results| + if value + entry = deserialize_entry(value) + unless entry.nil? || entry.expired? || entry.mismatched?(normalize_version(name, options)) + results[name] = entry.value + end + end + end + end + + # Write an entry to the cache. + # + # Requires Redis 2.6.12+ for extended SET options. + def write_entry(key, entry, unless_exist: false, raw: false, expires_in: nil, race_condition_ttl: nil, **options) + serialized_entry = serialize_entry(entry, raw: raw) + + # If race condition TTL is in use, ensure that cache entries + # stick around a bit longer after they would have expired + # so we can purposefully serve stale entries. + if race_condition_ttl && expires_in && expires_in > 0 && !raw + expires_in += 5.minutes + end + + failsafe :write_entry, returning: false do + if unless_exist || expires_in + modifiers = {} + modifiers[:nx] = unless_exist + modifiers[:px] = (1000 * expires_in.to_f).ceil if expires_in + + redis.with { |c| c.set key, serialized_entry, modifiers } + else + redis.with { |c| c.set key, serialized_entry } + end + end + end + + # Delete an entry from the cache. + def delete_entry(key, options) + failsafe :delete_entry, returning: false do + redis.with { |c| c.del key } + end + end + + # Nonstandard store provider API to write multiple values at once. + def write_multi_entries(entries, expires_in: nil, **options) + if entries.any? + if mset_capable? && expires_in.nil? + failsafe :write_multi_entries do + redis.with { |c| c.mapped_mset(serialize_entries(entries, raw: options[:raw])) } + end + else + super + end + end + end + + # Truncate keys that exceed 1kB. + def normalize_key(key, options) + truncate_key super.b + end + + def truncate_key(key) + if key.bytesize > max_key_bytesize + suffix = ":sha2:#{::Digest::SHA2.hexdigest(key)}" + truncate_at = max_key_bytesize - suffix.bytesize + "#{key.byteslice(0, truncate_at)}#{suffix}" + else + key + end + end + + def deserialize_entry(serialized_entry) + if serialized_entry + entry = Marshal.load(serialized_entry) rescue serialized_entry + entry.is_a?(Entry) ? entry : Entry.new(entry) + end + end + + def serialize_entry(entry, raw: false) + if raw + entry.value.to_s + else + Marshal.dump(entry) + end + end + + def serialize_entries(entries, raw: false) + entries.transform_values do |entry| + serialize_entry entry, raw: raw + end + end + + def failsafe(method, returning: nil) + yield + rescue ::Redis::BaseConnectionError => e + handle_exception exception: e, method: method, returning: returning + returning + end + + def handle_exception(exception:, method:, returning:) + if @error_handler + @error_handler.(method: method, exception: exception, returning: returning) + end + rescue => failsafe + warn "RedisCacheStore ignored exception in handle_exception: #{failsafe.class}: #{failsafe.message}\n #{failsafe.backtrace.join("\n ")}" + end + end + end +end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index a3eef1190a..39b32fc7f6 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative "../../core_ext/object/duplicable" -require_relative "../../core_ext/string/inflections" -require_relative "../../per_thread_registry" +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/string/inflections" +require "active_support/per_thread_registry" module ActiveSupport module Cache @@ -54,6 +54,17 @@ module ActiveSupport @data[key] end + def read_multi_entries(keys, options) + values = {} + + keys.each do |name| + entry = read_entry(name, options) + values[name] = entry.value if entry + end + + values + end + def write_entry(key, value, options) @data[key] = value true @@ -116,6 +127,19 @@ module ActiveSupport end end + def read_multi_entries(keys, options) + return super unless local_cache + + local_entries = local_cache.read_multi_entries(keys, options) + missed_keys = keys - local_entries.keys + + if missed_keys.any? + local_entries.merge!(super(missed_keys, options)) + else + local_entries + end + end + def write_entry(key, entry, options) if options[:unless_exist] local_cache.delete_entry(key, options) if local_cache diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index e1cbfc945c..a1b841ec3d 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require_relative "concern" -require_relative "descendants_tracker" -require_relative "core_ext/array/extract_options" -require_relative "core_ext/class/attribute" -require_relative "core_ext/kernel/reporting" -require_relative "core_ext/kernel/singleton_class" -require_relative "core_ext/string/filters" -require_relative "deprecation" +require "active_support/concern" +require "active_support/descendants_tracker" +require "active_support/core_ext/array/extract_options" +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 @@ -298,8 +298,8 @@ module ActiveSupport @kind = kind @filter = filter @key = compute_identifier filter - @if = Array(options[:if]) - @unless = Array(options[:unless]) + @if = check_conditionals(Array(options[:if])) + @unless = check_conditionals(Array(options[:unless])) end def filter; @key; end @@ -323,7 +323,7 @@ module ActiveSupport def duplicates?(other) case @filter - when Symbol, String + when Symbol matches?(other.kind, other.filter) else false @@ -350,9 +350,21 @@ module ActiveSupport end private + def check_conditionals(conditionals) + if conditionals.any? { |c| c.is_a?(String) } + raise ArgumentError, <<-MSG.squish + Passing string to be evaluated in :if and :unless conditional + options is not supported. Pass a symbol for an instance method, + or a lambda, proc or block, instead. + MSG + end + + conditionals + end + def compute_identifier(filter) case filter - when String, ::Proc + when ::Proc filter.object_id else filter @@ -427,7 +439,6 @@ module ActiveSupport # Filters support: # # Symbols:: A method to call. - # Strings:: Some content to evaluate. # Procs:: A proc to call with the object. # Objects:: An object with a <tt>before_foo</tt> method on it to call. # @@ -437,8 +448,6 @@ module ActiveSupport case filter when Symbol new(nil, filter, [], nil) - when String - new(nil, :instance_exec, [:value], compile_lambda(filter)) when Conditionals::Value new(filter, :call, [:target, :value], nil) when ::Proc @@ -455,10 +464,6 @@ module ActiveSupport new(filter, method_to_call, [:target], nil) end end - - def self.compile_lambda(filter) - eval("lambda { |value| #{filter} }") - end end # Execute before and after filters in a sequence instead of @@ -651,26 +656,17 @@ module ActiveSupport # # ===== Options # - # * <tt>:if</tt> - A symbol, a string (deprecated) 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. - # * <tt>:unless</tt> - A symbol, a string (deprecated) 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. + # * <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. + # * <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. # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the # existing chain rather than appended. def set_callback(name, *filter_list, &block) type, filters, options = normalize_callback_params(filter_list, block) - if options[:if].is_a?(String) || options[:unless].is_a?(String) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing string to be evaluated in :if and :unless conditional - options is deprecated and will be removed in Rails 5.2 without - replacement. Pass a symbol for an instance method, or a lambda, - proc or block, instead. - MSG - end - self_chain = get_callbacks name mapped = filters.map do |filter| Callback.build(self_chain, filter, type, options) @@ -695,13 +691,6 @@ module ActiveSupport def skip_callback(name, *filter_list, &block) type, filters, options = normalize_callback_params(filter_list, block) - if options[:if].is_a?(String) || options[:unless].is_a?(String) - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Passing string to :if and :unless conditional options is deprecated - and will be removed in Rails 5.2 without replacement. - MSG - end - options[:raise] = true unless options.key?(:raise) __update_callbacks(name) do |target, chain| @@ -760,8 +749,8 @@ module ActiveSupport # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after # callbacks should be terminated by the <tt>:terminator</tt> option. By # default after callbacks are executed no matter if callback chain was - # terminated or not. This option makes sense only when <tt>:terminator</tt> - # option is specified. + # terminated or not. This option has no effect if <tt>:terminator</tt> + # option is set to +nil+. # # * <tt>:scope</tt> - Indicates which methods should be executed when an # object is used as a callback. @@ -820,7 +809,9 @@ module ActiveSupport names.each do |name| name = name.to_sym - set_callbacks name, CallbackChain.new(name, options) + ([self] + ActiveSupport::DescendantsTracker.descendants(self)).each do |target| + target.set_callbacks name, CallbackChain.new(name, options) + end module_eval <<-RUBY, __FILE__, __LINE__ + 1 def _run_#{name}_callbacks(&block) diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index 32b5ca986b..b0a0d845e5 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -113,7 +113,7 @@ module ActiveSupport def append_features(base) if base.instance_variable_defined?(:@_dependencies) base.instance_variable_get(:@_dependencies) << self - return false + false else return false if base < self @_dependencies.each { |dep| base.include(dep) } diff --git a/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb new file mode 100644 index 0000000000..a8455c0048 --- /dev/null +++ b/activesupport/lib/active_support/concurrency/load_interlock_aware_monitor.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +require "monitor" + +module ActiveSupport + module Concurrency + # A monitor that will permit dependency loading while blocked waiting for + # the lock. + class LoadInterlockAwareMonitor < Monitor + # Enters an exclusive section, but allows dependency loading while blocked + def mon_enter + mon_try_enter || + ActiveSupport::Dependencies.interlock.permit_concurrent_loads { super } + end + end + end +end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 8e8e27b4ec..4d6f7819bb 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative "concern" -require_relative "ordered_options" -require_relative "core_ext/array/extract_options" -require_relative "core_ext/regexp" +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 fdc209ef68..6d83b76882 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative "array/wrap" -require_relative "array/access" -require_relative "array/conversions" -require_relative "array/extract_options" -require_relative "array/grouping" -require_relative "array/prepend_and_append" -require_relative "array/inquiry" +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_options" +require "active_support/core_ext/array/grouping" +require "active_support/core_ext/array/prepend_and_append" +require "active_support/core_ext/array/inquiry" diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index d67f99df0e..b7ff7a3907 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -35,8 +35,8 @@ class Array # people.without "Aaron", "Todd" # # => ["David", "Rafael"] # - # Note: This is an optimization of `Enumerable#without` that uses `Array#-` - # instead of `Array#reject` for performance reasons. + # Note: This is an optimization of <tt>Enumerable#without</tt> that uses <tt>Array#-</tt> + # instead of <tt>Array#reject</tt> for performance reasons. def without(*elements) self - elements end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 6e17a81f0f..ea688ed2ea 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require_relative "../../xml_mini" -require_relative "../hash/keys" -require_relative "../string/inflections" -require_relative "../object/to_param" -require_relative "../object/to_query" +require "active_support/xml_mini" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" class Array # Converts the array to a comma-separated sentence where the last element is @@ -181,7 +181,7 @@ class Array # </messages> # def to_xml(options = {}) - require_relative "../../builder" unless defined?(Builder) + require "active_support/builder" unless defined?(Builder) options = options.dup options[:indent] ||= 2 diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb index 4f4a9fa361..92c61bf201 100644 --- a/activesupport/lib/active_support/core_ext/array/inquiry.rb +++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../array_inquirer" +require "active_support/array_inquirer" class Array # Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way diff --git a/activesupport/lib/active_support/core_ext/big_decimal.rb b/activesupport/lib/active_support/core_ext/big_decimal.rb index 5568db689b..9e6a9d6331 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -require_relative "big_decimal/conversions" +require "active_support/core_ext/big_decimal/conversions" diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb index 65e1d68399..1c110fd07b 100644 --- a/activesupport/lib/active_support/core_ext/class.rb +++ b/activesupport/lib/active_support/core_ext/class.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -require_relative "class/attribute" -require_relative "class/subclasses" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/class/subclasses" diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 3453fc4ac6..fa33ff945f 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative "../kernel/singleton_class" -require_relative "../module/redefine_method" -require_relative "../array/extract_options" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/array/extract_options" class Class # Declare a class-level attribute whose value is inheritable by subclasses. @@ -98,7 +98,7 @@ class Class singleton_class.silence_redefinition_of_method("#{name}?") define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate - ivar = "@#{name}" + ivar = "@#{name}".to_sym singleton_class.silence_redefinition_of_method("#{name}=") define_singleton_method("#{name}=") do |val| diff --git a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb index b4dbcc6698..a77354e153 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute_accessors.rb @@ -3,4 +3,4 @@ # cattr_* became mattr_* aliases in 7dfbd91b0780fbd6a1dd9bfbc176e10894871d2d, # but we keep this around for libraries that directly require it knowing they # want cattr_*. No need to deprecate. -require_relative "../module/attribute_accessors" +require "active_support/core_ext/module/attribute_accessors" diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 4c910feb44..75e65337b7 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -1,8 +1,5 @@ # frozen_string_literal: true -require_relative "../module/anonymous" -require_relative "../module/reachable" - class Class begin # Test if this Ruby supports each_object against singleton_class diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb index 2a2ed5496f..cce73f2db2 100644 --- a/activesupport/lib/active_support/core_ext/date.rb +++ b/activesupport/lib/active_support/core_ext/date.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "date/acts_like" -require_relative "date/blank" -require_relative "date/calculations" -require_relative "date/conversions" -require_relative "date/zones" +require "active_support/core_ext/date/acts_like" +require "active_support/core_ext/date/blank" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/conversions" +require "active_support/core_ext/date/zones" diff --git a/activesupport/lib/active_support/core_ext/date/acts_like.rb b/activesupport/lib/active_support/core_ext/date/acts_like.rb index 81769129b5..c8077f3774 100644 --- a/activesupport/lib/active_support/core_ext/date/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/date/acts_like.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../object/acts_like" +require "active_support/core_ext/object/acts_like" class Date # Duck-types as a Date-like class. See Object#acts_like?. diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index 1a19ee8b1e..1cd7acb05d 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true require "date" -require_relative "../../duration" -require_relative "../object/acts_like" -require_relative "zones" -require_relative "../time/zones" -require_relative "../date_and_time/calculations" +require "active_support/duration" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" class Date include DateAndTime::Calculations diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 88fb572725..870119dc7f 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true require "date" -require_relative "../../inflector/methods" -require_relative "zones" -require_relative "../module/redefine_method" +require "active_support/inflector/methods" +require "active_support/core_ext/date/zones" +require "active_support/core_ext/module/redefine_method" class Date DATE_FORMATS = { diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb index 1aed070df0..2dcf97cff8 100644 --- a/activesupport/lib/active_support/core_ext/date/zones.rb +++ b/activesupport/lib/active_support/core_ext/date/zones.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "date" -require_relative "../date_and_time/zones" +require "active_support/core_ext/date_and_time/zones" class Date include DateAndTime::Zones 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 8546f7e57b..de13f00e60 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,6 @@ # frozen_string_literal: true -require_relative "../object/try" +require "active_support/core_ext/object/try" module DateAndTime module Calculations @@ -20,9 +20,9 @@ module DateAndTime advance(days: -1) end - # Returns a new date/time representing the previous day. - def prev_day - advance(days: -1) + # Returns a new date/time the specified number of days ago. + def prev_day(days = 1) + advance(days: -days) end # Returns a new date/time representing tomorrow. @@ -30,9 +30,9 @@ module DateAndTime advance(days: 1) end - # Returns a new date/time representing the next day. - def next_day - advance(days: 1) + # Returns a new date/time the specified number of days in the future. + def next_day(days = 1) + advance(days: days) end # Returns true if the date/time is today. @@ -60,6 +60,16 @@ module DateAndTime !WEEKEND_DAYS.include?(wday) end + # Returns true if the date/time 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>. + def after?(date_or_time) + self > date_or_time + end + # Returns a new date/time the specified number of days ago. def days_ago(days) advance(days: -days) @@ -188,9 +198,9 @@ module DateAndTime end end - # Short-hand for months_since(1). - def next_month - months_since(1) + # Returns a new date/time the specified number of months in the future. + def next_month(months = 1) + advance(months: months) end # Short-hand for months_since(3) @@ -198,9 +208,9 @@ module DateAndTime months_since(3) end - # Short-hand for years_since(1). - def next_year - years_since(1) + # Returns a new date/time the specified number of years in the future. + def next_year(years = 1) + advance(years: years) end # Returns a new date/time representing the given day in the previous week. @@ -223,11 +233,15 @@ module DateAndTime end alias_method :last_weekday, :prev_weekday + # Returns a new date/time the specified number of months ago. + def prev_month(months = 1) + advance(months: -months) + end + # Short-hand for months_ago(1). - def prev_month + def last_month months_ago(1) end - alias_method :last_month, :prev_month # Short-hand for months_ago(3). def prev_quarter @@ -235,11 +249,15 @@ module DateAndTime end alias_method :last_quarter, :prev_quarter + # Returns a new date/time the specified number of years ago. + def prev_year(years = 1) + advance(years: -years) + end + # Short-hand for years_ago(1). - def prev_year + def last_year years_ago(1) end - alias_method :last_year, :prev_year # Returns the number of days to the start of the week on the given day. # Week is assumed to start on +start_day+, default is @@ -322,20 +340,28 @@ module DateAndTime beginning_of_year..end_of_year end - # Returns specific next occurring day of week + # Returns a new date/time representing the next occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # 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 += 7 unless from_now > 0 - since(from_now.days) + advance(days: from_now) end - # Returns specific previous occurring day of week + # Returns a new date/time representing the previous occurrence of the specified day of week. + # + # today = Date.today # => Thu, 14 Dec 2017 + # 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 += 7 unless ago > 0 - ago(ago.days) + advance(days: -ago) end private diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb index 5b3aabf6a5..d33c36ef73 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../module/attribute_accessors" +require "active_support/core_ext/module/attribute_accessors" module DateAndTime module Compatibility diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb index b3d6773e4f..790dbeec1b 100644 --- a/activesupport/lib/active_support/core_ext/date_time.rb +++ b/activesupport/lib/active_support/core_ext/date_time.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "date_time/acts_like" -require_relative "date_time/blank" -require_relative "date_time/calculations" -require_relative "date_time/compatibility" -require_relative "date_time/conversions" +require "active_support/core_ext/date_time/acts_like" +require "active_support/core_ext/date_time/blank" +require "active_support/core_ext/date_time/calculations" +require "active_support/core_ext/date_time/compatibility" +require "active_support/core_ext/date_time/conversions" diff --git a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb index 02acb5fe21..5dccdfe219 100644 --- a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "date" -require_relative "../object/acts_like" +require "active_support/core_ext/object/acts_like" class DateTime # Duck-types as a Date-like class. See Object#acts_like?. diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb index bb9714545e..7600a067cc 100644 --- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb +++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -require_relative "../date_and_time/compatibility" -require_relative "../module/redefine_method" +require "active_support/core_ext/date_and_time/compatibility" +require "active_support/core_ext/module/redefine_method" class DateTime include DateAndTime::Compatibility silence_redefinition_of_method :to_time - # Either return an instance of `Time` with the same UTC offset - # as +self+ or an instance of `Time` representing the same time - # in the the local system timezone depending on the setting of + # Either return an instance of +Time+ with the same UTC offset + # as +self+ or an instance of +Time+ representing the same time + # in the local system timezone depending on the setting of # on the setting of +ActiveSupport.to_time_preserves_timezone+. def to_time preserve_timezone ? getlocal(utc_offset) : getlocal diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb index e4c8f9898d..29725c89f7 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true require "date" -require_relative "../../inflector/methods" -require_relative "../time/conversions" -require_relative "calculations" -require_relative "../../values/time_zone" +require "active_support/inflector/methods" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/calculations" +require "active_support/values/time_zone" class DateTime # Convert to a formatted string. See Time::DATE_FORMATS for predefined formats. diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 17733d955c..edde4f46b9 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -3,50 +3,37 @@ module Enumerable # Enumerable#sum was added in Ruby 2.4, but it only works with Numeric elements # when we omit an identity. + + # 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: + private :_original_sum_with_required_identity + + # Calculates a sum from the elements. # - # We tried shimming it to attempt the fast native method, rescue TypeError, - # and fall back to the compatible implementation, but that's much slower than - # just calling the compat method in the first place. - if Enumerable.instance_methods(false).include?(:sum) && !((?a..?b).sum rescue false) - # 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 - private :_original_sum_with_required_identity - # Calculates a sum from the elements. - # - # payments.sum { |p| p.price * p.tax_rate } - # payments.sum(&:price) - # - # The latter is a shortcut for: - # - # payments.inject(0) { |sum, p| sum + p.price } - # - # It can also calculate the sum without the use of a block. - # - # [5, 15, 10].sum # => 30 - # ['foo', 'bar'].sum # => "foobar" - # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5] - # - # The default sum of an empty list is zero. You can override this default: - # - # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) - def sum(identity = nil, &block) - if identity - _original_sum_with_required_identity(identity, &block) - elsif block_given? - map(&block).sum(identity) - else - inject(:+) || 0 - end - end - else - def sum(identity = nil, &block) - if block_given? - map(&block).sum(identity) - else - sum = identity ? inject(identity, :+) : inject(:+) - sum || identity || 0 - end + # payments.sum { |p| p.price * p.tax_rate } + # payments.sum(&:price) + # + # The latter is a shortcut for: + # + # payments.inject(0) { |sum, p| sum + p.price } + # + # It can also calculate the sum without the use of a block. + # + # [5, 15, 10].sum # => 30 + # ['foo', 'bar'].sum # => "foobar" + # [[1, 2], [3, 1, 5]].sum # => [1, 2, 3, 1, 5] + # + # The default sum of an empty list is zero. You can override this default: + # + # [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0) + def sum(identity = nil, &block) + if identity + _original_sum_with_required_identity(identity, &block) + elsif block_given? + map(&block).sum(identity) + else + inject(:+) || 0 end end @@ -133,27 +120,21 @@ class Range #:nodoc: end end -# Array#sum was added in Ruby 2.4 but it only works with Numeric elements. -# -# We tried shimming it to attempt the fast native method, rescue TypeError, -# and fall back to the compatible implementation, but that's much slower than -# just calling the compat method in the first place. -if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) - # Using Refinements here in order not to expose our internal method - using Module.new { - refine Array do - alias :orig_sum :sum - end - } +# Using Refinements here in order not to expose our internal method +using Module.new { + refine Array do + alias :orig_sum :sum + end +} - class Array - def sum(init = nil, &block) #:nodoc: - if init.is_a?(Numeric) || first.is_a?(Numeric) - init ||= 0 - orig_sum(init, &block) - else - super - end +class Array #:nodoc: + # Array#sum was added in Ruby 2.4 but it only works with Numeric elements. + def sum(init = nil, &block) + if init.is_a?(Numeric) || first.is_a?(Numeric) + init ||= 0 + orig_sum(init, &block) + else + super end end end diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb index 3c2364167d..64553bfa4e 100644 --- a/activesupport/lib/active_support/core_ext/file.rb +++ b/activesupport/lib/active_support/core_ext/file.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -require_relative "file/atomic" +require "active_support/core_ext/file/atomic" diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index a74a8c15a6..c4b9e5f1a0 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true -require_relative "hash/compact" -require_relative "hash/conversions" -require_relative "hash/deep_merge" -require_relative "hash/except" -require_relative "hash/indifferent_access" -require_relative "hash/keys" -require_relative "hash/reverse_merge" -require_relative "hash/slice" -require_relative "hash/transform_values" +require "active_support/core_ext/hash/conversions" +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/indifferent_access" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/hash/slice" diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb index d6364dd9f3..28c8d86b9b 100644 --- a/activesupport/lib/active_support/core_ext/hash/compact.rb +++ b/activesupport/lib/active_support/core_ext/hash/compact.rb @@ -1,29 +1,5 @@ # frozen_string_literal: true -class Hash - unless Hash.instance_methods(false).include?(:compact) - # Returns a hash with non +nil+ values. - # - # hash = { a: true, b: false, c: nil } - # hash.compact # => { a: true, b: false } - # hash # => { a: true, b: false, c: nil } - # { c: nil }.compact # => {} - # { c: true }.compact # => { c: true } - def compact - select { |_, value| !value.nil? } - end - end +require "active_support/deprecation" - unless Hash.instance_methods(false).include?(:compact!) - # Replaces current hash with non +nil+ values. - # Returns +nil+ if no changes were made, otherwise returns the hash. - # - # hash = { a: true, b: false, c: nil } - # hash.compact! # => { a: true, b: false } - # hash # => { a: true, b: false } - # { c: true }.compact! # => nil - def compact! - reject! { |_, value| value.nil? } - end - end -end +ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Hash#compact and Hash#compact! natively, so requiring active_support/core_ext/hash/compact is no longer necessary. Requiring it will raise LoadError in Rails 6.1." diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 0c0701134e..5b48254646 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -1,13 +1,13 @@ # frozen_string_literal: true -require_relative "../../xml_mini" -require_relative "../../time" -require_relative "../object/blank" -require_relative "../object/to_param" -require_relative "../object/to_query" -require_relative "../array/wrap" -require_relative "reverse_merge" -require_relative "../string/inflections" +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/array/wrap" +require "active_support/core_ext/hash/reverse_merge" +require "active_support/core_ext/string/inflections" class Hash # Returns a string containing an XML representation of its receiver: @@ -73,7 +73,7 @@ class Hash # configure your own builder with the <tt>:builder</tt> option. The method also accepts # options like <tt>:dasherize</tt> and friends, they are forwarded to the builder. def to_xml(options = {}) - require_relative "../../builder" unless defined?(Builder) + require "active_support/builder" unless defined?(Builder) options = options.dup options[:indent] ||= 2 @@ -165,7 +165,7 @@ module ActiveSupport Hash[params.map { |k, v| [k.to_s.tr("-", "_"), normalize_keys(v)] } ] when Array params.map { |v| normalize_keys(v) } - else + else params end end @@ -178,7 +178,7 @@ module ActiveSupport process_array(value) when String value - else + else raise "can't typecast #{value.class.name} - #{value.inspect}" end end diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index 4681d986db..a38f33f128 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../hash_with_indifferent_access" +require "active_support/hash_with_indifferent_access" class Hash # Returns an <tt>ActiveSupport::HashWithIndifferentAccess</tt> out of its receiver: diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 94dc225edb..2bd0a56ea4 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -21,9 +21,8 @@ class Hash # valid_keys = [:mass, :velocity, :time] # search(options.slice(*valid_keys)) def slice(*keys) - keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true) - keys.each_with_object(self.class.new) { |k, hash| hash[k] = self[k] if has_key?(k) } - end + keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) } + end unless method_defined?(:slice) # Replaces the hash with only the given keys. # Returns a hash containing the removed key/value pairs. @@ -31,7 +30,6 @@ class Hash # { a: 1, b: 2, c: 3, d: 4 }.slice!(:a, :b) # # => {:c=>3, :d=>4} def slice!(*keys) - keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true) omit = slice(*self.keys - keys) hash = slice(*keys) hash.default = default diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb index 4b19c9fc1f..fc15130c9e 100644 --- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb +++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb @@ -1,32 +1,5 @@ # frozen_string_literal: true -class Hash - # Returns a new hash with the results of running +block+ once for every value. - # The keys are unchanged. - # - # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 } # => { a: 2, b: 4, c: 6 } - # - # If you do not provide a +block+, it will return an Enumerator - # for chaining with other methods: - # - # { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 } - def transform_values - return enum_for(:transform_values) { size } unless block_given? - return {} if empty? - result = self.class.new - each do |key, value| - result[key] = yield(value) - end - result - end unless method_defined? :transform_values +require "active_support/deprecation" - # Destructively converts all values using the +block+ operations. - # Same as +transform_values+ but modifies +self+. - def transform_values! - return enum_for(:transform_values!) { size } unless block_given? - each do |key, value| - self[key] = yield(value) - end - end unless method_defined? :transform_values! - # TODO: Remove this file when supporting only Ruby 2.4+. -end +ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Hash#transform_values natively, so requiring active_support/core_ext/hash/transform_values is no longer necessary. Requiring it will raise LoadError in Rails 6.1." diff --git a/activesupport/lib/active_support/core_ext/integer.rb b/activesupport/lib/active_support/core_ext/integer.rb index df80e7ffdb..d22701306a 100644 --- a/activesupport/lib/active_support/core_ext/integer.rb +++ b/activesupport/lib/active_support/core_ext/integer.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true -require_relative "integer/multiple" -require_relative "integer/inflections" -require_relative "integer/time" +require "active_support/core_ext/integer/multiple" +require "active_support/core_ext/integer/inflections" +require "active_support/core_ext/integer/time" diff --git a/activesupport/lib/active_support/core_ext/integer/inflections.rb b/activesupport/lib/active_support/core_ext/integer/inflections.rb index 7192e92346..aef3266f28 100644 --- a/activesupport/lib/active_support/core_ext/integer/inflections.rb +++ b/activesupport/lib/active_support/core_ext/integer/inflections.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../inflector" +require "active_support/inflector" class Integer # Ordinalize turns a number into an ordinal string used to denote the diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb index 30c8f3bcf2..5efb89cf9f 100644 --- a/activesupport/lib/active_support/core_ext/integer/time.rb +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -1,29 +1,20 @@ # frozen_string_literal: true -require_relative "../../duration" -require_relative "../numeric/time" +require "active_support/duration" +require "active_support/core_ext/numeric/time" class Integer - # Enables the use of time calculations and declarations, like <tt>45.minutes + - # 2.hours + 4.years</tt>. + # Returns a Duration instance matching the number of months provided. # - # These methods use Time#advance for precise date calculations when using - # <tt>from_now</tt>, +ago+, etc. as well as adding or subtracting their - # results from a Time object. - # - # # equivalent to Time.now.advance(months: 1) - # 1.month.from_now - # - # # equivalent to Time.now.advance(years: 2) - # 2.years.from_now - # - # # equivalent to Time.now.advance(months: 4, years: 5) - # (4.months + 5.years).from_now + # 2.months # => 2 months def months ActiveSupport::Duration.months(self) end alias :month :months + # Returns a Duration instance matching the number of years provided. + # + # 2.years # => 2 years def years ActiveSupport::Duration.years(self) end diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb index 30810ce315..0f4356fbdd 100644 --- a/activesupport/lib/active_support/core_ext/kernel.rb +++ b/activesupport/lib/active_support/core_ext/kernel.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "kernel/agnostics" -require_relative "kernel/concern" -require_relative "kernel/reporting" -require_relative "kernel/singleton_class" +require "active_support/core_ext/kernel/agnostics" +require "active_support/core_ext/kernel/concern" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/kernel/singleton_class" diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb index 32af9981a5..0b2baed780 100644 --- a/activesupport/lib/active_support/core_ext/kernel/concern.rb +++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../module/concerning" +require "active_support/core_ext/module/concerning" module Kernel module_function diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb index 8445643730..d91e3fba6a 100644 --- a/activesupport/lib/active_support/core_ext/module.rb +++ b/activesupport/lib/active_support/core_ext/module.rb @@ -1,14 +1,14 @@ # frozen_string_literal: true -require_relative "module/aliasing" -require_relative "module/introspection" -require_relative "module/anonymous" -require_relative "module/reachable" -require_relative "module/attribute_accessors" -require_relative "module/attribute_accessors_per_thread" -require_relative "module/attr_internal" -require_relative "module/concerning" -require_relative "module/delegation" -require_relative "module/deprecation" -require_relative "module/redefine_method" -require_relative "module/remove_method" +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/module/reachable" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/attribute_accessors_per_thread" +require "active_support/core_ext/module/attr_internal" +require "active_support/core_ext/module/concerning" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/module/deprecation" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/module/remove_method" 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 e667c1f4a4..01fee0fb74 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,7 @@ # frozen_string_literal: true -require_relative "../array/extract_options" -require_relative "../regexp" +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 @@ -163,10 +163,10 @@ class Module # parent class. Similarly if parent class changes the value then that would # change the value of subclasses too. # - # class Male < Person + # class Citizen < Person # end # - # Male.new.hair_colors << :blue + # Citizen.new.hair_colors << :blue # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue] # # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>. 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 90a350c5dc..4b9b6ea9bd 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,7 @@ # frozen_string_literal: true -require_relative "../array/extract_options" -require_relative "../regexp" +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/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb index 17c202a24a..7bbbf321ab 100644 --- a/activesupport/lib/active_support/core_ext/module/concerning.rb +++ b/activesupport/lib/active_support/core_ext/module/concerning.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../concern" +require "active_support/concern" class Module # = Bite-sized separation of concerns @@ -22,7 +22,7 @@ class Module # # == Using comments: # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -30,7 +30,6 @@ class Module # has_many :events # # before_create :track_creation - # after_destroy :track_deletion # # private # def track_creation @@ -42,7 +41,7 @@ class Module # # Noisy syntax. # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -52,7 +51,6 @@ class Module # included do # has_many :events # before_create :track_creation - # after_destroy :track_deletion # end # # private @@ -70,7 +68,7 @@ class Module # increased overhead can be a reasonable tradeoff even if it reduces our # at-a-glance perception of how things work. # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -82,7 +80,7 @@ class Module # By quieting the mix-in noise, we arrive at a natural, low-ceremony way to # separate bite-sized concerns. # - # class Todo + # class Todo < ApplicationRecord # # Other todo implementation # # ... # @@ -90,7 +88,6 @@ class Module # included do # has_many :events # before_create :track_creation - # after_destroy :track_deletion # end # # private @@ -101,7 +98,7 @@ class Module # end # # Todo.ancestors - # # => [Todo, Todo::EventTracking, Object] + # # => [Todo, Todo::EventTracking, ApplicationRecord, Object] # # This small step has some wonderful ripple effects. We can # * grok the behavior of our class in one glance, diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 1840dc942f..7f42f44efb 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "set" -require_relative "../regexp" +require "active_support/core_ext/regexp" class Module # Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+ @@ -22,8 +22,9 @@ class Module # ==== Options # * <tt>:to</tt> - Specifies the target object # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix - # * <tt>:allow_nil</tt> - if set to true, prevents a +Module::DelegationError+ + # * <tt>:allow_nil</tt> - If set to true, prevents a +Module::DelegationError+ # from being raised + # * <tt>:private</tt> - If set to true, changes method visibility to private # # The macro receives one or more method names (specified as symbols or # strings) and the name of the target object via the <tt>:to</tt> option @@ -114,12 +115,26 @@ class Module # invoice.customer_name # => 'John Doe' # invoice.customer_address # => 'Vimmersvej 13' # + # The delegated methods are public by default. + # Pass <tt>private: true</tt> to change that. + # + # class User < ActiveRecord::Base + # has_one :profile + # delegate :first_name, to: :profile + # delegate :date_of_birth, to: :profile, private: true + # + # def age + # Date.today.year - date_of_birth.year + # end + # end + # + # User.new.first_name # => "Tomas" + # User.new.date_of_birth # => NoMethodError: private method `date_of_birth' called for #<User:0x00000008221340> + # User.new.age # => 2 + # # If the target is +nil+ and does not respond to the delegated method a - # +Module::DelegationError+ is raised, as with any other value. Sometimes, - # however, it makes sense to be robust to that situation and that is the - # purpose of the <tt>:allow_nil</tt> option: If the target is not +nil+, or it - # is and responds to the method, everything works as usual. But if it is +nil+ - # and does not respond to the delegated method, +nil+ is returned. + # +Module::DelegationError+ is raised. If you wish to instead return +nil+, + # use the <tt>:allow_nil</tt> option. # # class User < ActiveRecord::Base # has_one :profile @@ -154,7 +169,7 @@ class Module # Foo.new("Bar").name # raises NoMethodError: undefined method `name' # # The target method must be public, otherwise it will raise +NoMethodError+. - def delegate(*methods, to: nil, prefix: nil, allow_nil: nil) + def delegate(*methods, to: nil, prefix: nil, allow_nil: nil, private: nil) unless to raise ArgumentError, "Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter)." end @@ -176,7 +191,7 @@ class Module to = to.to_s to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to) - methods.map do |method| + method_names = methods.map do |method| # Attribute writer methods only accept one argument. Makes sure []= # methods still accept two arguments. definition = /[^\]]=$/.match?(method) ? "arg" : "*args, &block" @@ -216,6 +231,9 @@ class Module module_eval(method_def, file, line) end + + private(*method_names) if private + method_names end # When building decorators, a common pattern may emerge: diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb index 540385ef6f..c5bb598bd1 100644 --- a/activesupport/lib/active_support/core_ext/module/introspection.rb +++ b/activesupport/lib/active_support/core_ext/module/introspection.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../inflector" +require "active_support/inflector" class Module # Returns the name of the module containing this one. diff --git a/activesupport/lib/active_support/core_ext/module/reachable.rb b/activesupport/lib/active_support/core_ext/module/reachable.rb index 91b230b46c..e9cbda5245 100644 --- a/activesupport/lib/active_support/core_ext/module/reachable.rb +++ b/activesupport/lib/active_support/core_ext/module/reachable.rb @@ -1,10 +1,11 @@ # frozen_string_literal: true -require_relative "anonymous" -require_relative "../string/inflections" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/string/inflections" class Module def reachable? #:nodoc: !anonymous? && name.safe_constantize.equal?(self) end + deprecate :reachable? end diff --git a/activesupport/lib/active_support/core_ext/module/redefine_method.rb b/activesupport/lib/active_support/core_ext/module/redefine_method.rb index a0a6622ca4..5bd8e6e973 100644 --- a/activesupport/lib/active_support/core_ext/module/redefine_method.rb +++ b/activesupport/lib/active_support/core_ext/module/redefine_method.rb @@ -1,23 +1,14 @@ # frozen_string_literal: true class Module - if RUBY_VERSION >= "2.3" - # Marks the named method as intended to be redefined, if it exists. - # Suppresses the Ruby method redefinition warning. Prefer - # #redefine_method where possible. - def silence_redefinition_of_method(method) - if method_defined?(method) || private_method_defined?(method) - # This suppresses the "method redefined" warning; the self-alias - # looks odd, but means we don't need to generate a unique name - alias_method method, method - end - end - else - def silence_redefinition_of_method(method) - if method_defined?(method) || private_method_defined?(method) - alias_method :__rails_redefine, method - remove_method :__rails_redefine - end + # Marks the named method as intended to be redefined, if it exists. + # Suppresses the Ruby method redefinition warning. Prefer + # #redefine_method where possible. + def silence_redefinition_of_method(method) + if method_defined?(method) || private_method_defined?(method) + # This suppresses the "method redefined" warning; the self-alias + # looks odd, but means we don't need to generate a unique name + alias_method method, method end end diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb index 704f144bdd..97eb5f9eca 100644 --- a/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "redefine_method" +require "active_support/core_ext/module/redefine_method" class Module # Removes the named method, if it exists. diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb index d4f1e01140..6d37cd9dfd 100644 --- a/activesupport/lib/active_support/core_ext/name_error.rb +++ b/activesupport/lib/active_support/core_ext/name_error.rb @@ -10,6 +10,11 @@ class NameError # end # # => "HelloWorld" def missing_name + # Since ruby v2.3.0 `did_you_mean` gem is loaded by default. + # It extends NameError#message with spell corrections which are SLOW. + # We should use original_message message instead. + message = respond_to?(:original_message) ? original_message : self.message + if /undefined local variable or method/ !~ message $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message end diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb index 76e33a7cb0..fe778470f1 100644 --- a/activesupport/lib/active_support/core_ext/numeric.rb +++ b/activesupport/lib/active_support/core_ext/numeric.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require_relative "numeric/bytes" -require_relative "numeric/time" -require_relative "numeric/inquiry" -require_relative "numeric/conversions" +require "active_support/core_ext/numeric/bytes" +require "active_support/core_ext/numeric/time" +require "active_support/core_ext/numeric/conversions" diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index 05528f5069..7fcd0d0311 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative "../big_decimal/conversions" -require_relative "../../number_helper" -require_relative "../module/deprecation" +require "active_support/core_ext/big_decimal/conversions" +require "active_support/number_helper" +require "active_support/core_ext/module/deprecation" module ActiveSupport::NumericWithFormat # Provides options for converting numbers into formatted strings. @@ -108,19 +108,19 @@ module ActiveSupport::NumericWithFormat when Integer, String super(format) when :phone - return ActiveSupport::NumberHelper.number_to_phone(self, options || {}) + ActiveSupport::NumberHelper.number_to_phone(self, options || {}) when :currency - return ActiveSupport::NumberHelper.number_to_currency(self, options || {}) + ActiveSupport::NumberHelper.number_to_currency(self, options || {}) when :percentage - return ActiveSupport::NumberHelper.number_to_percentage(self, options || {}) + ActiveSupport::NumberHelper.number_to_percentage(self, options || {}) when :delimited - return ActiveSupport::NumberHelper.number_to_delimited(self, options || {}) + ActiveSupport::NumberHelper.number_to_delimited(self, options || {}) when :rounded - return ActiveSupport::NumberHelper.number_to_rounded(self, options || {}) + ActiveSupport::NumberHelper.number_to_rounded(self, options || {}) when :human - return ActiveSupport::NumberHelper.number_to_human(self, options || {}) + ActiveSupport::NumberHelper.number_to_human(self, options || {}) when :human_size - return ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) + ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) when Symbol super() else @@ -129,12 +129,6 @@ module ActiveSupport::NumericWithFormat end end -# Ruby 2.4+ unifies Fixnum & Bignum into Integer. -if 0.class == Integer - Integer.prepend ActiveSupport::NumericWithFormat -else - Fixnum.prepend ActiveSupport::NumericWithFormat - Bignum.prepend ActiveSupport::NumericWithFormat -end +Integer.prepend ActiveSupport::NumericWithFormat Float.prepend ActiveSupport::NumericWithFormat BigDecimal.prepend ActiveSupport::NumericWithFormat diff --git a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb index 15334c91f1..e05e40825b 100644 --- a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb +++ b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb @@ -1,28 +1,5 @@ # frozen_string_literal: true -unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3 - class Numeric - # Returns true if the number is positive. - # - # 1.positive? # => true - # 0.positive? # => false - # -1.positive? # => false - def positive? - self > 0 - end +require "active_support/deprecation" - # Returns true if the number is negative. - # - # -1.negative? # => true - # 0.negative? # => false - # 1.negative? # => false - def negative? - self < 0 - end - end - - class Complex - undef :positive? - undef :negative? - end -end +ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Numeric#positive? and Numeric#negative? natively, so requiring active_support/core_ext/numeric/inquiry is no longer necessary. Requiring it will raise LoadError in Rails 6.1." diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index 0cee87cb86..bc4627f7a2 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -1,25 +1,15 @@ # frozen_string_literal: true -require_relative "../../duration" -require_relative "../time/calculations" -require_relative "../time/acts_like" -require_relative "../date/calculations" -require_relative "../date/acts_like" +require "active_support/duration" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date/calculations" +require "active_support/core_ext/date/acts_like" class Numeric - # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. + # Returns a Duration instance matching the number of seconds provided. # - # These methods use Time#advance for precise date calculations when using from_now, ago, etc. - # as well as adding or subtracting their results from a Time object. For example: - # - # # equivalent to Time.current.advance(months: 1) - # 1.month.from_now - # - # # equivalent to Time.current.advance(years: 2) - # 2.years.from_now - # - # # equivalent to Time.current.advance(months: 4, years: 5) - # (4.months + 5.years).from_now + # 2.seconds # => 2 seconds def seconds ActiveSupport::Duration.seconds(self) end @@ -66,10 +56,10 @@ class Numeric alias :fortnight :fortnights # Returns the number of milliseconds equivalent to the seconds provided. - # Used with the standard time durations, like 1.hour.in_milliseconds -- - # so we can feed them to JavaScript functions like getTime(). + # Used with the standard time durations. # - # 2.in_milliseconds # => 2_000 + # 2.in_milliseconds # => 2000 + # 1.hour.in_milliseconds # => 3600000 def in_milliseconds self * 1000 end diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index 23f5eec8c7..efd34cc692 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -1,16 +1,16 @@ # frozen_string_literal: true -require_relative "object/acts_like" -require_relative "object/blank" -require_relative "object/duplicable" -require_relative "object/deep_dup" -require_relative "object/try" -require_relative "object/inclusion" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/object/duplicable" +require "active_support/core_ext/object/deep_dup" +require "active_support/core_ext/object/try" +require "active_support/core_ext/object/inclusion" -require_relative "object/conversions" -require_relative "object/instance_variables" +require "active_support/core_ext/object/conversions" +require "active_support/core_ext/object/instance_variables" -require_relative "object/json" -require_relative "object/to_param" -require_relative "object/to_query" -require_relative "object/with_options" +require "active_support/core_ext/object/json" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/object/with_options" diff --git a/activesupport/lib/active_support/core_ext/object/acts_like.rb b/activesupport/lib/active_support/core_ext/object/acts_like.rb index 2eb72f6b3a..403ee20e39 100644 --- a/activesupport/lib/active_support/core_ext/object/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/object/acts_like.rb @@ -7,6 +7,15 @@ class Object # <tt>x.acts_like?(:date)</tt> to do duck-type-safe comparisons, since classes that # we want to act like Time simply need to define an <tt>acts_like_time?</tt> method. def acts_like?(duck) - respond_to? :"acts_like_#{duck}?" + case duck + when :time + respond_to? :acts_like_time? + when :date + respond_to? :acts_like_date? + when :string + respond_to? :acts_like_string? + else + respond_to? :"acts_like_#{duck}?" + end end end diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 397adbdb5a..2ca431ab10 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -require_relative "../regexp" +require "active_support/core_ext/regexp" +require "concurrent/map" class Object # An object is blank if it's false, empty, or a whitespace string. @@ -102,6 +103,9 @@ end class String BLANK_RE = /\A[[:space:]]*\z/ + ENCODED_BLANKS = Concurrent::Map.new do |h, enc| + h[enc] = Regexp.new(BLANK_RE.source.encode(enc), BLANK_RE.options | Regexp::FIXEDENCODING) + end # A string is blank if it's empty or contains whitespaces only: # @@ -119,7 +123,12 @@ class String # The regexp that matches blank strings is expensive. For the case of empty # strings we can speed up this method (~3.5x) with an empty? call. The # penalty for the rest of strings is marginal. - empty? || BLANK_RE.match?(self) + empty? || + begin + BLANK_RE.match?(self) + rescue Encoding::CompatibilityError + ENCODED_BLANKS[self.encoding].match?(self) + end end end diff --git a/activesupport/lib/active_support/core_ext/object/conversions.rb b/activesupport/lib/active_support/core_ext/object/conversions.rb index fdc154188a..624fb8d77c 100644 --- a/activesupport/lib/active_support/core_ext/object/conversions.rb +++ b/activesupport/lib/active_support/core_ext/object/conversions.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "to_param" -require_relative "to_query" -require_relative "../array/conversions" -require_relative "../hash/conversions" +require "active_support/core_ext/object/to_param" +require "active_support/core_ext/object/to_query" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/hash/conversions" diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb index 4021b15de6..c66c5eb2d9 100644 --- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "duplicable" +require "active_support/core_ext/object/duplicable" class Object # Returns a deep copy of object if it's duplicable. If it's diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 1744a44df6..c78ee6bbfc 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -75,8 +75,11 @@ end class Symbol begin - :symbol.dup # Ruby 2.4.x. - "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0. + :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: @@ -108,8 +111,8 @@ require "bigdecimal" class BigDecimal # BigDecimals are duplicable: # - # BigDecimal.new("1.2").duplicable? # => true - # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> + # BigDecimal("1.2").duplicable? # => true + # BigDecimal("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> def duplicable? true end diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb index 30495313d2..416059d17b 100644 --- a/activesupport/lib/active_support/core_ext/object/json.rb +++ b/activesupport/lib/active_support/core_ext/object/json.rb @@ -5,15 +5,16 @@ require "json" require "bigdecimal" require "uri/generic" require "pathname" -require_relative "../big_decimal/conversions" # for #to_s -require_relative "../hash/except" -require_relative "../hash/slice" -require_relative "instance_variables" +require "active_support/core_ext/big_decimal/conversions" # for #to_s +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" +require "active_support/core_ext/object/instance_variables" require "time" -require_relative "../time/conversions" -require_relative "../date_time/conversions" -require_relative "../date/conversions" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/date_time/conversions" +require "active_support/core_ext/date/conversions" +#-- # The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting # their default behavior. That said, we need to define the basic to_json method in all of them, # otherwise they will always use to_json gem implementation, which is backwards incompatible in @@ -135,6 +136,12 @@ module Enumerable end end +class IO + def as_json(options = nil) #:nodoc: + to_s + end +end + class Range def as_json(options = nil) #:nodoc: to_s diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index c57488bcbc..6d2bdd70f3 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -require_relative "to_query" +require "active_support/core_ext/object/to_query" 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 47766f6012..2838fd76be 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../option_merger" +require "active_support/option_merger" class Object # An elegant way to factor duplication out of options passed to a series of @@ -62,7 +62,7 @@ class Object # # validates :content, length: { minimum: 50 }, if: -> { content.present? } # - # Hence the inherited default for `if` key is ignored. + # Hence the inherited default for +if+ key is ignored. # # NOTE: You cannot call class methods implicitly inside of with_options. # You can access these methods using the class name instead: diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb index 89bbbfcb81..4074e91d17 100644 --- a/activesupport/lib/active_support/core_ext/range.rb +++ b/activesupport/lib/active_support/core_ext/range.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true -require_relative "range/conversions" -require_relative "range/include_range" -require_relative "range/overlaps" -require_relative "range/each" +require "active_support/core_ext/range/conversions" +require "active_support/core_ext/range/include_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/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb index 37868f5875..8832fbcb3c 100644 --- a/activesupport/lib/active_support/core_ext/range/conversions.rb +++ b/activesupport/lib/active_support/core_ext/range/conversions.rb @@ -2,7 +2,13 @@ module ActiveSupport::RangeWithFormat RANGE_FORMATS = { - db: Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" } + db: -> (start, stop) do + case start + when String then "BETWEEN '#{start}' AND '#{stop}'" + else + "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" + end + end } # Convert range to a formatted string. See RANGE_FORMATS for predefined formats. diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb index cdff6393d7..2f22cd0e92 100644 --- a/activesupport/lib/active_support/core_ext/range/each.rb +++ b/activesupport/lib/active_support/core_ext/range/each.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/time_with_zone" + module ActiveSupport module EachTimeWithZone #:nodoc: def each(&block) @@ -15,7 +17,7 @@ module ActiveSupport private def ensure_iteration_allowed - raise TypeError, "can't iterate from #{first.class}" if first.is_a?(Time) + raise TypeError, "can't iterate from #{first.class}" if first.is_a?(TimeWithZone) end end end diff --git a/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb b/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb new file mode 100644 index 0000000000..5f80acf68e --- /dev/null +++ b/activesupport/lib/active_support/core_ext/range/include_time_with_zone.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +require "active_support/time_with_zone" + +module ActiveSupport + module IncludeTimeWithZone #:nodoc: + # Extends the default Range#include? to support ActiveSupport::TimeWithZone. + # + # (1.hour.ago..1.hour.from_now).include?(Time.current) # => true + # + def include?(value) + if first.is_a?(TimeWithZone) + cover?(value) + elsif last.is_a?(TimeWithZone) + cover?(value) + else + super + end + end + end +end + +Range.prepend(ActiveSupport::IncludeTimeWithZone) diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb index efbd708aee..d92943c7ae 100644 --- a/activesupport/lib/active_support/core_ext/regexp.rb +++ b/activesupport/lib/active_support/core_ext/regexp.rb @@ -4,8 +4,4 @@ class Regexp #:nodoc: def multiline? options & MULTILINE == MULTILINE end - - def match?(string, pos = 0) - !!match(string, pos) - end unless //.respond_to?(:match?) end diff --git a/activesupport/lib/active_support/core_ext/string.rb b/activesupport/lib/active_support/core_ext/string.rb index 491eec2fc9..757d15c51a 100644 --- a/activesupport/lib/active_support/core_ext/string.rb +++ b/activesupport/lib/active_support/core_ext/string.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true -require_relative "string/conversions" -require_relative "string/filters" -require_relative "string/multibyte" -require_relative "string/starts_ends_with" -require_relative "string/inflections" -require_relative "string/access" -require_relative "string/behavior" -require_relative "string/output_safety" -require_relative "string/exclude" -require_relative "string/strip" -require_relative "string/inquiry" -require_relative "string/indent" -require_relative "string/zones" +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/filters" +require "active_support/core_ext/string/multibyte" +require "active_support/core_ext/string/starts_ends_with" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/string/access" +require "active_support/core_ext/string/behavior" +require "active_support/core_ext/string/output_safety" +require "active_support/core_ext/string/exclude" +require "active_support/core_ext/string/strip" +require "active_support/core_ext/string/inquiry" +require "active_support/core_ext/string/indent" +require "active_support/core_ext/string/zones" diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb index f8f6524b2b..29a88b07ad 100644 --- a/activesupport/lib/active_support/core_ext/string/conversions.rb +++ b/activesupport/lib/active_support/core_ext/string/conversions.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "date" -require_relative "../time/calculations" +require "active_support/core_ext/time/calculations" class String # Converts a string to a Time value. diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 66e721eea3..df0e79afa8 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -78,6 +78,47 @@ class String "#{self[0, stop]}#{omission}" end + # Truncates +text+ to at most <tt>bytesize</tt> bytes in length without + # breaking string encoding by splitting multibyte characters or breaking + # grapheme clusters ("perceptual characters") by truncating at combining + # characters. + # + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size + # => 20 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize + # => 80 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20) + # => "🔪🔪🔪🔪…" + # + # The truncated text ends with the <tt>:omission</tt> string, defaulting + # to "…", for a total length not exceeding <tt>bytesize</tt>. + def truncate_bytes(truncate_at, omission: "…") + omission ||= "" + + case + when bytesize <= truncate_at + dup + when omission.bytesize > truncate_at + raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes" + when omission.bytesize == truncate_at + omission.dup + else + self.class.new.tap do |cut| + cut_at = truncate_at - omission.bytesize + + scan(/\X/) do |grapheme| + if cut.bytesize + grapheme.bytesize <= cut_at + cut << grapheme + else + break + end + end + + cut << omission + end + end + end + # Truncates a given +text+ after a given number of words (<tt>words_count</tt>): # # 'Once upon a time in a world far far away'.truncate_words(4) diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index b5bb385033..8af301734a 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../../inflector/methods" -require_relative "../../inflector/transliterate" +require "active_support/inflector/methods" +require "active_support/inflector/transliterate" # String inflections define new methods on the String class to transform names for different purposes. # For instance, you can figure out the name of a table from the name of a class. @@ -174,7 +174,7 @@ class String # <%= link_to(@person.name, person_path) %> # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> # - # To preserve the case of the characters in a string, use the `preserve_case` argument. + # To preserve the case of the characters in a string, use the +preserve_case+ argument. # # class Person # def to_param diff --git a/activesupport/lib/active_support/core_ext/string/inquiry.rb b/activesupport/lib/active_support/core_ext/string/inquiry.rb index 92069981b6..a796d5fb4f 100644 --- a/activesupport/lib/active_support/core_ext/string/inquiry.rb +++ b/activesupport/lib/active_support/core_ext/string/inquiry.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../string_inquirer" +require "active_support/string_inquirer" class String # Wraps the current string in the <tt>ActiveSupport::StringInquirer</tt> class, diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index fba5b166a2..6cceb46507 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../../multibyte" +require "active_support/multibyte" class String # == Multibyte proxy @@ -11,11 +11,14 @@ class String # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string. # - # >> "lj".upcase - # => "lj" # >> "lj".mb_chars.upcase.to_s # => "LJ" # + # NOTE: Ruby 2.4 and later support native Unicode case mappings: + # + # >> "lj".upcase + # => "LJ" + # # == Method chaining # # All the methods on the Chars proxy which normally return a string will return a Chars object. This allows 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 600b41da10..f3bdc2977e 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true require "erb" -require_relative "../kernel/singleton_class" -require_relative "../module/redefine_method" -require_relative "../../multibyte/unicode" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/redefine_method" +require "active_support/multibyte/unicode" class ERB module Util @@ -15,9 +15,6 @@ class ERB # A utility method for escaping HTML tag characters. # This method is also aliased as <tt>h</tt>. # - # In your ERB templates, use this method to escape any unsafe content. For example: - # <%= h @person.name %> - # # puts html_escape('is a > 0 & a < 10?') # # => is a > 0 & a < 10? def html_escape(s) @@ -253,7 +250,7 @@ class String # Marks a string as trusted safe. It will be inserted into HTML with no # additional escaping performed. It is your responsibility to ensure that the # string contains no malicious content. This method is equivalent to the - # `raw` helper in views. It is recommended that you use `sanitize` instead of + # +raw+ helper in views. It is recommended that you use +sanitize+ instead of # this method. It should never be called on user input. def html_safe ActiveSupport::SafeBuffer.new(self) diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb index cc26274e4a..6f9834bb16 100644 --- a/activesupport/lib/active_support/core_ext/string/strip.rb +++ b/activesupport/lib/active_support/core_ext/string/strip.rb @@ -20,6 +20,8 @@ class String # Technically, it looks for the least indented non-empty line # in the whole string, and removes that amount of leading whitespace. def strip_heredoc - gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze) + gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze).tap do |stripped| + stripped.freeze if frozen? + end end end diff --git a/activesupport/lib/active_support/core_ext/string/zones.rb b/activesupport/lib/active_support/core_ext/string/zones.rb index db30c03a8e..55dc231464 100644 --- a/activesupport/lib/active_support/core_ext/string/zones.rb +++ b/activesupport/lib/active_support/core_ext/string/zones.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "conversions" -require_relative "../time/zones" +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/time/zones" class String # Converts String to a TimeWithZone in the current zone if Time.zone or Time.zone_default diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb index 4e16274443..c809def05f 100644 --- a/activesupport/lib/active_support/core_ext/time.rb +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "time/acts_like" -require_relative "time/calculations" -require_relative "time/compatibility" -require_relative "time/conversions" -require_relative "time/zones" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/time/calculations" +require "active_support/core_ext/time/compatibility" +require "active_support/core_ext/time/conversions" +require "active_support/core_ext/time/zones" diff --git a/activesupport/lib/active_support/core_ext/time/acts_like.rb b/activesupport/lib/active_support/core_ext/time/acts_like.rb index 309418df42..8572b49639 100644 --- a/activesupport/lib/active_support/core_ext/time/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/time/acts_like.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../object/acts_like" +require "active_support/core_ext/object/acts_like" class Time # Duck-types as a Time-like class. See Object#acts_like?. diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index 0e51df44ef..120768dec5 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true -require_relative "../../duration" -require_relative "conversions" -require_relative "../../time_with_zone" -require_relative "zones" -require_relative "../date_and_time/calculations" -require_relative "../date/calculations" +require "active_support/duration" +require "active_support/core_ext/time/conversions" +require "active_support/time_with_zone" +require "active_support/core_ext/time/zones" +require "active_support/core_ext/date_and_time/calculations" +require "active_support/core_ext/date/calculations" class Time include DateAndTime::Calculations diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb index 147ae0f802..495e4f307b 100644 --- a/activesupport/lib/active_support/core_ext/time/compatibility.rb +++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../date_and_time/compatibility" -require_relative "../module/redefine_method" +require "active_support/core_ext/date_and_time/compatibility" +require "active_support/core_ext/module/redefine_method" class Time include DateAndTime::Compatibility diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index e3fc930ef6..345cb2832c 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../../inflector/methods" -require_relative "../../values/time_zone" +require "active_support/inflector/methods" +require "active_support/values/time_zone" class Time DATE_FORMATS = { diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index c48edb135f..a5588fd488 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative "../../time_with_zone" -require_relative "acts_like" -require_relative "../date_and_time/zones" +require "active_support/time_with_zone" +require "active_support/core_ext/time/acts_like" +require "active_support/core_ext/date_and_time/zones" class Time include DateAndTime::Zones @@ -55,10 +55,10 @@ class Time # end # end # - # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt> - # objects that have already been created, e.g. any model timestamp - # attributes that have been read before the block will remain in - # the application's default timezone. + # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt> + # objects that have already been created, e.g. any model timestamp + # attributes that have been read before the block will remain in + # the application's default timezone. def use_zone(time_zone) new_zone = find_zone!(time_zone) begin diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb index c93c0b5c2d..cdd81ae562 100644 --- a/activesupport/lib/active_support/core_ext/uri.rb +++ b/activesupport/lib/active_support/core_ext/uri.rb @@ -1,10 +1,8 @@ # frozen_string_literal: true require "uri" -str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese. -parser = URI::Parser.new -unless str == parser.unescape(parser.escape(str)) +if RUBY_VERSION < "2.6.0" require "active_support/core_ext/module/redefine_method" URI::Parser.class_eval do silence_redefinition_of_method :unescape @@ -13,7 +11,7 @@ unless str == parser.unescape(parser.escape(str)) # YK: My initial experiments say yes, but let's be sure please enc = str.encoding enc = Encoding::UTF_8 if enc == Encoding::US_ASCII - str.gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc) + str.dup.force_encoding(Encoding::ASCII_8BIT).gsub(escaped) { |match| [match[1, 2].hex].pack("C") }.force_encoding(enc) end end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index ad2f21205f..0f59558bb5 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -4,17 +4,17 @@ require "set" require "thread" require "concurrent/map" require "pathname" -require_relative "core_ext/module/aliasing" -require_relative "core_ext/module/attribute_accessors" -require_relative "core_ext/module/introspection" -require_relative "core_ext/module/anonymous" -require_relative "core_ext/object/blank" -require_relative "core_ext/kernel/reporting" -require_relative "core_ext/load_error" -require_relative "core_ext/name_error" -require_relative "core_ext/string/starts_ends_with" -require_relative "dependencies/interlock" -require_relative "inflector" +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/introspection" +require "active_support/core_ext/module/anonymous" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/load_error" +require "active_support/core_ext/name_error" +require "active_support/core_ext/string/starts_ends_with" +require "active_support/dependencies/interlock" +require "active_support/inflector" module ActiveSupport #:nodoc: module Dependencies #:nodoc: @@ -447,6 +447,7 @@ module ActiveSupport #:nodoc: mod = Module.new into.const_set const_name, mod autoloaded_constants << qualified_name unless autoload_once_paths.include?(base_path) + autoloaded_constants.uniq! mod end @@ -615,7 +616,7 @@ module ActiveSupport #:nodoc: return false if desc.is_a?(Module) && desc.anonymous? name = to_constant_name desc return false unless qualified_const_defined?(name) - return autoloaded_constants.include?(name) + autoloaded_constants.include?(name) end # Will the provided constant descriptor be unloaded? @@ -670,7 +671,7 @@ module ActiveSupport #:nodoc: when Module desc.name || raise(ArgumentError, "Anonymous modules have no name to be referenced by") - else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" + else raise TypeError, "Not a valid constant descriptor: #{desc.inspect}" end end diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb index 1c3775f5eb..1cee85d98f 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../inflector/methods" +require "active_support/inflector/methods" module ActiveSupport # Autoload and eager load conveniences for your library. diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb index 4e9595ed42..948be75638 100644 --- a/activesupport/lib/active_support/dependencies/interlock.rb +++ b/activesupport/lib/active_support/dependencies/interlock.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../concurrency/share_lock" +require "active_support/concurrency/share_lock" module ActiveSupport #:nodoc: module Dependencies #:nodoc: diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index e6550fdcd0..7271ab565b 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -14,13 +14,13 @@ module ActiveSupport # a circular require warning for active_support/deprecation.rb. # # So, we define the constant first, and load dependencies later. - require_relative "deprecation/instance_delegator" - require_relative "deprecation/behaviors" - require_relative "deprecation/reporting" - require_relative "deprecation/constant_accessor" - require_relative "deprecation/method_wrappers" - require_relative "deprecation/proxy_wrappers" - require_relative "core_ext/module/deprecation" + require "active_support/deprecation/instance_delegator" + require "active_support/deprecation/behaviors" + require "active_support/deprecation/reporting" + require "active_support/deprecation/constant_accessor" + require "active_support/deprecation/method_wrappers" + require "active_support/deprecation/proxy_wrappers" + require "active_support/core_ext/module/deprecation" include Singleton include InstanceDelegator @@ -35,7 +35,7 @@ module ActiveSupport # and the second is a library name. # # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') - def initialize(deprecation_horizon = "6.0", gem_name = "Rails") + def initialize(deprecation_horizon = "6.1", gem_name = "Rails") self.gem_name = gem_name self.deprecation_horizon = deprecation_horizon # By default, warnings are not silenced and debugging is off. diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 967320bcb9..66d6f3225a 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "../notifications" +require "active_support/notifications" module ActiveSupport # Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>. @@ -27,7 +27,7 @@ module ActiveSupport if defined?(Rails.logger) && Rails.logger Rails.logger else - require_relative "../logger" + require "active_support/logger" ActiveSupport::Logger.new($stderr) end logger.warn message @@ -85,7 +85,7 @@ module ActiveSupport # ActiveSupport::Deprecation.behavior = :stderr # ActiveSupport::Deprecation.behavior = [:stderr, :log] # ActiveSupport::Deprecation.behavior = MyCustomHandler - # ActiveSupport::Deprecation.behavior = ->(message, callstack) { + # ActiveSupport::Deprecation.behavior = ->(message, callstack, deprecation_horizon, gem_name) { # # custom stuff # } def behavior=(behavior) diff --git a/activesupport/lib/active_support/deprecation/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb index 0dfd96d134..1ed0015812 100644 --- a/activesupport/lib/active_support/deprecation/constant_accessor.rb +++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "../inflector/methods" - module ActiveSupport class Deprecation # DeprecatedConstantAccessor transforms a constant into a deprecated one by @@ -17,7 +15,7 @@ module ActiveSupport # # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) # - # (In a later update, the original implementation of `PLANETS` has been removed.) + # # (In a later update, the original implementation of `PLANETS` has been removed.) # # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) # include ActiveSupport::Deprecation::DeprecatedConstantAccessor @@ -29,6 +27,8 @@ module ActiveSupport # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] module DeprecatedConstantAccessor def self.included(base) + require "active_support/inflector/methods" + extension = Module.new do def const_missing(missing_const_name) if class_variable_defined?(:@@_deprecated_constants) diff --git a/activesupport/lib/active_support/deprecation/instance_delegator.rb b/activesupport/lib/active_support/deprecation/instance_delegator.rb index 539357c83e..8beda373a2 100644 --- a/activesupport/lib/active_support/deprecation/instance_delegator.rb +++ b/activesupport/lib/active_support/deprecation/instance_delegator.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../core_ext/kernel/singleton_class" -require_relative "../core_ext/module/delegation" +require "active_support/core_ext/kernel/singleton_class" +require "active_support/core_ext/module/delegation" module ActiveSupport class Deprecation diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index 8c942ed9a5..5be893d281 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -1,16 +1,14 @@ # frozen_string_literal: true -require_relative "../core_ext/module/aliasing" -require_relative "../core_ext/array/extract_options" +require "active_support/core_ext/module/aliasing" +require "active_support/core_ext/array/extract_options" module ActiveSupport class Deprecation module MethodWrapper # Declare that a method has been deprecated. # - # module Fred - # extend self - # + # class Fred # def aaa; end # def bbb; end # def ccc; end @@ -22,15 +20,15 @@ module ActiveSupport # ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead') # # => Fred # - # Fred.aaa + # Fred.new.aaa # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10) # # => nil # - # Fred.bbb + # Fred.new.bbb # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11) # # => nil # - # Fred.ccc + # Fred.new.ccc # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12) # # => nil # @@ -39,7 +37,7 @@ module ActiveSupport # ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator) # # => [:ddd] # - # Fred.ddd + # Fred.new.ddd # DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15) # # => nil # @@ -48,7 +46,7 @@ module ActiveSupport # custom_deprecator.deprecate_methods(Fred, eee: :zzz) # # => [:eee] # - # Fred.eee + # Fred.new.eee # DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18) # # => nil def deprecate_methods(target_module, *method_names) @@ -62,6 +60,13 @@ module ActiveSupport deprecator.deprecation_warning(method_name, options[method_name]) super(*args, &block) end + + case + when target_module.protected_method_defined?(method_name) + protected method_name + when target_module.private_method_defined?(method_name) + private method_name + end end end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 1920d75faf..896c0d2d8e 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require_relative "../inflector/methods" -require_relative "../core_ext/regexp" +require "active_support/core_ext/regexp" module ActiveSupport class Deprecation @@ -114,7 +113,7 @@ module ActiveSupport # # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) # - # (In a later update, the original implementation of `PLANETS` has been removed.) + # # (In a later update, the original implementation of `PLANETS` has been removed.) # # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006') @@ -125,6 +124,8 @@ module ActiveSupport # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] class DeprecatedConstantProxy < DeprecationProxy def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance, message: "#{old_const} is deprecated! Use #{new_const} instead.") + require "active_support/inflector/methods" + @old_const = old_const @new_const = new_const @deprecator = deprecator diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index 242e21b782..7075b5b869 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -61,7 +61,7 @@ module ActiveSupport case message when Symbol then "#{warning} (use #{message} instead)" when String then "#{warning} (#{message})" - else warning + else warning end end @@ -104,7 +104,7 @@ module ActiveSupport end end - RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + RAILS_GEM_ROOT = File.expand_path("../../../..", __dir__) + "/" def ignored_callstack(path) path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG["rubylibdir"]) diff --git a/activesupport/lib/active_support/digest.rb b/activesupport/lib/active_support/digest.rb new file mode 100644 index 0000000000..fba10fbdcf --- /dev/null +++ b/activesupport/lib/active_support/digest.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +module ActiveSupport + class Digest #:nodoc: + class <<self + def hash_digest_class + @hash_digest_class ||= ::Digest::MD5 + end + + def hash_digest_class=(klass) + raise ArgumentError, "#{klass} is expected to implement hexdigest class method" unless klass.respond_to?(:hexdigest) + @hash_digest_class = klass + end + + def hexdigest(arg) + hash_digest_class.hexdigest(arg)[0...32] + end + end + end +end diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 34024fa8c6..88897f811e 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require_relative "core_ext/array/conversions" -require_relative "core_ext/module/delegation" -require_relative "core_ext/object/acts_like" -require_relative "core_ext/string/filters" -require_relative "deprecation" +require "active_support/core_ext/array/conversions" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/string/filters" +require "active_support/deprecation" module ActiveSupport # Provides accurate date and time measurements using Date#advance and @@ -194,7 +194,6 @@ module ActiveSupport end parts[:seconds] = remainder - parts.reject! { |k, v| v.zero? } new(value, parts) end @@ -211,6 +210,7 @@ module ActiveSupport def initialize(value, parts) #:nodoc: @value, @parts = value, parts.to_h @parts.default = 0 + @parts.reject! { |k, v| v.zero? } end def coerce(other) #:nodoc: @@ -370,6 +370,8 @@ module ActiveSupport alias :before :ago def inspect #:nodoc: + return "0 seconds" if parts.empty? + parts. reduce(::Hash.new(0)) { |h, (l, r)| h[l] += r; h }. sort_by { |unit, _ | PARTS.index(unit) }. @@ -381,6 +383,14 @@ module ActiveSupport to_i end + def init_with(coder) #:nodoc: + initialize(coder["value"], coder["parts"]) + end + + def encode_with(coder) #:nodoc: + coder.map = { "value" => @value, "parts" => @parts } + end + # Build ISO 8601 Duration string for this duration. # The +precision+ parameter can be used to limit seconds' precision of duration. def iso8601(precision: nil) diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb index a002424cd9..1847eeaa86 100644 --- a/activesupport/lib/active_support/duration/iso8601_parser.rb +++ b/activesupport/lib/active_support/duration/iso8601_parser.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "strscan" -require_relative "../core_ext/regexp" +require "active_support/core_ext/regexp" module ActiveSupport class Duration @@ -118,7 +118,7 @@ module ActiveSupport raise_parsing_error "(only last part can be fractional)" end - return true + true end end end diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb index 985eac113f..84ae29c1ec 100644 --- a/activesupport/lib/active_support/duration/iso8601_serializer.rb +++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require_relative "../core_ext/object/blank" -require_relative "../core_ext/hash/transform_values" +require "active_support/core_ext/object/blank" module ActiveSupport class Duration diff --git a/activesupport/lib/active_support/encrypted_configuration.rb b/activesupport/lib/active_support/encrypted_configuration.rb index b403048627..3c6da10548 100644 --- a/activesupport/lib/active_support/encrypted_configuration.rb +++ b/activesupport/lib/active_support/encrypted_configuration.rb @@ -11,8 +11,9 @@ module ActiveSupport delegate :[], :fetch, to: :config delegate_missing_to :options - def initialize(config_path:, key_path:, env_key:) - super content_path: config_path, key_path: key_path, env_key: env_key + def initialize(config_path:, key_path:, env_key:, raise_if_missing_key:) + super content_path: config_path, key_path: key_path, + env_key: env_key, raise_if_missing_key: raise_if_missing_key end # Allow a config to be started without a file present @@ -22,6 +23,12 @@ module ActiveSupport "" end + def write(contents) + deserialize(contents) + + super + end + def config @config ||= deserialize(read).deep_symbolize_keys end @@ -31,12 +38,8 @@ module ActiveSupport @options ||= ActiveSupport::InheritableOptions.new(config) end - def serialize(config) - config.present? ? YAML.dump(config) : "" - end - def deserialize(config) - config.present? ? YAML.load(config) : {} + config.present? ? YAML.load(config, content_path) : {} end end end diff --git a/activesupport/lib/active_support/encrypted_file.rb b/activesupport/lib/active_support/encrypted_file.rb index 1c4c1cb457..c66f1b557e 100644 --- a/activesupport/lib/active_support/encrypted_file.rb +++ b/activesupport/lib/active_support/encrypted_file.rb @@ -2,8 +2,6 @@ require "pathname" require "active_support/message_encryptor" -require "active_support/core_ext/string/strip" -require "active_support/core_ext/module/delegation" module ActiveSupport class EncryptedFile @@ -28,11 +26,11 @@ module ActiveSupport end - attr_reader :content_path, :key_path, :env_key + attr_reader :content_path, :key_path, :env_key, :raise_if_missing_key - def initialize(content_path:, key_path:, env_key:) + def initialize(content_path:, key_path:, env_key:, raise_if_missing_key:) @content_path, @key_path = Pathname.new(content_path), Pathname.new(key_path) - @env_key = env_key + @env_key, @raise_if_missing_key = env_key, raise_if_missing_key end def key @@ -40,7 +38,7 @@ module ActiveSupport end def read - if content_path.exist? + if !key.nil? && content_path.exist? decrypt content_path.binread else raise MissingContentError, content_path @@ -59,7 +57,7 @@ module ActiveSupport private def writing(contents) - tmp_file = "#{content_path.basename}.#{Process.pid}" + tmp_file = "#{Process.pid}.#{content_path.basename.to_s.chomp('.enc')}" tmp_path = Pathname.new File.join(Dir.tmpdir, tmp_file) tmp_path.binwrite contents @@ -95,7 +93,7 @@ module ActiveSupport end def handle_missing_key - raise MissingKeyError, key_path: key_path, env_key: env_key + raise MissingKeyError, key_path: key_path, env_key: env_key if raise_if_missing_key end end end diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb index fd87d84795..f48c586cad 100644 --- a/activesupport/lib/active_support/execution_wrapper.rb +++ b/activesupport/lib/active_support/execution_wrapper.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "callbacks" +require "active_support/callbacks" module ActiveSupport class ExecutionWrapper diff --git a/activesupport/lib/active_support/executor.rb b/activesupport/lib/active_support/executor.rb index e6487ba69d..ce391b07ec 100644 --- a/activesupport/lib/active_support/executor.rb +++ b/activesupport/lib/active_support/executor.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "execution_wrapper" +require "active_support/execution_wrapper" module ActiveSupport class Executor < ExecutionWrapper diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index bcf7b9de64..1a0bb10815 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "core_ext/time/calculations" +require "active_support/core_ext/time/calculations" module ActiveSupport # FileUpdateChecker specifies the API used by Rails to watch files diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index 2a7ef2f820..c951ad16a3 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -7,8 +7,8 @@ module ActiveSupport end module VERSION - MAJOR = 5 - MINOR = 2 + MAJOR = 6 + MINOR = 0 TINY = 0 PRE = "alpha" diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 12291af443..e4afc8af93 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "core_ext/hash/keys" -require_relative "core_ext/hash/reverse_merge" +require "active_support/core_ext/hash/keys" +require "active_support/core_ext/hash/reverse_merge" module ActiveSupport # Implements a hash where keys <tt>:foo</tt> and <tt>"foo"</tt> are considered @@ -177,20 +177,18 @@ module ActiveSupport super(convert_key(key), *extras) end - if Hash.new.respond_to?(:dig) - # Same as <tt>Hash#dig</tt> where the key passed as argument can be - # either a string or a symbol: - # - # counters = ActiveSupport::HashWithIndifferentAccess.new - # counters[:foo] = { bar: 1 } - # - # counters.dig('foo', 'bar') # => 1 - # counters.dig(:foo, :bar) # => 1 - # counters.dig(:zoo) # => nil - def dig(*args) - args[0] = convert_key(args[0]) if args.size > 0 - super(*args) - end + # Same as <tt>Hash#dig</tt> where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = { bar: 1 } + # + # counters.dig('foo', 'bar') # => 1 + # counters.dig(:foo, :bar) # => 1 + # counters.dig(:zoo) # => nil + def dig(*args) + args[0] = convert_key(args[0]) if args.size > 0 + super(*args) end # Same as <tt>Hash#default</tt> where the key passed as argument can be @@ -228,7 +226,7 @@ module ActiveSupport # hash.fetch_values('a', 'c') # => KeyError: key not found: "c" def fetch_values(*indices, &block) indices.collect { |key| fetch(key, &block) } - end if Hash.method_defined?(:fetch_values) + end # Returns a shallow copy of the hash. # @@ -306,6 +304,29 @@ module ActiveSupport dup.tap { |hash| hash.transform_values!(*args, &block) } end + def transform_keys(*args, &block) + return to_enum(:transform_keys) unless block_given? + dup.tap { |hash| hash.transform_keys!(*args, &block) } + end + + def transform_keys! + return enum_for(:transform_keys!) { size } unless block_given? + keys.each do |key| + self[yield(key)] = delete(key) + end + self + end + + def slice(*keys) + keys.map! { |key| convert_key(key) } + self.class.new(super) + end + + def slice!(*keys) + keys.map! { |key| convert_key(key) } + super + end + def compact dup.tap(&:compact!) end diff --git a/activesupport/lib/active_support/i18n.rb b/activesupport/lib/active_support/i18n.rb index 80f1475630..39dab1cc71 100644 --- a/activesupport/lib/active_support/i18n.rb +++ b/activesupport/lib/active_support/i18n.rb @@ -1,15 +1,16 @@ # frozen_string_literal: true -require_relative "core_ext/hash/deep_merge" -require_relative "core_ext/hash/except" -require_relative "core_ext/hash/slice" +require "active_support/core_ext/hash/deep_merge" +require "active_support/core_ext/hash/except" +require "active_support/core_ext/hash/slice" begin require "i18n" rescue LoadError => e $stderr.puts "The i18n gem is not available. Please add it to your Gemfile and run bundle install" raise e end -require_relative "lazy_load_hooks" +require "active_support/lazy_load_hooks" ActiveSupport.run_load_hooks(:i18n) I18n.load_path << File.expand_path("locale/en.yml", __dir__) +I18n.load_path << File.expand_path("locale/en.rb", __dir__) diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 369aecff69..ce8bfbfd8c 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true require "active_support" -require_relative "file_update_checker" -require_relative "core_ext/array/wrap" +require "active_support/file_update_checker" +require "active_support/core_ext/array/wrap" # :enddoc: diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index e8e1657111..baf1cb3038 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "inflector/inflections" +require "active_support/inflector/inflections" #-- # Defines the standard inflection rules. These are the starting point for diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb index a6adb15a18..d77f04c9c5 100644 --- a/activesupport/lib/active_support/inflector.rb +++ b/activesupport/lib/active_support/inflector.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true # in case active_support/inflector is required without the rest of active_support -require_relative "inflector/inflections" -require_relative "inflector/transliterate" -require_relative "inflector/methods" +require "active_support/inflector/inflections" +require "active_support/inflector/transliterate" +require "active_support/inflector/methods" -require_relative "inflections" -require_relative "core_ext/string/inflections" +require "active_support/inflections" +require "active_support/core_ext/string/inflections" diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 36dfbc5f42..7e5dff1d6d 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true require "concurrent/map" -require_relative "../core_ext/array/prepend_and_append" -require_relative "../core_ext/regexp" -require_relative "../i18n" +require "active_support/core_ext/array/prepend_and_append" +require "active_support/core_ext/regexp" +require "active_support/i18n" +require "active_support/deprecation" module ActiveSupport module Inflector @@ -67,16 +68,21 @@ module ActiveSupport end attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex + deprecate :acronym_regex + + attr_reader :acronyms_camelize_regex, :acronyms_underscore_regex # :nodoc: def initialize - @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/ + @plurals, @singulars, @uncountables, @humans, @acronyms = [], [], Uncountables.new, [], {} + define_acronym_regex_patterns end # Private, for the test suite. def initialize_dup(orig) # :nodoc: - %w(plurals singulars uncountables humans acronyms acronym_regex).each do |scope| + %w(plurals singulars uncountables humans acronyms).each do |scope| instance_variable_set("@#{scope}", orig.send(scope).dup) end + define_acronym_regex_patterns end # Specifies a new acronym. An acronym must be specified as it will appear @@ -130,7 +136,7 @@ module ActiveSupport # camelize 'mcdonald' # => 'McDonald' def acronym(word) @acronyms[word.downcase] = word - @acronym_regex = /#{@acronyms.values.join("|")}/ + define_acronym_regex_patterns end # Specifies a new pluralization rule and its replacement. The rule can @@ -221,10 +227,18 @@ module ActiveSupport case scope when :all @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, [] - else + else instance_variable_set "@#{scope}", [] end end + + private + + def define_acronym_regex_patterns + @acronym_regex = @acronyms.empty? ? /(?=a)b/ : /#{@acronyms.values.join("|")}/ + @acronyms_camelize_regex = /^(?:#{@acronym_regex}(?=\b|[A-Z_])|\w)/ + @acronyms_underscore_regex = /(?:(?<=([A-Za-z\d]))|\b)(#{@acronym_regex})(?=\b|[^a-z])/ + end end # Yields a singleton instance of Inflector::Inflections so you can specify diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 9398ed60a4..339b93b8da 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../inflections" -require_relative "../core_ext/regexp" +require "active_support/inflections" +require "active_support/core_ext/regexp" module ActiveSupport # The Inflector transforms words from singular to plural, class names to table @@ -71,7 +71,7 @@ module ActiveSupport if uppercase_first_letter string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize } else - string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase } + string = string.sub(inflections.acronyms_camelize_regex) { |match| match.downcase } end string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } string.gsub!("/".freeze, "::".freeze) @@ -92,7 +92,7 @@ module ActiveSupport def underscore(camel_cased_word) return camel_cased_word unless /[A-Z-]|::/.match?(camel_cased_word) word = camel_cased_word.to_s.gsub("::".freeze, "/".freeze) - word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" } + word.gsub!(inflections.acronyms_underscore_regex) { "#{$1 && '_'.freeze }#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze) word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze) word.tr!("-".freeze, "_".freeze) @@ -138,7 +138,7 @@ module ActiveSupport result.tr!("_".freeze, " ".freeze) result.gsub!(/([a-z\d]*)/i) do |match| - "#{inflections.acronyms[match] || match.downcase}" + "#{inflections.acronyms[match.downcase] || match.downcase}" end if capitalize @@ -341,18 +341,7 @@ module ActiveSupport # ordinal(-11) # => "th" # ordinal(-1021) # => "st" def ordinal(number) - abs_number = number.to_i.abs - - if (11..13).include?(abs_number % 100) - "th" - else - case abs_number % 10 - when 1; "st" - when 2; "nd" - when 3; "rd" - else "th" - end - end + I18n.translate("number.nth.ordinals", number: number) end # Turns a number into an ordinal string used to denote the position in an @@ -365,7 +354,7 @@ module ActiveSupport # ordinalize(-11) # => "-11th" # ordinalize(-1021) # => "-1021st" def ordinalize(number) - "#{number}#{ordinal(number)}" + I18n.translate("number.nth.ordinalized", number: number) end private diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index aa7b21734e..6f2ca4999c 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../core_ext/string/multibyte" -require_relative "../i18n" +require "active_support/core_ext/string/multibyte" +require "active_support/i18n" module ActiveSupport module Inflector @@ -71,17 +71,23 @@ module ActiveSupport # a 'pretty' URL. # # parameterize("Donald E. Knuth") # => "donald-e-knuth" - # parameterize("^trés|Jolie-- ") # => "tres-jolie" + # parameterize("^très|Jolie-- ") # => "tres-jolie" # - # To use a custom separator, override the `separator` argument. + # To use a custom separator, override the +separator+ argument. # # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth" - # parameterize("^trés|Jolie-- ", separator: '_') # => "tres_jolie" + # parameterize("^très|Jolie__ ", separator: '_') # => "tres_jolie" # - # To preserve the case of the characters in a string, use the `preserve_case` argument. + # To preserve the case of the characters in a string, use the +preserve_case+ argument. # # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth" - # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie" + # parameterize("^très|Jolie-- ", preserve_case: true) # => "tres-Jolie" + # + # It preserves dashes and underscores unless they are used as separators: + # + # parameterize("^très|Jolie__ ") # => "tres-jolie__" + # parameterize("^très|Jolie-- ", separator: "_") # => "tres_jolie--" + # parameterize("^très_Jolie-- ", separator: ".") # => "tres_jolie--" # def parameterize(string, separator: "-", preserve_case: false) # Replace accented chars with their ASCII equivalents. diff --git a/activesupport/lib/active_support/json.rb b/activesupport/lib/active_support/json.rb index b5672025fb..d7887175c0 100644 --- a/activesupport/lib/active_support/json.rb +++ b/activesupport/lib/active_support/json.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true -require_relative "json/decoding" -require_relative "json/encoding" +require "active_support/json/decoding" +require "active_support/json/encoding" diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index caa4082dde..8c0e016dc5 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../core_ext/module/attribute_accessors" -require_relative "../core_ext/module/delegation" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/module/delegation" require "json" module ActiveSupport diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 4016d13364..de1b8ac8cf 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../core_ext/object/json" -require_relative "../core_ext/module/delegation" +require "active_support/core_ext/object/json" +require "active_support/core_ext/module/delegation" module ActiveSupport class << self @@ -54,9 +54,13 @@ module ActiveSupport class EscapedString < String #:nodoc: def to_json(*) if Encoding.escape_html_entities_in_json - super.gsub ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS + s = super + s.gsub! ESCAPE_REGEX_WITH_HTML_ENTITIES, ESCAPED_CHARS + s else - super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS + s = super + s.gsub! ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS + s end end diff --git a/activesupport/lib/active_support/locale/en.rb b/activesupport/lib/active_support/locale/en.rb new file mode 100644 index 0000000000..26c2280c95 --- /dev/null +++ b/activesupport/lib/active_support/locale/en.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +{ + en: { + number: { + nth: { + ordinals: lambda do |_key, number:, **_options| + abs_number = number.to_i.abs + + if (11..13).cover?(abs_number % 100) + "th" + else + case abs_number % 10 + when 1 then "st" + when 2 then "nd" + when 3 then "rd" + else "th" + end + end + end, + + ordinalized: lambda do |_key, number:, **_options| + "#{number}#{ActiveSupport::Inflector.ordinal(number)}" + end + } + } + } +} diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb index 05a11221bf..0f7be06c8e 100644 --- a/activesupport/lib/active_support/log_subscriber.rb +++ b/activesupport/lib/active_support/log_subscriber.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative "core_ext/module/attribute_accessors" -require_relative "core_ext/class/attribute" -require_relative "subscriber" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/core_ext/class/attribute" +require "active_support/subscriber" module ActiveSupport # ActiveSupport::LogSubscriber is an object set to consume diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 5b2abfc57c..3f19ef5009 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative "../log_subscriber" -require_relative "../logger" -require_relative "../notifications" +require "active_support/log_subscriber" +require "active_support/logger" +require "active_support/notifications" module ActiveSupport class LogSubscriber diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb index 3397ac4c9f..8152a182b4 100644 --- a/activesupport/lib/active_support/logger.rb +++ b/activesupport/lib/active_support/logger.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "logger_silence" -require_relative "logger_thread_safe_level" +require "active_support/logger_silence" +require "active_support/logger_thread_safe_level" require "logger" module ActiveSupport diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb index 693c7a1947..89f32b6782 100644 --- a/activesupport/lib/active_support/logger_silence.rb +++ b/activesupport/lib/active_support/logger_silence.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "concern" -require_relative "core_ext/module/attribute_accessors" +require "active_support/concern" +require "active_support/core_ext/module/attribute_accessors" require "concurrent" module LoggerSilence diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb index 3c7f53d92c..ba32813d3d 100644 --- a/activesupport/lib/active_support/logger_thread_safe_level.rb +++ b/activesupport/lib/active_support/logger_thread_safe_level.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "concern" +require "active_support/concern" module ActiveSupport module LoggerThreadSafeLevel # :nodoc: diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 27620f56be..8b73270894 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -2,9 +2,10 @@ require "openssl" require "base64" -require_relative "core_ext/array/extract_options" -require_relative "message_verifier" -require_relative "messages/metadata" +require "active_support/core_ext/array/extract_options" +require "active_support/core_ext/module/attribute_accessors" +require "active_support/message_verifier" +require "active_support/messages/metadata" module ActiveSupport # MessageEncryptor is a simple way to encrypt values which get stored @@ -54,10 +55,36 @@ module ActiveSupport # # Then the messages can be verified and returned upto the expire time. # Thereafter, verifying returns +nil+. + # + # === Rotating keys + # + # MessageEncryptor also supports rotating out old configurations by falling + # back to a stack of encryptors. Call +rotate+ to build and add an encryptor + # so +decrypt_and_verify+ will also try the fallback. + # + # By default any rotated encryptors use the values of the primary + # encryptor unless specified otherwise. + # + # You'd give your encryptor the new defaults: + # + # crypt = ActiveSupport::MessageEncryptor.new(@secret, cipher: "aes-256-gcm") + # + # Then gradually rotate the old values out by adding them as fallbacks. Any message + # generated with the old values will then work until the rotation is removed. + # + # crypt.rotate old_secret # Fallback to an old secret instead of @secret. + # crypt.rotate cipher: "aes-256-cbc" # Fallback to an old cipher instead of aes-256-gcm. + # + # Though if both the secret and the cipher was changed at the same time, + # the above should be combined into: + # + # crypt.rotate old_secret, cipher: "aes-256-cbc" class MessageEncryptor - class << self - attr_accessor :use_authenticated_message_encryption #:nodoc: + prepend Messages::Rotator::Encryptor + + cattr_accessor :use_authenticated_message_encryption, instance_accessor: false, default: false + class << self def default_cipher #:nodoc: if use_authenticated_message_encryption "aes-256-gcm" @@ -126,7 +153,7 @@ module ActiveSupport # Decrypt and verify a message. We need to verify the message in order to # avoid padding attacks. Reference: https://www.limited-entropy.com/padding-oracle-attacks/. - def decrypt_and_verify(data, purpose: nil) + def decrypt_and_verify(data, purpose: nil, **) _decrypt(verifier.verify(data), purpose) end diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 7110d6d2c9..83c39c0a86 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -1,9 +1,10 @@ # frozen_string_literal: true require "base64" -require_relative "core_ext/object/blank" -require_relative "security_utils" -require_relative "messages/metadata" +require "active_support/core_ext/object/blank" +require "active_support/security_utils" +require "active_support/messages/metadata" +require "active_support/messages/rotator" module ActiveSupport # +MessageVerifier+ makes it easy to generate and verify messages which are @@ -30,7 +31,7 @@ module ActiveSupport # # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default. # If you want to use a different hash algorithm, you can change it by providing - # `:digest` key as an option while initializing the verifier: + # +:digest+ key as an option while initializing the verifier: # # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256') # @@ -73,7 +74,33 @@ module ActiveSupport # Then the messages can be verified and returned upto the expire time. # Thereafter, the +verified+ method returns +nil+ while +verify+ raises # <tt>ActiveSupport::MessageVerifier::InvalidSignature</tt>. + # + # === Rotating keys + # + # MessageVerifier also supports rotating out old configurations by falling + # back to a stack of verifiers. Call +rotate+ to build and add a verifier to + # so either +verified+ or +verify+ will also try verifying with the fallback. + # + # By default any rotated verifiers use the values of the primary + # verifier unless specified otherwise. + # + # You'd give your verifier the new defaults: + # + # verifier = ActiveSupport::MessageVerifier.new(@secret, digest: "SHA512", serializer: JSON) + # + # Then gradually rotate the old values out by adding them as fallbacks. Any message + # generated with the old values will then work until the rotation is removed. + # + # verifier.rotate old_secret # Fallback to an old secret instead of @secret. + # verifier.rotate digest: "SHA256" # Fallback to an old digest instead of SHA512. + # verifier.rotate serializer: Marshal # Fallback to an old serializer instead of JSON. + # + # Though the above would most likely be combined into one rotation: + # + # verifier.rotate old_secret, digest: "SHA256", serializer: Marshal class MessageVerifier + prepend Messages::Rotator::Verifier + class InvalidSignature < StandardError; end def initialize(secret, options = {}) @@ -120,7 +147,7 @@ module ActiveSupport # # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff" # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format - def verified(signed_message, purpose: nil) + def verified(signed_message, purpose: nil, **) if valid_message?(signed_message) begin data = signed_message.split("--".freeze)[0] @@ -145,8 +172,8 @@ module ActiveSupport # # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature - def verify(signed_message, purpose: nil) - verified(signed_message, purpose: purpose) || raise(InvalidSignature) + def verify(*args) + verified(*args) || raise(InvalidSignature) end # Generates a signed message for the provided value. diff --git a/activesupport/lib/active_support/messages/rotation_configuration.rb b/activesupport/lib/active_support/messages/rotation_configuration.rb new file mode 100644 index 0000000000..bd50d6d348 --- /dev/null +++ b/activesupport/lib/active_support/messages/rotation_configuration.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + class RotationConfiguration # :nodoc: + attr_reader :signed, :encrypted + + def initialize + @signed, @encrypted = [], [] + end + + def rotate(kind, *args) + case kind + when :signed + @signed << args + when :encrypted + @encrypted << args + end + end + end + end +end diff --git a/activesupport/lib/active_support/messages/rotator.rb b/activesupport/lib/active_support/messages/rotator.rb new file mode 100644 index 0000000000..823a399d67 --- /dev/null +++ b/activesupport/lib/active_support/messages/rotator.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +module ActiveSupport + module Messages + module Rotator # :nodoc: + def initialize(*, **options) + super + + @options = options + @rotations = [] + end + + def rotate(*secrets, **options) + @rotations << build_rotation(*secrets, @options.merge(options)) + end + + module Encryptor + include Rotator + + def decrypt_and_verify(*args, on_rotation: nil, **options) + super + rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature + run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, options) } || raise + end + + private + def build_rotation(secret = @secret, sign_secret = @sign_secret, options) + self.class.new(secret, sign_secret, options) + end + end + + module Verifier + include Rotator + + def verified(*args, on_rotation: nil, **options) + super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, options) } + end + + private + def build_rotation(secret = @secret, options) + self.class.new(secret, options) + end + end + + private + def run_rotations(on_rotation) + @rotations.find do |rotation| + if message = yield(rotation) rescue next + on_rotation.call if on_rotation + return message + end + end + end + end + end +end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index d676827e8e..8152b8fd22 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require_relative "../json" -require_relative "../core_ext/string/access" -require_relative "../core_ext/string/behavior" -require_relative "../core_ext/module/delegation" -require_relative "../core_ext/regexp" +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/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index a64223c0e0..4f0e1165ef 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -11,7 +11,7 @@ module ActiveSupport NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation - UNICODE_VERSION = "9.0.0" + UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"] # The default normalization used for operations that require # normalization. It can be set to any of the normalizations @@ -21,96 +21,13 @@ module ActiveSupport attr_accessor :default_normalization_form @default_normalization_form = :kc - # Hangul character boundaries and properties - HANGUL_SBASE = 0xAC00 - HANGUL_LBASE = 0x1100 - HANGUL_VBASE = 0x1161 - HANGUL_TBASE = 0x11A7 - HANGUL_LCOUNT = 19 - HANGUL_VCOUNT = 21 - HANGUL_TCOUNT = 28 - HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT - HANGUL_SCOUNT = 11172 - HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT - - # Detect whether the codepoint is in a certain character class. Returns - # +true+ when it's in the specified character class and +false+ otherwise. - # Valid character classes are: <tt>:cr</tt>, <tt>:lf</tt>, <tt>:l</tt>, - # <tt>:v</tt>, <tt>:lv</tt>, <tt>:lvt</tt> and <tt>:t</tt>. - # - # Primarily used by the grapheme cluster support. - def in_char_class?(codepoint, classes) - classes.detect { |c| database.boundary[c] === codepoint } ? true : false - end - # Unpack the string at grapheme boundaries. Returns a list of character # lists. # # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]] # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]] def unpack_graphemes(string) - codepoints = string.codepoints.to_a - unpacked = [] - pos = 0 - marker = 0 - eoc = codepoints.length - while (pos < eoc) - pos += 1 - previous = codepoints[pos - 1] - current = codepoints[pos] - - # See http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules - should_break = - if pos == eoc - true - # GB3. CR X LF - elsif previous == database.boundary[:cr] && current == database.boundary[:lf] - false - # GB4. (Control|CR|LF) ÷ - elsif previous && in_char_class?(previous, [:control, :cr, :lf]) - true - # GB5. ÷ (Control|CR|LF) - elsif in_char_class?(current, [:control, :cr, :lf]) - true - # GB6. L X (L|V|LV|LVT) - elsif database.boundary[:l] === previous && in_char_class?(current, [:l, :v, :lv, :lvt]) - false - # GB7. (LV|V) X (V|T) - elsif in_char_class?(previous, [:lv, :v]) && in_char_class?(current, [:v, :t]) - false - # GB8. (LVT|T) X (T) - elsif in_char_class?(previous, [:lvt, :t]) && database.boundary[:t] === current - false - # GB9. X (Extend | ZWJ) - elsif in_char_class?(current, [:extend, :zwj]) - false - # GB9a. X SpacingMark - elsif database.boundary[:spacingmark] === current - false - # GB9b. Prepend X - elsif database.boundary[:prepend] === previous - false - # GB10. (E_Base | EBG) Extend* X E_Modifier - elsif (marker...pos).any? { |i| in_char_class?(codepoints[i], [:e_base, :e_base_gaz]) && codepoints[i + 1...pos].all? { |c| database.boundary[:extend] === c } } && database.boundary[:e_modifier] === current - false - # GB11. ZWJ X (Glue_After_Zwj | EBG) - elsif database.boundary[:zwj] === previous && in_char_class?(current, [:glue_after_zwj, :e_base_gaz]) - false - # GB12. ^ (RI RI)* RI X RI - # GB13. [^RI] (RI RI)* RI X RI - elsif codepoints[marker..pos].all? { |c| database.boundary[:regional_indicator] === c } && codepoints[marker..pos].count { |c| database.boundary[:regional_indicator] === c }.even? - false - # GB999. Any ÷ Any - else - true - end - - if should_break - unpacked << codepoints[marker..pos - 1] - marker = pos - end - end - unpacked + string.scan(/\X/).map(&:codepoints) end # Reverse operation of unpack_graphemes. @@ -120,100 +37,18 @@ module ActiveSupport unpacked.flatten.pack("U*") end - # Re-order codepoints so the string becomes canonical. - def reorder_characters(codepoints) - length = codepoints.length - 1 - pos = 0 - while pos < length do - cp1, cp2 = database.codepoints[codepoints[pos]], database.codepoints[codepoints[pos + 1]] - if (cp1.combining_class > cp2.combining_class) && (cp2.combining_class > 0) - codepoints[pos..pos + 1] = cp2.code, cp1.code - pos += (pos > 0 ? -1 : 1) - else - pos += 1 - end - end - codepoints - end - # Decompose composed characters to the decomposed form. def decompose(type, codepoints) - codepoints.inject([]) do |decomposed, cp| - # if it's a hangul syllable starter character - if HANGUL_SBASE <= cp && cp < HANGUL_SLAST - sindex = cp - HANGUL_SBASE - ncp = [] # new codepoints - ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT - ncp << HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT - tindex = sindex % HANGUL_TCOUNT - ncp << (HANGUL_TBASE + tindex) unless tindex == 0 - decomposed.concat ncp - # if the codepoint is decomposable in with the current decomposition type - elsif (ncp = database.codepoints[cp].decomp_mapping) && (!database.codepoints[cp].decomp_type || type == :compatibility) - decomposed.concat decompose(type, ncp.dup) - else - decomposed << cp - end + if type == :compatibility + codepoints.pack("U*").unicode_normalize(:nfkd).codepoints + else + codepoints.pack("U*").unicode_normalize(:nfd).codepoints end end # Compose decomposed characters to the composed form. def compose(codepoints) - pos = 0 - eoa = codepoints.length - 1 - starter_pos = 0 - starter_char = codepoints[0] - previous_combining_class = -1 - while pos < eoa - pos += 1 - lindex = starter_char - HANGUL_LBASE - # -- Hangul - if 0 <= lindex && lindex < HANGUL_LCOUNT - vindex = codepoints[starter_pos + 1] - HANGUL_VBASE rescue vindex = -1 - if 0 <= vindex && vindex < HANGUL_VCOUNT - tindex = codepoints[starter_pos + 2] - HANGUL_TBASE rescue tindex = -1 - if 0 <= tindex && tindex < HANGUL_TCOUNT - j = starter_pos + 2 - eoa -= 2 - else - tindex = 0 - j = starter_pos + 1 - eoa -= 1 - end - codepoints[starter_pos..j] = (lindex * HANGUL_VCOUNT + vindex) * HANGUL_TCOUNT + tindex + HANGUL_SBASE - end - starter_pos += 1 - starter_char = codepoints[starter_pos] - # -- Other characters - else - current_char = codepoints[pos] - current = database.codepoints[current_char] - if current.combining_class > previous_combining_class - if ref = database.composition_map[starter_char] - composition = ref[current_char] - else - composition = nil - end - unless composition.nil? - codepoints[starter_pos] = composition - starter_char = composition - codepoints.delete_at pos - eoa -= 1 - pos -= 1 - previous_combining_class = -1 - else - previous_combining_class = current.combining_class - end - else - previous_combining_class = current.combining_class - end - if current.combining_class == 0 - starter_pos = pos - starter_char = codepoints[pos] - end - end - end - codepoints + codepoints.pack("U*").unicode_normalize(:nfc).codepoints end # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars. @@ -266,129 +101,37 @@ module ActiveSupport def normalize(string, form = nil) form ||= @default_normalization_form # See http://www.unicode.org/reports/tr15, Table 1 - codepoints = string.codepoints.to_a case form when :d - reorder_characters(decompose(:canonical, codepoints)) + string.unicode_normalize(:nfd) when :c - compose(reorder_characters(decompose(:canonical, codepoints))) + string.unicode_normalize(:nfc) when :kd - reorder_characters(decompose(:compatibility, codepoints)) + string.unicode_normalize(:nfkd) when :kc - compose(reorder_characters(decompose(:compatibility, codepoints))) - else + string.unicode_normalize(:nfkc) + else raise ArgumentError, "#{form} is not a valid normalization variant", caller - end.pack("U*".freeze) + end end def downcase(string) - apply_mapping string, :lowercase_mapping + string.downcase end def upcase(string) - apply_mapping string, :uppercase_mapping + string.upcase end def swapcase(string) - apply_mapping string, :swapcase_mapping - end - - # Holds data about a codepoint in the Unicode database. - class Codepoint - attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping - - # Initializing Codepoint object with default values - def initialize - @combining_class = 0 - @uppercase_mapping = 0 - @lowercase_mapping = 0 - end - - def swapcase_mapping - uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping - end - end - - # Holds static data from the Unicode database. - class UnicodeDatabase - ATTRIBUTES = :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252 - - attr_writer(*ATTRIBUTES) - - def initialize - @codepoints = Hash.new(Codepoint.new) - @composition_exclusion = [] - @composition_map = {} - @boundary = {} - @cp1252 = {} - end - - # Lazy load the Unicode database so it's only loaded when it's actually used - ATTRIBUTES.each do |attr_name| - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{attr_name} # def codepoints - load # load - @#{attr_name} # @codepoints - end # end - EOS - end - - # Loads the Unicode database and returns all the internal objects of - # UnicodeDatabase. - def load - begin - @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, "rb") { |f| Marshal.load f.read } - rescue => e - raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable") - end - - # Redefine the === method so we can write shorter rules for grapheme cluster breaks - @boundary.each_key do |k| - @boundary[k].instance_eval do - def ===(other) - detect { |i| i === other } ? true : false - end - end if @boundary[k].kind_of?(Array) - end - - # define attr_reader methods for the instance variables - class << self - attr_reader(*ATTRIBUTES) - end - end - - # Returns the directory in which the data files are stored. - def self.dirname - File.expand_path("../values", __dir__) - end - - # Returns the filename for the data file for this version. - def self.filename - File.expand_path File.join(dirname, "unicode_tables.dat") - end + string.swapcase end private - def apply_mapping(string, mapping) - database.codepoints - string.each_codepoint.map do |codepoint| - cp = database.codepoints[codepoint] - if cp && (ncp = cp.send(mapping)) && ncp > 0 - ncp - else - codepoint - end - end.pack("U*") - end - def recode_windows1252_chars(string) string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) end - - def database - @database ||= UnicodeDatabase.new - end end end end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 96a3463905..6207de8094 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative "notifications/instrumenter" -require_relative "notifications/fanout" -require_relative "per_thread_registry" +require "active_support/notifications/instrumenter" +require "active_support/notifications/fanout" +require "active_support/per_thread_registry" module ActiveSupport # = Notifications diff --git a/activesupport/lib/active_support/number_helper/number_converter.rb b/activesupport/lib/active_support/number_helper/number_converter.rb index 5ea9c8f113..06ba797a13 100644 --- a/activesupport/lib/active_support/number_helper/number_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_converter.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true -require_relative "../core_ext/big_decimal/conversions" -require_relative "../core_ext/object/blank" -require_relative "../core_ext/hash/keys" -require_relative "../i18n" -require_relative "../core_ext/class/attribute" +require "active_support/core_ext/big_decimal/conversions" +require "active_support/core_ext/object/blank" +require "active_support/core_ext/hash/keys" +require "active_support/i18n" +require "active_support/core_ext/class/attribute" module ActiveSupport module NumberHelper diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb index 1943b9e295..a25e22cbd3 100644 --- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require_relative "../core_ext/numeric/inquiry" - module ActiveSupport module NumberHelper class NumberToCurrencyConverter < NumberConverter # :nodoc: diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb index 3b62fe6819..eb528a0583 100644 --- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -37,26 +37,6 @@ module ActiveSupport private - def digits_and_rounded_number(precision) - if zero? - [1, 0] - else - digits = digit_count(number) - multiplier = 10**(digits - precision) - rounded_number = calculate_rounded_number(multiplier) - digits = digit_count(rounded_number) # After rounding, the number of digits may have changed - [digits, rounded_number] - end - end - - def calculate_rounded_number(multiplier) - (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier - end - - def digit_count(number) - number.zero? ? 1 : (Math.log10(absolute_number(number)) + 1).floor - end - def strip_insignificant_zeros options[:strip_insignificant_zeros] end diff --git a/activesupport/lib/active_support/number_helper/rounding_helper.rb b/activesupport/lib/active_support/number_helper/rounding_helper.rb index a5b28296a2..2ad8d49c4e 100644 --- a/activesupport/lib/active_support/number_helper/rounding_helper.rb +++ b/activesupport/lib/active_support/number_helper/rounding_helper.rb @@ -36,7 +36,7 @@ module ActiveSupport return 0 if number.zero? digits = digit_count(number) multiplier = 10**(digits - precision) - (number / BigDecimal.new(multiplier.to_f.to_s)).round * multiplier + (number / BigDecimal(multiplier.to_f.to_s)).round * multiplier end def convert_to_decimal(number) diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb index 42cbbe7c42..ab9ca727f6 100644 --- a/activesupport/lib/active_support/option_merger.rb +++ b/activesupport/lib/active_support/option_merger.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "core_ext/hash/deep_merge" +require "active_support/core_ext/hash/deep_merge" module ActiveSupport class OptionMerger #:nodoc: diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index fa7825b3ba..c4e419f546 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "core_ext/object/blank" +require "active_support/core_ext/object/blank" module ActiveSupport # Usually key value pairs are handled something like this: @@ -24,7 +24,7 @@ module ActiveSupport # To raise an exception when the value is blank, append a # bang to the key name, like: # - # h.dog! # => raises KeyError: key not found: :dog + # h.dog! # => raises KeyError: :dog is blank # class OrderedOptions < Hash alias_method :_get, :[] # preserve the original #[] method @@ -46,7 +46,7 @@ module ActiveSupport bangs = name_string.chomp!("!") if bangs - fetch(name_string.to_sym).presence || raise(KeyError.new("#{name_string} is blank.")) + self[name_string].presence || raise(KeyError.new(":#{name_string} is blank")) else self[name_string] end diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index dd0cc6a604..eb92fb4371 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "core_ext/module/delegation" +require "active_support/core_ext/module/delegation" module ActiveSupport # NOTE: This approach has been deprecated for end-user code in favor of {thread_mattr_accessor}[rdoc-ref:Module#thread_mattr_accessor] and friends. diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb index fe82d1cc14..8b727a69ec 100644 --- a/activesupport/lib/active_support/rails.rb +++ b/activesupport/lib/active_support/rails.rb @@ -11,25 +11,19 @@ # Rails and can change anytime. # Defines Object#blank? and Object#present?. -require_relative "core_ext/object/blank" +require "active_support/core_ext/object/blank" # Rails own autoload, eager_load, etc. -require_relative "dependencies/autoload" +require "active_support/dependencies/autoload" # Support for ClassMethods and the included macro. -require_relative "concern" +require "active_support/concern" # Defines Class#class_attribute. -require_relative "core_ext/class/attribute" +require "active_support/core_ext/class/attribute" # Defines Module#delegate. -require_relative "core_ext/module/delegation" +require "active_support/core_ext/module/delegation" # Defines ActiveSupport::Deprecation. -require_relative "deprecation" - -# Defines Regexp#match?. -# -# This should be removed when Rails needs Ruby 2.4 or later, and the require -# added where other Regexp extensions are being used (easy to grep). -require_relative "core_ext/regexp" +require "active_support/deprecation" diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index fe132ca7c6..605b50d346 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "active_support" -require_relative "i18n_railtie" +require "active_support/i18n_railtie" module ActiveSupport class Railtie < Rails::Railtie # :nodoc: @@ -10,9 +10,11 @@ module ActiveSupport config.eager_load_namespaces << ActiveSupport initializer "active_support.set_authenticated_message_encryption" do |app| - if app.config.active_support.respond_to?(:use_authenticated_message_encryption) - ActiveSupport::MessageEncryptor.use_authenticated_message_encryption = - app.config.active_support.use_authenticated_message_encryption + config.after_initialize do + unless app.config.active_support.use_authenticated_message_encryption.nil? + ActiveSupport::MessageEncryptor.use_authenticated_message_encryption = + app.config.active_support.use_authenticated_message_encryption + end end end @@ -36,14 +38,14 @@ module ActiveSupport rescue TZInfo::DataSourceNotFound => e raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install" end - require_relative "core_ext/time/zones" + require "active_support/core_ext/time/zones" Time.zone_default = Time.find_zone!(app.config.time_zone) end # Sets the default week start # If assigned value is not a valid day symbol (e.g. :sunday, :monday, ...), an exception will be raised. initializer "active_support.initialize_beginning_of_week" do |app| - require_relative "core_ext/date/calculations" + require "active_support/core_ext/date/calculations" beginning_of_week_default = Date.find_beginning_of_week!(app.config.beginning_of_week) Date.beginning_of_week_default = beginning_of_week_default @@ -66,5 +68,13 @@ module ActiveSupport ActiveSupport.send(k, v) if ActiveSupport.respond_to? k end end + + initializer "active_support.set_hash_digest_class" do |app| + config.after_initialize do + if app.config.active_support.use_sha1_digests + ActiveSupport::Digest.hash_digest_class = ::Digest::SHA1 + end + end + end end end diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb index 44062e3491..b26d9c3665 100644 --- a/activesupport/lib/active_support/reloader.rb +++ b/activesupport/lib/active_support/reloader.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -require_relative "execution_wrapper" +require "active_support/execution_wrapper" module ActiveSupport #-- @@ -28,14 +28,17 @@ module ActiveSupport define_callbacks :class_unload + # Registers a callback that will run once at application startup and every time the code is reloaded. def self.to_prepare(*args, &block) set_callback(:prepare, *args, &block) end + # Registers a callback that will run immediately before the classes are unloaded. def self.before_class_unload(*args, &block) set_callback(:class_unload, *args, &block) end + # Registers a callback that will run immediately after the classes are unloaded. def self.after_class_unload(*args, &block) set_callback(:class_unload, :after, *args, &block) end diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index 2f6aeef48a..e0fa29cacb 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -require_relative "concern" -require_relative "core_ext/class/attribute" -require_relative "core_ext/string/inflections" +require "active_support/concern" +require "active_support/core_ext/class/attribute" +require "active_support/core_ext/string/inflections" module ActiveSupport # Rescuable module adds support for easier exception handling. diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb index 51870559ec..20b6b9cd3f 100644 --- a/activesupport/lib/active_support/security_utils.rb +++ b/activesupport/lib/active_support/security_utils.rb @@ -1,17 +1,15 @@ # frozen_string_literal: true -require "digest" +require "digest/sha2" module ActiveSupport module SecurityUtils - # Constant time string comparison. + # Constant time string comparison, for fixed length strings. # # The values compared should be of fixed length, such as strings - # that have already been processed by HMAC. This should not be used - # on variable length plaintext strings because it could leak length info - # via timing attacks. - def secure_compare(a, b) - return false unless a.bytesize == b.bytesize + # that have already been processed by HMAC. Raises in case of length mismatch. + def fixed_length_secure_compare(a, b) + raise ArgumentError, "string length mismatch." unless a.bytesize == b.bytesize l = a.unpack "C#{a.bytesize}" @@ -19,11 +17,15 @@ module ActiveSupport b.each_byte { |byte| res |= byte ^ l.shift } res == 0 end - module_function :secure_compare + module_function :fixed_length_secure_compare - def variable_size_secure_compare(a, b) # :nodoc: - secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) + # Constant time string comparison, for variable length strings. + # + # The values are first processed by SHA256, so that we don't leak length info + # via timing attacks. + def secure_compare(a, b) + fixed_length_secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b)) && a == b end - module_function :variable_size_secure_compare + module_function :secure_compare end end diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index 7913bb815e..8ad39f7a05 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "per_thread_registry" -require_relative "notifications" +require "active_support/per_thread_registry" +require "active_support/notifications" module ActiveSupport # ActiveSupport::Subscriber is an object set to consume @@ -54,25 +54,20 @@ module ActiveSupport @@subscribers ||= [] end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :subscriber, :notifier, :namespace - private + attr_reader :subscriber, :notifier, :namespace - def add_event_subscriber(event) # :doc: - return if %w{ start finish }.include?(event.to_s) + def add_event_subscriber(event) # :doc: + return if %w{ start finish }.include?(event.to_s) - pattern = "#{event}.#{namespace}" + pattern = "#{event}.#{namespace}" - # Don't add multiple subscribers (eg. if methods are redefined). - return if subscriber.patterns.include?(pattern) + # Don't add multiple subscribers (eg. if methods are redefined). + return if subscriber.patterns.include?(pattern) - subscriber.patterns << pattern - notifier.subscribe(pattern, subscriber) - end + subscriber.patterns << pattern + notifier.subscribe(pattern, subscriber) + end end attr_reader :patterns # :nodoc: diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index fe13eaed4e..b069ac94d4 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative "core_ext/module/delegation" -require_relative "core_ext/object/blank" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/object/blank" require "logger" -require_relative "logger" +require "active_support/logger" module ActiveSupport # Wraps any standard Logger object to provide tagging capabilities. @@ -52,7 +52,9 @@ module ActiveSupport def tags_text tags = current_tags - if tags.any? + if tags.one? + "[#{tags[0]}] " + elsif tags.any? tags.collect { |tag| "[#{tag}] " }.join end end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index bc42758f76..f17743b6db 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -2,16 +2,16 @@ gem "minitest" # make sure we get the gem, not stdlib require "minitest" -require_relative "testing/tagged_logging" -require_relative "testing/setup_and_teardown" -require_relative "testing/assertions" -require_relative "testing/deprecation" -require_relative "testing/declarative" -require_relative "testing/isolation" -require_relative "testing/constant_lookup" -require_relative "testing/time_helpers" -require_relative "testing/file_fixtures" -require_relative "core_ext/kernel/reporting" +require "active_support/testing/tagged_logging" +require "active_support/testing/setup_and_teardown" +require "active_support/testing/assertions" +require "active_support/testing/deprecation" +require "active_support/testing/declarative" +require "active_support/testing/isolation" +require "active_support/testing/constant_lookup" +require "active_support/testing/time_helpers" +require "active_support/testing/file_fixtures" +require "active_support/testing/parallelization" module ActiveSupport class TestCase < ::Minitest::Test @@ -40,12 +40,97 @@ module ActiveSupport def test_order ActiveSupport.test_order ||= :random end + + # Parallelizes the test suite. + # + # Takes a +workers+ argument that controls how many times the process + # is forked. For each process a new database will be created suffixed + # with the worker number. + # + # test-database-0 + # test-database-1 + # + # If <tt>ENV["PARALLEL_WORKERS"]</tt> is set the workers argument will be ignored + # and the environment variable will be used instead. This is useful for CI + # environments, or other environments where you may need more workers than + # you do for local testing. + # + # If the number of workers is set to +1+ or fewer, the tests will not be + # parallelized. + # + # The default parallelization method is to fork processes. If you'd like to + # use threads instead you can pass <tt>with: :threads</tt> to the +parallelize+ + # method. Note the threaded parallelization does not create multiple + # database and will not work with system tests at this time. + # + # parallelize(workers: 2, with: :threads) + # + # 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"] + + return if workers <= 1 + + executor = case with + when :processes + Testing::Parallelization.new(workers) + when :threads + Minitest::Parallel::Executor.new(workers) + else + raise ArgumentError, "#{with} is not a supported parallelization executor." + end + + self.lock_threads = false if defined?(self.lock_threads) && with == :threads + + Minitest.parallel_executor = executor + + parallelize_me! + end + + # Set up hook for parallel testing. This can be used if you have multiple + # databases or any behavior that needs to be run after the process is forked + # but before the tests run. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_setup do + # # create databases + # end + # end + def parallelize_setup(&block) + ActiveSupport::Testing::Parallelization.after_fork_hook do |worker| + yield worker + end + end + + # Clean up hook for parallel testing. This can be used to drop databases + # if your app uses multiple write/read databases or other clean up before + # the tests finish. This runs before the forked process is closed. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_teardown do + # # drop databases + # end + # end + def parallelize_teardown(&block) + ActiveSupport::Testing::Parallelization.run_cleanup_hook do |worker| + yield worker + end + end end alias_method :method_name, :name include ActiveSupport::Testing::TaggedLogging - include ActiveSupport::Testing::SetupAndTeardown + prepend ActiveSupport::Testing::SetupAndTeardown include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation include ActiveSupport::Testing::TimeHelpers diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index e2bc51ff7a..a891ff616d 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -58,6 +58,12 @@ module ActiveSupport # post :create, params: { article: {...} } # end # + # A hash of expressions/numeric differences can also be passed in and evaluated. + # + # assert_difference ->{ Article.count } => 1, ->{ Notification.count } => 2 do + # post :create, params: { article: {...} } + # end + # # A lambda or a list of lambdas can be passed in and evaluated: # # assert_difference ->{ Article.count }, 2 do @@ -73,20 +79,28 @@ module ActiveSupport # assert_difference 'Article.count', -1, 'An Article should be destroyed' do # post :delete, params: { id: ... } # end - def assert_difference(expression, difference = 1, message = nil, &block) - expressions = Array(expression) - - exps = expressions.map { |e| + def assert_difference(expression, *args, &block) + expressions = + if expression.is_a?(Hash) + message = args[0] + expression + else + difference = args[0] || 1 + message = args[1] + Hash[Array(expression).map { |e| [e, difference] }] + end + + exps = expressions.keys.map { |e| e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } } before = exps.map(&:call) retval = yield - expressions.zip(exps).each_with_index do |(code, e), i| - error = "#{code.inspect} didn't change by #{difference}" + expressions.zip(exps, before) do |(code, diff), exp, before_value| + error = "#{code.inspect} didn't change by #{diff}" error = "#{message}.\n#{error}" if message - assert_equal(before[i] + difference, e.call, error) + assert_equal(before_value + diff, exp.call, error) end retval @@ -156,11 +170,12 @@ module ActiveSupport after = exp.call - if to == UNTRACKED - error = "#{expression.inspect} didn't change" - error = "#{message}.\n#{error}" if message - assert_not_equal before, after, error - else + error = "#{expression.inspect} didn't change" + error = "#{error}. It was already #{to}" if before == to + error = "#{message}.\n#{error}" if message + assert before != after, error + + unless to == UNTRACKED error = "#{expression.inspect} didn't change to #{to}" error = "#{message}.\n#{error}" if message assert to === after, error @@ -190,7 +205,7 @@ module ActiveSupport error = "#{expression.inspect} did change to #{after}" error = "#{message}.\n#{error}" if message - assert_equal before, after, error + assert before == after, error retval end diff --git a/activesupport/lib/active_support/testing/constant_lookup.rb b/activesupport/lib/active_support/testing/constant_lookup.rb index 0fedd486fb..51167e9237 100644 --- a/activesupport/lib/active_support/testing/constant_lookup.rb +++ b/activesupport/lib/active_support/testing/constant_lookup.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../concern" -require_relative "../inflector" +require "active_support/concern" +require "active_support/inflector" module ActiveSupport module Testing diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb index 4fda4832cc..f655435729 100644 --- a/activesupport/lib/active_support/testing/deprecation.rb +++ b/activesupport/lib/active_support/testing/deprecation.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../deprecation" -require_relative "../core_ext/regexp" +require "active_support/deprecation" +require "active_support/core_ext/regexp" module ActiveSupport module Testing diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 954197a3cc..652a10da23 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -45,7 +45,8 @@ module ActiveSupport end } end - result = Marshal.dump(dup) + test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup + result = Marshal.dump(test_result) end write.puts [result].pack("m") @@ -55,7 +56,7 @@ module ActiveSupport write.close result = read.read Process.wait2(pid) - return result.unpack("m")[0] + result.unpack1("m") end end @@ -69,8 +70,9 @@ module ActiveSupport if ENV["ISOLATION_TEST"] yield + test_result = defined?(Minitest::Result) ? Minitest::Result.from(self) : dup File.open(ENV["ISOLATION_OUTPUT"], "w") do |file| - file.puts [Marshal.dump(dup)].pack("m") + file.puts [Marshal.dump(test_result)].pack("m") end exit! else @@ -96,7 +98,7 @@ module ActiveSupport nil end - return tmpfile.read.unpack("m")[0] + return tmpfile.read.unpack1("m") end end end diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb new file mode 100644 index 0000000000..59c8486f41 --- /dev/null +++ b/activesupport/lib/active_support/testing/parallelization.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require "drb" +require "drb/unix" + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + class Server + include DRb::DRbUndumped + + def initialize + @queue = Queue.new + end + + def record(reporter, result) + reporter.synchronize do + reporter.record(result) + end + end + + def <<(o) + @queue << o + end + + def pop; @queue.pop; end + end + + @after_fork_hooks = [] + + def self.after_fork_hook(&blk) + @after_fork_hooks << blk + end + + def self.after_fork_hooks + @after_fork_hooks + end + + @run_cleanup_hooks = [] + + def self.run_cleanup_hook(&blk) + @run_cleanup_hooks << blk + end + + def self.run_cleanup_hooks + @run_cleanup_hooks + end + + def initialize(queue_size) + @queue_size = queue_size + @queue = Server.new + @pool = [] + + @url = DRb.start_service("drbunix:", @queue).uri + end + + def after_fork(worker) + self.class.after_fork_hooks.each do |cb| + cb.call(worker) + end + end + + def run_cleanup(worker) + self.class.run_cleanup_hooks.each do |cb| + cb.call(worker) + end + end + + def start + @pool = @queue_size.times.map do |worker| + fork do + DRb.stop_service + + after_fork(worker) + + 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) + + queue.record(reporter, result) + end + + run_cleanup(worker) + end + end + end + + def <<(work) + @queue << work + end + + def shutdown + @queue_size.times { @queue << nil } + @pool.each { |pid| Process.waitpid pid } + end + end + end +end diff --git a/activesupport/lib/active_support/testing/setup_and_teardown.rb b/activesupport/lib/active_support/testing/setup_and_teardown.rb index 18de7185d9..35321cd157 100644 --- a/activesupport/lib/active_support/testing/setup_and_teardown.rb +++ b/activesupport/lib/active_support/testing/setup_and_teardown.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true -require_relative "../concern" -require_relative "../callbacks" +require "active_support/callbacks" module ActiveSupport module Testing @@ -19,11 +18,10 @@ module ActiveSupport # end # end module SetupAndTeardown - extend ActiveSupport::Concern - - included do - include ActiveSupport::Callbacks - define_callbacks :setup, :teardown + def self.prepended(klass) + klass.include ActiveSupport::Callbacks + klass.define_callbacks :setup, :teardown + klass.extend ClassMethods end module ClassMethods @@ -44,7 +42,12 @@ module ActiveSupport end def after_teardown # :nodoc: - run_callbacks :teardown + begin + run_callbacks :teardown + rescue => e + self.failures << Minitest::UnexpectedError.new(e) + end + super end end diff --git a/activesupport/lib/active_support/testing/stream.rb b/activesupport/lib/active_support/testing/stream.rb index d070a1793d..127cfe1e12 100644 --- a/activesupport/lib/active_support/testing/stream.rb +++ b/activesupport/lib/active_support/testing/stream.rb @@ -33,7 +33,7 @@ module ActiveSupport yield stream_io.rewind - return captured_stream.read + captured_stream.read ensure captured_stream.close captured_stream.unlink diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index fa5f46736c..801ea2909b 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../core_ext/string/strip" # for strip_heredoc -require_relative "../core_ext/time/calculations" +require "active_support/core_ext/module/redefine_method" +require "active_support/core_ext/time/calculations" require "concurrent/map" module ActiveSupport @@ -43,7 +43,7 @@ module ActiveSupport def unstub_object(stub) singleton_class = stub.object.singleton_class - singleton_class.send :undef_method, stub.method_name + singleton_class.send :silence_redefinition_of_method, stub.method_name singleton_class.send :alias_method, stub.method_name, stub.original_method singleton_class.send :undef_method, stub.original_method end @@ -111,7 +111,7 @@ module ActiveSupport # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 def travel_to(date_or_time) if block_given? && simple_stubs.stubbing(Time, :now) - travel_to_nested_block_call = <<-MSG.strip_heredoc + travel_to_nested_block_call = <<~MSG Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing. diff --git a/activesupport/lib/active_support/time.rb b/activesupport/lib/active_support/time.rb index fb1c5cee1c..51854675bf 100644 --- a/activesupport/lib/active_support/time.rb +++ b/activesupport/lib/active_support/time.rb @@ -9,12 +9,12 @@ end require "date" require "time" -require_relative "core_ext/time" -require_relative "core_ext/date" -require_relative "core_ext/date_time" +require "active_support/core_ext/time" +require "active_support/core_ext/date" +require "active_support/core_ext/date_time" -require_relative "core_ext/integer/time" -require_relative "core_ext/numeric/time" +require "active_support/core_ext/integer/time" +require "active_support/core_ext/numeric/time" -require_relative "core_ext/string/conversions" -require_relative "core_ext/string/zones" +require "active_support/core_ext/string/conversions" +require "active_support/core_ext/string/zones" diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 59cafa193e..7e71318404 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -1,9 +1,9 @@ # frozen_string_literal: true -require_relative "duration" -require_relative "values/time_zone" -require_relative "core_ext/object/acts_like" -require_relative "core_ext/date_and_time/compatibility" +require "active_support/duration" +require "active_support/values/time_zone" +require "active_support/core_ext/object/acts_like" +require "active_support/core_ext/date_and_time/compatibility" module ActiveSupport # A Time-like class that can represent a time in any time zone. Necessary @@ -225,6 +225,8 @@ module ActiveSupport def <=>(other) utc <=> other end + alias_method :before?, :< + alias_method :after?, :> # Returns true if the current object's time is within the specified # +min+ and +max+ time. diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index c871f11422..5f709c5fd9 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -2,7 +2,6 @@ require "tzinfo" require "concurrent/map" -require_relative "../core_ext/object/blank" module ActiveSupport # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. @@ -30,7 +29,7 @@ module ActiveSupport class TimeZone # Keys are Rails TimeZone names, values are TZInfo identifiers. MAPPING = { - "International Date Line West" => "Pacific/Midway", + "International Date Line West" => "Etc/GMT+12", "Midway Island" => "Pacific/Midway", "American Samoa" => "Pacific/Pago_Pago", "Hawaii" => "Pacific/Honolulu", @@ -238,7 +237,7 @@ module ActiveSupport when Numeric, ActiveSupport::Duration arg *= 3600 if arg.abs <= 13 all.find { |z| z.utc_offset == arg.to_i } - else + else raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" end end @@ -256,22 +255,32 @@ module ActiveSupport @country_zones[code] ||= load_country_zones(code) end + def clear #:nodoc: + @lazy_zones_map = Concurrent::Map.new + @country_zones = Concurrent::Map.new + @zones = nil + @zones_map = nil + end + private def load_country_zones(code) country = TZInfo::Country.get(code) country.zone_identifiers.map do |tz_id| if MAPPING.value?(tz_id) - self[MAPPING.key(tz_id)] + MAPPING.inject([]) do |memo, (key, value)| + memo << self[key] if value == tz_id + memo + end else create(tz_id, nil, TZInfo::Timezone.new(tz_id)) end - end.sort! + end.flatten(1).sort! end def zones_map - @zones_map ||= begin - MAPPING.each_key { |place| self[place] } # load all the zones - @lazy_zones_map + @zones_map ||= MAPPING.each_with_object({}) do |(name, _), zones| + timezone = self[name] + zones[name] = timezone if timezone end end end @@ -506,7 +515,7 @@ module ActiveSupport # Available so that TimeZone instances respond like TZInfo::Timezone # instances. def period_for_local(time, dst = true) - tzinfo.period_for_local(time, dst) + tzinfo.period_for_local(time, dst) { |periods| periods.last } end def periods_for_local(time) #:nodoc: diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat Binary files differdeleted file mode 100644 index f7d9c48bbe..0000000000 --- a/activesupport/lib/active_support/values/unicode_tables.dat +++ /dev/null diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index d49ee4508e..e42eee07a3 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -3,9 +3,9 @@ require "time" require "base64" require "bigdecimal" -require_relative "core_ext/module/delegation" -require_relative "core_ext/string/inflections" -require_relative "core_ext/date_time/calculations" +require "active_support/core_ext/module/delegation" +require "active_support/core_ext/string/inflections" +require "active_support/core_ext/date_time/calculations" module ActiveSupport # = XmlMini @@ -48,10 +48,6 @@ module ActiveSupport "Array" => "array", "Hash" => "hash" } - - # No need to map these on Ruby 2.4+ - TYPE_NAMES["Fixnum"] = "integer" unless 0.class == Integer - TYPE_NAMES["Bignum"] = "integer" unless 0.class == Integer end FORMATTING = { @@ -83,7 +79,7 @@ module ActiveSupport end, "boolean" => Proc.new { |boolean| %w(1 true).include?(boolean.to_s.strip) }, "string" => Proc.new { |string| string.to_s }, - "yaml" => Proc.new { |yaml| YAML::load(yaml) rescue yaml }, + "yaml" => Proc.new { |yaml| YAML.load(yaml) rescue yaml }, "base64Binary" => Proc.new { |bin| ::Base64.decode64(bin) }, "binary" => Proc.new { |bin, entity| _parse_binary(bin, entity) }, "file" => Proc.new { |file, entity| _parse_file(file, entity) } @@ -199,7 +195,7 @@ module ActiveSupport if name.is_a?(Module) name else - require_relative "xml_mini/#{name.downcase}" + require "active_support/xml_mini/#{name.downcase}" ActiveSupport.const_get("XmlMini_#{name}") end end diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index ecfe389f10..7f94a64016 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -5,7 +5,7 @@ raise "JRuby is required to use the JDOM backend for XmlMini" unless RUBY_PLATFO require "jruby" include Java -require_relative "../core_ext/object/blank" +require "active_support/core_ext/object/blank" java_import javax.xml.parsers.DocumentBuilder unless defined? DocumentBuilder java_import javax.xml.parsers.DocumentBuilderFactory unless defined? DocumentBuilderFactory diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index b1be38fadf..0b000fea60 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "libxml" -require_relative "../core_ext/object/blank" +require "active_support/core_ext/object/blank" require "stringio" module ActiveSupport diff --git a/activesupport/lib/active_support/xml_mini/libxmlsax.rb b/activesupport/lib/active_support/xml_mini/libxmlsax.rb index 3ad494310d..dcf16e6084 100644 --- a/activesupport/lib/active_support/xml_mini/libxmlsax.rb +++ b/activesupport/lib/active_support/xml_mini/libxmlsax.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require "libxml" -require_relative "../core_ext/object/blank" +require "active_support/core_ext/object/blank" require "stringio" module ActiveSupport diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb index 1987330c25..5ee6fc8159 100644 --- a/activesupport/lib/active_support/xml_mini/nokogiri.rb +++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb @@ -6,7 +6,7 @@ rescue LoadError => e $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" raise e end -require_relative "../core_ext/object/blank" +require "active_support/core_ext/object/blank" require "stringio" module ActiveSupport diff --git a/activesupport/lib/active_support/xml_mini/nokogirisax.rb b/activesupport/lib/active_support/xml_mini/nokogirisax.rb index fb57eb8867..b01ed00a14 100644 --- a/activesupport/lib/active_support/xml_mini/nokogirisax.rb +++ b/activesupport/lib/active_support/xml_mini/nokogirisax.rb @@ -6,7 +6,7 @@ rescue LoadError => e $stderr.puts "You don't have nokogiri installed in your application. Please add it to your Gemfile and run bundle install" raise e end -require_relative "../core_ext/object/blank" +require "active_support/core_ext/object/blank" require "stringio" module ActiveSupport diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb index 5fd8978fc0..32458d5b0d 100644 --- a/activesupport/lib/active_support/xml_mini/rexml.rb +++ b/activesupport/lib/active_support/xml_mini/rexml.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -require_relative "../core_ext/kernel/reporting" -require_relative "../core_ext/object/blank" +require "active_support/core_ext/kernel/reporting" +require "active_support/core_ext/object/blank" require "stringio" module ActiveSupport |