aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib')
-rw-r--r--activesupport/lib/active_support.rb15
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb6
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb136
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb48
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb81
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb35
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb34
-rw-r--r--activesupport/lib/active_support/callbacks.rb21
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb88
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb14
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb27
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/zones.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb36
-rw-r--r--activesupport/lib/active_support/core_ext/hash/compact.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/concern.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb141
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb72
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/qualified_const.rb42
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb113
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb18
-rw-r--r--activesupport/lib/active_support/dependencies.rb67
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb24
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb6
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb17
-rw-r--r--activesupport/lib/active_support/duration.rb26
-rw-r--r--activesupport/lib/active_support/duration/iso8601_parser.rb122
-rw-r--r--activesupport/lib/active_support/duration/iso8601_serializer.rb51
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb155
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb117
-rw-r--r--activesupport/lib/active_support/executor.rb6
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb26
-rw-r--r--activesupport/lib/active_support/gem_version.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb20
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb6
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb11
-rw-r--r--activesupport/lib/active_support/locale/en.yml2
-rw-r--r--activesupport/lib/active_support/logger.rb33
-rw-r--r--activesupport/lib/active_support/logger_silence.rb12
-rw-r--r--activesupport/lib/active_support/logger_thread_safe_level.rb31
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb4
-rw-r--r--activesupport/lib/active_support/message_verifier.rb8
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb51
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb4
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb10
-rw-r--r--activesupport/lib/active_support/number_helper.rb133
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb8
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb13
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb8
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb2
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb5
-rw-r--r--activesupport/lib/active_support/reloader.rb129
-rw-r--r--activesupport/lib/active_support/rescuable.rb136
-rw-r--r--activesupport/lib/active_support/security_utils.rb7
-rw-r--r--activesupport/lib/active_support/test_case.rb22
-rw-r--r--activesupport/lib/active_support/testing/composite_filter.rb54
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb23
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb22
-rw-r--r--activesupport/lib/active_support/xml_mini.rb33
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb4
95 files changed, 1914 insertions, 697 deletions
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 63277a65b4..11569add37 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2015 David Heinemeier Hansson
+# Copyright (c) 2005-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -26,6 +26,7 @@ require "active_support/dependencies/autoload"
require "active_support/version"
require "active_support/logger"
require "active_support/lazy_load_hooks"
+require "active_support/core_ext/date_and_time/compatibility"
module ActiveSupport
extend ActiveSupport::Autoload
@@ -33,9 +34,13 @@ module ActiveSupport
autoload :Concern
autoload :Dependencies
autoload :DescendantsTracker
+ autoload :ExecutionWrapper
+ autoload :Executor
autoload :FileUpdateChecker
+ autoload :EventedFileUpdateChecker
autoload :LogSubscriber
autoload :Notifications
+ autoload :Reloader
eager_autoload do
autoload :BacktraceCleaner
@@ -81,6 +86,14 @@ module ActiveSupport
def self.halt_callback_chains_on_return_false=(value)
Callbacks.halt_and_display_warning_on_return_false = value
end
+
+ def self.to_time_preserves_timezone
+ DateAndTime::Compatibility.preserve_timezone
+ end
+
+ def self.to_time_preserves_timezone=(value)
+ DateAndTime::Compatibility.preserve_timezone = value
+ end
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb
index f59ddf5403..ea328f603e 100644
--- a/activesupport/lib/active_support/array_inquirer.rb
+++ b/activesupport/lib/active_support/array_inquirer.rb
@@ -9,8 +9,10 @@ module ActiveSupport
# variants.desktop? # => false
class ArrayInquirer < Array
# Passes each element of +candidates+ collection to ArrayInquirer collection.
- # The method returns true if at least one element is the same. If +candidates+
- # collection is not given, method returns true.
+ # The method returns true if any element from the ArrayInquirer collection
+ # is equal to the stringified or symbolized form of any element in the +candidates+ collection.
+ #
+ # If +candidates+ collection is not given, method returns true.
#
# variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
#
diff --git a/activesupport/lib/active_support/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 805b7a714f..3988b147ac 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -38,7 +38,7 @@ module ActiveSupport
options[:level] ||= :info
result = nil
- ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield }
+ ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
result
else
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index d0e53eaf05..bc114e0785 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.
@@ -157,20 +158,20 @@ module ActiveSupport
attr_reader :silence, :options
alias :silence? :silence
- # Create a new cache. The options will be passed to any write method calls
+ # Creates a new cache. The options will be passed to any write method calls
# except for <tt>:namespace</tt> which can be used to set the global
# namespace for the cache.
def initialize(options = nil)
@options = options ? options.dup : {}
end
- # Silence the logger.
+ # Silences the logger.
def silence!
@silence = true
self
end
- # Silence the logger within a block.
+ # Silences the logger within a block.
def mute
previous_silence, @silence = defined?(@silence) && @silence, true
yield
@@ -197,10 +198,17 @@ module ActiveSupport
# cache.fetch('city') # => "Duckburgh"
#
# You may also specify additional options via the +options+ argument.
- # Setting <tt>force: true</tt> will force a cache miss:
+ # Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
+ # the cache value as missing even if it's present. Passing a block is
+ # required when `force` is true so this always results in a cache write.
#
# cache.write('today', 'Monday')
- # cache.fetch('today', force: true) # => nil
+ # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
+ # cache.fetch('today', force: true) # => ArgumentError
+ #
+ # The `:force` option is useful when you're calling some other method to
+ # ask whether you should force a cache write. Otherwise, it's clearer to
+ # just call `Cache#write`.
#
# Setting <tt>:compress</tt> will store a large cache entry set by the call
# in a compressed format.
@@ -254,10 +262,11 @@ module ActiveSupport
# end
# end
#
- # # val_1 => "new value 1"
- # # val_2 => "original value"
- # # sleep 10 # First thread extend the life of cache by another 10 seconds
- # # cache.fetch('foo') => "new value 1"
+ # cache.fetch('foo') # => "original value"
+ # sleep 10 # First thread extended the life of cache by another 10 seconds
+ # cache.fetch('foo') # => "new value 1"
+ # val_1 # => "new value 1"
+ # val_2 # => "original value"
#
# Other options will be handled by the specific cache store implementation.
# Internally, #fetch calls #read_entry, and calls #write_entry on a cache
@@ -275,21 +284,23 @@ module ActiveSupport
def fetch(name, options = nil)
if block_given?
options = merged_options(options)
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
+ entry = nil
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)
+ payload[:super_operation] = :fetch if payload
+ payload[:hit] = !!entry if payload
+ end
- 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
+ if entry
+ get_entry_value(entry, name, options)
+ else
+ save_block_result_to_cache(name, options) { |_name| yield _name }
end
+ elsif options && options[:force]
+ raise ArgumentError, 'Missing block: Calling `Cache#fetch` with `force: true` requires a block.'
else
read(name, options)
end
@@ -302,7 +313,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
@@ -321,7 +332,7 @@ module ActiveSupport
end
end
- # Read multiple values at once from the cache. Options can be passed
+ # Reads multiple values at once from the cache. Options can be passed
# in the last argument.
#
# Some cache implementation may optimize this method.
@@ -331,21 +342,19 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- instrument_multi(:read, names, options) do |payload|
- results = {}
- names.each do |name|
- key = namespaced_key(name, options)
- entry = read_entry(key, options)
- if entry
- if entry.expired?
- delete_entry(key, options)
- else
- results[name] = entry.value
- end
+ results = {}
+ names.each do |name|
+ key = normalize_key(name, options)
+ entry = read_entry(key, options)
+ if entry
+ if entry.expired?
+ delete_entry(key, options)
+ else
+ results[name] = entry.value
end
end
- results
end
+ results
end
# Fetches data from the cache, using the given keys. If there is data in
@@ -386,7 +395,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
@@ -397,7 +406,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
@@ -408,12 +417,12 @@ 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
- # Delete all entries with keys matching the pattern.
+ # Deletes all entries with keys matching the pattern.
#
# Options are passed to the underlying cache implementation.
#
@@ -422,7 +431,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
end
- # Increment an integer value in the cache.
+ # Increments an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
@@ -431,7 +440,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support increment")
end
- # Decrement an integer value in the cache.
+ # Decrements an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
@@ -440,7 +449,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support decrement")
end
- # Cleanup the cache by removing expired entries.
+ # Cleanups the cache by removing expired entries.
#
# Options are passed to the underlying cache implementation.
#
@@ -449,7 +458,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support cleanup")
end
- # Clear the entire cache. Be careful with this method since it could
+ # Clears the entire cache. Be careful with this method since it could
# affect other processes if shared cache is being used.
#
# The options hash is passed to the underlying cache implementation.
@@ -460,7 +469,7 @@ module ActiveSupport
end
protected
- # Add the namespace defined in the options to a pattern designed to
+ # Adds the namespace defined in the options to a pattern designed to
# match keys. Implementations that support delete_matched should call
# this method to translate a pattern that matches names into one that
# matches namespaced keys.
@@ -479,26 +488,26 @@ module ActiveSupport
end
end
- # Read an entry from the cache implementation. Subclasses must implement
+ # Reads an entry from the cache implementation. Subclasses must implement
# this method.
def read_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
- # Write an entry to the cache implementation. Subclasses must implement
+ # Writes an entry to the cache implementation. Subclasses must implement
# this method.
def write_entry(key, entry, options) # :nodoc:
raise NotImplementedError.new
end
- # Delete an entry from the cache implementation. Subclasses must
+ # Deletes an entry from the cache implementation. Subclasses must
# implement this method.
def delete_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
private
- # Merge the default options with ones specific to a method call.
+ # Merges the default options with ones specific to a method call.
def merged_options(call_options) # :nodoc:
if call_options
options.merge(call_options)
@@ -507,7 +516,7 @@ module ActiveSupport
end
end
- # Expand key to be a consistent string value. Invoke +cache_key+ if
+ # Expands key to be a consistent string value. Invokes +cache_key+ if
# object responds to +cache_key+. Otherwise, +to_param+ method will be
# called. If the key is a Hash, then keys will be sorted alphabetically.
def expanded_key(key) # :nodoc:
@@ -527,9 +536,9 @@ module ActiveSupport
key.to_param
end
- # Prefix a key with the namespace. Namespace and key will be delimited
+ # Prefixes 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
@@ -537,25 +546,22 @@ 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}: #{namespaced_key(key, options)}#{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)
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
end
- def instrument_multi(operation, keys, options = nil)
- log do
- formatted_keys = keys.map { |k| "- #{k}" }.join("\n")
- "Caches multi #{operation}:\n#{formatted_keys}#{options.blank? ? "" : " (#{options.inspect})"}"
- end
-
- payload = { key: keys }
- payload.merge!(options) if options.is_a?(Hash)
- ActiveSupport::Notifications.instrument("cache_#{operation}_multi.active_support", payload) { yield(payload) }
- end
-
def log
return unless logger && logger.debug? && !silence?
logger.debug(yield)
@@ -578,12 +584,12 @@ module ActiveSupport
end
def get_entry_value(entry, name, options)
- instrument(:fetch_hit, name, options) { |payload| }
+ instrument(:fetch_hit, name, options) { }
entry.value
end
def save_block_result_to_cache(name, options)
- result = instrument(:generate, name, options) do |payload|
+ result = instrument(:generate, name, options) do
yield(name)
end
@@ -601,7 +607,7 @@ module ActiveSupport
class Entry # :nodoc:
DEFAULT_COMPRESS_LIMIT = 16.kilobytes
- # Create a new cache entry for the specified value. Options supported are
+ # Creates a new cache entry for the specified value. Options supported are
# +:compress+, +:compress_threshold+, and +:expires_in+.
def initialize(value, options = {})
if should_compress?(value, options)
@@ -620,7 +626,7 @@ module ActiveSupport
compressed? ? uncompress(@value) : @value
end
- # Check if the entry is expired. The +expires_in+ parameter can override
+ # Checks if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
@expires_in && @created_at + @expires_in <= Time.now.to_f
@@ -655,7 +661,7 @@ module ActiveSupport
end
end
- # Duplicate the value in a class. This is used by cache implementations that don't natively
+ # Duplicates the value in a class. This is used by cache implementations that don't natively
# serialize entries to protect against accidental cache modifications.
def dup_value!
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 9a88fc286a..99c55b1aa4 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -17,6 +17,7 @@ module ActiveSupport
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
EXCLUDED_DIRS = ['.', '..'].freeze
+ GITKEEP_FILES = ['.gitkeep', '.keep'].freeze
def initialize(cache_path, options = nil)
super(options)
@@ -24,10 +25,10 @@ module ActiveSupport
end
# Deletes all items from the cache. In this case it deletes all the entries in the specified
- # file store directory except for .gitkeep. Be careful which directory is specified in your
+ # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
# config file when using +FileStore+ because everything in that directory will be deleted.
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
+ root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
rescue Errno::ENOENT
end
@@ -60,7 +61,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 +69,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 +78,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,7 +116,8 @@ module ActiveSupport
end
# Translate a key into a file path.
- def key_file_path(key)
+ def normalize_key(key, options)
+ key = super
fname = URI.encode_www_form_component(key)
if fname.size > FILEPATH_MAX_SIZE
@@ -139,6 +138,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
@@ -148,7 +155,7 @@ module ActiveSupport
# Delete empty directories in the cache.
def delete_empty_directories(dir)
return if File.realpath(dir) == File.realpath(cache_path)
- if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty?
+ if exclude_from(dir, EXCLUDED_DIRS).empty?
Dir.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
@@ -175,7 +182,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)
@@ -187,6 +194,11 @@ module ActiveSupport
end
end
end
+
+ # Exclude entries from source directory
+ def exclude_from(source, excludes)
+ Dir.entries(source).reject { |f| excludes.include?(f) }
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index e2f536ef1e..2ca4b51efa 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -36,13 +36,13 @@ module ActiveSupport
end
def write_entry(key, entry, options) # :nodoc:
- retval = super
- if options[:raw] && local_cache && retval
+ if options[:raw] && local_cache
raw_entry = Entry.new(entry.value.to_s)
raw_entry.expires_at = entry.expires_at
- local_cache.write_entry(key, raw_entry, options)
+ super(key, raw_entry, options)
+ else
+ super
end
- retval
end
end
@@ -96,16 +96,14 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- instrument_multi(:read, names, options) do
- keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}]
- raw_values = @data.get_multi(keys_to_names.keys, :raw => true)
- values = {}
- raw_values.each do |key, value|
- entry = deserialize_entry(value)
- values[keys_to_names[key]] = entry.value unless entry.expired?
- end
- values
+ 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|
+ entry = deserialize_entry(value)
+ values[keys_to_names[key]] = entry.value unless entry.expired?
end
+ values
end
# Increment a cached value. This method uses the memcached incr atomic
@@ -115,11 +113,10 @@ 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)
+ rescue_error_with nil do
+ @data.incr(normalize_key(name, options), amount)
+ end
end
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
end
# Decrement a cached value. This method uses the memcached decr atomic
@@ -129,20 +126,16 @@ 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)
+ rescue_error_with nil do
+ @data.decr(normalize_key(name, options), amount)
+ end
end
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
end
# Clear the entire cache on all memcached servers. This method should
# be used with care when shared cache is being used.
def clear(options = nil)
- @data.flush_all
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
+ rescue_error_with(nil) { @data.flush_all }
end
# Get the statistics from the memcached servers.
@@ -153,10 +146,7 @@ module ActiveSupport
protected
# Read an entry from the cache.
def read_entry(key, options) # :nodoc:
- deserialize_entry(@data.get(escape_key(key), options))
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
+ rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) }
end
# Write an entry to the cache.
@@ -168,18 +158,14 @@ 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)
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- false
+ rescue_error_with false do
+ @data.send(method, key, value, expires_in, options)
+ end
end
# Delete an entry from the cache.
def delete_entry(key, options) # :nodoc:
- @data.delete(escape_key(key))
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- false
+ rescue_error_with(false) { @data.delete(key) }
end
private
@@ -187,22 +173,35 @@ 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
entry.is_a?(Entry) ? entry : Entry.new(entry)
- else
- nil
end
end
+
+ def rescue_error_with(fallback)
+ yield
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
+ fallback
+ 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/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index d521061004..1c678dc2af 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -79,24 +79,28 @@ 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
@@ -120,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` instead.
+ 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 d43fde03a9..904d3f0eb0 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -71,7 +71,7 @@ module ActiveSupport
# 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 }
+ mattr_accessor(:halt_and_display_warning_on_return_false, instance_writer: false) { true }
# Runs the callbacks for the given event.
#
@@ -295,6 +295,13 @@ module ActiveSupport
class Callback #:nodoc:#
def self.build(chain, filter, kind, options)
+ if filter.is_a?(String)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing string to define callback is deprecated and will be removed
+ in Rails 5.1 without replacement.
+ MSG
+ end
+
new chain.name, filter, kind, options, chain.config
end
@@ -564,18 +571,18 @@ module ActiveSupport
# Install a callback for the given event.
#
- # set_callback :save, :before, :before_meth
- # set_callback :save, :after, :after_meth, if: :condition
+ # set_callback :save, :before, :before_method
+ # set_callback :save, :after, :after_method, if: :condition
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
#
# The second argument indicates whether the callback is to be run +:before+,
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
# means the first example above can also be written as:
#
- # set_callback :save, :before_meth
+ # set_callback :save, :before_method
#
# The callback can be specified as a symbol naming an instance method; as a
- # proc, lambda, or block; as a string to be instance evaluated; or as an
+ # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
# object that responds to a certain method determined by the <tt>:scope</tt>
# argument to +define_callbacks+.
#
@@ -735,7 +742,7 @@ module ActiveSupport
options = names.extract_options!
names.each do |name|
- class_attribute "_#{name}_callbacks"
+ class_attribute "_#{name}_callbacks", instance_writer: false
set_callbacks name, CallbackChain.new(name, options)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -775,7 +782,7 @@ module ActiveSupport
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.
+ Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1.
To explicitly halt the callback chain, please use `throw :abort` instead.
MSG
end
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
index ca48164c54..89e63aefd4 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -6,12 +6,6 @@ module ActiveSupport
# A share/exclusive lock, otherwise known as a read/write lock.
#
# https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
- #--
- # Note that a pending Exclusive lock attempt does not block incoming
- # Share requests (i.e., we are "read-preferring"). That seems
- # consistent with the behavior of "loose" upgrades, but may be the
- # wrong choice otherwise: it nominally reduces the possibility of
- # deadlock by risking starvation instead.
class ShareLock
include MonitorMixin
@@ -48,17 +42,11 @@ module ActiveSupport
def start_exclusive(purpose: nil, compatible: [], no_wait: false)
synchronize do
unless @exclusive_thread == Thread.current
- if busy?(purpose)
+ if busy_for_exclusive?(purpose)
return false if no_wait
- loose_shares = @sharing.delete(Thread.current)
- @waiting[Thread.current] = compatible if loose_shares
-
- begin
- @cv.wait_while { busy?(purpose) }
- ensure
- @waiting.delete Thread.current
- @sharing[Thread.current] = loose_shares if loose_shares
+ yield_shares(purpose: purpose, compatible: compatible, block_share: true) do
+ @cv.wait_while { busy_for_exclusive?(purpose) }
end
end
@exclusive_thread = Thread.current
@@ -71,13 +59,19 @@ module ActiveSupport
# Relinquish the exclusive lock. Must only be called by the thread
# that called start_exclusive (and currently holds the lock).
- def stop_exclusive
+ def stop_exclusive(compatible: [])
synchronize do
raise "invalid unlock" if @exclusive_thread != Thread.current
@exclusive_depth -= 1
if @exclusive_depth == 0
@exclusive_thread = nil
+
+ if eligible_waiters?(compatible)
+ yield_shares(compatible: compatible, block_share: true) do
+ @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) }
+ end
+ end
@cv.broadcast
end
end
@@ -85,8 +79,16 @@ module ActiveSupport
def start_sharing
synchronize do
- if @exclusive_thread && @exclusive_thread != Thread.current
+ if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current
+ # We already hold a lock; nothing to wait for
+ elsif @waiting[Thread.current]
+ # We're nested inside a +yield_shares+ call: we'll resume as
+ # soon as there isn't an exclusive lock in our way
@cv.wait_while { @exclusive_thread }
+ else
+ # This is an initial / outermost share call: any outstanding
+ # requests for an exclusive lock get to go first
+ @cv.wait_while { busy_for_sharing?(false) }
end
@sharing[Thread.current] += 1
end
@@ -109,12 +111,12 @@ module ActiveSupport
# the block.
#
# See +start_exclusive+ for other options.
- def exclusive(purpose: nil, compatible: [], no_wait: false)
+ def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false)
if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
begin
yield
ensure
- stop_exclusive
+ stop_exclusive(compatible: after_compatible)
end
end
end
@@ -129,14 +131,56 @@ module ActiveSupport
end
end
+ # Temporarily give up all held Share locks while executing the
+ # supplied block, allowing any +compatible+ exclusive lock request
+ # to proceed.
+ def yield_shares(purpose: nil, compatible: [], block_share: false)
+ loose_shares = previous_wait = nil
+ synchronize do
+ if loose_shares = @sharing.delete(Thread.current)
+ if previous_wait = @waiting[Thread.current]
+ purpose = nil unless purpose == previous_wait[0]
+ compatible &= previous_wait[1]
+ end
+ compatible |= [false] unless block_share
+ @waiting[Thread.current] = [purpose, compatible]
+ end
+
+ @cv.broadcast
+ end
+
+ begin
+ yield
+ ensure
+ synchronize do
+ @cv.wait_while { @exclusive_thread && @exclusive_thread != Thread.current }
+
+ if previous_wait
+ @waiting[Thread.current] = previous_wait
+ else
+ @waiting.delete Thread.current
+ end
+ @sharing[Thread.current] = loose_shares if loose_shares
+ end
+ end
+ end
+
private
# Must be called within synchronize
- def busy?(purpose)
- (@exclusive_thread && @exclusive_thread != Thread.current) ||
- @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } ||
+ def busy_for_exclusive?(purpose)
+ busy_for_sharing?(purpose) ||
@sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
end
+
+ def busy_for_sharing?(purpose)
+ (@exclusive_thread && @exclusive_thread != Thread.current) ||
+ @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) }
+ end
+
+ def eligible_waiters?(compatible)
+ @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } }
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 3177d8498e..37d833887a 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -73,4 +73,18 @@ class Array
def forty_two
self[41]
end
+
+ # Equal to <tt>self[-3]</tt>.
+ #
+ # %w( a b c d e ).third_to_last # => "c"
+ def third_to_last
+ self[-3]
+ end
+
+ # Equal to <tt>self[-2]</tt>.
+ #
+ # %w( a b c d e ).second_to_last # => "d"
+ def second_to_last
+ self[-2]
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 87ae052eb0..ea9d85f6e3 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -89,28 +89,19 @@ class Array
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
def split(value = nil)
+ arr = self.dup
+ result = []
if block_given?
- inject([[]]) do |results, element|
- if yield(element)
- results << []
- else
- results.last << element
- end
-
- results
+ while (idx = arr.index { |i| yield i })
+ result << arr.shift(idx)
+ arr.shift
end
else
- results, arr = [[]], self.dup
- until arr.empty?
- if (idx = arr.index(value))
- results.last.concat(arr.shift(idx))
- arr.shift
- results << []
- else
- results.last.concat(arr.shift(arr.size))
- end
+ while (idx = arr.index(value))
+ result << arr.shift(idx)
+ arr.shift
end
- results
end
+ result << arr
end
end
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 22fc7ecf92..074e2eabf8 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -3,10 +3,8 @@ require 'bigdecimal/util'
module ActiveSupport
module BigDecimalWithDefaultFormat #:nodoc:
- DEFAULT_STRING_FORMAT = 'F'
-
- def to_s(format = nil)
- super(format || DEFAULT_STRING_FORMAT)
+ def to_s(format = 'F')
+ super(format)
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..1d8c33b43e 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|
@@ -25,8 +26,6 @@ class Class
# Returns an array with the direct children of +self+.
#
- # Integer.subclasses # => [Fixnum, Bignum]
- #
# class Foo; end
# class Bar < Foo; end
# class Baz < Bar; end
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index ed8bca77ac..9a6d7bb415 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -80,6 +80,7 @@ class Date
#
# date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
def to_time(form = :local)
+ raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form)
::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 e079af594d..6206546672 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/try'
+
module DateAndTime
module Calculations
DAYS_INTO_WEEK = {
@@ -51,6 +53,11 @@ module DateAndTime
WEEKEND_DAYS.include?(wday)
end
+ # Returns true if the date/time does not fall on a Saturday or Sunday.
+ def on_weekday?
+ !WEEKEND_DAYS.include?(wday)
+ end
+
# Returns a new date/time the specified number of days ago.
def days_ago(days)
advance(:days => -days)
@@ -287,6 +294,11 @@ module DateAndTime
end
alias :at_end_of_year :end_of_year
+ # Returns a Range representing the whole day of the current date/time.
+ def all_day
+ beginning_of_day..end_of_day
+ end
+
# Returns a Range representing the whole week of the current date/time.
# Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
def all_week(start_day = Date.beginning_of_week)
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
new file mode 100644
index 0000000000..19e596a144
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -0,0 +1,18 @@
+require 'active_support/core_ext/module/attribute_accessors'
+
+module DateAndTime
+ module Compatibility
+ # If true, +to_time+ preserves the timezone offset of receiver.
+ #
+ # NOTE: With Ruby 2.4+ the default for +to_time+ changed from
+ # converting to the local system time, to preserving the offset
+ # of the receiver. For backwards compatibility we're overriding
+ # this behavior, but new apps will have an initializer that sets
+ # this to true, because the new behavior is preferred.
+ mattr_accessor(:preserve_timezone, instance_writer: false) { false }
+
+ def to_time
+ preserve_timezone ? getlocal(utc_offset) : getlocal
+ end
+ end
+end
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 d29a8db5cf..e2432c8f8a 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
@@ -5,7 +5,7 @@ module DateAndTime
#
# Time.zone = 'Hawaii' # => 'Hawaii'
# 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
+ # 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
# instead of the operating system's time zone.
@@ -14,7 +14,7 @@ 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
- # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00: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
time = acts_like?(:time) ? self : nil
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index bcb228b09a..86177488c0 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,5 +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/compatibility'
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/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 95617fb8c2..9e89a33491 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -28,6 +28,13 @@ class DateTime
end_of_day.to_i - to_i
end
+ # Returns the fraction of a second as a +Rational+
+ #
+ # DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2)
+ def subsec
+ sec_fraction
+ end
+
# Returns a new DateTime where one or more of the elements have been changed
# according to the +options+ parameter. The time options (<tt>:hour</tt>,
# <tt>:min</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is
@@ -143,14 +150,32 @@ class DateTime
end
alias :at_end_of_minute :end_of_minute
- # Adjusts DateTime to UTC by adding its offset value; offset is set to 0.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
+ def localtime(utc_offset = nil)
+ utc = new_offset(0)
+
+ Time.utc(
+ utc.year, utc.month, utc.day,
+ utc.hour, utc.min, utc.sec + utc.sec_fraction
+ ).getlocal(utc_offset)
+ end
+ alias_method :getlocal, :localtime
+
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
#
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
- # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
+ # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC
def utc
- new_offset(0)
+ utc = new_offset(0)
+
+ Time.utc(
+ utc.year, utc.month, utc.day,
+ utc.hour, utc.min, utc.sec + utc.sec_fraction
+ )
end
+ alias_method :getgm, :utc
alias_method :getutc, :utc
+ alias_method :gmtime, :utc
# Returns +true+ if <tt>offset == 0</tt>.
def utc?
@@ -165,13 +190,10 @@ class DateTime
# Layers additional behavior on DateTime#<=> so that Time and
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
def <=>(other)
- if other.kind_of?(Infinity)
- super
- elsif other.respond_to? :to_datetime
+ if other.respond_to? :to_datetime
super other.to_datetime rescue nil
else
- nil
+ super
end
end
-
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
new file mode 100644
index 0000000000..03e4a2adfa
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/date_and_time/compatibility'
+
+class DateTime
+ prepend DateAndTime::Compatibility
+end
diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb
deleted file mode 100644
index c39f358395..0000000000
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-require 'date'
-require 'active_support/core_ext/date_and_time/zones'
-
-class DateTime
- include DateAndTime::Zones
-end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index fc7531d088..8ebe758078 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -17,11 +17,12 @@ module Enumerable
# The default sum of an empty list is zero. You can override this default:
#
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
- def sum(identity = 0, &block)
+ def sum(identity = nil, &block)
if block_given?
map(&block).sum(identity)
else
- inject { |sum, element| sum + element } || identity
+ sum = identity ? inject(identity, :+) : inject(:+)
+ sum || identity || 0
end
end
@@ -33,7 +34,9 @@ module Enumerable
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
def index_by
if block_given?
- Hash[map { |elem| [yield(elem), elem] }]
+ result = {}
+ each { |elem| result[yield(elem)] = elem }
+ result
else
to_enum(:index_by) { size if respond_to?(:size) }
end
@@ -91,15 +94,36 @@ end
class Range #:nodoc:
# Optimize range sum to use arithmetic progression if a block is not given and
# we have a range of numeric values.
- def sum(identity = 0)
+ def sum(identity = nil)
if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
super
else
actual_last = exclude_end? ? (last - 1) : last
if actual_last >= first
- (actual_last - first + 1) * (actual_last + first) / 2
+ sum = identity || 0
+ sum + (actual_last - first + 1) * (actual_last + first) / 2
else
- identity
+ identity || 0
+ end
+ end
+ end
+end
+
+# Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
+#
+# We tried shimming it to attempt the fast native method, rescue TypeError,
+# and fall back to the compatible implementation, but that's much slower than
+# just calling the compat method in the first place.
+if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
+ class Array
+ alias :orig_sum :sum
+
+ def sum(init = nil, &block) #:nodoc:
+ if init.is_a?(Numeric) || first.is_a?(Numeric)
+ init ||= 0
+ orig_sum(init, &block)
+ else
+ super
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb
index 5dc9a05ec7..62ea579c65 100644
--- a/activesupport/lib/active_support/core_ext/hash/compact.rb
+++ b/activesupport/lib/active_support/core_ext/hash/compact.rb
@@ -1,9 +1,9 @@
class Hash
# Returns a hash with non +nil+ values.
#
- # hash = { a: true, b: false, c: nil}
- # hash.compact # => { a: true, b: false}
- # hash # => { a: true, b: false, c: nil}
+ # hash = { a: true, b: false, c: nil }
+ # hash.compact # => { a: true, b: false }
+ # hash # => { a: true, b: false, c: nil }
# { c: nil }.compact # => {}
def compact
self.select { |_, value| !value.nil? }
@@ -11,9 +11,9 @@ class Hash
# Replaces current hash with non +nil+ values.
#
- # hash = { a: true, b: false, c: nil}
- # hash.compact! # => { a: true, b: false}
- # hash # => { a: true, b: false}
+ # hash = { a: true, b: false, c: nil }
+ # hash.compact! # => { a: true, b: false }
+ # hash # => { a: true, b: false }
def compact!
self.reject! { |_, value| value.nil? }
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 8594d9bf2e..2fc514cfce 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -31,7 +31,7 @@ class Hash
# with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
# callable can add nodes by using <tt>options[:builder]</tt>.
#
- # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
+ # {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml
# # => "<b>foo</b>"
#
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
@@ -55,8 +55,7 @@ class Hash
#
# XML_TYPE_NAMES = {
# "Symbol" => "symbol",
- # "Fixnum" => "integer",
- # "Bignum" => "integer",
+ # "Integer" => "integer",
# "BigDecimal" => "decimal",
# "Float" => "float",
# "TrueClass" => "boolean",
@@ -138,6 +137,8 @@ end
module ActiveSupport
class XMLConverter # :nodoc:
+ # Raised if the XML contains attributes with type="yaml" or
+ # type="symbol". Read Hash#from_xml for more details.
class DisallowedType < StandardError
def initialize(type)
super "Disallowed type attribute: #{type.inspect}"
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 8b2366c4b3..1bfa18aeee 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -11,7 +11,7 @@ class Hash
# hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
def transform_keys
return enum_for(:transform_keys) { size } unless block_given?
- result = self.class.new
+ result = {}
each_key do |key|
result[yield(key)] = self[key]
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb
index bf72caa058..18bcc01fa4 100644
--- a/activesupport/lib/active_support/core_ext/kernel/concern.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/module/concerning'
module Kernel
+ module_function
+
# A shortcut to define a toplevel concern, not within a module.
#
# See Module::Concerning for more.
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 8afc258df8..d0197af95f 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,4 +1,6 @@
module Kernel
+ module_function
+
# Sets $VERBOSE to nil for the duration of the block and back to its original
# value afterwards.
#
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index e333b26133..edfc8296fe 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -3,9 +3,12 @@ module ActiveSupport
def load(source)
super(source)
rescue ArgumentError, NameError => exc
- if exc.message.match(%r|undefined class/module (.+)|)
+ if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|)
# try loading the class/module
- $1.constantize
+ loaded = $1.constantize
+
+ raise unless $1 == loaded.name
+
# if it is an IO we need to go back to read the object
source.rewind if source.respond_to?(:rewind)
retry
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index b4efff8b24..ef038331c2 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors_per_thread'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/concerning'
require 'active_support/core_ext/module/delegation'
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 bf175a8a70..567ac825e9 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
@@ -27,7 +27,7 @@ class Module
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
#
# module HairColors
- # mattr_writer :hair_colors, instance_reader: false
+ # mattr_reader :hair_colors, instance_reader: false
# end
#
# class Person
@@ -40,7 +40,7 @@ class Module
# Also, you can pass a block to set up the attribute with a default value.
#
# module HairColors
- # cattr_reader :hair_colors do
+ # mattr_reader :hair_colors do
# [:brown, :black, :blonde, :red]
# end
# end
@@ -49,7 +49,7 @@ class Module
# include HairColors
# end
#
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
@@ -105,7 +105,7 @@ class Module
#
# Also, you can pass a block to set up the attribute with a default value.
#
- # class HairColors
+ # module HairColors
# mattr_writer :hair_colors do
# [:brown, :black, :blonde, :red]
# end
@@ -150,8 +150,8 @@ class Module
# include HairColors
# end
#
- # Person.hair_colors = [:brown, :black, :blonde, :red]
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # HairColors.hair_colors = [:brown, :black, :blonde, :red]
+ # HairColors.hair_colors # => [:brown, :black, :blonde, :red]
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
#
# If a subclass changes the value then that would also change the value for
@@ -161,8 +161,8 @@ class Module
# class Male < Person
# end
#
- # Male.hair_colors << :blue
- # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
+ # Male.new.hair_colors << :blue
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
#
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
new file mode 100644
index 0000000000..0b3d18301e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -0,0 +1,141 @@
+require 'active_support/core_ext/array/extract_options'
+
+# Extends the module object with class/module and instance accessors for
+# class/module attributes, just like the native attr* accessors for instance
+# attributes, but does so on a per-thread basis.
+#
+# So the values are scoped within the Thread.current space under the class name
+# of the module.
+class Module
+ # Defines a per-thread class attribute and creates class and instance reader methods.
+ # The underlying per-thread class variable is set to +nil+, if it is not previously defined.
+ #
+ # module Current
+ # thread_mattr_reader :user
+ # end
+ #
+ # Current.user # => nil
+ # Thread.current[:attr_Current_user] = "DHH"
+ # Current.user # => "DHH"
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # module Foo
+ # thread_mattr_reader :"1_Badname"
+ # end
+ # # => 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>.
+ #
+ # class Current
+ # thread_mattr_reader :user, instance_reader: false
+ # end
+ #
+ # Current.new.user # => NoMethodError
+ def thread_mattr_reader(*syms)
+ options = syms.extract_options!
+
+ syms.each do |sym|
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def self.#{sym}
+ Thread.current[:"attr_#{name}_#{sym}"]
+ end
+ EOS
+
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{sym}
+ Thread.current[:"attr_#{name}_#{sym}"]
+ end
+ EOS
+ end
+ end
+ end
+ alias :thread_cattr_reader :thread_mattr_reader
+
+ # Defines a per-thread class attribute and creates a class and instance writer methods to
+ # allow assignment to the attribute.
+ #
+ # module Current
+ # thread_mattr_writer :user
+ # end
+ #
+ # Current.user = "DHH"
+ # Thread.current[:attr_Current_user] # => "DHH"
+ #
+ # If you want to opt out the instance writer method, pass
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # class Current
+ # thread_mattr_writer :user, instance_writer: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ def thread_mattr_writer(*syms)
+ options = syms.extract_options!
+ syms.each do |sym|
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def self.#{sym}=(obj)
+ Thread.current[:"attr_#{name}_#{sym}"] = obj
+ end
+ EOS
+
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{sym}=(obj)
+ Thread.current[:"attr_#{name}_#{sym}"] = obj
+ end
+ EOS
+ end
+ end
+ end
+ alias :thread_cattr_writer :thread_mattr_writer
+
+ # Defines both class and instance accessors for class attributes.
+ #
+ # class Account
+ # thread_mattr_accessor :user
+ # end
+ #
+ # Account.user = "DHH"
+ # Account.user # => "DHH"
+ # Account.new.user # => "DHH"
+ #
+ # If a subclass changes the value, the parent class' value is not changed.
+ # Similarly, if the parent class changes the value, the value of subclasses
+ # is not changed.
+ #
+ # class Customer < Account
+ # end
+ #
+ # Customer.user = "Rafael"
+ # Customer.user # => "Rafael"
+ # Account.user # => "DHH"
+ #
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ #
+ # class Current
+ # thread_mattr_accessor :user, instance_writer: false, instance_reader: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ # Current.new.user # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # class Current
+ # mattr_accessor :user, instance_accessor: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ # Current.new.user # => NoMethodError
+ def thread_mattr_accessor(*syms, &blk)
+ thread_mattr_reader(*syms, &blk)
+ thread_mattr_writer(*syms, &blk)
+ end
+ alias :thread_cattr_accessor :thread_mattr_accessor
+end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 0d46248582..7f968d10b5 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -148,15 +148,11 @@ class Module
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
#
# The target method must be public, otherwise it will raise +NoMethodError+.
- #
- def delegate(*methods)
- options = methods.pop
- unless options.is_a?(Hash) && to = options[:to]
+ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
+ unless to
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
end
- prefix, allow_nil = options.values_at(:prefix, :allow_nil)
-
if prefix == true && to =~ /^[^a-z_]/
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end
@@ -215,4 +211,68 @@ class Module
module_eval(method_def, file, line)
end
end
+
+ # When building decorators, a common pattern may emerge:
+ #
+ # class Partition
+ # def initialize(first_event)
+ # @events = [ first_event ]
+ # end
+ #
+ # def people
+ # if @events.first.detail.people.any?
+ # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ # else
+ # @events.collect(&:creator).uniq
+ # end
+ # end
+ #
+ # private
+ # def respond_to_missing?(name, include_private = false)
+ # @events.respond_to?(name, include_private)
+ # end
+ #
+ # def method_missing(method, *args, &block)
+ # @events.send(method, *args, &block)
+ # end
+ # end
+ #
+ # With `Module#delegate_missing_to`, the above is condensed to:
+ #
+ # class Partition
+ # delegate_missing_to :@events
+ #
+ # def initialize(first_event)
+ # @events = [ first_event ]
+ # end
+ #
+ # def people
+ # if @events.first.detail.people.any?
+ # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ # else
+ # @events.collect(&:creator).uniq
+ # end
+ # end
+ # end
+ #
+ # The target can be anything callable withing the object. E.g. instance
+ # variables, methods, constants ant the likes.
+ def delegate_missing_to(target)
+ target = target.to_s
+ target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
+
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def respond_to_missing?(name, include_private = false)
+ #{target}.respond_to?(name, include_private)
+ end
+
+ def method_missing(method, *args, &block)
+ if #{target}.respond_to?(method)
+ #{target}.public_send(method, *args, &block)
+ else
+ super
+ end
+ end
+ RUBY
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index 56d670fbe8..f3f2e7f5fc 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -13,8 +13,8 @@ class Module
#
# class MyLib::Deprecator
# def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
- # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
- # Kernel.warn message
+ # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
+ # Kernel.warn message
# end
# end
def deprecate(*method_names)
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index f1d26ef28f..fa692e1b0e 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -57,6 +57,10 @@ class Module
end
def local_constants #:nodoc:
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Module#local_constants is deprecated and will be removed in Rails 5.1.
+ Use Module#constants(false) instead.
+ MSG
constants(false)
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
index 65525013db..3ea39d4267 100644
--- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb
+++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
@@ -3,13 +3,16 @@ require 'active_support/core_ext/string/inflections'
#--
# Allows code reuse in the methods below without polluting Module.
#++
-module QualifiedConstUtils
- def self.raise_if_absolute(path)
- raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
- end
- def self.names(path)
- path.split('::')
+module ActiveSupport
+ module QualifiedConstUtils
+ def self.raise_if_absolute(path)
+ raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
+ end
+
+ def self.names(path)
+ path.split('::')
+ end
end
end
@@ -24,9 +27,14 @@ end
#++
class Module
def qualified_const_defined?(path, search_parents=true)
- QualifiedConstUtils.raise_if_absolute(path)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Module#qualified_const_defined? is deprecated in favour of the builtin
+ Module#const_defined? and will be removed in Rails 5.1.
+ MESSAGE
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
+ ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
+
+ ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name|
return unless mod.const_defined?(name, search_parents)
mod.const_get(name)
end
@@ -34,19 +42,29 @@ class Module
end
def qualified_const_get(path)
- QualifiedConstUtils.raise_if_absolute(path)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Module#qualified_const_get is deprecated in favour of the builtin
+ Module#const_get and will be removed in Rails 5.1.
+ MESSAGE
+
+ ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
+ ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name|
mod.const_get(name)
end
end
def qualified_const_set(path, value)
- QualifiedConstUtils.raise_if_absolute(path)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Module#qualified_const_set is deprecated in favour of the builtin
+ Module#const_set and will be removed in Rails 5.1.
+ MESSAGE
+
+ ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
const_name = path.demodulize
mod_name = path.deconstantize
- mod = mod_name.empty? ? self : qualified_const_get(mod_name)
+ mod = mod_name.empty? ? self : const_get(mod_name)
mod.const_set(const_name, value)
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 9a3651f29a..6586a351f8 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/number_helper'
+require 'active_support/core_ext/module/deprecation'
module ActiveSupport::NumericWithFormat
@@ -14,70 +15,72 @@ module ActiveSupport::NumericWithFormat
# ==== Examples
#
# Phone Numbers:
- # 5551234.to_s(:phone) # => 555-1234
- # 1235551234.to_s(:phone) # => 123-555-1234
- # 1235551234.to_s(:phone, area_code: true) # => (123) 555-1234
- # 1235551234.to_s(:phone, delimiter: ' ') # => 123 555 1234
- # 1235551234.to_s(:phone, area_code: true, extension: 555) # => (123) 555-1234 x 555
- # 1235551234.to_s(:phone, country_code: 1) # => +1-123-555-1234
+ # 5551234.to_s(:phone) # => "555-1234"
+ # 1235551234.to_s(:phone) # => "123-555-1234"
+ # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234"
+ # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234"
+ # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
+ # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234"
# 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
- # # => +1.123.555.1234 x 1343
+ # # => "+1.123.555.1234 x 1343"
#
# Currency:
- # 1234567890.50.to_s(:currency) # => $1,234,567,890.50
- # 1234567890.506.to_s(:currency) # => $1,234,567,890.51
- # 1234567890.506.to_s(:currency, precision: 3) # => $1,234,567,890.506
- # 1234567890.506.to_s(:currency, locale: :fr) # => 1 234 567 890,51 €
+ # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
+ # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
+ # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
+ # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €"
# -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
- # # => ($1,234,567,890.50)
+ # # => "($1,234,567,890.50)"
# 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '')
- # # => &pound;1234567890,50
+ # # => "&pound;1234567890,50"
# 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
- # # => 1234567890,50 &pound;
+ # # => "1234567890,50 &pound;"
#
# Percentage:
- # 100.to_s(:percentage) # => 100.000%
- # 100.to_s(:percentage, precision: 0) # => 100%
- # 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.000 %
+ # 100.to_s(:percentage) # => "100.000%"
+ # 100.to_s(:percentage, precision: 0) # => "100%"
+ # 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.000 %"
#
# Delimited:
- # 12345678.to_s(:delimited) # => 12,345,678
- # 12345678.05.to_s(:delimited) # => 12,345,678.05
- # 12345678.to_s(:delimited, delimiter: '.') # => 12.345.678
- # 12345678.to_s(:delimited, delimiter: ',') # => 12,345,678
- # 12345678.05.to_s(:delimited, separator: ' ') # => 12,345,678 05
- # 12345678.05.to_s(:delimited, locale: :fr) # => 12 345 678,05
+ # 12345678.to_s(:delimited) # => "12,345,678"
+ # 12345678.05.to_s(:delimited) # => "12,345,678.05"
+ # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
+ # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
+ # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
+ # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
# 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
- # # => 98 765 432,98
+ # # => "98 765 432,98"
#
# Rounded:
- # 111.2345.to_s(:rounded) # => 111.235
- # 111.2345.to_s(:rounded, precision: 2) # => 111.23
- # 13.to_s(:rounded, precision: 5) # => 13.00000
- # 389.32314.to_s(:rounded, precision: 0) # => 389
- # 111.2345.to_s(:rounded, significant: true) # => 111
- # 111.2345.to_s(:rounded, precision: 1, significant: true) # => 100
- # 13.to_s(:rounded, precision: 5, significant: true) # => 13.000
- # 111.234.to_s(:rounded, locale: :fr) # => 111,234
+ # 111.2345.to_s(:rounded) # => "111.235"
+ # 111.2345.to_s(:rounded, precision: 2) # => "111.23"
+ # 13.to_s(:rounded, precision: 5) # => "13.00000"
+ # 389.32314.to_s(:rounded, precision: 0) # => "389"
+ # 111.2345.to_s(:rounded, significant: true) # => "111"
+ # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
+ # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
+ # 111.234.to_s(:rounded, locale: :fr) # => "111,234"
# 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
- # # => 13
- # 389.32314.to_s(:rounded, precision: 4, significant: true) # => 389.3
+ # # => "13"
+ # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
# 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
- # # => 1.111,23
+ # # => "1.111,23"
#
# Human-friendly size in Bytes:
- # 123.to_s(:human_size) # => 123 Bytes
- # 1234.to_s(:human_size) # => 1.21 KB
- # 12345.to_s(:human_size) # => 12.1 KB
- # 1234567.to_s(:human_size) # => 1.18 MB
- # 1234567890.to_s(:human_size) # => 1.15 GB
- # 1234567890123.to_s(:human_size) # => 1.12 TB
- # 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
+ # 123.to_s(:human_size) # => "123 Bytes"
+ # 1234.to_s(:human_size) # => "1.21 KB"
+ # 12345.to_s(:human_size) # => "12.1 KB"
+ # 1234567.to_s(:human_size) # => "1.18 MB"
+ # 1234567890.to_s(:human_size) # => "1.15 GB"
+ # 1234567890123.to_s(:human_size) # => "1.12 TB"
+ # 1234567890123456.to_s(:human_size) # => "1.1 PB"
+ # 1234567890123456789.to_s(:human_size) # => "1.07 EB"
+ # 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.1228 TB"
# 524288000.to_s(:human_size, precision: 5) # => "500 MB"
#
@@ -117,7 +120,11 @@ module ActiveSupport::NumericWithFormat
when :human_size
return ActiveSupport::NumberHelper.number_to_human_size(self, options)
else
- super
+ if is_a?(Float) || format.is_a?(Symbol)
+ super()
+ else
+ super
+ end
end
end
@@ -127,6 +134,12 @@ module ActiveSupport::NumericWithFormat
deprecate to_formatted_s: :to_s
end
-[Fixnum, Bignum, Float, BigDecimal].each do |klass|
- klass.prepend(ActiveSupport::NumericWithFormat)
+# Ruby 2.4+ unifies Fixnum & Bignum into Integer.
+if Integer == Fixnum
+ Integer.prepend ActiveSupport::NumericWithFormat
+else
+ Fixnum.prepend ActiveSupport::NumericWithFormat
+ Bignum.prepend ActiveSupport::NumericWithFormat
end
+Float.prepend ActiveSupport::NumericWithFormat
+BigDecimal.prepend ActiveSupport::NumericWithFormat
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 6c4a975495..c6ece22f8d 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -25,17 +25,17 @@ class Numeric
# Returns a Duration instance matching the number of minutes provided.
#
- # 2.minutes # => 120 seconds
+ # 2.minutes # => 2 minutes
def minutes
- ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]])
+ ActiveSupport::Duration.new(self * 60, [[:minutes, self]])
end
alias :minute :minutes
# Returns a Duration instance matching the number of hours provided.
#
- # 2.hours # => 7_200 seconds
+ # 2.hours # => 2 hours
def hours
- ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]])
+ ActiveSupport::Duration.new(self * 3600, [[:hours, self]])
end
alias :hour :hours
@@ -49,17 +49,17 @@ class Numeric
# Returns a Duration instance matching the number of weeks provided.
#
- # 2.weeks # => 14 days
+ # 2.weeks # => 2 weeks
def weeks
- ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
+ ActiveSupport::Duration.new(self * 7.days, [[:weeks, self]])
end
alias :week :weeks
# Returns a Duration instance matching the number of fortnights provided.
#
- # 2.fortnights # => 28 days
+ # 2.fortnights # => 4 weeks
def fortnights
- ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]])
+ ActiveSupport::Duration.new(self * 2.weeks, [[:weeks, self * 2]])
end
alias :fortnight :fortnights
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 039c50a4a2..cb74bad73e 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -112,7 +112,10 @@ class String
#
# @return [true, false]
def blank?
- BLANK_RE === self
+ # The regexp that matches blank strings is expensive. For the case of empty
+ # strings we can speed up this method (~3.5x) with an empty? call. The
+ # penalty for the rest of strings is marginal.
+ empty? || BLANK_RE === self
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index befa5aee21..9bc5ee65ba 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -70,7 +70,7 @@ class Numeric
# Numbers are not duplicable:
#
# 3.duplicable? # => false
- # 3.dup # => TypeError: can't dup Fixnum
+ # 3.dup # => TypeError: can't dup Integer
def duplicable?
false
end
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 0db787010c..d49b2fbe54 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -197,3 +197,9 @@ class Process::Status #:nodoc:
{ :exitstatus => exitstatus, :pid => pid }
end
end
+
+class Exception
+ def as_json(options = nil)
+ to_s
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 8c16d95b62..3b6d9da216 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -99,7 +99,7 @@ class Object
#
# "a".try!(:upcase) # => "A"
# nil.try!(:upcase) # => nil
- # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
+ # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
end
class Delegator
diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb
index 83eced50bf..965436c23a 100644
--- a/activesupport/lib/active_support/core_ext/range/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/range/conversions.rb
@@ -1,34 +1,31 @@
-class Range
+module ActiveSupport::RangeWithFormat
RANGE_FORMATS = {
:db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
}
# Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
#
- # This method is aliased to <tt>to_s</tt>.
- #
# range = (1..100) # => 1..100
#
- # range.to_formatted_s # => "1..100"
# range.to_s # => "1..100"
- #
- # range.to_formatted_s(:db) # => "BETWEEN '1' AND '100'"
# range.to_s(:db) # => "BETWEEN '1' AND '100'"
#
- # == Adding your own range formats to to_formatted_s
+ # == Adding your own range formats to to_s
# You can add your own formats to the Range::RANGE_FORMATS hash.
# Use the format name as the hash key and a Proc instance.
#
# # config/initializers/range_formats.rb
# Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
- def to_formatted_s(format = :default)
+ def to_s(format = :default)
if formatter = RANGE_FORMATS[format]
formatter.call(first, last)
else
- to_default_s
+ super()
end
end
alias_method :to_default_s, :to_s
- alias_method :to_s, :to_formatted_s
+ alias_method :to_formatted_s, :to_s
end
+
+Range.prepend(ActiveSupport::RangeWithFormat)
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index ebd0dd3fc7..213a91aa7a 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,5 +1,5 @@
class String
- # If you pass a single Fixnum, returns a substring of one character at that
+ # If you pass a single integer, returns a substring of one character at that
# position. The first character of the string is at position 0, the next at
# position 1, and so on. If a range is supplied, a substring containing
# characters at offsets given by the range is returned. In both cases, if an
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index fd79a40e31..946976c5e9 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -18,7 +18,8 @@ class String
# "12/13/2012".to_time # => ArgumentError: argument out of range
def to_time(form = :local)
parts = Date._parse(self, false)
- return if parts.empty?
+ used_keys = %i(year mon mday hour min sec sec_fraction offset)
+ return if (parts.keys & used_keys).empty?
now = Time.now
time = Time.new(
@@ -31,7 +32,7 @@ class String
parts.fetch(:offset, form == :utc ? 0 : nil)
)
- form == :utc ? time.utc : time.getlocal
+ form == :utc ? time.utc : time.to_time
end
# Converts a string to a Date value.
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index cc71b8155d..7277f51076 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -222,6 +222,15 @@ class String
ActiveSupport::Inflector.humanize(self, options)
end
+ # Converts just the first character to uppercase.
+ #
+ # 'what a Lovely Day'.upcase_first # => "What a Lovely Day"
+ # 'w'.upcase_first # => "W"
+ # ''.upcase_first # => ""
+ def upcase_first
+ ActiveSupport::Inflector.upcase_first(self)
+ end
+
# Creates a foreign key name from a class name.
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
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 510fa48189..005ad93b08 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -5,7 +5,6 @@ class ERB
module Util
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
- HTML_ESCAPE_REGEXP = /[&"'><]/
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
@@ -37,7 +36,7 @@ class ERB
if s.html_safe?
s
else
- ActiveSupport::Multibyte::Unicode.tidy_bytes(s).gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
+ CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
end
end
module_function :unwrapped_html_escape
@@ -142,6 +141,7 @@ module ActiveSupport #:nodoc:
alias_method :original_concat, :concat
private :original_concat
+ # Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers.
class SafeConcatError < StandardError
def initialize
super 'Could not concatenate to the buffer because it is not html safe.'
@@ -171,7 +171,7 @@ module ActiveSupport #:nodoc:
original_concat(value)
end
- def initialize(*)
+ def initialize(str = '')
@html_safe = true
super
end
@@ -243,15 +243,14 @@ module ActiveSupport #:nodoc:
private
def html_escape_interpolated_argument(arg)
- (!html_safe? || arg.html_safe?) ? arg :
- arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE)
+ (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
end
end
end
class String
# Marks a string as trusted safe. It will be inserted into HTML with no
- # additional escaping performed. It is your responsibilty to ensure that the
+ # additional escaping performed. It is your responsibility to ensure that the
# string contains no malicious content. This method is equivalent to the
# `raw` helper in views. It is recommended that you use `sanitize` instead of
# this method. It should never be called on user input.
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index 72c3234630..0bce632222 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/calculations'
+require 'active_support/core_ext/time/compatibility'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/time/zones'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 82e003fc3b..e81b48ab26 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -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
@@ -67,6 +73,13 @@ class Time
end_of_day.to_i - to_i
end
+ # Returns the fraction of a second as a +Rational+
+ #
+ # Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2)
+ def sec_fraction
+ subsec
+ end
+
# Returns a new Time where one or more of the elements have been changed according
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
# <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
@@ -156,7 +169,6 @@ class Time
# Returns a new Time representing the start of the day (0:00)
def beginning_of_day
- #(self - seconds_since_midnight).change(usec: 0)
change(:hour => 0)
end
alias :midnight :beginning_of_day
@@ -215,11 +227,6 @@ class Time
end
alias :at_end_of_minute :end_of_minute
- # Returns a Range representing the whole day of the current time.
- def all_day
- beginning_of_day..end_of_day
- end
-
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
new file mode 100644
index 0000000000..945319461b
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/date_and_time/compatibility'
+
+class Time
+ prepend DateAndTime::Compatibility
+end
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 877dc84ec8..7a60f94996 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -40,7 +40,23 @@ class Time
Thread.current[:time_zone] = find_zone!(time_zone)
end
- # Allows override of <tt>Time.zone</tt> locally inside supplied block; resets <tt>Time.zone</tt> to existing value when done.
+ # Allows override of <tt>Time.zone</tt> locally inside supplied block;
+ # resets <tt>Time.zone</tt> to existing value when done.
+ #
+ # class ApplicationController < ActionController::Base
+ # around_action :set_time_zone
+ #
+ # private
+ #
+ # def set_time_zone
+ # Time.use_zone(current_user.timezone) { yield }
+ # end
+ # end
+ #
+ # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
+ # objects that have already been created, e.g. any model timestamp
+ # attributes that have been read before the block will remain in
+ # the application's default timezone.
def use_zone(time_zone)
new_zone = find_zone!(time_zone)
begin
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index af18ff746f..57f6286de3 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -88,15 +88,6 @@ module ActiveSupport #:nodoc:
mattr_accessor :explicitly_unloadable_constants
self.explicitly_unloadable_constants = []
- # The logger is used for generating information on the action run-time
- # (including benchmarking) if available. Can be set to nil for no logging.
- # Compatible with both Ruby's own Logger and Log4r loggers.
- mattr_accessor :logger
-
- # Set to +true+ to enable logging of const_missing and file loads.
- mattr_accessor :log_activity
- self.log_activity = false
-
# The WatchStack keeps a stack of the modules being watched as files are
# loaded. If a file in the process of being loaded (parent.rb) triggers the
# load of another file (child.rb) the stack will ensure that child.rb
@@ -143,11 +134,11 @@ module ActiveSupport #:nodoc:
next unless mod.is_a?(Module)
# Get a list of the constants that were added
- new_constants = mod.local_constants - original_constants
+ new_constants = mod.constants(false) - original_constants
- # self[namespace] returns an Array of the constants that are being evaluated
+ # @stack[namespace] returns an Array of the constants that are being evaluated
# for that namespace. For instance, if parent.rb requires child.rb, the first
- # element of self[Object] will be an Array of the constants that were present
+ # element of @stack[Object] will be an Array of the constants that were present
# before parent.rb was required. The second element will be an Array of the
# constants that were present before child.rb was required.
@stack[namespace].each do |namespace_constants|
@@ -171,7 +162,7 @@ module ActiveSupport #:nodoc:
@watching << namespaces.map do |namespace|
module_name = Dependencies.to_constant_name(namespace)
original_constants = Dependencies.qualified_const_defined?(module_name) ?
- Inflector.constantize(module_name).local_constants : []
+ Inflector.constantize(module_name).constants(false) : []
@stack[module_name] << original_constants
module_name
@@ -262,7 +253,7 @@ module ActiveSupport #:nodoc:
end
def load_dependency(file)
- if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching?
+ if Dependencies.load? && Dependencies.constant_watch_stack.watching?
Dependencies.new_constants_in(Object) { yield }
else
yield
@@ -352,7 +343,6 @@ module ActiveSupport #:nodoc:
end
def clear
- log_call
Dependencies.unload_interlock do
loaded.clear
loading.clear
@@ -361,7 +351,6 @@ module ActiveSupport #:nodoc:
end
def require_or_load(file_name, const_path = nil)
- log_call file_name, const_path
file_name = $` if file_name =~ /\.rb\z/
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
@@ -377,8 +366,6 @@ module ActiveSupport #:nodoc:
begin
if load?
- log "loading #{file_name}"
-
# Enable warnings if this file has not been loaded before and
# warnings_on_first_load is set.
load_args = ["#{file_name}.rb"]
@@ -390,7 +377,6 @@ module ActiveSupport #:nodoc:
enable_warnings { result = load_file(*load_args) }
end
else
- log "requiring #{file_name}"
result = require file_name
end
rescue Exception
@@ -483,7 +469,6 @@ module ActiveSupport #:nodoc:
# set of names that the file at +path+ may define. See
# +loadable_constants_for_path+ for more details.
def load_file(path, const_paths = loadable_constants_for_path(path))
- log_call path, const_paths
const_paths = [const_paths].compact unless const_paths.is_a? Array
parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object }
@@ -494,7 +479,6 @@ module ActiveSupport #:nodoc:
autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
autoloaded_constants.uniq!
- log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
result
end
@@ -508,8 +492,6 @@ module ActiveSupport #:nodoc:
# it is not possible to load the constant into from_mod, try its parent
# module using +const_missing+.
def load_missing_constant(from_mod, const_name)
- log_call from_mod, const_name
-
unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod)
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
end
@@ -673,25 +655,20 @@ module ActiveSupport #:nodoc:
# exception, any new constants are regarded as being only partially defined
# and will be removed immediately.
def new_constants_in(*descs)
- log_call(*descs)
-
constant_watch_stack.watch_namespaces(descs)
- aborting = true
+ success = false
begin
yield # Now yield to the code that is to define new constants.
- aborting = false
+ success = true
ensure
new_constants = constant_watch_stack.new_constants
- log "New constants: #{new_constants * ', '}"
- return new_constants unless aborting
+ return new_constants if success
- log "Error during loading, removing partially loaded constants "
- new_constants.each { |c| remove_constant(c) }.clear
+ # Remove partially loaded constants.
+ new_constants.each { |c| remove_constant(c) }
end
-
- []
end
# Convert the provided const desc to a qualified constant name (as a string).
@@ -738,8 +715,6 @@ module ActiveSupport #:nodoc:
parent = constantize(parent_name)
end
- log "removing constant #{const}"
-
# In an autoloaded user.rb like this
#
# autoload :Foo, 'foo'
@@ -760,7 +735,7 @@ module ActiveSupport #:nodoc:
begin
constantized = parent.const_get(to_remove, false)
rescue NameError
- log "the constant #{const} is not reachable anymore, skipping"
+ # The constant is no longer reachable, just skip it.
return
else
constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
@@ -770,27 +745,9 @@ module ActiveSupport #:nodoc:
begin
parent.instance_eval { remove_const to_remove }
rescue NameError
- log "the constant #{const} is not reachable anymore, skipping"
+ # The constant is no longer reachable, just skip it.
end
end
-
- protected
- def log_call(*args)
- if log_activity?
- arg_str = args.collect(&:inspect) * ', '
- /in `([a-z_\?\!]+)'/ =~ caller(1).first
- selector = $1 || '<unknown>'
- log "called #{selector}(#{arg_str})"
- end
- end
-
- def log(msg)
- logger.debug "Dependencies: #{msg}" if log_activity?
- end
-
- def log_activity?
- logger && log_activity
- end
end
end
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
index fbeb904684..f1865ca2f8 100644
--- a/activesupport/lib/active_support/dependencies/interlock.rb
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -8,25 +8,23 @@ module ActiveSupport #:nodoc:
end
def loading
- @lock.exclusive(purpose: :load, compatible: [:load]) do
+ @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do
yield
end
end
def unloading
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do
yield
end
end
- # Attempt to obtain an "unloading" (exclusive) lock. If possible,
- # execute the supplied block while holding the lock. If there is
- # concurrent activity, return immediately (without executing the
- # block) instead of waiting.
- def attempt_unloading
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do
- yield
- end
+ def start_unloading
+ @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload])
+ end
+
+ def done_unloading
+ @lock.stop_exclusive(compatible: [:load, :unload])
end
def start_running
@@ -42,6 +40,12 @@ module ActiveSupport #:nodoc:
yield
end
end
+
+ def permit_concurrent_loads
+ @lock.yield_shares(compatible: [:load]) do
+ yield
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 46e9996d59..b581710067 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -32,7 +32,7 @@ module ActiveSupport
# and the second is a library name
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = '5.0', gem_name = 'Rails')
+ def initialize(deprecation_horizon = '5.2', gem_name = 'Rails')
self.gem_name = gem_name
self.deprecation_horizon = deprecation_horizon
# By default, warnings are not silenced and debugging is off.
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 28d2d78643..dc24e2d0e1 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,6 +1,8 @@
require "active_support/notifications"
module ActiveSupport
+ # Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>.
+ # You would set <tt>:raise</tt>, as a behaviour to raise errors and proactively report exceptions from deprecations.
class DeprecationException < StandardError
end
@@ -9,7 +11,7 @@ module ActiveSupport
DEFAULT_BEHAVIORS = {
raise: ->(message, callstack) {
e = DeprecationException.new(message)
- e.set_backtrace(callstack)
+ e.set_backtrace(callstack.map(&:to_s))
raise e
},
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 32fe8025fe..f5ea6669ce 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -21,15 +21,15 @@ module ActiveSupport
# # => [:aaa, :bbb, :ccc]
#
# Fred.aaa
- # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.0. (called from irb_binding at (irb):10)
+ # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
# # => nil
#
# 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)
+ # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11)
# # => nil
#
# Fred.ccc
- # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.0 (use Bar#ccc instead). (called from irb_binding at (irb):12)
+ # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12)
# # => nil
#
# Passing in a custom deprecator:
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index 6f0ad445fc..0cb2d4d22e 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -80,7 +80,7 @@ module ActiveSupport
# example.old_request.to_s
# # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of
# @request.to_s
- # (Bactrace information…)
+ # (Backtrace information…)
# "special_request"
#
# example.request.to_s
@@ -118,7 +118,7 @@ module ActiveSupport
#
# PLANETS.map { |planet| planet.capitalize }
# # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
- # (Bactrace information…)
+ # (Backtrace information…)
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index f89fc0fe14..de5b233679 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -1,3 +1,5 @@
+require 'rbconfig'
+
module ActiveSupport
class Deprecation
module Reporting
@@ -63,7 +65,6 @@ module ActiveSupport
def deprecation_message(callstack, message = nil)
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
- message += '.' unless message =~ /\.$/
"DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
end
@@ -81,17 +82,17 @@ module ActiveSupport
def extract_callstack(callstack)
return _extract_callstack(callstack) if callstack.first.is_a? String
- rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
offending_line = callstack.find { |frame|
- frame.absolute_path && !frame.absolute_path.start_with?(rails_gem_root)
+ frame.absolute_path && !ignored_callstack(frame.absolute_path)
} || callstack.first
+
[offending_line.path, offending_line.lineno, offending_line.label]
end
def _extract_callstack(callstack)
warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE
- rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
- offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first
+ offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first
+
if offending_line
if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
md.captures
@@ -100,6 +101,12 @@ module ActiveSupport
end
end
end
+
+ RAILS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/"
+
+ def ignored_callstack(path)
+ path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG['rubylibdir'])
+ end
end
end
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index c63b61e97a..47d09f4f5a 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -9,6 +9,9 @@ module ActiveSupport
class Duration
attr_accessor :value, :parts
+ autoload :ISO8601Parser, 'active_support/duration/iso8601_parser'
+ autoload :ISO8601Serializer, 'active_support/duration/iso8601_serializer'
+
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts
end
@@ -117,7 +120,7 @@ module ActiveSupport
def inspect #:nodoc:
parts.
reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
- sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
+ sort_by {|unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit)}.
map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
to_sentence(locale: ::I18n.default_locale)
end
@@ -130,6 +133,23 @@ module ActiveSupport
@value.respond_to?(method, include_private)
end
+ # Creates a new Duration from string formatted according to ISO 8601 Duration.
+ #
+ # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
+ # This method allows negative parts to be present in pattern.
+ # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
+ def self.parse(iso8601duration)
+ parts = ISO8601Parser.new(iso8601duration).parse!
+ time = ::Time.current
+ new(time.advance(parts) - time, parts)
+ end
+
+ # Build ISO 8601 Duration string for this duration.
+ # The +precision+ parameter can be used to limit seconds' precision of duration.
+ def iso8601(precision: nil)
+ ISO8601Serializer.new(self, precision: precision).serialize
+ end
+
delegate :<=>, to: :value
protected
@@ -139,6 +159,10 @@ module ActiveSupport
if t.acts_like?(:time) || t.acts_like?(:date)
if type == :seconds
t.since(sign * number)
+ elsif type == :minutes
+ t.since(sign * number * 60)
+ elsif type == :hours
+ t.since(sign * number * 3600)
else
t.advance(type => sign * number)
end
diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb
new file mode 100644
index 0000000000..07af58ad99
--- /dev/null
+++ b/activesupport/lib/active_support/duration/iso8601_parser.rb
@@ -0,0 +1,122 @@
+require 'strscan'
+
+module ActiveSupport
+ class Duration
+ # Parses a string formatted according to ISO 8601 Duration into the hash.
+ #
+ # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
+ #
+ # This parser allows negative parts to be present in pattern.
+ class ISO8601Parser # :nodoc:
+ class ParsingError < ::ArgumentError; end
+
+ PERIOD_OR_COMMA = /\.|,/
+ PERIOD = '.'.freeze
+ COMMA = ','.freeze
+
+ SIGN_MARKER = /\A\-|\+|/
+ DATE_MARKER = /P/
+ TIME_MARKER = /T/
+ DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
+ TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/
+
+ DATE_TO_PART = { 'Y' => :years, 'M' => :months, 'W' => :weeks, 'D' => :days }
+ TIME_TO_PART = { 'H' => :hours, 'M' => :minutes, 'S' => :seconds }
+
+ DATE_COMPONENTS = [:years, :months, :days]
+ TIME_COMPONENTS = [:hours, :minutes, :seconds]
+
+ attr_reader :parts, :scanner
+ attr_accessor :mode, :sign
+
+ def initialize(string)
+ @scanner = StringScanner.new(string)
+ @parts = {}
+ @mode = :start
+ @sign = 1
+ end
+
+ def parse!
+ while !finished?
+ case mode
+ when :start
+ if scan(SIGN_MARKER)
+ self.sign = (scanner.matched == '-') ? -1 : 1
+ self.mode = :sign
+ else
+ raise_parsing_error
+ end
+
+ when :sign
+ if scan(DATE_MARKER)
+ self.mode = :date
+ else
+ raise_parsing_error
+ end
+
+ when :date
+ if scan(TIME_MARKER)
+ self.mode = :time
+ elsif scan(DATE_COMPONENT)
+ parts[DATE_TO_PART[scanner[2]]] = number * sign
+ else
+ raise_parsing_error
+ end
+
+ when :time
+ if scan(TIME_COMPONENT)
+ parts[TIME_TO_PART[scanner[2]]] = number * sign
+ else
+ raise_parsing_error
+ end
+
+ end
+ end
+
+ validate!
+ parts
+ end
+
+ private
+
+ def finished?
+ scanner.eos?
+ end
+
+ # Parses number which can be a float with either comma or period.
+ def number
+ scanner[1] =~ PERIOD_OR_COMMA ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
+ end
+
+ def scan(pattern)
+ scanner.scan(pattern)
+ end
+
+ def raise_parsing_error(reason = nil)
+ raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
+ end
+
+ # Checks for various semantic errors as stated in ISO 8601 standard.
+ def validate!
+ raise_parsing_error('is empty duration') if parts.empty?
+
+ # Mixing any of Y, M, D with W is invalid.
+ if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
+ raise_parsing_error('mixing weeks with other date parts not allowed')
+ end
+
+ # Specifying an empty T part is invalid.
+ if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
+ raise_parsing_error('time part marker is present but time part is empty')
+ end
+
+ fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
+ unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
+ raise_parsing_error '(only last part can be fractional)'
+ end
+
+ return true
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb
new file mode 100644
index 0000000000..05c6a083a9
--- /dev/null
+++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb
@@ -0,0 +1,51 @@
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/transform_values'
+
+module ActiveSupport
+ class Duration
+ # Serializes duration to string according to ISO 8601 Duration format.
+ class ISO8601Serializer
+ def initialize(duration, precision: nil)
+ @duration = duration
+ @precision = precision
+ end
+
+ # Builds and returns output string.
+ def serialize
+ output = 'P'
+ parts, sign = normalize
+ output << "#{parts[:years]}Y" if parts.key?(:years)
+ output << "#{parts[:months]}M" if parts.key?(:months)
+ output << "#{parts[:weeks]}W" if parts.key?(:weeks)
+ output << "#{parts[:days]}D" if parts.key?(:days)
+ time = ''
+ time << "#{parts[:hours]}H" if parts.key?(:hours)
+ time << "#{parts[:minutes]}M" if parts.key?(:minutes)
+ if parts.key?(:seconds)
+ time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
+ end
+ output << "T#{time}" if time.present?
+ "#{sign}#{output}"
+ end
+
+ private
+
+ # Return pair of duration's parts and whole duration sign.
+ # Parts are summarized (as they can become repetitive due to addition, etc).
+ # Zero parts are removed as not significant.
+ # If all parts are negative it will negate all of them and return minus as a sign.
+ def normalize
+ parts = @duration.parts.each_with_object(Hash.new(0)) do |(k,v),p|
+ p[k] += v unless v.zero?
+ end
+ # If all parts are negative - let's make a negative duration
+ sign = ''
+ if parts.values.all? { |v| v < 0 }
+ sign = '-'
+ parts.transform_values!(&:-@)
+ end
+ [parts, sign]
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
new file mode 100644
index 0000000000..21fdf7bb80
--- /dev/null
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -0,0 +1,155 @@
+require 'set'
+require 'pathname'
+require 'concurrent/atomic/atomic_boolean'
+
+module ActiveSupport
+ class EventedFileUpdateChecker #: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?
+ # Loading listen triggers warnings. These are originated by a legit
+ # usage of attr_* macros for private attributes, but adds a lot of noise
+ # to our test suite. Thus, we lazy load it and disable warnings locally.
+ silence_warnings do
+ begin
+ require 'listen'
+ rescue LoadError => e
+ raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
+ end
+ end
+ 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?
+ yield if block_given?
+ 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
+ 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 ascendant_of?(lcsp, 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|
+ ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
+ end
+ end
+
+ # Array#- preserves order.
+ dirs - descendants
+ end
+
+ private
+
+ def ascendant_of?(base, other)
+ base != other && other.ascend do |ascendant|
+ break true if base == ascendant
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
new file mode 100644
index 0000000000..00c5745a25
--- /dev/null
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -0,0 +1,117 @@
+require 'active_support/callbacks'
+
+module ActiveSupport
+ class ExecutionWrapper
+ include ActiveSupport::Callbacks
+
+ Null = Object.new # :nodoc:
+ def Null.complete! # :nodoc:
+ end
+
+ define_callbacks :run
+ define_callbacks :complete
+
+ def self.to_run(*args, &block)
+ set_callback(:run, *args, &block)
+ end
+
+ def self.to_complete(*args, &block)
+ set_callback(:complete, *args, &block)
+ end
+
+ # Register an object to be invoked during both the +run+ and
+ # +complete+ steps.
+ #
+ # +hook.complete+ will be passed the value returned from +hook.run+,
+ # and will only be invoked if +run+ has previously been called.
+ # (Mostly, this means it won't be invoked if an exception occurs in
+ # a preceding +to_run+ block; all ordinary +to_complete+ blocks are
+ # invoked in that situation.)
+ def self.register_hook(hook, outer: false)
+ if outer
+ run_args = [prepend: true]
+ complete_args = [:after]
+ else
+ run_args = complete_args = []
+ end
+
+ to_run(*run_args) do
+ hook_state[hook] = hook.run
+ end
+ to_complete(*complete_args) do
+ if hook_state.key?(hook)
+ hook.complete hook_state[hook]
+ end
+ end
+ end
+
+ # Run this execution.
+ #
+ # Returns an instance, whose +complete!+ method *must* be invoked
+ # after the work has been performed.
+ #
+ # Where possible, prefer +wrap+.
+ def self.run!
+ if active?
+ Null
+ else
+ new.tap do |instance|
+ success = nil
+ begin
+ instance.run!
+ success = true
+ ensure
+ instance.complete! unless success
+ end
+ end
+ end
+ end
+
+ # Perform the work in the supplied block as an execution.
+ def self.wrap
+ return yield if active?
+
+ instance = run!
+ begin
+ yield
+ ensure
+ instance.complete!
+ end
+ end
+
+ class << self # :nodoc:
+ attr_accessor :active
+ end
+
+ def self.inherited(other) # :nodoc:
+ super
+ other.active = Concurrent::Hash.new
+ end
+
+ self.active = Concurrent::Hash.new
+
+ def self.active? # :nodoc:
+ @active[Thread.current]
+ end
+
+ def run! # :nodoc:
+ self.class.active[Thread.current] = true
+ run_callbacks(:run)
+ end
+
+ # Complete this in-flight execution. This method *must* be called
+ # exactly once on the result of any call to +run!+.
+ #
+ # Where possible, prefer +wrap+.
+ def complete!
+ run_callbacks(:complete)
+ ensure
+ self.class.active.delete Thread.current
+ end
+
+ private
+ def hook_state
+ @_hook_state ||= {}
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/executor.rb b/activesupport/lib/active_support/executor.rb
new file mode 100644
index 0000000000..602fb11a44
--- /dev/null
+++ b/activesupport/lib/active_support/executor.rb
@@ -0,0 +1,6 @@
+require 'active_support/execution_wrapper'
+
+module ActiveSupport
+ class Executor < ExecutionWrapper
+ end
+end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 78b627c286..b5667b6ac8 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/time/calculations'
+
module ActiveSupport
# FileUpdateChecker specifies the API used by Rails to watch files
# and control reloading. The API depends on four methods:
@@ -23,7 +25,7 @@ module ActiveSupport
# I18n.reload!
# end
#
- # ActionDispatch::Reloader.to_prepare do
+ # ActiveSupport::Reloader.to_prepare do
# i18n_reloader.execute_if_updated
# end
class FileUpdateChecker
@@ -35,7 +37,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
@@ -81,6 +83,7 @@ module ActiveSupport
# Execute the block given if updated.
def execute_if_updated
if updated?
+ yield if block_given?
execute
true
else
@@ -111,7 +114,24 @@ module ActiveSupport
# reloading is not triggered.
def max_mtime(paths)
time_now = Time.now
- paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max
+ max_mtime = nil
+
+ # Time comparisons are performed with #compare_without_coercion because
+ # AS redefines these operators in a way that is much slower and does not
+ # bring any benefit in this particular code.
+ #
+ # Read t1.compare_without_coercion(t2) < 0 as t1 < t2.
+ paths.each do |path|
+ mtime = File.mtime(path)
+
+ next if time_now.compare_without_coercion(mtime) < 0
+
+ if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0
+ max_mtime = mtime
+ end
+ end
+
+ max_mtime
end
def compile_glob(hash)
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index ece68bbcb6..74f2d8dd4b 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -6,7 +6,7 @@ module ActiveSupport
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
PRE = "alpha"
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 4ff35a45a1..03770a197c 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -68,8 +68,10 @@ module ActiveSupport
end
end
- def default(key = nil)
- if key.is_a?(Symbol) && include?(key = key.to_s)
+ def default(*args)
+ arg_key = args.first
+
+ if include?(key = convert_key(arg_key))
self[key]
else
super
@@ -159,6 +161,20 @@ module ActiveSupport
alias_method :has_key?, :key?
alias_method :member?, :key?
+
+ # Same as <tt>Hash#[]</tt> where the key passed as argument can be
+ # either a string or a symbol:
+ #
+ # counters = ActiveSupport::HashWithIndifferentAccess.new
+ # counters[:foo] = 1
+ #
+ # counters['foo'] # => 1
+ # counters[:foo] # => 1
+ # counters[:zoo] # => nil
+ def [](key)
+ super(convert_key(key))
+ end
+
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
# either a string or a symbol:
#
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 6775eec34b..6cc7c90c12 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
@@ -64,8 +64,8 @@ module I18n
end
app.reloaders << reloader
- ActionDispatch::Reloader.to_prepare do
- reloader.execute_if_updated
+ app.reloader.to_run do
+ reloader.execute_if_updated { require_unload_lock! }
# TODO: remove the following line as soon as the return value of
# callbacks is ignored, that is, returning `false` does not
# display a deprecation warning or halts the callback chain.
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 595b0339cc..f94e12e14f 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -140,6 +140,15 @@ module ActiveSupport
result
end
+ # Converts just the first character to uppercase.
+ #
+ # upcase_first('what a Lovely Day') # => "What a Lovely Day"
+ # upcase_first('w') # => "W"
+ # upcase_first('') # => ""
+ def upcase_first(string)
+ string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ''
+ end
+
# Capitalizes all the words and replaces some characters in the string to
# create a nicer looking title. +titleize+ is meant for creating pretty
# output. It is not used in the Rails internals.
@@ -173,7 +182,7 @@ module ActiveSupport
#
# Singular names are not handled correctly:
#
- # classify('calculus') # => "Calculu"
+ # classify('calculus') # => "Calculus"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index a4563ace8f..c64b7598ee 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -106,6 +106,8 @@ en:
mb: "MB"
gb: "GB"
tb: "TB"
+ pb: "PB"
+ eb: "EB"
# Used in NumberHelper.number_to_human()
decimal_units:
format: "%n %u"
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 33fccdcf95..de48e717b6 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,11 +1,23 @@
-require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/logger_silence'
+require 'active_support/logger_thread_safe_level'
require 'logger'
module ActiveSupport
class Logger < ::Logger
+ include ActiveSupport::LoggerThreadSafeLevel
include LoggerSilence
+ # Returns true if the logger destination matches one of the sources
+ #
+ # logger = Logger.new(STDOUT)
+ # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
+ # # => true
+ def self.logger_outputs_to?(logger, *sources)
+ logdev = logger.instance_variable_get("@logdev")
+ logger_source = logdev.dev if logdev.respond_to?(:dev)
+ sources.any? { |source| source == logger_source }
+ end
+
# Broadcasts logs to multiple loggers.
def self.broadcast(logger) # :nodoc:
Module.new do
@@ -38,12 +50,31 @@ module ActiveSupport
logger.level = level
super(level)
end
+
+ define_method(:local_level=) do |level|
+ logger.local_level = level if logger.respond_to?(:local_level=)
+ super(level) if respond_to?(:local_level=)
+ end
end
end
def initialize(*args)
super
@formatter = SimpleFormatter.new
+ after_initialize if respond_to? :after_initialize
+ end
+
+ def add(severity, message = nil, progname = nil, &block)
+ return true if @logdev.nil? || (severity || UNKNOWN) < level
+ super
+ end
+
+ Logger::Severity.constants.each do |severity|
+ class_eval(<<-EOT, __FILE__, __LINE__ + 1)
+ def #{severity.downcase}? # def debug?
+ Logger::#{severity} >= level # DEBUG >= level
+ end # end
+ EOT
end
# Simple formatter which only displays the message.
diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb
index a8efdef944..3eb8098c77 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -1,8 +1,10 @@
require 'active_support/concern'
+require 'active_support/core_ext/module/attribute_accessors'
+require 'concurrent'
module LoggerSilence
extend ActiveSupport::Concern
-
+
included do
cattr_accessor :silencer
self.silencer = true
@@ -12,13 +14,15 @@ module LoggerSilence
def silence(temporary_level = Logger::ERROR)
if silencer
begin
- old_logger_level, self.level = level, temporary_level
+ old_local_level = local_level
+ self.local_level = temporary_level
+
yield self
ensure
- self.level = old_logger_level
+ self.local_level = old_local_level
end
else
yield self
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb
new file mode 100644
index 0000000000..5fedb5e689
--- /dev/null
+++ b/activesupport/lib/active_support/logger_thread_safe_level.rb
@@ -0,0 +1,31 @@
+require 'active_support/concern'
+
+module ActiveSupport
+ module LoggerThreadSafeLevel # :nodoc:
+ extend ActiveSupport::Concern
+
+ def after_initialize
+ @local_levels = Concurrent::Map.new(initial_capacity: 2)
+ end
+
+ def local_log_id
+ Thread.current.__id__
+ end
+
+ def local_level
+ @local_levels[local_log_id]
+ end
+
+ def local_level=(level)
+ if level
+ @local_levels[local_log_id] = level
+ else
+ @local_levels.delete(local_log_id)
+ end
+ end
+
+ def level
+ local_level || super
+ end
+ end
+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/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 64c5232cf4..4c3deffe6e 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -15,7 +15,7 @@ module ActiveSupport
# In the authentication filter:
#
# id, time = @verifier.verify(cookies[:remember_me])
- # if time < Time.now
+ # if Time.now < time
# self.current_user = User.find(id)
# end
#
@@ -24,6 +24,12 @@ module ActiveSupport
# hash upon initialization:
#
# @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
+ #
+ # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
+ # If you want to use a different hash algorithm, you can change it by providing
+ # `:digest` key as an option while initializing the verifier:
+ #
+ # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
class MessageVerifier
class InvalidSignature < StandardError; end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 586002b03b..72b20fff06 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -87,19 +87,44 @@ module ActiveSupport
pos += 1
previous = codepoints[pos-1]
current = codepoints[pos]
- if (
- # CR X LF
- ( previous == database.boundary[:cr] and current == database.boundary[:lf] ) or
- # L X (L|V|LV|LVT)
- ( database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or
- # (LV|V) X (V|T)
- ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or
- # (LVT|T) X (T)
- ( in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current ) or
- # X Extend
- (database.boundary[:extend] === current)
- )
- else
+
+ should_break =
+ # GB3. CR X LF
+ if previous == database.boundary[:cr] and current == database.boundary[:lf]
+ false
+ # GB4. (Control|CR|LF) ÷
+ elsif previous and in_char_class?(previous, [:control,:cr,:lf])
+ true
+ # GB5. ÷ (Control|CR|LF)
+ elsif in_char_class?(current, [:control,:cr,:lf])
+ true
+ # GB6. L X (L|V|LV|LVT)
+ elsif database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt])
+ false
+ # GB7. (LV|V) X (V|T)
+ elsif in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t])
+ false
+ # GB8. (LVT|T) X (T)
+ elsif in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current
+ false
+ # GB8a. Regional_Indicator X Regional_Indicator
+ elsif database.boundary[:regional_indicator] === previous and database.boundary[:regional_indicator] === current
+ false
+ # GB9. X Extend
+ elsif database.boundary[:extend] === current
+ false
+ # GB9a. X SpacingMark
+ elsif database.boundary[:spacingmark] === current
+ false
+ # GB9b. Prepend X
+ elsif database.boundary[:prepend] === previous
+ false
+ # GB10. Any ÷ Any
+ else
+ true
+ end
+
+ if should_break
unpacked << codepoints[marker..pos-1]
marker = pos
end
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 7798c7ec60..c53f9c1039 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -42,8 +42,8 @@ module ActiveSupport
listeners_for(name).each { |s| s.start(name, id, payload) }
end
- def finish(name, id, payload)
- listeners_for(name).each { |s| s.finish(name, id, payload) }
+ def finish(name, id, payload, listeners = listeners_for(name))
+ listeners.each { |s| s.finish(name, id, payload) }
end
def publish(name, *args)
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 075ddc2382..91f94cb2d7 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -15,14 +15,16 @@ module ActiveSupport
# and publish it. Notice that events get sent even if an error occurs
# in the passed-in block.
def instrument(name, payload={})
- start name, payload
+ # some of the listeners might have state
+ listeners_state = start name, payload
begin
yield payload
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
+ payload[:exception_object] = e
raise e
ensure
- finish name, payload
+ finish_with_state listeners_state, name, payload
end
end
@@ -36,6 +38,10 @@ module ActiveSupport
@notifier.finish name, @id, payload
end
+ def finish_with_state(listeners_state, name, payload)
+ @notifier.finish name, @id, payload, listeners_state
+ end
+
private
def unique_id
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 248521e677..7a49bbb960 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -15,7 +15,7 @@ module ActiveSupport
extend self
- # Formats a +number+ into a US phone number (e.g., (555)
+ # Formats a +number+ into a phone number (US by default e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
@@ -27,19 +27,26 @@ module ActiveSupport
# end of the generated number.
# * <tt>:country_code</tt> - Sets the country code for the phone
# number.
+ # * <tt>:pattern</tt> - Specifies how the number is divided into three
+ # groups with the custom regexp to override the default format.
# ==== Examples
#
- # number_to_phone(5551234) # => 555-1234
- # number_to_phone('5551234') # => 555-1234
- # number_to_phone(1235551234) # => 123-555-1234
- # number_to_phone(1235551234, area_code: true) # => (123) 555-1234
- # number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234
- # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
- # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
- # number_to_phone('123a456') # => 123a456
+ # number_to_phone(5551234) # => "555-1234"
+ # number_to_phone('5551234') # => "555-1234"
+ # number_to_phone(1235551234) # => "123-555-1234"
+ # number_to_phone(1235551234, area_code: true) # => "(123) 555-1234"
+ # number_to_phone(1235551234, delimiter: ' ') # => "123 555 1234"
+ # number_to_phone(1235551234, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
+ # number_to_phone(1235551234, country_code: 1) # => "+1-123-555-1234"
+ # number_to_phone('123a456') # => "123a456"
#
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
- # # => +1.123.555.1234 x 1343
+ # # => "+1.123.555.1234 x 1343"
+ #
+ # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
+ # # => "(755) 6123-4567"
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
+ # # => "133-1234-5678"
def number_to_phone(number, options = {})
NumberToPhoneConverter.convert(number, options)
end
@@ -47,6 +54,14 @@ module ActiveSupport
# Formats a +number+ into a currency string (e.g., $13.65). You
# can customize the format in the +options+ hash.
#
+ # The currency unit and number formatting of the current locale will be used
+ # unless otherwise specified in the provided options. No currency conversion
+ # is performed. If the user is given a way to change their locale, they will
+ # also be able to change the relative value of the currency displayed with
+ # this helper. If your application will ever support multiple locales, you
+ # may want to specify a constant <tt>:locale</tt> option or consider
+ # using a library capable of currency conversion.
+ #
# ==== Options
#
# * <tt>:locale</tt> - Sets the locale to be used for formatting
@@ -70,18 +85,18 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_currency(1234567890.50) # => $1,234,567,890.50
- # number_to_currency(1234567890.506) # => $1,234,567,890.51
- # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
- # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
- # number_to_currency('123a456') # => $123a456
+ # number_to_currency(1234567890.50) # => "$1,234,567,890.50"
+ # number_to_currency(1234567890.506) # => "$1,234,567,890.51"
+ # number_to_currency(1234567890.506, precision: 3) # => "$1,234,567,890.506"
+ # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €"
+ # number_to_currency('123a456') # => "$123a456"
#
# number_to_currency(-1234567890.50, negative_format: '(%u%n)')
- # # => ($1,234,567,890.50)
+ # # => "($1,234,567,890.50)"
# number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '')
- # # => &pound;1234567890,50
+ # # => "&pound;1234567890,50"
# number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
- # # => 1234567890,50 &pound;
+ # # => "1234567890,50 &pound;"
def number_to_currency(number, options = {})
NumberToCurrencyConverter.convert(number, options)
end
@@ -110,15 +125,15 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_percentage(100) # => 100.000%
- # number_to_percentage('98') # => 98.000%
- # 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) # => 1000,000%
- # number_to_percentage(1000, precision: nil) # => 1000%
- # number_to_percentage('98a') # => 98a%
- # number_to_percentage(100, format: '%n %') # => 100.000 %
+ # number_to_percentage(100) # => "100.000%"
+ # number_to_percentage('98') # => "98.000%"
+ # 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) # => "1000,000%"
+ # number_to_percentage(1000, precision: nil) # => "1000%"
+ # number_to_percentage('98a') # => "98a%"
+ # number_to_percentage(100, format: '%n %') # => "100.000 %"
def number_to_percentage(number, options = {})
NumberToPercentageConverter.convert(number, options)
end
@@ -141,19 +156,19 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_delimited(12345678) # => 12,345,678
- # number_to_delimited('123456') # => 123,456
- # number_to_delimited(12345678.05) # => 12,345,678.05
- # number_to_delimited(12345678, delimiter: '.') # => 12.345.678
- # number_to_delimited(12345678, delimiter: ',') # => 12,345,678
- # number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05
- # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05
- # number_to_delimited('112a') # => 112a
+ # number_to_delimited(12345678) # => "12,345,678"
+ # number_to_delimited('123456') # => "123,456"
+ # number_to_delimited(12345678.05) # => "12,345,678.05"
+ # number_to_delimited(12345678, delimiter: '.') # => "12.345.678"
+ # number_to_delimited(12345678, delimiter: ',') # => "12,345,678"
+ # number_to_delimited(12345678.05, separator: ' ') # => "12,345,678 05"
+ # number_to_delimited(12345678.05, locale: :fr) # => "12 345 678,05"
+ # number_to_delimited('112a') # => "112a"
# number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
- # # => 98 765 432,98
+ # # => "98 765 432,98"
# number_to_delimited("123456.78",
# delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/)
- # # => 1,23,456.78
+ # # => "1,23,456.78"
def number_to_delimited(number, options = {})
NumberToDelimitedConverter.convert(number, options)
end
@@ -182,22 +197,22 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_rounded(111.2345) # => 111.235
- # number_to_rounded(111.2345, precision: 2) # => 111.23
- # number_to_rounded(13, precision: 5) # => 13.00000
- # number_to_rounded(389.32314, precision: 0) # => 389
- # number_to_rounded(111.2345, significant: true) # => 111
- # number_to_rounded(111.2345, precision: 1, significant: true) # => 100
- # number_to_rounded(13, precision: 5, significant: true) # => 13.000
- # number_to_rounded(13, precision: nil) # => 13
- # number_to_rounded(111.234, locale: :fr) # => 111,234
+ # number_to_rounded(111.2345) # => "111.235"
+ # number_to_rounded(111.2345, precision: 2) # => "111.23"
+ # number_to_rounded(13, precision: 5) # => "13.00000"
+ # number_to_rounded(389.32314, precision: 0) # => "389"
+ # number_to_rounded(111.2345, significant: true) # => "111"
+ # number_to_rounded(111.2345, precision: 1, significant: true) # => "100"
+ # number_to_rounded(13, precision: 5, significant: true) # => "13.000"
+ # number_to_rounded(13, precision: nil) # => "13"
+ # number_to_rounded(111.234, locale: :fr) # => "111,234"
#
# number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
- # # => 13
+ # # => "13"
#
- # number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3
+ # number_to_rounded(389.32314, precision: 4, significant: true) # => "389.3"
# number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
- # # => 1.111,23
+ # # => "1.111,23"
def number_to_rounded(number, options = {})
NumberToRoundedConverter.convert(number, options)
end
@@ -229,15 +244,17 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_human_size(123) # => 123 Bytes
- # number_to_human_size(1234) # => 1.21 KB
- # number_to_human_size(12345) # => 12.1 KB
- # number_to_human_size(1234567) # => 1.18 MB
- # number_to_human_size(1234567890) # => 1.15 GB
- # number_to_human_size(1234567890123) # => 1.12 TB
- # number_to_human_size(1234567, precision: 2) # => 1.2 MB
- # number_to_human_size(483989, precision: 2) # => 470 KB
- # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
+ # number_to_human_size(123) # => "123 Bytes"
+ # number_to_human_size(1234) # => "1.21 KB"
+ # number_to_human_size(12345) # => "12.1 KB"
+ # number_to_human_size(1234567) # => "1.18 MB"
+ # number_to_human_size(1234567890) # => "1.15 GB"
+ # number_to_human_size(1234567890123) # => "1.12 TB"
+ # number_to_human_size(1234567890123456) # => "1.1 PB"
+ # number_to_human_size(1234567890123456789) # => "1.07 EB"
+ # number_to_human_size(1234567, precision: 2) # => "1.2 MB"
+ # number_to_human_size(483989, precision: 2) # => "470 KB"
+ # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB"
# number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
# number_to_human_size(524288000, precision: 5) # => "500 MB"
def number_to_human_size(number, options = {})
diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
index 7986eb50f0..57f40f33bf 100644
--- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/numeric/inquiry'
+
module ActiveSupport
module NumberHelper
class NumberToCurrencyConverter < NumberConverter # :nodoc:
@@ -7,7 +9,7 @@ module ActiveSupport
number = self.number.to_s.strip
format = options[:format]
- if is_negative?(number)
+ if number.to_f.negative?
format = options[:negative_format]
number = absolute_value(number)
end
@@ -18,10 +20,6 @@ module ActiveSupport
private
- def is_negative?(number)
- number.to_f.phase != 0
- end
-
def absolute_value(number)
number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, '')
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
index 45ae8f1a93..43c5540b6f 100644
--- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -12,7 +12,7 @@ module ActiveSupport
private
def parts
- left, right = number.to_s.split('.')
+ left, right = number.to_s.split('.'.freeze)
left.gsub!(delimiter_pattern) do |digit_to_delimit|
"#{digit_to_delimit}#{options[:delimiter]}"
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
index a4a8690bcd..a83b368b7f 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -1,7 +1,7 @@
module ActiveSupport
module NumberHelper
class NumberToHumanSizeConverter < NumberConverter #:nodoc:
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb]
self.namespace = :human
self.validate_float = true
diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
index af2ee56d91..dee74fa7a6 100644
--- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -18,12 +18,16 @@ module ActiveSupport
end
def convert_with_area_code(number)
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ default_pattern = /(\d{1,3})(\d{3})(\d{4}$)/
+ number.gsub!(regexp_pattern(default_pattern),
+ "(\\1) \\2#{delimiter}\\3")
number
end
def convert_without_area_code(number)
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ default_pattern = /(\d{0,3})(\d{3})(\d{4})$/
+ number.gsub!(regexp_pattern(default_pattern),
+ "\\1#{delimiter}\\2#{delimiter}\\3")
number.slice!(0, 1) if start_with_delimiter?(number)
number
end
@@ -43,6 +47,11 @@ module ActiveSupport
def phone_ext(ext)
ext.blank? ? "" : " x #{ext}"
end
+
+ def regexp_pattern(default_pattern)
+ opts.fetch :pattern, default_pattern
+ end
+
end
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index 981c562551..9fb7dfb779 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -29,9 +29,11 @@ module ActiveSupport
formatted_string =
if BigDecimal === rounded_number && rounded_number.finite?
- s = rounded_number.to_s('F') + '0'*precision
- a, b = s.split('.', 2)
- a + '.' + b[0, precision]
+ s = rounded_number.to_s('F')
+ s << '0'.freeze * precision
+ a, b = s.split('.'.freeze, 2)
+ a << '.'.freeze
+ a << b[0, precision]
else
"%00.#{precision}f" % rounded_number
end
diff --git a/activesupport/lib/active_support/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 4680d5acb7..b1658f0f27 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -5,7 +5,7 @@ YAML.add_builtin_type("omap") do |type, val|
end
module ActiveSupport
- # <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
+ # DEPRECATED: <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
# insertion order.
#
# oh = ActiveSupport::OrderedHash.new
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index 506dd950cb..88e2b12cc7 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -1,4 +1,9 @@
+require 'active_support/core_ext/module/delegation'
+
module ActiveSupport
+ # NOTE: This approach has been deprecated for end-user code in favor of thread_mattr_accessor and friends.
+ # Please use that approach instead.
+ #
# This module is used to encapsulate access to thread local variables.
#
# Instead of polluting the thread locals namespace:
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
new file mode 100644
index 0000000000..5623bdd349
--- /dev/null
+++ b/activesupport/lib/active_support/reloader.rb
@@ -0,0 +1,129 @@
+require 'active_support/execution_wrapper'
+
+module ActiveSupport
+ #--
+ # This class defines several callbacks:
+ #
+ # to_prepare -- Run once at application startup, and also from
+ # +to_run+.
+ #
+ # to_run -- Run before a work run that is reloading. If
+ # +reload_classes_only_on_change+ is true (the default), the class
+ # unload will have already occurred.
+ #
+ # to_complete -- Run after a work run that has reloaded. If
+ # +reload_classes_only_on_change+ is false, the class unload will
+ # have occurred after the work run, but before this callback.
+ #
+ # before_class_unload -- Run immediately before the classes are
+ # unloaded.
+ #
+ # after_class_unload -- Run immediately after the classes are
+ # unloaded.
+ #
+ class Reloader < ExecutionWrapper
+ define_callbacks :prepare
+
+ define_callbacks :class_unload
+
+ def self.to_prepare(*args, &block)
+ set_callback(:prepare, *args, &block)
+ end
+
+ def self.before_class_unload(*args, &block)
+ set_callback(:class_unload, *args, &block)
+ end
+
+ def self.after_class_unload(*args, &block)
+ set_callback(:class_unload, :after, *args, &block)
+ end
+
+ to_run(:after) { self.class.prepare! }
+
+ # Initiate a manual reload
+ def self.reload!
+ executor.wrap do
+ new.tap do |instance|
+ begin
+ instance.run!
+ ensure
+ instance.complete!
+ end
+ end
+ end
+ prepare!
+ end
+
+ def self.run! # :nodoc:
+ if check!
+ super
+ else
+ Null
+ end
+ end
+
+ # Run the supplied block as a work unit, reloading code as needed
+ def self.wrap
+ executor.wrap do
+ super
+ end
+ end
+
+ class_attribute :executor
+ class_attribute :check
+
+ self.executor = Executor
+ self.check = lambda { false }
+
+ def self.check! # :nodoc:
+ @should_reload ||= check.call
+ end
+
+ def self.reloaded! # :nodoc:
+ @should_reload = false
+ end
+
+ def self.prepare! # :nodoc:
+ new.run_callbacks(:prepare)
+ end
+
+ def initialize
+ super
+ @locked = false
+ end
+
+ # Acquire the ActiveSupport::Dependencies::Interlock unload lock,
+ # ensuring it will be released automatically
+ def require_unload_lock!
+ unless @locked
+ ActiveSupport::Dependencies.interlock.start_unloading
+ @locked = true
+ end
+ end
+
+ # Release the unload lock if it has been previously obtained
+ def release_unload_lock!
+ if @locked
+ @locked = false
+ ActiveSupport::Dependencies.interlock.done_unloading
+ end
+ end
+
+ def run! # :nodoc:
+ super
+ release_unload_lock!
+ end
+
+ def class_unload!(&block) # :nodoc:
+ require_unload_lock!
+ run_callbacks(:class_unload, &block)
+ end
+
+ def complete! # :nodoc:
+ super
+ self.class.reloaded!
+ ensure
+ release_unload_lock!
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index fcf5553061..2c05deee41 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -1,7 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/array/extract_options'
module ActiveSupport
# Rescuable module adds support for easier exception handling.
@@ -48,14 +47,12 @@ module ActiveSupport
# end
#
# Exceptions raised inside exception handlers are not propagated up.
- def rescue_from(*klasses, &block)
- options = klasses.extract_options!
-
- unless options.has_key?(:with)
+ def rescue_from(*klasses, with: nil, &block)
+ unless with
if block_given?
- options[:with] = block
+ with = block
else
- raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
+ raise ArgumentError, 'Need a handler. Pass the with: keyword argument or provide a block.'
end
end
@@ -65,55 +62,104 @@ module ActiveSupport
elsif klass.is_a?(String)
klass
else
- raise ArgumentError, "#{klass} is neither an Exception nor a String"
+ raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class"
end
# Put the new handler at the end because the list is read in reverse.
- self.rescue_handlers += [[key, options[:with]]]
+ self.rescue_handlers += [[key, with]]
end
end
- end
- # Tries to rescue the exception by looking up and calling a registered handler.
- def rescue_with_handler(exception)
- if handler = handler_for_rescue(exception)
- handler.arity != 0 ? handler.call(exception) : handler.call
- true # don't rely on the return value of the handler
+ # Matches an exception to a handler based on the exception class.
+ #
+ # If no handler matches the exception, check for a handler matching the
+ # (optional) exception.cause. If no handler matches the exception or its
+ # cause, this returns nil so you can deal with unhandled exceptions.
+ # Be sure to re-raise unhandled exceptions if this is what you expect.
+ #
+ # begin
+ # …
+ # rescue => exception
+ # rescue_with_handler(exception) || raise
+ # end
+ #
+ # Returns the exception if it was handled and nil if it was not.
+ def rescue_with_handler(exception, object: self)
+ if handler = handler_for_rescue(exception, object: object)
+ handler.call exception
+ exception
+ end
end
- end
- def handler_for_rescue(exception)
- # We go from right to left because pairs are pushed onto rescue_handlers
- # as rescue_from declarations are found.
- _, rescuer = self.class.rescue_handlers.reverse.detect do |klass_name, handler|
- # The purpose of allowing strings in rescue_from is to support the
- # declaration of handler associations for exception classes whose
- # definition is yet unknown.
- #
- # Since this loop needs the constants it would be inconsistent to
- # assume they should exist at this point. An early raised exception
- # could trigger some other handler and the array could include
- # precisely a string whose corresponding constant has not yet been
- # seen. This is why we are tolerant to unknown constants.
- #
- # Note that this tolerance only matters if the exception was given as
- # a string, otherwise a NameError will be raised by the interpreter
- # itself when rescue_from CONSTANT is executed.
- klass = self.class.const_get(klass_name) rescue nil
- klass ||= (klass_name.constantize rescue nil)
- klass === exception if klass
+ def handler_for_rescue(exception, object: self) #:nodoc:
+ case rescuer = find_rescue_handler(exception)
+ when Symbol
+ method = object.method(rescuer)
+ if method.arity == 0
+ -> e { method.call }
+ else
+ method
+ end
+ when Proc
+ if rescuer.arity == 0
+ -> e { object.instance_exec(&rescuer) }
+ else
+ -> e { object.instance_exec(e, &rescuer) }
+ end
+ end
end
- case rescuer
- when Symbol
- method(rescuer)
- when Proc
- if rescuer.arity == 0
- Proc.new { instance_exec(&rescuer) }
- else
- Proc.new { |_exception| instance_exec(_exception, &rescuer) }
+ private
+ def find_rescue_handler(exception)
+ if exception
+ # Handlers are in order of declaration but the most recently declared
+ # is the highest priority match, so we search for matching handlers
+ # in reverse.
+ _, handler = rescue_handlers.reverse_each.detect do |class_or_name, _|
+ if klass = constantize_rescue_handler_class(class_or_name)
+ klass === exception
+ end
+ end
+
+ handler || find_rescue_handler(exception.cause)
+ end
end
- end
+
+ def constantize_rescue_handler_class(class_or_name)
+ case class_or_name
+ when String, Symbol
+ begin
+ # Try a lexical lookup first since we support
+ #
+ # class Super
+ # rescue_from 'Error', with: …
+ # end
+ #
+ # class Sub
+ # class Error < StandardError; end
+ # end
+ #
+ # so an Error raised in Sub will hit the 'Error' handler.
+ const_get class_or_name
+ rescue NameError
+ class_or_name.safe_constantize
+ end
+ else
+ class_or_name
+ end
+ end
+ end
+
+ # Delegates to the class method, but uses the instance as the subject for
+ # rescue_from handlers (method calls, instance_exec blocks).
+ def rescue_with_handler(exception)
+ self.class.rescue_with_handler exception, object: self
+ end
+
+ # Internal handler lookup. Delegates to class method. Some libraries call
+ # this directly, so keeping it around for compatibility.
+ def handler_for_rescue(exception) #:nodoc:
+ self.class.handler_for_rescue exception, object: self
end
end
end
diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index 64c4801179..9be8613ada 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -1,3 +1,5 @@
+require 'digest'
+
module ActiveSupport
module SecurityUtils
# Constant time string comparison.
@@ -16,5 +18,10 @@ module ActiveSupport
res == 0
end
module_function :secure_compare
+
+ def variable_size_secure_compare(a, b) # :nodoc:
+ secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b))
+ end
+ module_function :variable_size_secure_compare
end
end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index ae6f00b861..1fc12d0bc1 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -9,7 +9,6 @@ require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
require 'active_support/testing/time_helpers'
require 'active_support/testing/file_fixtures'
-require 'active_support/testing/composite_filter'
require 'active_support/core_ext/kernel/reporting'
module ActiveSupport
@@ -39,15 +38,6 @@ module ActiveSupport
def test_order
ActiveSupport.test_order ||= :random
end
-
- def run(reporter, options = {})
- if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ }
- options[:filter] = \
- Testing::CompositeFilter.new(self, options[:filter], options[:patterns])
- end
-
- super
- end
end
alias_method :method_name, :name
@@ -76,12 +66,20 @@ module ActiveSupport
alias :assert_not_respond_to :refute_respond_to
alias :assert_not_same :refute_same
- # Reveals the intention that the block should not raise any exception.
+
+ # Assertion that the block should not raise an exception.
+ #
+ # Passes if evaluated code in the yielded block raises no exception.
#
# assert_nothing_raised do
- # ...
+ # perform_service(param: 'no_exception')
# end
def assert_nothing_raised(*args)
+ if args.present?
+ ActiveSupport::Deprecation.warn(
+ "Passing arguments to assert_nothing_raised " \
+ "is deprecated and will be removed in Rails 5.1.")
+ end
yield
end
end
diff --git a/activesupport/lib/active_support/testing/composite_filter.rb b/activesupport/lib/active_support/testing/composite_filter.rb
deleted file mode 100644
index bde723e30b..0000000000
--- a/activesupport/lib/active_support/testing/composite_filter.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'method_source'
-
-module ActiveSupport
- module Testing
- class CompositeFilter # :nodoc:
- def initialize(runnable, filter, patterns)
- @runnable = runnable
- @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact
- end
-
- def ===(method)
- @filters.any? { |filter| filter === method }
- end
-
- private
- def derive_regexp(filter)
- filter =~ %r%/(.*)/% ? Regexp.new($1) : filter
- end
-
- def derive_line_filters(patterns)
- patterns.map do |file_and_line|
- file, line = file_and_line.split(':')
- Filter.new(@runnable, file, line) if file
- end
- end
-
- class Filter # :nodoc:
- def initialize(runnable, file, line)
- @runnable, @file = runnable, File.expand_path(file)
- @line = line.to_i if line
- end
-
- def ===(method)
- return unless @runnable.method_defined?(method)
-
- if @line
- test_file, test_range = definition_for(@runnable.instance_method(method))
- test_file == @file && test_range.include?(@line)
- else
- @runnable.instance_method(method).source_location.first == @file
- end
- end
-
- private
- def definition_for(method)
- file, start_line = method.source_location
- end_line = method.source.count("\n") + start_line - 1
-
- return file, start_line..end_line
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 79cc748cf5..b1cec43124 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,6 +1,7 @@
require 'active_support/duration'
require 'active_support/values/time_zone'
require 'active_support/core_ext/object/acts_like'
+require 'active_support/core_ext/date_and_time/compatibility'
module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary
@@ -44,20 +45,21 @@ module ActiveSupport
PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze }
PRECISIONS[0] = '%FT%T'.freeze
- include Comparable
+ include Comparable, DateAndTime::Compatibility
attr_reader :time_zone
def initialize(utc_time, time_zone, local_time = nil, period = nil)
- @utc, @time_zone, @time = utc_time, time_zone, local_time
+ @utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil
+ @time_zone, @time = time_zone, local_time
@period = @utc ? period : get_period_and_ensure_valid_local_time(period)
end
- # Returns a Time or DateTime instance that represents the time in +time_zone+.
+ # Returns a <tt>Time</tt> instance that represents the time in +time_zone+.
def time
@time ||= period.to_local(@utc)
end
- # Returns a Time or DateTime instance that represents the time in UTC.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
def utc
@utc ||= period.to_utc(@time)
end
@@ -77,10 +79,9 @@ module ActiveSupport
utc.in_time_zone(new_zone)
end
- # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your
- # system's <tt>ENV['TZ']</tt> zone.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
def localtime(utc_offset = nil)
- utc.respond_to?(:getlocal) ? utc.getlocal(utc_offset) : utc.to_time.getlocal(utc_offset)
+ utc.getlocal(utc_offset)
end
alias_method :getlocal, :localtime
@@ -401,11 +402,6 @@ module ActiveSupport
utc.to_r
end
- # Returns an instance of Time in the system timezone.
- def to_time
- utc.to_time
- end
-
# Returns an instance of DateTime with the timezone's UTC offset
#
# Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000
@@ -454,7 +450,6 @@ module ActiveSupport
# Ensure proxy class responds to all methods that underlying time instance
# responds to.
def respond_to_missing?(sym, include_priv)
- # consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
return false if sym.to_sym == :acts_like_date?
time.respond_to?(sym, include_priv)
end
@@ -482,7 +477,7 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
- ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec, Rational(time.nsec, 1000))
+ ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec)
end
def duration_of_variable_length?(obj)
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 6404f65612..19420cee5e 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,7 +1,6 @@
require 'tzinfo'
require 'concurrent/map'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/try'
module ActiveSupport
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
@@ -86,7 +85,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",
@@ -184,6 +184,7 @@ module ActiveSupport
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '')
@lazy_zones_map = Concurrent::Map.new
+ @country_zones = Concurrent::Map.new
class << self
# Assumes self represents an offset from UTC in seconds (as returned from
@@ -242,7 +243,18 @@ module ActiveSupport
# A convenience method for returning a collection of TimeZone objects
# for time zones in the USA.
def us_zones
- @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
+ country_zones(:us)
+ end
+
+ # A convenience method for returning a collection of TimeZone objects
+ # for time zones in the country specified by its ISO 3166-1 Alpha2 code.
+ def country_zones(country_code)
+ code = country_code.to_s.upcase
+ @country_zones[code] ||=
+ TZInfo::Country.get(code).zone_identifiers.map do |tz_id|
+ name = MAPPING.key(tz_id)
+ name && self[name]
+ end.compact.sort!
end
private
@@ -266,7 +278,6 @@ module ActiveSupport
@name = name
@utc_offset = utc_offset
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
- @current_period = nil
end
# Returns the offset of this time zone from UTC in seconds.
@@ -274,8 +285,7 @@ module ActiveSupport
if @utc_offset
@utc_offset
else
- @current_period ||= tzinfo.current_period if tzinfo
- @current_period.utc_offset if @current_period
+ tzinfo.current_period.utc_offset if tzinfo && tzinfo.current_period
end
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index df7b081993..99fc26549e 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -32,20 +32,25 @@ module ActiveSupport
"binary" => "base64"
} unless defined?(DEFAULT_ENCODINGS)
- TYPE_NAMES = {
- "Symbol" => "symbol",
- "Fixnum" => "integer",
- "Bignum" => "integer",
- "BigDecimal" => "decimal",
- "Float" => "float",
- "TrueClass" => "boolean",
- "FalseClass" => "boolean",
- "Date" => "date",
- "DateTime" => "dateTime",
- "Time" => "dateTime",
- "Array" => "array",
- "Hash" => "hash"
- } unless defined?(TYPE_NAMES)
+ unless defined?(TYPE_NAMES)
+ TYPE_NAMES = {
+ "Symbol" => "symbol",
+ "Integer" => "integer",
+ "BigDecimal" => "decimal",
+ "Float" => "float",
+ "TrueClass" => "boolean",
+ "FalseClass" => "boolean",
+ "Date" => "date",
+ "DateTime" => "dateTime",
+ "Time" => "dateTime",
+ "Array" => "array",
+ "Hash" => "hash"
+ }
+
+ # No need to map these on Ruby 2.4+
+ TYPE_NAMES["Fixnum"] = "integer" unless Fixnum == Integer
+ TYPE_NAMES["Bignum"] = "integer" unless Bignum == Integer
+ end
FORMATTING = {
"symbol" => Proc.new { |symbol| symbol.to_s },
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 924ed72345..95af5af2c0 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -20,11 +20,9 @@ module ActiveSupport
data = StringIO.new(data || '')
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
silence_warnings { require 'rexml/document' } unless defined?(REXML::Document)
doc = REXML::Document.new(data)