diff options
Diffstat (limited to 'activesupport/lib')
75 files changed, 759 insertions, 433 deletions
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 588d6c49f9..3a2a7d28cb 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -34,6 +34,7 @@ module ActiveSupport autoload :Dependencies autoload :DescendantsTracker autoload :FileUpdateChecker + autoload :FileEventedUpdateChecker autoload :LogSubscriber autoload :Notifications @@ -75,11 +76,11 @@ module ActiveSupport cattr_accessor :test_order # :nodoc: def self.halt_callback_chains_on_return_false - Callbacks::CallbackChain.halt_and_display_warning_on_return_false + Callbacks.halt_and_display_warning_on_return_false end def self.halt_callback_chains_on_return_false=(value) - Callbacks::CallbackChain.halt_and_display_warning_on_return_false = value + Callbacks.halt_and_display_warning_on_return_false = value end end diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index 8253a76383..5011014e96 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -8,6 +8,7 @@ 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' +require 'active_support/core_ext/string/strip' module ActiveSupport # See ActiveSupport::Cache::Store for documentation. @@ -275,15 +276,20 @@ module ActiveSupport def fetch(name, options = nil) if block_given? options = merged_options(options) - key = namespaced_key(name, options) + key = normalize_key(name, options) - cached_entry = find_cached_entry(key, name, options) unless options[:force] - entry = handle_expired_entry(cached_entry, key, options) + instrument(:read, name, options) do |payload| + cached_entry = read_entry(key, options) unless options[:force] + payload[:super_operation] = :fetch if payload + entry = handle_expired_entry(cached_entry, key, options) - if entry - get_entry_value(entry, name, options) - else - save_block_result_to_cache(name, options) { |_name| yield _name } + if entry + payload[:hit] = true if payload + get_entry_value(entry, name, options) + else + payload[:hit] = false if payload + save_block_result_to_cache(name, options) { |_name| yield _name } + end end else read(name, options) @@ -297,7 +303,7 @@ module ActiveSupport # Options are passed to the underlying cache implementation. def read(name, options = nil) options = merged_options(options) - key = namespaced_key(name, options) + key = normalize_key(name, options) instrument(:read, name, options) do |payload| entry = read_entry(key, options) if entry @@ -329,7 +335,7 @@ module ActiveSupport instrument_multi(:read, names, options) do |payload| results = {} names.each do |name| - key = namespaced_key(name, options) + key = normalize_key(name, options) entry = read_entry(key, options) if entry if entry.expired? @@ -381,7 +387,7 @@ module ActiveSupport instrument(:write, name, options) do entry = Entry.new(value, options) - write_entry(namespaced_key(name, options), entry, options) + write_entry(normalize_key(name, options), entry, options) end end @@ -392,7 +398,7 @@ module ActiveSupport options = merged_options(options) instrument(:delete, name) do - delete_entry(namespaced_key(name, options), options) + delete_entry(normalize_key(name, options), options) end end @@ -403,7 +409,7 @@ module ActiveSupport options = merged_options(options) instrument(:exist?, name) do - entry = read_entry(namespaced_key(name, options), options) + entry = read_entry(normalize_key(name, options), options) (entry && !entry.expired?) || false end end @@ -524,7 +530,7 @@ module ActiveSupport # Prefix a key with the namespace. Namespace and key will be delimited # with a colon. - def namespaced_key(key, options) + def normalize_key(key, options) key = expanded_key(key) namespace = options[:namespace] if options prefix = namespace.is_a?(Proc) ? namespace.call : namespace @@ -532,8 +538,16 @@ module ActiveSupport key end + def namespaced_key(*args) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `namespaced_key` is deprecated and will be removed from Rails 5.1. + Please use `normalize_key` which will return a fully resolved key. + MESSAGE + normalize_key(*args) + end + def instrument(operation, key, options = nil) - log { "Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}" } + log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" } payload = { :key => key } payload.merge!(options) if options.is_a?(Hash) @@ -556,13 +570,6 @@ module ActiveSupport logger.debug(yield) end - def find_cached_entry(key, name, options) - instrument(:read, name, options) do |payload| - payload[:super_operation] = :fetch if payload - read_entry(key, options) - end - end - def handle_expired_entry(entry, key, options) if entry && entry.expired? race_ttl = options[:race_condition_ttl].to_i diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index e6a8b84214..dff2443bc8 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -10,6 +10,7 @@ module ActiveSupport # FileStore implements the Strategy::LocalCache strategy which implements # an in-memory cache inside of a block. class FileStore < Store + prepend Strategy::LocalCache attr_reader :cache_path DIR_FORMATTER = "%03X" @@ -20,7 +21,6 @@ module ActiveSupport def initialize(cache_path, options = nil) super(options) @cache_path = cache_path.to_s - extend Strategy::LocalCache end # Deletes all items from the cache. In this case it deletes all the entries in the specified @@ -60,7 +60,7 @@ module ActiveSupport matcher = key_matcher(matcher, options) search_dir(cache_path) do |path| key = file_path_key(path) - delete_entry(key, options) if key.match(matcher) + delete_entry(path, options) if key.match(matcher) end end end @@ -68,9 +68,8 @@ module ActiveSupport protected def read_entry(key, options) - file_name = key_file_path(key) - if File.exist?(file_name) - File.open(file_name) { |f| Marshal.load(f) } + if File.exist?(key) + File.open(key) { |f| Marshal.load(f) } end rescue => e logger.error("FileStoreError (#{e}): #{e.message}") if logger @@ -78,23 +77,21 @@ module ActiveSupport end def write_entry(key, entry, options) - file_name = key_file_path(key) - return false if options[:unless_exist] && File.exist?(file_name) - ensure_cache_path(File.dirname(file_name)) - File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)} + return false if options[:unless_exist] && File.exist?(key) + ensure_cache_path(File.dirname(key)) + File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)} true end def delete_entry(key, options) - file_name = key_file_path(key) - if File.exist?(file_name) + if File.exist?(key) begin - File.delete(file_name) - delete_empty_directories(File.dirname(file_name)) + File.delete(key) + delete_empty_directories(File.dirname(key)) true rescue => e # Just in case the error was caused by another process deleting the file first. - raise e if File.exist?(file_name) + raise e if File.exist?(key) false end end @@ -118,12 +115,14 @@ module ActiveSupport end # Translate a key into a file path. - def key_file_path(key) - if key.size > FILEPATH_MAX_SIZE - key = Digest::MD5.hexdigest(key) + def normalize_key(key, options) + key = super + fname = URI.encode_www_form_component(key) + + if fname.size > FILEPATH_MAX_SIZE + fname = Digest::MD5.hexdigest(key) end - fname = URI.encode_www_form_component(key) hash = Zlib.adler32(fname) hash, dir_1 = hash.divmod(0x1000) dir_2 = hash.modulo(0x1000) @@ -138,6 +137,14 @@ module ActiveSupport File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths) end + def key_file_path(key) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `key_file_path` is deprecated and will be removed from Rails 5.1. + Please use `normalize_key` which will return a fully resolved key or nothing. + MESSAGE + key + end + # Translate a file path into a key. def file_path_key(path) fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last @@ -174,7 +181,7 @@ module ActiveSupport # Modifies the amount of an already existing integer value that is stored in the cache. # If the key is not found nothing is done. def modify_value(name, amount, options) - file_name = key_file_path(namespaced_key(name, options)) + file_name = normalize_key(name, options) lock_file(file_name) do options = merged_options(options) diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 47133bf550..4b0ad37586 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -24,6 +24,31 @@ module ActiveSupport # MemCacheStore implements the Strategy::LocalCache strategy which implements # an in-memory cache inside of a block. class MemCacheStore < Store + # Provide support for raw values in the local cache strategy. + module LocalCacheWithRaw # :nodoc: + protected + 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) # :nodoc: + retval = super + if options[:raw] && local_cache && retval + raw_entry = Entry.new(entry.value.to_s) + raw_entry.expires_at = entry.expires_at + local_cache.write_entry(key, raw_entry, options) + end + retval + end + end + + prepend Strategy::LocalCache + prepend LocalCacheWithRaw + ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n # Creates a new Dalli::Client instance with specified addresses and options. @@ -63,9 +88,6 @@ module ActiveSupport UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)} @data = self.class.build_mem_cache(*(addresses + [mem_cache_options])) end - - extend Strategy::LocalCache - extend LocalCacheWithRaw end # Reads multiple values from the cache using a single call to the @@ -75,7 +97,7 @@ module ActiveSupport options = merged_options(options) instrument_multi(:read, names, options) do - keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}] + keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}] raw_values = @data.get_multi(keys_to_names.keys, :raw => true) values = {} raw_values.each do |key, value| @@ -93,7 +115,7 @@ module ActiveSupport def increment(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:increment, name, :amount => amount) do - @data.incr(escape_key(namespaced_key(name, options)), amount) + @data.incr(normalize_key(name, options), amount) end rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger @@ -107,7 +129,7 @@ module ActiveSupport def decrement(name, amount = 1, options = nil) # :nodoc: options = merged_options(options) instrument(:decrement, name, :amount => amount) do - @data.decr(escape_key(namespaced_key(name, options)), amount) + @data.decr(normalize_key(name, options), amount) end rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger @@ -131,7 +153,7 @@ module ActiveSupport protected # Read an entry from the cache. def read_entry(key, options) # :nodoc: - deserialize_entry(@data.get(escape_key(key), options)) + deserialize_entry(@data.get(key, options)) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger nil @@ -146,7 +168,7 @@ module ActiveSupport # Set the memcache expire a few minutes in the future to support race condition ttls on read expires_in += 5.minutes end - @data.send(method, escape_key(key), value, expires_in, options) + @data.send(method, key, value, expires_in, options) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger false @@ -154,7 +176,7 @@ module ActiveSupport # Delete an entry from the cache. def delete_entry(key, options) # :nodoc: - @data.delete(escape_key(key)) + @data.delete(key) rescue Dalli::DalliError => e logger.error("DalliError (#{e}): #{e.message}") if logger false @@ -165,14 +187,22 @@ module ActiveSupport # Memcache keys are binaries. So we need to force their encoding to binary # before applying the regular expression to ensure we are escaping all # characters properly. - def escape_key(key) - key = key.to_s.dup + def normalize_key(key, options) + 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 end + def escape_key(key) + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `escape_key` is deprecated and will be removed from Rails 5.1. + Please use `normalize_key` which will return a fully resolved key or nothing. + MESSAGE + key + end + def deserialize_entry(raw_value) if raw_value entry = Marshal.load(raw_value) rescue raw_value @@ -181,28 +211,6 @@ module ActiveSupport nil end end - - # Provide support for raw values in the local cache strategy. - module LocalCacheWithRaw # :nodoc: - protected - 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) # :nodoc: - retval = super - if options[:raw] && local_cache && retval - raw_entry = Entry.new(entry.value.to_s) - raw_entry.expires_at = entry.expires_at - local_cache.write_entry(key, raw_entry, options) - end - retval - end - end end end end diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 90bb2c38c3..896c28ad8b 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -75,30 +75,12 @@ module ActiveSupport # Increment an integer value in the cache. def increment(name, amount = 1, options = nil) - synchronize do - options = merged_options(options) - if num = read(name, options) - num = num.to_i + amount - write(name, num, options) - num - else - nil - end - end + modify_value(name, amount, options) end # Decrement an integer value in the cache. def decrement(name, amount = 1, options = nil) - synchronize do - options = merged_options(options) - if num = read(name, options) - num = num.to_i - amount - write(name, num, options) - num - else - nil - end - end + modify_value(name, -amount, options) end def delete_matched(matcher, options = nil) @@ -167,6 +149,19 @@ module ActiveSupport !!entry end end + + private + + def modify_value(name, amount, options) + synchronize do + options = merged_options(options) + if num = read(name, options) + num = num.to_i + amount + write(name, num, options) + num + end + end + end end end end diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb index 4427eaafcd..0564ce5312 100644 --- a/activesupport/lib/active_support/cache/null_store.rb +++ b/activesupport/lib/active_support/cache/null_store.rb @@ -8,10 +8,7 @@ module ActiveSupport # be cached inside blocks that utilize this strategy. See # ActiveSupport::Cache::Strategy::LocalCache for more details. class NullStore < Store - def initialize(options = nil) - super(options) - extend Strategy::LocalCache - end + prepend Strategy::LocalCache def clear(options = nil) end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index fe5bc82c30..df38dbcf11 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -60,6 +60,10 @@ module ActiveSupport def delete_entry(key, options) !!@data.delete(key) end + + def fetch_entry(key, options = nil) # :nodoc: + @data.fetch(key) { @data[key] = yield } + end end # Use a local cache for the duration of block. @@ -75,36 +79,35 @@ module ActiveSupport end def clear(options = nil) # :nodoc: - local_cache.clear(options) if local_cache + return super unless cache = local_cache + cache.clear(options) super end def cleanup(options = nil) # :nodoc: - local_cache.clear(options) if local_cache + return super unless cache = local_cache + cache.clear(options) super end def increment(name, amount = 1, options = nil) # :nodoc: + return super unless local_cache value = bypass_local_cache{super} - set_cache_value(value, name, amount, options) + write_cache_value(name, value, options) value end def decrement(name, amount = 1, options = nil) # :nodoc: + return super unless local_cache value = bypass_local_cache{super} - set_cache_value(value, name, amount, options) + write_cache_value(name, value, options) value end protected def read_entry(key, options) # :nodoc: - if local_cache - entry = local_cache.read_entry(key, options) - unless entry - entry = super - local_cache.write_entry(key, entry, options) - end - entry + if cache = local_cache + cache.fetch_entry(key) { super } else super end @@ -121,13 +124,21 @@ module ActiveSupport end def set_cache_value(value, name, amount, options) # :nodoc: - if local_cache - local_cache.mute do - if value - local_cache.write(name, value, options) - else - local_cache.delete(name, options) - end + ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc) + `set_cache_value` is deprecated and will be removed from Rails 5.1. + Please use `write_cache_value` + MESSAGE + write_cache_value name, value, options + end + + def write_cache_value(name, value, options) # :nodoc: + name = normalize_key(name, options) + cache = local_cache + cache.mute do + if value + cache.write(name, value, options) + else + cache.delete(name, options) end end end diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index fefba5b0fd..d43fde03a9 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -4,6 +4,7 @@ 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/module/attribute_accessors' require 'active_support/core_ext/string/filters' require 'active_support/deprecation' require 'thread' @@ -66,6 +67,12 @@ module ActiveSupport CALLBACK_FILTER_TYPES = [:before, :after, :around] + # If true, Active Record and Active Model callbacks returning +false+ will + # halt the entire callback chain and display a deprecation message. + # If false, callback chains will only be halted by calling +throw :abort+. + # Defaults to +true+. + mattr_accessor(:halt_and_display_warning_on_return_false) { true } + # Runs the callbacks for the given event. # # Calls the before and around callbacks in the order they were set, yields @@ -126,14 +133,10 @@ module ActiveSupport def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter) halted_lambda = chain_config[:terminator] - if chain_config.key?(:terminator) && user_conditions.any? + if user_conditions.any? halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) - elsif chain_config.key? :terminator - halting(callback_sequence, user_callback, halted_lambda, filter) - elsif user_conditions.any? - conditional(callback_sequence, user_callback, user_conditions) else - simple callback_sequence, user_callback + halting(callback_sequence, user_callback, halted_lambda, filter) end end @@ -175,42 +178,15 @@ module ActiveSupport end end private_class_method :halting - - def self.conditional(callback_sequence, user_callback, user_conditions) - callback_sequence.before do |env| - target = env.target - value = env.value - - if user_conditions.all? { |c| c.call(target, value) } - user_callback.call target, value - end - - env - end - end - private_class_method :conditional - - def self.simple(callback_sequence, user_callback) - callback_sequence.before do |env| - user_callback.call env.target, env.value - - env - end - end - private_class_method :simple end class After def self.build(callback_sequence, user_callback, user_conditions, chain_config) if chain_config[:skip_after_callbacks_if_terminated] - if chain_config.key?(:terminator) && user_conditions.any? + if user_conditions.any? halting_and_conditional(callback_sequence, user_callback, user_conditions) - elsif chain_config.key?(:terminator) - halting(callback_sequence, user_callback) - elsif user_conditions.any? - conditional callback_sequence, user_callback, user_conditions else - simple callback_sequence, user_callback + halting(callback_sequence, user_callback) end else if user_conditions.any? @@ -273,14 +249,10 @@ module ActiveSupport class Around def self.build(callback_sequence, user_callback, user_conditions, chain_config) - if chain_config.key?(:terminator) && user_conditions.any? + if user_conditions.any? halting_and_conditional(callback_sequence, user_callback, user_conditions) - elsif chain_config.key? :terminator - halting(callback_sequence, user_callback) - elsif user_conditions.any? - conditional(callback_sequence, user_callback, user_conditions) else - simple(callback_sequence, user_callback) + halting(callback_sequence, user_callback) end end @@ -318,33 +290,6 @@ module ActiveSupport end end private_class_method :halting - - def self.conditional(callback_sequence, user_callback, user_conditions) - callback_sequence.around do |env, &run| - target = env.target - value = env.value - - if user_conditions.all? { |c| c.call(target, value) } - user_callback.call(target, value) { - run.call.value - } - env - else - run.call - end - end - end - private_class_method :conditional - - def self.simple(callback_sequence, user_callback) - callback_sequence.around do |env, &run| - user_callback.call(env.target, env.value) { - run.call.value - } - env - end - end - private_class_method :simple end end @@ -512,12 +457,6 @@ module ActiveSupport attr_reader :name, :config - # If true, any callback returning +false+ will halt the entire callback - # chain and display a deprecation message. If false, callback chains will - # only be halted by calling +throw :abort+. Defaults to +true+. - class_attribute :halt_and_display_warning_on_return_false - self.halt_and_display_warning_on_return_false = true - def initialize(name, config) @name = name @config = { @@ -598,23 +537,12 @@ module ActiveSupport Proc.new do |target, result_lambda| terminate = true catch(:abort) do - result = result_lambda.call if result_lambda.is_a?(Proc) - if halt_and_display_warning_on_return_false && result == false - display_deprecation_warning_for_false_terminator - else - terminate = false - end + result_lambda.call if result_lambda.is_a?(Proc) + terminate = false end terminate end end - - def display_deprecation_warning_for_false_terminator - ActiveSupport::Deprecation.warn(<<-MSG.squish) - Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails. - To explicitly halt a callback chain, please use `throw :abort` instead. - MSG - end end module ClassMethods @@ -748,7 +676,8 @@ module ActiveSupport # # In this example, if any before validate callbacks returns +false+, # any successive before and around callback is not executed. - # Defaults to +false+, meaning no value halts the chain. + # + # The default terminator halts the chain when a callback throws +:abort+. # # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after # callbacks should be terminated by the <tt>:terminator</tt> option. By @@ -819,13 +748,37 @@ module ActiveSupport protected - def get_callbacks(name) + def get_callbacks(name) # :nodoc: send "_#{name}_callbacks" end - def set_callbacks(name, callbacks) + def set_callbacks(name, callbacks) # :nodoc: send "_#{name}_callbacks=", callbacks end + + def deprecated_false_terminator # :nodoc: + Proc.new do |target, result_lambda| + terminate = true + catch(:abort) do + result = result_lambda.call if result_lambda.is_a?(Proc) + if Callbacks.halt_and_display_warning_on_return_false && result == false + display_deprecation_warning_for_false_terminator + else + terminate = false + end + end + terminate + end + end + + private + + def display_deprecation_warning_for_false_terminator + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in the next release of Rails. + To explicitly halt the callback chain, please use `throw :abort` instead. + MSG + end end end end diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb index 7b8df0df04..4abe5ece6f 100644 --- a/activesupport/lib/active_support/concurrency/latch.rb +++ b/activesupport/lib/active_support/concurrency/latch.rb @@ -1,4 +1,4 @@ -require 'concurrent/atomics' +require 'concurrent/atomic/count_down_latch' module ActiveSupport module Concurrency @@ -8,7 +8,7 @@ module ActiveSupport ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.") super(count) end - + alias_method :release, :count_down def await diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index d80df21e7d..8718b7e1e5 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -32,7 +32,7 @@ class Array # ['one', 'two', 'three'].to_sentence # => "one, two, and three" # # ['one', 'two'].to_sentence(passing: 'invalid option') - # # => ArgumentError: Unknown key :passing + # # => ArgumentError: Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale # # ['one', 'two'].to_sentence(two_words_connector: '-') # # => "one-two" @@ -85,7 +85,9 @@ class Array # Extends <tt>Array#to_s</tt> to convert a collection of elements into a # comma separated id list if <tt>:db</tt> argument is given as the format. # - # Blog.all.to_formatted_s(:db) # => "1,2,3" + # Blog.all.to_formatted_s(:db) # => "1,2,3" + # Blog.none.to_formatted_s(:db) # => "null" + # [1,2].to_formatted_s # => "[1, 2]" def to_formatted_s(format = :default) case format when :db diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 234283e792..22fc7ecf92 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -1,15 +1,14 @@ require 'bigdecimal' require 'bigdecimal/util' -class BigDecimal - DEFAULT_STRING_FORMAT = 'F' - alias_method :to_default_s, :to_s +module ActiveSupport + module BigDecimalWithDefaultFormat #:nodoc: + DEFAULT_STRING_FORMAT = 'F' - def to_s(format = nil, options = nil) - if format.is_a?(Symbol) - to_formatted_s(format, options || {}) - else - to_default_s(format || DEFAULT_STRING_FORMAT) + def to_s(format = nil) + super(format || DEFAULT_STRING_FORMAT) end end end + +BigDecimal.prepend(ActiveSupport::BigDecimalWithDefaultFormat) diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index f2b7bb3ef1..802d988af2 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -75,11 +75,15 @@ class Class instance_predicate = options.fetch(:instance_predicate, true) attrs.each do |name| + remove_possible_singleton_method(name) define_singleton_method(name) { nil } + + remove_possible_singleton_method("#{name}?") define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate ivar = "@#{name}" + remove_possible_singleton_method("#{name}=") define_singleton_method("#{name}=") do |val| singleton_class.class_eval do remove_possible_method(name) @@ -110,10 +114,15 @@ class Class self.class.public_send name end end + + remove_possible_method "#{name}?" define_method("#{name}?") { !!public_send(name) } if instance_predicate end - attr_writer name if instance_writer + if instance_writer + remove_possible_method "#{name}=" + attr_writer name + end end end end diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb index 3c4bfc5f1e..b0f9a8be34 100644 --- a/activesupport/lib/active_support/core_ext/class/subclasses.rb +++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb @@ -3,7 +3,8 @@ require 'active_support/core_ext/module/reachable' class Class begin - ObjectSpace.each_object(Class.new) {} + # Test if this Ruby supports each_object against singleton_class + ObjectSpace.each_object(Numeric.singleton_class) {} def descendants # :nodoc: descendants = [] @@ -12,7 +13,7 @@ class Class end descendants end - rescue StandardError # JRuby + rescue StandardError # JRuby 9.0.4.0 and earlier def descendants # :nodoc: descendants = [] ObjectSpace.each_object(Class) do |k| diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb index 465fedda80..7f0f4639a2 100644 --- a/activesupport/lib/active_support/core_ext/date.rb +++ b/activesupport/lib/active_support/core_ext/date.rb @@ -1,5 +1,5 @@ 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/blank.rb b/activesupport/lib/active_support/core_ext/date/blank.rb new file mode 100644 index 0000000000..71627b6a6f --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date/blank.rb @@ -0,0 +1,12 @@ +require 'date' + +class Date #:nodoc: + # No Date is blank: + # + # Date.today.blank? # => false + # + # @return [false] + def blank? + false + end +end diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb index c60e833441..d589b67bf7 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -26,7 +26,7 @@ class Date Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start) end - # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol. + # Returns week start day symbol (e.g. :monday), or raises an +ArgumentError+ for invalid day symbol. def find_beginning_of_week!(week_start) raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start) week_start diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 31479a1269..ed8bca77ac 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -75,10 +75,10 @@ class Date # # date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007 # - # date.to_time # => Sat Nov 10 00:00:00 0800 2007 - # date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007 + # date.to_time # => 2007-11-10 00:00:00 0800 + # date.to_time(:local) # => 2007-11-10 00:00:00 0800 # - # date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007 + # date.to_time(:utc) # => 2007-11-10 00:00:00 UTC def to_time(form = :local) ::Time.send(form, year, month, day) end 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 40811dafc0..e079af594d 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 @@ -135,7 +135,7 @@ module DateAndTime end alias :at_end_of_quarter :end_of_quarter - # Return a new date/time at the beginning of the year. + # Returns a new date/time at the beginning of the year. # # today = Date.today # => Fri, 10 Jul 2015 # today.beginning_of_year # => Thu, 01 Jan 2015 diff --git a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb index 96c6df9407..d29a8db5cf 100644 --- a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb +++ b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb @@ -4,7 +4,7 @@ module DateAndTime # if Time.zone_default is set. Otherwise, it returns the current time. # # Time.zone = 'Hawaii' # => 'Hawaii' - # DateTime.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 # # This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone @@ -14,7 +14,6 @@ module DateAndTime # and the conversion will be based on that zone instead of <tt>Time.zone</tt>. # # Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 - # DateTime.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 def in_time_zone(zone = ::Time.zone) time_zone = ::Time.find_zone! zone diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb index e8a27b9f38..bcb228b09a 100644 --- a/activesupport/lib/active_support/core_ext/date_time.rb +++ b/activesupport/lib/active_support/core_ext/date_time.rb @@ -1,4 +1,5 @@ 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/conversions' require 'active_support/core_ext/date_time/zones' diff --git a/activesupport/lib/active_support/core_ext/date_time/blank.rb b/activesupport/lib/active_support/core_ext/date_time/blank.rb new file mode 100644 index 0000000000..56981b75fb --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date_time/blank.rb @@ -0,0 +1,12 @@ +require 'date' + +class DateTime #:nodoc: + # No DateTime is ever blank: + # + # DateTime.now.blank? # => false + # + # @return [false] + def blank? + false + end +end 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 2a9c09fc29..f59d05b214 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -40,6 +40,8 @@ class DateTime alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s) alias_method :to_s, :to_formatted_s + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. # # datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24)) # datetime.formatted_offset # => "-06:00" diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index fc7531d088..8a74ad4d66 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -21,7 +21,7 @@ module Enumerable if block_given? map(&block).sum(identity) else - inject { |sum, element| sum + element } || identity + inject(:+) || identity 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 28cb3e2a3b..6df7b4121b 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -6,7 +6,7 @@ class Hash # # { a: 1 }.with_indifferent_access['a'] # => 1 def with_indifferent_access - ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self) + ActiveSupport::HashWithIndifferentAccess.new(self) end # Called when object is nested under an object that receives diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index c30044b9ff..8b2366c4b3 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -1,12 +1,16 @@ class Hash - # Returns a new hash with all keys converted using the block operation. + # Returns a new hash with all keys converted using the +block+ operation. # # hash = { name: 'Rob', age: '28' } # - # hash.transform_keys{ |key| key.to_s.upcase } - # # => {"NAME"=>"Rob", "AGE"=>"28"} + # hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"} + # + # If you do not provide a +block+, it will return an Enumerator + # for chaining with other methods: + # + # hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"} def transform_keys - return enum_for(:transform_keys) unless block_given? + return enum_for(:transform_keys) { size } unless block_given? result = self.class.new each_key do |key| result[yield(key)] = self[key] @@ -14,10 +18,10 @@ class Hash result end - # Destructively converts all keys using the block operations. - # Same as transform_keys but modifies +self+. + # Destructively converts all keys using the +block+ operations. + # Same as +transform_keys+ but modifies +self+. def transform_keys! - return enum_for(:transform_keys!) unless block_given? + return enum_for(:transform_keys!) { size } unless block_given? keys.each do |key| self[yield(key)] = delete(key) end @@ -60,7 +64,7 @@ class Hash alias_method :to_options!, :symbolize_keys! # Validates all keys in a hash match <tt>*valid_keys</tt>, raising - # ArgumentError on a mismatch. + # +ArgumentError+ on a mismatch. # # Note that keys are treated differently than HashWithIndifferentAccess, # meaning that string and symbol keys will not match. 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 e9bcce761f..7d507ac998 100644 --- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb +++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb @@ -2,10 +2,15 @@ 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 } + # { 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) unless block_given? + return enum_for(:transform_values) { size } unless block_given? + return {} if empty? result = self.class.new each do |key, value| result[key] = yield(value) @@ -13,9 +18,10 @@ class Hash result end - # Destructive +transform_values+ + # Destructively converts all values using the +block+ operations. + # Same as +transform_values+ but modifies +self+. def transform_values! - return enum_for(:transform_values!) unless block_given? + return enum_for(:transform_values!) { size } unless block_given? each do |key, value| self[key] = yield(value) end diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb index f0b7382ef3..87185b024f 100644 --- a/activesupport/lib/active_support/core_ext/integer/time.rb +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -23,7 +23,7 @@ class Integer alias :month :months def years - ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]]) + ActiveSupport::Duration.new(self * 365.25.days.to_i, [[:years, self]]) end alias :year :years end diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb index 20a0856e71..e333b26133 100644 --- a/activesupport/lib/active_support/core_ext/marshal.rb +++ b/activesupport/lib/active_support/core_ext/marshal.rb @@ -6,7 +6,7 @@ module ActiveSupport if exc.message.match(%r|undefined class/module (.+)|) # try loading the class/module $1.constantize - # if it is a IO we need to go back to read the object + # if it is an IO we need to go back to read the object source.rewind if source.respond_to?(:rewind) retry else diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb index 0ecc67a855..510c9a5430 100644 --- a/activesupport/lib/active_support/core_ext/module/anonymous.rb +++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb @@ -7,7 +7,7 @@ class Module # m = Module.new # m.name # => nil # - # +anonymous?+ method returns true if module does not have a name: + # +anonymous?+ method returns true if module does not have a name, false otherwise: # # Module.new.anonymous? # => true # @@ -18,8 +18,10 @@ class Module # via the +module+ or +class+ keyword or by an explicit assignment: # # m = Module.new # creates an anonymous module - # M = m # => m gets a name here as a side-effect + # m.anonymous? # => true + # M = m # m gets a name here as a side-effect # m.name # => "M" + # m.anonymous? # => false def anonymous? name.nil? end diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index a77da573fe..124f90dc0f 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -5,7 +5,7 @@ require 'active_support/core_ext/array/extract_options' # attributes. class Module # Defines a class attribute and creates a class and instance reader methods. - # The underlying the class variable is set to +nil+, if it is not previously + # The underlying class variable is set to +nil+, if it is not previously # defined. # # module HairColors @@ -19,9 +19,9 @@ class Module # The attribute name must be a valid method name in Ruby. # # module Foo - # mattr_reader :"1_Badname " + # mattr_reader :"1_Badname" # end - # # => NameError: invalid attribute name + # # => NameError: invalid attribute name: 1_Badname # # If you want to opt out the creation on the instance reader method, pass # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>. @@ -53,7 +53,7 @@ class Module def mattr_reader(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@#{sym} = nil unless defined? @@#{sym} @@ -119,7 +119,7 @@ class Module def mattr_writer(*syms) options = syms.extract_options! syms.each do |sym| - raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/ + raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/ class_eval(<<-EOS, __FILE__, __LINE__ + 1) @@#{sym} = nil unless defined? @@#{sym} diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 9dc0dee1bd..0d46248582 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -5,10 +5,11 @@ class Module # option is not used. class DelegationError < NoMethodError; end - RUBY_RESERVED_WORDS = Set.new( - %w(alias and BEGIN begin break case class def defined? do else elsif END - end ensure false for if in module next nil not or redo rescue retry - return self super then true undef unless until when while yield) + DELEGATION_RESERVED_METHOD_NAMES = Set.new( + %w(_ arg args alias and BEGIN begin block break case class def defined? do + else elsif END end ensure false for if in module next nil not or redo + rescue retry return self super then true undef unless until when while + yield) ).freeze # Provides a +delegate+ class method to easily expose contained objects' @@ -171,7 +172,7 @@ class Module line = line.to_i to = to.to_s - to = "self.#{to}" if RUBY_RESERVED_WORDS.include?(to) + to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to) methods.each do |method| # Attribute writer methods only accept one argument. Makes sure []= 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 52632d2c6b..d5ec16d68a 100644 --- a/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -6,10 +6,30 @@ class Module end end + # Removes the named singleton method, if it exists. + def remove_possible_singleton_method(method) + singleton_class.instance_eval do + remove_possible_method(method) + end + end + # Replaces the existing method definition, if there is one, with the passed # block as its body. def redefine_method(method, &block) + visibility = method_visibility(method) remove_possible_method(method) define_method(method, &block) + send(visibility, method) + end + + def method_visibility(method) # :nodoc: + case + when private_method_defined?(method) + :private + when protected_method_defined?(method) + :protected + else + :public + end end end diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index 0c8ff79237..9a3651f29a 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -1,7 +1,7 @@ require 'active_support/core_ext/big_decimal/conversions' require 'active_support/number_helper' -class Numeric +module ActiveSupport::NumericWithFormat # Provides options for converting numbers into formatted strings. # Options are provided for phone numbers, currency, percentage, @@ -41,7 +41,7 @@ class Numeric # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => 1.000,000% # 302.24398923423.to_s(:percentage, precision: 5) # => 302.24399% # 1000.to_s(:percentage, locale: :fr) # => 1 000,000% - # 100.to_s(:percentage, format: '%n %') # => 100 % + # 100.to_s(:percentage, format: '%n %') # => 100.000 % # # Delimited: # 12345678.to_s(:delimited) # => 12,345,678 @@ -78,7 +78,7 @@ class Numeric # 1234567.to_s(:human_size, precision: 2) # => 1.2 MB # 483989.to_s(:human_size, precision: 2) # => 470 KB # 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB - # 1234567890123.to_s(:human_size, precision: 5) # => "1.1229 TB" + # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB" # 524288000.to_s(:human_size, precision: 5) # => "500 MB" # # Human-friendly format: @@ -97,7 +97,10 @@ class Numeric # 1234567.to_s(:human, precision: 1, # separator: ',', # significant: false) # => "1,2 Million" - def to_formatted_s(format = :default, options = {}) + def to_s(*args) + format, options = args + options ||= {} + case format when :phone return ActiveSupport::NumberHelper.number_to_phone(self, options) @@ -114,32 +117,16 @@ class Numeric when :human_size return ActiveSupport::NumberHelper.number_to_human_size(self, options) else - self.to_default_s - end - end - - [Fixnum, Bignum].each do |klass| - klass.class_eval do - alias_method :to_default_s, :to_s - def to_s(base_or_format = 10, options = nil) - if base_or_format.is_a?(Symbol) - to_formatted_s(base_or_format, options || {}) - else - to_default_s(base_or_format) - end - end + super end end - Float.class_eval do - alias_method :to_default_s, :to_s - def to_s(*args) - if args.empty? - to_default_s - else - to_formatted_s(*args) - end - end + def to_formatted_s(*args) + to_s(*args) end + deprecate to_formatted_s: :to_s +end +[Fixnum, Bignum, Float, BigDecimal].each do |klass| + klass.prepend(ActiveSupport::NumericWithFormat) end diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb index 548c91638c..039c50a4a2 100644 --- a/activesupport/lib/active_support/core_ext/object/blank.rb +++ b/activesupport/lib/active_support/core_ext/object/blank.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - class Object # An object is blank if it's false, empty, or a whitespace string. # For example, +false+, '', ' ', +nil+, [], and {} are all blank. @@ -129,3 +127,14 @@ class Numeric #:nodoc: false end end + +class Time #:nodoc: + # No Time is blank: + # + # Time.now.blank? # => false + # + # @return [false] + def blank? + false + end +end 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 ad5b2af161..8dfeed0066 100644 --- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -39,9 +39,15 @@ class Hash # hash[:a][:c] # => nil # dup[:a][:c] # => "c" def deep_dup - each_with_object(dup) do |(key, value), hash| - hash.delete(key) - hash[key.deep_dup] = value.deep_dup + hash = dup + each_pair do |key, value| + if key.frozen? && ::String === key + hash[key] = value.deep_dup + else + hash.delete(key) + hash[key.deep_dup] = value.deep_dup + end end + hash end end diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb index 55f281b213..d4c17dfb07 100644 --- a/activesupport/lib/active_support/core_ext/object/inclusion.rb +++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb @@ -5,7 +5,7 @@ class Object # characters = ["Konata", "Kagami", "Tsukasa"] # "Konata".in?(characters) # => true # - # This will throw an ArgumentError if the argument doesn't respond + # This will throw an +ArgumentError+ if the argument doesn't respond # to +#include?+. def in?(another_object) another_object.include?(self) @@ -18,7 +18,7 @@ class Object # # params[:bucket_type].presence_in %w( project calendar ) # - # This will throw an ArgumentError if the argument doesn't respond to +#include?+. + # This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+. # # @return [Object] def presence_in(another_object) diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index c67eb25b68..8c16d95b62 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -94,7 +94,7 @@ class Object # :call-seq: # try!(*a, &b) # - # Same as #try, but raises a NoMethodError exception if the receiver is + # Same as #try, but raises a +NoMethodError+ exception if the receiver is # not +nil+ and does not implement the tried method. # # "a".try!(:upcase) # => "A" diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb index 6cdbea1f37..98cf7430f7 100644 --- a/activesupport/lib/active_support/core_ext/securerandom.rb +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -10,8 +10,8 @@ module SecureRandom # # The result may contain alphanumeric characters except 0, O, I and l # - # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE" - # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7" + # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE" + # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7" # def self.base58(n = 16) SecureRandom.random_bytes(n).unpack("C*").map do |byte| diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index b2e713077c..cc71b8155d 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -164,8 +164,26 @@ class String # # <%= link_to(@person.name, person_path) %> # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> - def parameterize(sep = '-'.freeze) - ActiveSupport::Inflector.parameterize(self, sep) + # + # To preserve the case of the characters in a string, use the `preserve_case` argument. + # + # class Person + # def to_param + # "#{id}-#{name.parameterize(preserve_case: true)}" + # end + # end + # + # @person = Person.find(1) + # # => #<Person id: 1, name: "Donald E. Knuth"> + # + # <%= link_to(@person.name, person_path) %> + # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a> + def parameterize(sep = :unused, separator: '-', preserve_case: false) + unless sep == :unused + ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.") + separator = sep + end + ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case) end # Creates the name of a table like Rails does for models to table names. This method diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index 7055f7f699..cc6f2158e7 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -9,12 +9,10 @@ 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. # - # name = 'Claus Müller' - # name.reverse # => "rell??M sualC" - # name.length # => 13 - # - # name.mb_chars.reverse.to_s # => "rellüM sualC" - # name.mb_chars.length # => 12 + # >> "lj".upcase + # => "lj" + # >> "lj".mb_chars.upcase.to_s + # => "LJ" # # == Method chaining # 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 c676b26b06..510fa48189 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -37,7 +37,7 @@ class ERB if s.html_safe? s else - s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE) + ActiveSupport::Multibyte::Unicode.tidy_bytes(s).gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE) end end module_function :unwrapped_html_escape @@ -50,7 +50,7 @@ class ERB # html_escape_once('<< Accept & Checkout') # # => "<< Accept & Checkout" def html_escape_once(s) - result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) + result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE) s.html_safe? ? result.html_safe : result end @@ -86,7 +86,7 @@ class ERB # use inside HTML attributes. # # If your JSON is being used downstream for insertion into the DOM, be aware of - # whether or not it is being inserted via +html()+. Most JQuery plugins do this. + # whether or not it is being inserted via +html()+. Most jQuery plugins do this. # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated # content returned by your JSON. # diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb index 9fdd9d8d2e..55b9b87352 100644 --- a/activesupport/lib/active_support/core_ext/string/strip.rb +++ b/activesupport/lib/active_support/core_ext/string/strip.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/object/try' - class String # Strips indentation in heredocs. # @@ -20,7 +18,6 @@ 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 - indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0 - gsub(/^[ \t]{#{indent}}/, '') + gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze) end end diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index f13d09f3ad..675db8a36b 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -16,9 +16,9 @@ class Time super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone)) end - # Return the number of days in the given month. + # Returns the number of days in the given month. # If no year is specified, it will use the current year. - def days_in_month(month, year = now.year) + def days_in_month(month, year = current.year) if month == 2 && ::Date.gregorian_leap?(year) 29 else @@ -26,6 +26,12 @@ class Time end end + # Returns the number of days in the given year. + # If no year is specified, it will use the current year. + def days_in_year(year = current.year) + days_in_month(2, year) + 337 + end + # Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>. def current ::Time.zone ? ::Time.zone.now : ::Time.now @@ -51,9 +57,9 @@ class Time # Returns the number of seconds since 00:00:00. # - # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0 - # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296 - # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399 + # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0.0 + # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296.0 + # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399.0 def seconds_since_midnight to_i - change(:hour => 0).to_i + (usec / 1.0e+6) end diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index dbf1f2f373..536c4bf525 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -6,6 +6,7 @@ class Time :db => '%Y-%m-%d %H:%M:%S', :number => '%Y%m%d%H%M%S', :nsec => '%Y%m%d%H%M%S%9N', + :usec => '%Y%m%d%H%M%S%6N', :time => '%H:%M', :short => '%d %b %H:%M', :long => '%B %d, %Y %H:%M', @@ -24,7 +25,7 @@ class Time # # This method is aliased to <tt>to_s</tt>. # - # time = Time.now # => Thu Jan 18 06:10:17 CST 2007 + # time = Time.now # => 2007-01-18 06:10:17 -06:00 # # time.to_formatted_s(:time) # => "06:10" # time.to_s(:time) # => "06:10" @@ -55,7 +56,8 @@ class Time alias_method :to_default_s, :to_s alias_method :to_s, :to_formatted_s - # Returns the UTC offset as an +HH:MM formatted string. + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. # # Time.local(2000).formatted_offset # => "-06:00" # Time.local(2000).formatted_offset(false) # => "-0600" diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 133d3938eb..877dc84ec8 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -53,7 +53,7 @@ class Time # Returns a TimeZone instance matching the time zone provided. # Accepts the time zone in any format supported by <tt>Time.zone=</tt>. - # Raises an ArgumentError for invalid time zones. + # Raises an +ArgumentError+ for invalid time zones. # # Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...> # Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...> diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb index 0b2ff817c3..c6c183edd9 100644 --- a/activesupport/lib/active_support/core_ext/uri.rb +++ b/activesupport/lib/active_support/core_ext/uri.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - 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 diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 8215a3085e..af18ff746f 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -1,6 +1,6 @@ require 'set' require 'thread' -require 'thread_safe' +require 'concurrent/map' require 'pathname' require 'active_support/core_ext/module/aliasing' require 'active_support/core_ext/module/attribute_accessors' @@ -25,21 +25,21 @@ module ActiveSupport #:nodoc: # :doc: # Execute the supplied block without interference from any - # concurrent loads + # concurrent loads. def self.run_interlock Dependencies.interlock.running { yield } end # Execute the supplied block while holding an exclusive lock, # preventing any other thread from being inside a #run_interlock - # block at the same time + # block at the same time. def self.load_interlock Dependencies.interlock.loading { yield } end # Execute the supplied block while holding an exclusive lock, # preventing any other thread from being inside a #run_interlock - # block at the same time + # block at the same time. def self.unload_interlock Dependencies.interlock.unloading { yield } end @@ -585,7 +585,7 @@ module ActiveSupport #:nodoc: class ClassCache def initialize - @store = ThreadSafe::Cache.new + @store = Concurrent::Map.new end def empty? diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index c74e9c40ac..32fe8025fe 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -9,37 +9,61 @@ module ActiveSupport # module Fred # extend self # - # def foo; end - # def bar; end - # def baz; end + # def aaa; end + # def bbb; end + # def ccc; end + # def ddd; end + # def eee; end # end # - # ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead') - # # => [:foo, :bar, :baz] + # Using the default deprecator: + # ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead') + # # => [:aaa, :bbb, :ccc] # - # Fred.foo - # # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1." + # Fred.aaa + # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.0. (called from irb_binding at (irb):10) + # # => nil # - # Fred.bar - # # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)." + # Fred.bbb + # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.0 (use zzz instead). (called from irb_binding at (irb):11) + # # => nil # - # Fred.baz - # # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)." + # Fred.ccc + # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.0 (use Bar#ccc instead). (called from irb_binding at (irb):12) + # # => nil + # + # Passing in a custom deprecator: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator) + # # => [:ddd] + # + # Fred.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 + # + # Using a custom deprecator directly: + # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem') + # custom_deprecator.deprecate_methods(Fred, eee: :zzz) + # # => [:eee] + # + # Fred.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) options = method_names.extract_options! - deprecator = options.delete(:deprecator) || ActiveSupport::Deprecation.instance + deprecator = options.delete(:deprecator) || self method_names += options.keys - method_names.each do |method_name| - mod = Module.new do + mod = Module.new do + method_names.each do |method_name| define_method(method_name) do |*args, &block| deprecator.deprecation_warning(method_name, options[method_name]) super(*args, &block) end end - - target_module.prepend(mod) end + + target_module.prepend(mod) end end end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index 6572ff78a6..6f0ad445fc 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -111,7 +111,7 @@ module ActiveSupport # # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) # - # (In a later update, the orignal 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') diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index bbe25c9260..f89fc0fe14 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -83,7 +83,7 @@ module ActiveSupport rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" offending_line = callstack.find { |frame| - !frame.absolute_path.start_with?(rails_gem_root) + frame.absolute_path && !frame.absolute_path.start_with?(rails_gem_root) } || callstack.first [offending_line.path, offending_line.lineno, offending_line.label] end diff --git a/activesupport/lib/active_support/file_evented_update_checker.rb b/activesupport/lib/active_support/file_evented_update_checker.rb new file mode 100644 index 0000000000..bb0f26f874 --- /dev/null +++ b/activesupport/lib/active_support/file_evented_update_checker.rb @@ -0,0 +1,147 @@ +require 'listen' +require 'set' +require 'pathname' +require 'concurrent/atomic/atomic_boolean' + +module ActiveSupport + class FileEventedUpdateChecker #:nodoc: all + def initialize(files, dirs = {}, &block) + @ph = PathHelper.new + @files = files.map { |f| @ph.xpath(f) }.to_set + + @dirs = {} + dirs.each do |dir, exts| + @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) } + end + + @block = block + @updated = Concurrent::AtomicBoolean.new(false) + @lcsp = @ph.longest_common_subpath(@dirs.keys) + + if (dtw = directories_to_watch).any? + Listen.to(*dtw, &method(:changed)).start + end + end + + def updated? + @updated.true? + end + + def execute + @updated.make_false + @block.call + end + + def execute_if_updated + if updated? + execute + true + end + end + + private + + def changed(modified, added, removed) + unless updated? + @updated.make_true if (modified + added + removed).any? { |f| watching?(f) } + end + end + + def watching?(file) + file = @ph.xpath(file) + + if @files.member?(file) + true + elsif file.directory? + false + else + ext = @ph.normalize_extension(file.extname) + + file.dirname.ascend do |dir| + if @dirs.fetch(dir, []).include?(ext) + break true + elsif dir == @lcsp || dir.root? + break false + end + end + end + end + + def directories_to_watch + dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) } + dtw.compact! + dtw.uniq! + + @ph.filter_out_descendants(dtw) + end + + class PathHelper + using Module.new { + refine Pathname do + def ascendant_of?(other) + self != other && other.ascend do |ascendant| + break true if self == ascendant + end + end + end + } + + def xpath(path) + Pathname.new(path).expand_path + end + + def normalize_extension(ext) + ext.to_s.sub(/\A\./, '') + end + + # Given a collection of Pathname objects returns the longest subpath + # common to all of them, or +nil+ if there is none. + def longest_common_subpath(paths) + return if paths.empty? + + lcsp = Pathname.new(paths[0]) + + paths[1..-1].each do |path| + until lcsp.ascendant_of?(path) + if lcsp.root? + # If we get here a root directory is not an ascendant of path. + # This may happen if there are paths in different drives on + # Windows. + return + else + lcsp = lcsp.parent + end + end + end + + lcsp + end + + # Returns the deepest existing ascendant, which could be the argument itself. + def existing_parent(dir) + dir.ascend do |ascendant| + break ascendant if ascendant.directory? + end + end + + # Filters out directories which are descendants of others in the collection (stable). + def filter_out_descendants(dirs) + return dirs if dirs.length < 2 + + dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length } + descendants = [] + + until dirs_sorted_by_nparts.empty? + dir = dirs_sorted_by_nparts.shift + + dirs_sorted_by_nparts.reject! do |possible_descendant| + dir.ascendant_of?(possible_descendant) && descendants << possible_descendant + end + end + + # Array#- preserves order. + dirs - descendants + end + end + end +end diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index 78b627c286..1fa9335080 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -35,7 +35,7 @@ module ActiveSupport # This method must also receive a block that will be called once a path # changes. The array of files and list of directories cannot be changed # after FileUpdateChecker has been initialized. - def initialize(files, dirs={}, &block) + def initialize(files, dirs = {}, &block) @files = files.freeze @glob = compile_glob(dirs) @block = block diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index 7068f09d87..ece68bbcb6 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -1,5 +1,5 @@ module ActiveSupport - # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt> + # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>. def self.gem_version Gem::Version.new VERSION::STRING end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 0371f760b0..4ff35a45a1 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -59,6 +59,10 @@ module ActiveSupport if constructor.respond_to?(:to_hash) super() update(constructor) + + hash = constructor.to_hash + self.default = hash.default if hash.default + self.default_proc = hash.default_proc if hash.default_proc else super(constructor) end @@ -73,11 +77,12 @@ module ActiveSupport end def self.new_from_hash_copying_default(hash) - hash = hash.to_hash - new(hash).tap do |new_hash| - new_hash.default = hash.default - new_hash.default_proc = hash.default_proc if hash.default_proc - end + ActiveSupport::Deprecation.warn(<<-MSG.squish) + `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default` + has been deprecated, and will be removed in Rails 5.1. The behavior of + this method is now identical to the behavior of `.new`. + MSG + new(hash) end def self.[](*args) @@ -206,7 +211,7 @@ module ActiveSupport # hash['a'] = nil # hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1} def reverse_merge(other_hash) - super(self.class.new_from_hash_copying_default(other_hash)) + super(self.class.new(other_hash)) end # Same semantics as +reverse_merge+ but modifies the receiver in-place. @@ -219,7 +224,7 @@ module ActiveSupport # h = { "a" => 100, "b" => 200 } # h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400} def replace(other_hash) - super(self.class.new_from_hash_copying_default(other_hash)) + super(self.class.new(other_hash)) end # Removes the specified key from the hash. diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index 6775eec34b..82aacf3b24 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -56,7 +56,7 @@ module I18n I18n.enforce_available_locales = enforce_available_locales directories = watched_dirs_with_extensions(reloadable_paths) - reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup, directories) do + reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do I18n.load_path.keep_if { |p| File.exist?(p) } I18n.load_path |= reloadable_paths.map(&:existent).flatten diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 42560f3515..f3e52b48ac 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -1,4 +1,4 @@ -require 'thread_safe' +require 'concurrent/map' require 'active_support/core_ext/array/prepend_and_append' require 'active_support/i18n' @@ -25,7 +25,7 @@ module ActiveSupport # singularization rules that is runs. This guarantees that your rules run # before any of the rules that may already have been loaded. class Inflections - @__instance__ = ThreadSafe::Cache.new + @__instance__ = Concurrent::Map.new class Uncountables < Array def initialize diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index ab5372ac43..595b0339cc 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -1,5 +1,3 @@ -# encoding: utf-8 - require 'active_support/inflections' module ActiveSupport diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 7b28eeb6e2..871cfb8a72 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'active_support/core_ext/string/multibyte' require 'active_support/i18n' @@ -69,26 +68,44 @@ module ActiveSupport # # parameterize("Donald E. Knuth") # => "donald-e-knuth" # parameterize("^trés|Jolie-- ") # => "tres-jolie" - def parameterize(string, sep = '-') - # replace accented chars with their ascii equivalents + # + # To use a custom separator, override the `separator` argument. + # + # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth" + # parameterize("^trés|Jolie-- ", separator: '_') # => "tres_jolie" + # + # 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" + # + def parameterize(string, sep = :unused, separator: '-', preserve_case: false) + unless sep == :unused + ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.") + separator = sep + end + # Replace accented chars with their ASCII equivalents. parameterized_string = transliterate(string) - # Turn unwanted chars into the separator - parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep) - unless sep.nil? || sep.empty? - if sep == "-".freeze - re_duplicate_seperator = /-{2,}/ + + # Turn unwanted chars into the separator. + parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator) + + unless separator.nil? || separator.empty? + if separator == "-".freeze + re_duplicate_separator = /-{2,}/ re_leading_trailing_separator = /^-|-$/i else - re_sep = Regexp.escape(sep) - re_duplicate_seperator = /#{re_sep}{2,}/ + re_sep = Regexp.escape(separator) + re_duplicate_separator = /#{re_sep}{2,}/ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i end # No more than one of the separator in a row. - parameterized_string.gsub!(re_duplicate_seperator, sep) + parameterized_string.gsub!(re_duplicate_separator, separator) # Remove leading/trailing separator. parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze) end - parameterized_string.downcase! + + parameterized_string.downcase! unless preserve_case parameterized_string end end diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb index 51d2da3a79..7f73f9ddfc 100644 --- a/activesupport/lib/active_support/key_generator.rb +++ b/activesupport/lib/active_support/key_generator.rb @@ -1,8 +1,8 @@ -require 'thread_safe' +require 'concurrent/map' require 'openssl' module ActiveSupport - # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2 + # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2. # It can be used to derive a number of keys for various purposes from a given secret. # This lets Rails applications have a single secure secret, but avoid reusing that # key in multiple incompatible contexts. @@ -24,11 +24,11 @@ module ActiveSupport # CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid # re-executing the key generation process when it's called using the same salt and - # key_size + # key_size. class CachingKeyGenerator def initialize(key_generator) @key_generator = key_generator - @cache_keys = ThreadSafe::Cache.new + @cache_keys = Concurrent::Map.new end # Returns a derived key suitable for use. The default key_size is chosen diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index cbc20c103d..588ed67c81 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -10,8 +10,7 @@ module ActiveSupport # class SyncLogSubscriberTest < ActiveSupport::TestCase # include ActiveSupport::LogSubscriber::TestHelper # - # def setup - # super + # setup do # ActiveRecord::LogSubscriber.attach_to(:active_record) # end # diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index c82a13511e..2dde01c844 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -34,8 +34,8 @@ module ActiveSupport # Initialize a new MessageEncryptor. +secret+ must be at least as long as # the cipher key size. For the default 'aes-256-cbc' cipher, this is 256 # bits. If you are using a user-entered secret, you can generate a suitable - # key with <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or - # similar. + # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key + # derivation function. # # Options: # * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 45cf6fc1ef..707cf200b5 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'active_support/json' require 'active_support/core_ext/string/access' require 'active_support/core_ext/string/behavior' @@ -87,7 +86,8 @@ module ActiveSupport #:nodoc: end # Works like <tt>String#slice!</tt>, but returns an instance of - # Chars, or nil if the string was not modified. + # Chars, or nil if the string was not modified. The string will not be + # modified if the range given is out of bounds # # string = 'Welcome' # string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c"> @@ -95,7 +95,10 @@ module ActiveSupport #:nodoc: # string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo"> # string # => 'me' def slice!(*args) - chars(@wrapped_string.slice!(*args)) + string_sliced = @wrapped_string.slice!(*args) + if string_sliced + chars(string_sliced) + end end # Reverses all characters in the string. diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 3f6a4c8457..586002b03b 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 module ActiveSupport module Multibyte module Unicode @@ -257,7 +256,7 @@ module ActiveSupport # * <tt>string</tt> - The string to perform normalization on. # * <tt>form</tt> - The form you want to normalize in. Should be one of # the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>. - # Default is ActiveSupport::Multibyte.default_normalization_form. + # Default is ActiveSupport::Multibyte::Unicode.default_normalization_form. def normalize(string, form=nil) form ||= @default_normalization_form # See http://www.unicode.org/reports/tr15, Table 1 diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 0131fe2572..7798c7ec60 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -1,5 +1,5 @@ require 'mutex_m' -require 'thread_safe' +require 'concurrent/map' module ActiveSupport module Notifications @@ -12,7 +12,7 @@ module ActiveSupport def initialize @subscribers = [] - @listeners_for = ThreadSafe::Cache.new + @listeners_for = Concurrent::Map.new super end @@ -51,7 +51,7 @@ module ActiveSupport end def listeners_for(name) - # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics) + # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics) @listeners_for[name] || synchronize do # use synchronisation when accessing @subscribers @listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) } diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 9762d95145..248521e677 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -115,10 +115,10 @@ module ActiveSupport # number_to_percentage(100, precision: 0) # => 100% # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000% # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% - # number_to_percentage(1000, locale: :fr) # => 1 000,000% - # number_to_percentage:(1000, precision: nil) # => 1000% + # number_to_percentage(1000, locale: :fr) # => 1000,000% + # number_to_percentage(1000, precision: nil) # => 1000% # number_to_percentage('98a') # => 98a% - # number_to_percentage(100, format: '%n %') # => 100 % + # number_to_percentage(100, format: '%n %') # => 100.000 % def number_to_percentage(number, options = {}) NumberToPercentageConverter.convert(number, options) end diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb index 5c6fe2df83..7a1f8171c0 100644 --- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb @@ -20,9 +20,11 @@ module ActiveSupport exponent = calculate_exponent(units) @number = number / (10 ** exponent) + until (rounded_number = NumberToRoundedConverter.convert(number, options)) != NumberToRoundedConverter.convert(1000, options) + @number = number / 1000.0 + exponent += 3 + end unit = determine_unit(units, exponent) - - rounded_number = NumberToRoundedConverter.convert(number, options) format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, unit).strip end diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index 45864990ce..53a55bd986 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -20,7 +20,7 @@ module ActiveSupport # To raise an exception when the value is blank, append a # bang to the key name, like: # - # h.dog! # => raises KeyError + # h.dog! # => raises KeyError: key not found: :dog # class OrderedOptions < Hash alias_method :_get, :[] # preserve the original #[] method diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index ca2e4d5625..a909a65bb6 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/module/delegation' + module ActiveSupport # This module is used to encapsulate access to thread local variables. # @@ -43,9 +45,9 @@ module ActiveSupport protected def method_missing(name, *args, &block) # :nodoc: # Caches the method definition as a singleton method of the receiver. - define_singleton_method(name) do |*a, &b| - instance.public_send(name, *a, &b) - end + # + # By letting #delegate handle it, we avoid an enclosure that'll capture args. + singleton_class.delegate name, to: :instance send(name, *args, &block) end diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index cd0fb51009..845788b669 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -26,7 +26,7 @@ module ActiveSupport unless zone_default raise 'Value assigned to config.time_zone not recognized. ' \ - 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.' + 'Run "rake time:zones:all" for a time zone names list.' end Time.zone_default = zone_default diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index d87ce3474d..29305e0082 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/object/blank' module ActiveSupport module Testing module Assertions - # Assert that an expression is not truthy. Passes if <tt>object</tt> is + # Asserts that an expression is not truthy. Passes if <tt>object</tt> is # +nil+ or +false+. "Truthy" means "considered true in a conditional" # like <tt>if foo</tt>. # @@ -68,13 +68,15 @@ module ActiveSupport } before = exps.map(&:call) - yield + retval = yield expressions.zip(exps).each_with_index do |(code, e), i| error = "#{code.inspect} didn't change by #{difference}" error = "#{message}.\n#{error}" if message assert_equal(before[i] + difference, e.call, error) end + + retval end # Assertion that the numeric result of evaluating an expression is not diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb index 6c94c611b6..5dfa14eeba 100644 --- a/activesupport/lib/active_support/testing/deprecation.rb +++ b/activesupport/lib/active_support/testing/deprecation.rb @@ -3,8 +3,8 @@ require 'active_support/deprecation' module ActiveSupport module Testing module Deprecation #:nodoc: - def assert_deprecated(match = nil, &block) - result, warnings = collect_deprecations(&block) + def assert_deprecated(match = nil, deprecator = nil, &block) + result, warnings = collect_deprecations(deprecator, &block) assert !warnings.empty?, "Expected a deprecation warning within the block but received none" if match match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp) @@ -13,22 +13,23 @@ module ActiveSupport result end - def assert_not_deprecated(&block) - result, deprecations = collect_deprecations(&block) + def assert_not_deprecated(deprecator = nil, &block) + result, deprecations = collect_deprecations(deprecator, &block) assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}" result end - def collect_deprecations - old_behavior = ActiveSupport::Deprecation.behavior + def collect_deprecations(deprecator = nil) + deprecator ||= ActiveSupport::Deprecation + old_behavior = deprecator.behavior deprecations = [] - ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack| + deprecator.behavior = Proc.new do |message, callstack| deprecations << message end result = yield [result, deprecations] ensure - ActiveSupport::Deprecation.behavior = old_behavior + deprecator.behavior = old_behavior end end end diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb index 4c6a0801b8..affb84cda5 100644 --- a/activesupport/lib/active_support/testing/file_fixtures.rb +++ b/activesupport/lib/active_support/testing/file_fixtures.rb @@ -18,7 +18,7 @@ module ActiveSupport # Returns a +Pathname+ to the fixture file named +fixture_name+. # - # Raises ArgumentError if +fixture_name+ can't be found. + # Raises +ArgumentError+ if +fixture_name+ can't be found. def file_fixture(fixture_name) path = Pathname.new(File.join(file_fixture_path, fixture_name)) diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 1de0a19998..edf8b30a0a 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -41,7 +41,23 @@ module ActiveSupport pid = fork do read.close yield - write.puts [Marshal.dump(self.dup)].pack("m") + begin + if error? + failures.map! { |e| + begin + Marshal.dump e + e + rescue TypeError + ex = Exception.new e.message + ex.set_backtrace e.backtrace + Minitest::UnexpectedError.new ex + end + } + end + result = Marshal.dump(self.dup) + end + + write.puts [result].pack("m") exit! end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 07fc787550..79cc748cf5 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -14,7 +14,7 @@ module ActiveSupport # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' # Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00 # Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45 EST -05:00 - # Time.zone.at(1170361845) # => Sat, 10 Feb 2007 15:30:45 EST -05:00 + # Time.zone.at(1171139445) # => Sat, 10 Feb 2007 15:30:45 EST -05:00 # Time.zone.now # => Sun, 18 May 2008 13:07:55 EDT -04:00 # Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -05:00 # @@ -41,6 +41,9 @@ module ActiveSupport 'Time' end + PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze } + PRECISIONS[0] = '%FT%T'.freeze + include Comparable attr_reader :time_zone @@ -99,7 +102,7 @@ module ActiveSupport # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' # Time.zone.now.utc? # => false def utc? - time_zone.name == 'UTC' + period.offset.abbreviation == :UTC || period.offset.abbreviation == :UCT end alias_method :gmt?, :utc? @@ -142,11 +145,7 @@ module ActiveSupport # # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00" def xmlschema(fraction_digits = 0) - fraction = if fraction_digits.to_i > 0 - (".%06i" % time.usec)[0, fraction_digits.to_i + 1] - end - - "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}" + "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z'.freeze)}" end alias_method :iso8601, :xmlschema @@ -285,8 +284,8 @@ module ActiveSupport # the current object's time and the +other+ time. # # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' - # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EST -05:00 - # now - 1000 # => Sun, 02 Nov 2014 01:09:48 EST -05:00 + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00 + # now - 1000 # => Mon, 03 Nov 2014 00:09:48 EST -05:00 # # If subtracting a Duration of variable length (i.e., years, months, days), # move backward from #time, otherwise move backward from #utc, for accuracy @@ -295,8 +294,8 @@ module ActiveSupport # For instance, a time - 24.hours will go subtract exactly 24 hours, while a # time - 1.day will subtract 23-25 hours, depending on the day. # - # now - 24.hours # => Sat, 01 Nov 2014 02:26:28 EDT -04:00 - # now - 1.day # => Sat, 01 Nov 2014 01:26:28 EDT -04:00 + # now - 24.hours # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now - 1.day # => Sun, 02 Nov 2014 00:26:28 EDT -04:00 def -(other) if other.acts_like?(:time) to_time - other.to_time @@ -308,10 +307,48 @@ module ActiveSupport end end + # Subtracts an interval of time from the current object's time and returns + # the result as a new TimeWithZone object. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00 + # now.ago(1000) # => Mon, 03 Nov 2014 00:09:48 EST -05:00 + # + # If we're subtracting a Duration of variable length (i.e., years, months, + # days), move backward from #time, otherwise move backward from #utc, for + # accuracy when moving across DST boundaries. + # + # For instance, <tt>time.ago(24.hours)</tt> will move back exactly 24 hours, + # while <tt>time.ago(1.day)</tt> will move back 23-25 hours, depending on + # the day. + # + # now.ago(24.hours) # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now.ago(1.day) # => Sun, 02 Nov 2014 00:26:28 EDT -04:00 def ago(other) since(-other) end + # Uses Date to provide precise Time calculations for years, months, and days + # according to the proleptic Gregorian calendar. The result is returned as a + # new TimeWithZone object. + # + # The +options+ parameter takes a hash with any of these keys: + # <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>, + # <tt>:hours</tt>, <tt>:minutes</tt>, <tt>:seconds</tt>. + # + # If advancing by a value of variable length (i.e., years, weeks, months, + # days), move forward from #time, otherwise move forward from #utc, for + # accuracy when moving across DST boundaries. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now.advance(seconds: 1) # => Sun, 02 Nov 2014 01:26:29 EDT -04:00 + # now.advance(minutes: 1) # => Sun, 02 Nov 2014 01:27:28 EDT -04:00 + # now.advance(hours: 1) # => Sun, 02 Nov 2014 01:26:28 EST -05:00 + # now.advance(days: 1) # => Mon, 03 Nov 2014 01:26:28 EST -05:00 + # now.advance(weeks: 1) # => Sun, 09 Nov 2014 01:26:28 EST -05:00 + # now.advance(months: 1) # => Tue, 02 Dec 2014 01:26:28 EST -05:00 + # now.advance(years: 1) # => Mon, 02 Nov 2015 01:26:28 EST -05:00 def advance(options) # If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time, # otherwise advance from #utc, for accuracy when moving across DST boundaries @@ -388,6 +425,11 @@ module ActiveSupport end alias_method :kind_of?, :is_a? + # An instance of ActiveSupport::TimeWithZone is never blank + def blank? + false + end + def freeze period; utc; time # preload instance variables before freezing super diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 2699a064d7..7ca3592520 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -1,5 +1,5 @@ require 'tzinfo' -require 'thread_safe' +require 'concurrent/map' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/try' @@ -23,15 +23,9 @@ module ActiveSupport # config.time_zone = 'Eastern Time (US & Canada)' # end # - # Time.zone # => #<TimeZone:0x514834...> + # Time.zone # => #<ActiveSupport::TimeZone:0x514834...> # Time.zone.name # => "Eastern Time (US & Canada)" # Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00 - # - # The version of TZInfo bundled with Active Support only includes the - # definitions necessary to support the zones defined by the TimeZone class. - # If you need to use zones that aren't defined by TimeZone, you'll need to - # install the TZInfo gem (if a recent version of the gem is installed locally, - # this will be used instead of the bundled version.) class TimeZone # Keys are Rails TimeZone names, values are TZInfo identifiers. MAPPING = { @@ -92,7 +86,8 @@ module ActiveSupport "Paris" => "Europe/Paris", "Amsterdam" => "Europe/Amsterdam", "Berlin" => "Europe/Berlin", - "Bern" => "Europe/Berlin", + "Bern" => "Europe/Zurich", + "Zurich" => "Europe/Zurich", "Rome" => "Europe/Rome", "Stockholm" => "Europe/Stockholm", "Vienna" => "Europe/Vienna", @@ -189,13 +184,13 @@ module ActiveSupport UTC_OFFSET_WITH_COLON = '%s%02d:%02d' UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '') - @lazy_zones_map = ThreadSafe::Cache.new + @lazy_zones_map = Concurrent::Map.new class << self # Assumes self represents an offset from UTC in seconds (as returned from # Time#utc_offset) and turns this into an +HH:MM formatted string. # - # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" + # ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00" def seconds_to_utc_offset(seconds, colon = true) format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON sign = (seconds < 0 ? '-' : '+') @@ -285,8 +280,12 @@ module ActiveSupport end end - # Returns the offset of this time zone as a formatted string, of the - # format "+HH:MM". + # Returns a formatted string of the offset from UTC, or an alternative + # string if the time zone is already UTC. + # + # zone = ActiveSupport::TimeZone['Central Time (US & Canada)'] + # zone.formatted_offset # => "-06:00" + # zone.formatted_offset(false) # => "-0600" def formatted_offset(colon=true, alternate_utc_string = nil) utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon) end @@ -384,7 +383,7 @@ module ActiveSupport time_now.utc.in_time_zone(self) end - # Return the current date in this time zone. + # Returns the current date in this time zone. def today tzinfo.now.to_date end |