aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support')
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb44
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb51
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb46
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb116
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb37
-rw-r--r--activesupport/lib/active_support/cache/null_store.rb5
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb49
-rw-r--r--activesupport/lib/active_support/callbacks.rb185
-rw-r--r--activesupport/lib/active_support/concern.rb2
-rw-r--r--activesupport/lib/active_support/concurrency/latch.rb22
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb142
-rw-r--r--activesupport/lib/active_support/core_ext/array.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/array/inquiry.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/array/wrap.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb45
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/blank.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb55
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/zones.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/blank.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb56
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/aliasing.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module/anonymous.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb12
-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/concerning.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/module/qualified_const.rb42
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/numeric.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb41
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/inquiry.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb76
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/range/each.rb34
-rw-r--r--activesupport/lib/active_support/core_ext/range/include_range.rb40
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/filters.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/string/strip.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb32
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/uri.rb4
-rw-r--r--activesupport/lib/active_support/dependencies.rb115
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb47
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb12
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb58
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb71
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb15
-rw-r--r--activesupport/lib/active_support/duration.rb28
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb150
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/gem_version.rb4
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb37
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb19
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb41
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb48
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb47
-rw-r--r--activesupport/lib/active_support/json/decoding.rb10
-rw-r--r--activesupport/lib/active_support/json/encoding.rb4
-rw-r--r--activesupport/lib/active_support/key_generator.rb8
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb2
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb6
-rw-r--r--activesupport/lib/active_support/logger.rb8
-rw-r--r--activesupport/lib/active_support/logger_silence.rb5
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb6
-rw-r--r--activesupport/lib/active_support/message_verifier.rb8
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb17
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb11
-rw-r--r--activesupport/lib/active_support/notifications.rb4
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb12
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb9
-rw-r--r--activesupport/lib/active_support/number_helper.rb16
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb4
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb9
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb8
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb6
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb2
-rw-r--r--activesupport/lib/active_support/ordered_options.rb16
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb11
-rw-r--r--activesupport/lib/active_support/rails.rb4
-rw-r--r--activesupport/lib/active_support/railtie.rb9
-rw-r--r--activesupport/lib/active_support/rescuable.rb2
-rw-r--r--activesupport/lib/active_support/subscriber.rb13
-rw-r--r--activesupport/lib/active_support/test_case.rb17
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb26
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb9
-rw-r--r--activesupport/lib/active_support/testing/composite_filter.rb54
-rw-r--r--activesupport/lib/active_support/testing/deprecation.rb17
-rw-r--r--activesupport/lib/active_support/testing/file_fixtures.rb2
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb26
-rw-r--r--activesupport/lib/active_support/testing/method_call_assertions.rb41
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb94
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb99
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin1001806 -> 1068675 bytes
-rw-r--r--activesupport/lib/active_support/xml_mini.rb3
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb11
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb11
132 files changed, 2139 insertions, 943 deletions
diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb
new file mode 100644
index 0000000000..f59ddf5403
--- /dev/null
+++ b/activesupport/lib/active_support/array_inquirer.rb
@@ -0,0 +1,44 @@
+module ActiveSupport
+ # Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check
+ # its string-like contents:
+ #
+ # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
+ #
+ # variants.phone? # => true
+ # variants.tablet? # => true
+ # 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.
+ #
+ # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
+ #
+ # variants.any? # => true
+ # variants.any?(:phone, :tablet) # => true
+ # variants.any?('phone', 'desktop') # => true
+ # variants.any?(:desktop, :watch) # => false
+ def any?(*candidates, &block)
+ if candidates.none?
+ super
+ else
+ candidates.any? do |candidate|
+ include?(candidate.to_sym) || include?(candidate.to_s)
+ end
+ end
+ end
+
+ private
+ def respond_to_missing?(name, include_private = false)
+ name[-1] == '?'
+ end
+
+ def method_missing(name, *args)
+ if name[-1] == '?'
+ any?(name[0..-2])
+ else
+ super
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index d06f22ad5c..e161ec4cca 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -25,7 +25,7 @@ module ActiveSupport
# of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
# These two methods will give you a completely untouched backtrace.
#
- # Inspired by the Quiet Backtrace gem by Thoughtbot.
+ # Inspired by the Quiet Backtrace gem by thoughtbot.
class BacktraceCleaner
def initialize
@filters, @silencers = [], []
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 837974bc85..5011014e96 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -8,6 +8,7 @@ require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/string/strip'
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
@@ -26,7 +27,7 @@ module ActiveSupport
end
class << self
- # Creates a new CacheStore object according to the given options.
+ # Creates a new Store object according to the given options.
#
# If no arguments are passed to this method, then a new
# ActiveSupport::Cache::MemoryStore object will be returned.
@@ -275,15 +276,20 @@ module ActiveSupport
def fetch(name, options = nil)
if block_given?
options = merged_options(options)
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
- cached_entry = find_cached_entry(key, name, options) unless options[:force]
- entry = handle_expired_entry(cached_entry, key, options)
+ instrument(:read, name, options) do |payload|
+ cached_entry = read_entry(key, options) unless options[:force]
+ payload[:super_operation] = :fetch if payload
+ entry = handle_expired_entry(cached_entry, key, options)
- if entry
- get_entry_value(entry, name, options)
- else
- save_block_result_to_cache(name, options) { |_name| yield _name }
+ if entry
+ payload[:hit] = true if payload
+ get_entry_value(entry, name, options)
+ else
+ payload[:hit] = false if payload
+ save_block_result_to_cache(name, options) { |_name| yield _name }
+ end
end
else
read(name, options)
@@ -297,7 +303,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def read(name, options = nil)
options = merged_options(options)
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
instrument(:read, name, options) do |payload|
entry = read_entry(key, options)
if entry
@@ -329,7 +335,7 @@ module ActiveSupport
instrument_multi(:read, names, options) do |payload|
results = {}
names.each do |name|
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
entry = read_entry(key, options)
if entry
if entry.expired?
@@ -381,7 +387,7 @@ module ActiveSupport
instrument(:write, name, options) do
entry = Entry.new(value, options)
- write_entry(namespaced_key(name, options), entry, options)
+ write_entry(normalize_key(name, options), entry, options)
end
end
@@ -392,7 +398,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:delete, name) do
- delete_entry(namespaced_key(name, options), options)
+ delete_entry(normalize_key(name, options), options)
end
end
@@ -403,7 +409,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:exist?, name) do
- entry = read_entry(namespaced_key(name, options), options)
+ entry = read_entry(normalize_key(name, options), options)
(entry && !entry.expired?) || false
end
end
@@ -524,7 +530,7 @@ module ActiveSupport
# Prefix a key with the namespace. Namespace and key will be delimited
# with a colon.
- def namespaced_key(key, options)
+ def normalize_key(key, options)
key = expanded_key(key)
namespace = options[:namespace] if options
prefix = namespace.is_a?(Proc) ? namespace.call : namespace
@@ -532,8 +538,16 @@ module ActiveSupport
key
end
+ def namespaced_key(*args)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ `namespaced_key` is deprecated and will be removed from Rails 5.1.
+ Please use `normalize_key` which will return a fully resolved key.
+ MESSAGE
+ normalize_key(*args)
+ end
+
def instrument(operation, key, options = nil)
- log { "Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}" }
+ log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
@@ -556,13 +570,6 @@ module ActiveSupport
logger.debug(yield)
end
- def find_cached_entry(key, name, options)
- instrument(:read, name, options) do |payload|
- payload[:super_operation] = :fetch if payload
- read_entry(key, options)
- end
- end
-
def handle_expired_entry(entry, key, options)
if entry && entry.expired?
race_ttl = options[:race_condition_ttl].to_i
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index d08ecd2f7d..dff2443bc8 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -10,6 +10,7 @@ module ActiveSupport
# FileStore implements the Strategy::LocalCache strategy which implements
# an in-memory cache inside of a block.
class FileStore < Store
+ prepend Strategy::LocalCache
attr_reader :cache_path
DIR_FORMATTER = "%03X"
@@ -20,7 +21,6 @@ module ActiveSupport
def initialize(cache_path, options = nil)
super(options)
@cache_path = cache_path.to_s
- extend Strategy::LocalCache
end
# Deletes all items from the cache. In this case it deletes all the entries in the specified
@@ -29,6 +29,7 @@ module ActiveSupport
def clear(options = nil)
root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
+ rescue Errno::ENOENT
end
# Preemptively iterates through all stored keys and removes the ones which have expired.
@@ -59,7 +60,7 @@ module ActiveSupport
matcher = key_matcher(matcher, options)
search_dir(cache_path) do |path|
key = file_path_key(path)
- delete_entry(key, options) if key.match(matcher)
+ delete_entry(path, options) if key.match(matcher)
end
end
end
@@ -67,9 +68,8 @@ module ActiveSupport
protected
def read_entry(key, options)
- file_name = key_file_path(key)
- if File.exist?(file_name)
- File.open(file_name) { |f| Marshal.load(f) }
+ if File.exist?(key)
+ File.open(key) { |f| Marshal.load(f) }
end
rescue => e
logger.error("FileStoreError (#{e}): #{e.message}") if logger
@@ -77,23 +77,21 @@ module ActiveSupport
end
def write_entry(key, entry, options)
- file_name = key_file_path(key)
- return false if options[:unless_exist] && File.exist?(file_name)
- ensure_cache_path(File.dirname(file_name))
- File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)}
+ return false if options[:unless_exist] && File.exist?(key)
+ ensure_cache_path(File.dirname(key))
+ File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)}
true
end
def delete_entry(key, options)
- file_name = key_file_path(key)
- if File.exist?(file_name)
+ if File.exist?(key)
begin
- File.delete(file_name)
- delete_empty_directories(File.dirname(file_name))
+ File.delete(key)
+ delete_empty_directories(File.dirname(key))
true
rescue => e
# Just in case the error was caused by another process deleting the file first.
- raise e if File.exist?(file_name)
+ raise e if File.exist?(key)
false
end
end
@@ -117,12 +115,14 @@ module ActiveSupport
end
# Translate a key into a file path.
- def key_file_path(key)
- if key.size > FILEPATH_MAX_SIZE
- key = Digest::MD5.hexdigest(key)
+ def normalize_key(key, options)
+ key = super
+ fname = URI.encode_www_form_component(key)
+
+ if fname.size > FILEPATH_MAX_SIZE
+ fname = Digest::MD5.hexdigest(key)
end
- fname = URI.encode_www_form_component(key)
hash = Zlib.adler32(fname)
hash, dir_1 = hash.divmod(0x1000)
dir_2 = hash.modulo(0x1000)
@@ -137,6 +137,14 @@ module ActiveSupport
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
end
+ def key_file_path(key)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ `key_file_path` is deprecated and will be removed from Rails 5.1.
+ Please use `normalize_key` which will return a fully resolved key or nothing.
+ MESSAGE
+ key
+ end
+
# Translate a file path into a key.
def file_path_key(path)
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
@@ -173,7 +181,7 @@ module ActiveSupport
# Modifies the amount of an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
def modify_value(name, amount, options)
- file_name = key_file_path(namespaced_key(name, options))
+ file_name = normalize_key(name, options)
lock_file(file_name) do
options = merged_options(options)
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 73ae3acea5..174913365a 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -24,9 +24,41 @@ module ActiveSupport
# MemCacheStore implements the Strategy::LocalCache strategy which implements
# an in-memory cache inside of a block.
class MemCacheStore < Store
+ # Provide support for raw values in the local cache strategy.
+ module LocalCacheWithRaw # :nodoc:
+ protected
+ def read_entry(key, options)
+ entry = super
+ if options[:raw] && local_cache && entry
+ entry = deserialize_entry(entry.value)
+ end
+ entry
+ end
+
+ def write_entry(key, entry, options) # :nodoc:
+ if options[:raw] && local_cache
+ raw_entry = Entry.new(entry.value.to_s)
+ raw_entry.expires_at = entry.expires_at
+ super(key, raw_entry, options)
+ else
+ super
+ end
+ end
+ end
+
+ prepend Strategy::LocalCache
+ prepend LocalCacheWithRaw
+
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
- def self.build_mem_cache(*addresses)
+ # Creates a new Dalli::Client instance with specified addresses and options.
+ # By default address is equal localhost:11211.
+ #
+ # ActiveSupport::Cache::MemCacheStore.build_mem_cache
+ # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil>
+ # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290')
+ # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil>
+ def self.build_mem_cache(*addresses) # :nodoc:
addresses = addresses.flatten
options = addresses.extract_options!
addresses = ["localhost:11211"] if addresses.empty?
@@ -56,9 +88,6 @@ module ActiveSupport
UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
end
-
- extend Strategy::LocalCache
- extend LocalCacheWithRaw
end
# Reads multiple values from the cache using a single call to the
@@ -68,7 +97,7 @@ module ActiveSupport
options = merged_options(options)
instrument_multi(:read, names, options) do
- keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}]
+ keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}]
raw_values = @data.get_multi(keys_to_names.keys, :raw => true)
values = {}
raw_values.each do |key, value|
@@ -86,11 +115,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
@@ -100,20 +128,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.
@@ -124,10 +148,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.
@@ -139,18 +160,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
@@ -158,44 +175,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
- # Provide support for raw values in the local cache strategy.
- module LocalCacheWithRaw # :nodoc:
- protected
- def read_entry(key, options)
- entry = super
- if options[:raw] && local_cache && entry
- entry = deserialize_entry(entry.value)
- end
- entry
- end
-
- def write_entry(key, entry, options) # :nodoc:
- retval = super
- if options[:raw] && local_cache && retval
- raw_entry = Entry.new(entry.value.to_s)
- raw_entry.expires_at = entry.expires_at
- local_cache.write_entry(key, raw_entry, options)
- end
- retval
- end
- end
+ 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 8a0523d0e2..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)
@@ -126,7 +108,7 @@ module ActiveSupport
PER_ENTRY_OVERHEAD = 240
- def cached_size(key, entry)
+ def cached_size(key, entry) # :nodoc:
key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD
end
@@ -167,6 +149,19 @@ module ActiveSupport
!!entry
end
end
+
+ private
+
+ def modify_value(name, amount, options)
+ synchronize do
+ options = merged_options(options)
+ if num = read(name, options)
+ num = num.to_i + amount
+ write(name, num, options)
+ num
+ end
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb
index 4427eaafcd..0564ce5312 100644
--- a/activesupport/lib/active_support/cache/null_store.rb
+++ b/activesupport/lib/active_support/cache/null_store.rb
@@ -8,10 +8,7 @@ module ActiveSupport
# be cached inside blocks that utilize this strategy. See
# ActiveSupport::Cache::Strategy::LocalCache for more details.
class NullStore < Store
- def initialize(options = nil)
- super(options)
- extend Strategy::LocalCache
- end
+ prepend Strategy::LocalCache
def clear(options = nil)
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index a913736fc3..df38dbcf11 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -60,6 +60,10 @@ module ActiveSupport
def delete_entry(key, options)
!!@data.delete(key)
end
+
+ def fetch_entry(key, options = nil) # :nodoc:
+ @data.fetch(key) { @data[key] = yield }
+ end
end
# Use a local cache for the duration of block.
@@ -75,36 +79,35 @@ module ActiveSupport
end
def clear(options = nil) # :nodoc:
- local_cache.clear(options) if local_cache
+ return super unless cache = local_cache
+ cache.clear(options)
super
end
def cleanup(options = nil) # :nodoc:
- local_cache.clear(options) if local_cache
+ return super unless cache = local_cache
+ cache.clear(options)
super
end
def increment(name, amount = 1, options = nil) # :nodoc:
+ return super unless local_cache
value = bypass_local_cache{super}
- set_cache_value(value, name, amount, options)
+ write_cache_value(name, value, options)
value
end
def decrement(name, amount = 1, options = nil) # :nodoc:
+ return super unless local_cache
value = bypass_local_cache{super}
- set_cache_value(value, name, amount, options)
+ write_cache_value(name, value, options)
value
end
protected
def read_entry(key, options) # :nodoc:
- if local_cache
- entry = local_cache.read_entry(key, options)
- unless entry
- entry = super
- local_cache.write_entry(key, entry, options)
- end
- entry
+ if cache = local_cache
+ cache.fetch_entry(key) { super }
else
super
end
@@ -120,14 +123,22 @@ module ActiveSupport
super
end
- def set_cache_value(value, name, amount, options)
- if local_cache
- local_cache.mute do
- if value
- local_cache.write(name, value, options)
- else
- local_cache.delete(name, options)
- end
+ def set_cache_value(value, name, amount, options) # :nodoc:
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ `set_cache_value` is deprecated and will be removed from Rails 5.1.
+ Please use `write_cache_value`
+ MESSAGE
+ write_cache_value name, value, options
+ end
+
+ def write_cache_value(name, value, options) # :nodoc:
+ name = normalize_key(name, options)
+ cache = local_cache
+ cache.mute do
+ if value
+ cache.write(name, value, options)
+ else
+ cache.delete(name, options)
end
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 37f9494272..bf560ec1fa 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -4,7 +4,9 @@ require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/filters'
+require 'active_support/deprecation'
require 'thread'
module ActiveSupport
@@ -65,6 +67,12 @@ module ActiveSupport
CALLBACK_FILTER_TYPES = [:before, :after, :around]
+ # If true, Active Record and Active Model callbacks returning +false+ will
+ # halt the entire callback chain and display a deprecation message.
+ # If false, callback chains will only be halted by calling +throw :abort+.
+ # Defaults to +true+.
+ mattr_accessor(:halt_and_display_warning_on_return_false) { true }
+
# Runs the callbacks for the given event.
#
# Calls the before and around callbacks in the order they were set, yields
@@ -84,9 +92,9 @@ module ActiveSupport
private
- def _run_callbacks(callbacks, &block)
+ def __run_callbacks__(callbacks, &block)
if callbacks.empty?
- block.call if block
+ yield if block_given?
else
runner = callbacks.compile
e = Filters::Environment.new(self, false, nil, block)
@@ -125,14 +133,10 @@ module ActiveSupport
def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
halted_lambda = chain_config[:terminator]
- if chain_config.key?(:terminator) && user_conditions.any?
+ if user_conditions.any?
halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
- elsif chain_config.key? :terminator
- halting(callback_sequence, user_callback, halted_lambda, filter)
- elsif user_conditions.any?
- conditional(callback_sequence, user_callback, user_conditions)
else
- simple callback_sequence, user_callback
+ halting(callback_sequence, user_callback, halted_lambda, filter)
end
end
@@ -174,42 +178,15 @@ module ActiveSupport
end
end
private_class_method :halting
-
- def self.conditional(callback_sequence, user_callback, user_conditions)
- callback_sequence.before do |env|
- target = env.target
- value = env.value
-
- if user_conditions.all? { |c| c.call(target, value) }
- user_callback.call target, value
- end
-
- env
- end
- end
- private_class_method :conditional
-
- def self.simple(callback_sequence, user_callback)
- callback_sequence.before do |env|
- user_callback.call env.target, env.value
-
- env
- end
- end
- private_class_method :simple
end
class After
def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config[:skip_after_callbacks_if_terminated]
- if chain_config.key?(:terminator) && user_conditions.any?
+ if user_conditions.any?
halting_and_conditional(callback_sequence, user_callback, user_conditions)
- elsif chain_config.key?(:terminator)
- halting(callback_sequence, user_callback)
- elsif user_conditions.any?
- conditional callback_sequence, user_callback, user_conditions
else
- simple callback_sequence, user_callback
+ halting(callback_sequence, user_callback)
end
else
if user_conditions.any?
@@ -272,14 +249,10 @@ module ActiveSupport
class Around
def self.build(callback_sequence, user_callback, user_conditions, chain_config)
- if chain_config.key?(:terminator) && user_conditions.any?
+ if user_conditions.any?
halting_and_conditional(callback_sequence, user_callback, user_conditions)
- elsif chain_config.key? :terminator
- halting(callback_sequence, user_callback)
- elsif user_conditions.any?
- conditional(callback_sequence, user_callback, user_conditions)
else
- simple(callback_sequence, user_callback)
+ halting(callback_sequence, user_callback)
end
end
@@ -317,38 +290,18 @@ module ActiveSupport
end
end
private_class_method :halting
-
- def self.conditional(callback_sequence, user_callback, user_conditions)
- callback_sequence.around do |env, &run|
- target = env.target
- value = env.value
-
- if user_conditions.all? { |c| c.call(target, value) }
- user_callback.call(target, value) {
- run.call.value
- }
- env
- else
- run.call
- end
- end
- end
- private_class_method :conditional
-
- def self.simple(callback_sequence, user_callback)
- callback_sequence.around do |env, &run|
- user_callback.call(env.target, env.value) {
- run.call.value
- }
- env
- end
- end
- private_class_method :simple
end
end
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
@@ -511,12 +464,6 @@ module ActiveSupport
attr_reader :name, :config
- # If true, any callback returning +false+ will halt the entire callback
- # chain and display a deprecation message. If false, callback chains will
- # only be halted by calling +throw :abort+. Defaults to +true+.
- class_attribute :halt_and_display_warning_on_return_false
- self.halt_and_display_warning_on_return_false = true
-
def initialize(name, config)
@name = name
@config = {
@@ -597,23 +544,12 @@ module ActiveSupport
Proc.new do |target, result_lambda|
terminate = true
catch(:abort) do
- result = result_lambda.call if result_lambda.is_a?(Proc)
- if halt_and_display_warning_on_return_false && result == false
- display_deprecation_warning_for_false_terminator
- else
- terminate = false
- end
+ result_lambda.call if result_lambda.is_a?(Proc)
+ terminate = false
end
terminate
end
end
-
- def display_deprecation_warning_for_false_terminator
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails.
- To explicitly halt a callback chain, please use `throw :abort` instead.
- MSG
- end
end
module ClassMethods
@@ -639,14 +575,14 @@ module ActiveSupport
# set_callback :save, :after, :after_meth, if: :condition
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
#
- # The second arguments indicates whether the callback is to be run +:before+,
+ # 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
#
# 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+.
#
@@ -662,10 +598,12 @@ module ActiveSupport
#
# ===== Options
#
- # * <tt>:if</tt> - A symbol naming an instance method or a proc; the
- # callback will be called only when it returns a +true+ value.
- # * <tt>:unless</tt> - A symbol naming an instance method or a proc; the
- # callback will be called only when it returns a +false+ value.
+ # * <tt>:if</tt> - A symbol, a string or an array of symbols and strings,
+ # each naming an instance method or a proc; the callback will be called
+ # only when they all return a true value.
+ # * <tt>:unless</tt> - A symbol, a string or an array of symbols and
+ # strings, each naming an instance method or a proc; the callback will
+ # be called only when they all return a false value.
# * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
# existing chain rather than appended.
def set_callback(name, *filter_list, &block)
@@ -688,19 +626,27 @@ module ActiveSupport
# class Writer < Person
# skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
# end
+ #
+ # An <tt>ArgumentError</tt> will be raised if the callback has not
+ # already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
def skip_callback(name, *filter_list, &block)
type, filters, options = normalize_callback_params(filter_list, block)
+ options[:raise] = true unless options.key?(:raise)
__update_callbacks(name) do |target, chain|
filters.each do |filter|
- filter = chain.find {|c| c.matches?(type, filter) }
+ callback = chain.find {|c| c.matches?(type, filter) }
- if filter && options.any?
- new_filter = filter.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
- chain.insert(chain.index(filter), new_filter)
+ if !callback && options[:raise]
+ raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined"
end
- chain.delete(filter)
+ if callback && (options.key?(:if) || options.key?(:unless))
+ new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless])
+ chain.insert(chain.index(callback), new_callback)
+ end
+
+ chain.delete(callback)
end
target.set_callbacks name, chain
end
@@ -730,14 +676,15 @@ module ActiveSupport
# callback chain, preventing following before and around callbacks from
# being called and the event from being triggered.
# This should be a lambda to be executed.
- # The current object and the return result of the callback will be called
- # with the lambda.
+ # The current object and the result lambda of the callback will be provided
+ # to the terminator lambda.
#
- # define_callbacks :validate, terminator: ->(target, result) { result == false }
+ # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
#
# In this example, if any before validate callbacks returns +false+,
# any successive before and around callback is not executed.
- # Defaults to +false+, meaning no value halts the chain.
+ #
+ # The default terminator halts the chain when a callback throws +:abort+.
#
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
# callbacks should be terminated by the <tt>:terminator</tt> option. By
@@ -800,7 +747,7 @@ module ActiveSupport
module_eval <<-RUBY, __FILE__, __LINE__ + 1
def _run_#{name}_callbacks(&block)
- _run_callbacks(_#{name}_callbacks, &block)
+ __run_callbacks__(_#{name}_callbacks, &block)
end
RUBY
end
@@ -808,13 +755,37 @@ module ActiveSupport
protected
- def get_callbacks(name)
+ def get_callbacks(name) # :nodoc:
send "_#{name}_callbacks"
end
- def set_callbacks(name, callbacks)
+ def set_callbacks(name, callbacks) # :nodoc:
send "_#{name}_callbacks=", callbacks
end
+
+ def deprecated_false_terminator # :nodoc:
+ Proc.new do |target, result_lambda|
+ terminate = true
+ catch(:abort) do
+ result = result_lambda.call if result_lambda.is_a?(Proc)
+ if Callbacks.halt_and_display_warning_on_return_false && result == false
+ display_deprecation_warning_for_false_terminator
+ else
+ terminate = false
+ end
+ end
+ terminate
+ end
+ end
+
+ private
+
+ def display_deprecation_warning_for_false_terminator
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in the next release of Rails.
+ To explicitly halt the callback chain, please use `throw :abort` instead.
+ MSG
+ end
end
end
end
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index 4082d2d464..0403eb70ca 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -132,7 +132,7 @@ module ActiveSupport
end
def class_methods(&class_methods_module_definition)
- mod = const_defined?(:ClassMethods) ?
+ mod = const_defined?(:ClassMethods, false) ?
const_get(:ClassMethods) :
const_set(:ClassMethods, Module.new)
diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb
index 1507de433e..4abe5ece6f 100644
--- a/activesupport/lib/active_support/concurrency/latch.rb
+++ b/activesupport/lib/active_support/concurrency/latch.rb
@@ -1,26 +1,18 @@
-require 'thread'
-require 'monitor'
+require 'concurrent/atomic/count_down_latch'
module ActiveSupport
module Concurrency
- class Latch
+ class Latch < Concurrent::CountDownLatch
+
def initialize(count = 1)
- @count = count
- @lock = Monitor.new
- @cv = @lock.new_cond
+ ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.")
+ super(count)
end
- def release
- @lock.synchronize do
- @count -= 1 if @count > 0
- @cv.broadcast if @count.zero?
- end
- end
+ alias_method :release, :count_down
def await
- @lock.synchronize do
- @cv.wait_while { @count > 0 }
- end
+ wait(nil)
end
end
end
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
new file mode 100644
index 0000000000..ca48164c54
--- /dev/null
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -0,0 +1,142 @@
+require 'thread'
+require 'monitor'
+
+module ActiveSupport
+ module Concurrency
+ # 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
+
+ # We track Thread objects, instead of just using counters, because
+ # we need exclusive locks to be reentrant, and we need to be able
+ # to upgrade share locks to exclusive.
+
+
+ def initialize
+ super()
+
+ @cv = new_cond
+
+ @sharing = Hash.new(0)
+ @waiting = {}
+ @exclusive_thread = nil
+ @exclusive_depth = 0
+ end
+
+ # Returns false if +no_wait+ is set and the lock is not
+ # immediately available. Otherwise, returns true after the lock
+ # has been acquired.
+ #
+ # +purpose+ and +compatible+ work together; while this thread is
+ # waiting for the exclusive lock, it will yield its share (if any)
+ # to any other attempt whose +purpose+ appears in this attempt's
+ # +compatible+ list. This allows a "loose" upgrade, which, being
+ # less strict, prevents some classes of deadlocks.
+ #
+ # For many resources, loose upgrades are sufficient: if a thread
+ # is awaiting a lock, it is not running any other code. With
+ # +purpose+ matching, it is possible to yield only to other
+ # threads whose activity will not interfere.
+ def start_exclusive(purpose: nil, compatible: [], no_wait: false)
+ synchronize do
+ unless @exclusive_thread == Thread.current
+ if busy?(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
+ end
+ end
+ @exclusive_thread = Thread.current
+ end
+ @exclusive_depth += 1
+
+ true
+ end
+ end
+
+ # Relinquish the exclusive lock. Must only be called by the thread
+ # that called start_exclusive (and currently holds the lock).
+ def stop_exclusive
+ synchronize do
+ raise "invalid unlock" if @exclusive_thread != Thread.current
+
+ @exclusive_depth -= 1
+ if @exclusive_depth == 0
+ @exclusive_thread = nil
+ @cv.broadcast
+ end
+ end
+ end
+
+ def start_sharing
+ synchronize do
+ if @exclusive_thread && @exclusive_thread != Thread.current
+ @cv.wait_while { @exclusive_thread }
+ end
+ @sharing[Thread.current] += 1
+ end
+ end
+
+ def stop_sharing
+ synchronize do
+ if @sharing[Thread.current] > 1
+ @sharing[Thread.current] -= 1
+ else
+ @sharing.delete Thread.current
+ @cv.broadcast
+ end
+ end
+ end
+
+ # Execute the supplied block while holding the Exclusive lock. If
+ # +no_wait+ is set and the lock is not immediately available,
+ # returns +nil+ without yielding. Otherwise, returns the result of
+ # the block.
+ #
+ # See +start_exclusive+ for other options.
+ def exclusive(purpose: nil, compatible: [], no_wait: false)
+ if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
+ begin
+ yield
+ ensure
+ stop_exclusive
+ end
+ end
+ end
+
+ # Execute the supplied block while holding the Share lock.
+ def sharing
+ start_sharing
+ begin
+ yield
+ ensure
+ stop_sharing
+ 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) } ||
+ @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb
index 7d0c1e4c8d..7551551bd7 100644
--- a/activesupport/lib/active_support/core_ext/array.rb
+++ b/activesupport/lib/active_support/core_ext/array.rb
@@ -4,3 +4,4 @@ require 'active_support/core_ext/array/conversions'
require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/array/grouping'
require 'active_support/core_ext/array/prepend_and_append'
+require 'active_support/core_ext/array/inquiry'
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index 080e3b5ef7..8718b7e1e5 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -32,7 +32,7 @@ class Array
# ['one', 'two', 'three'].to_sentence # => "one, two, and three"
#
# ['one', 'two'].to_sentence(passing: 'invalid option')
- # # => ArgumentError: Unknown key :passing
+ # # => ArgumentError: Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale
#
# ['one', 'two'].to_sentence(two_words_connector: '-')
# # => "one-two"
@@ -74,7 +74,7 @@ class Array
when 0
''
when 1
- self[0].to_s.dup
+ "#{self[0]}"
when 2
"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
else
@@ -85,7 +85,9 @@ class Array
# Extends <tt>Array#to_s</tt> to convert a collection of elements into a
# comma separated id list if <tt>:db</tt> argument is given as the format.
#
- # Blog.all.to_formatted_s(:db) # => "1,2,3"
+ # Blog.all.to_formatted_s(:db) # => "1,2,3"
+ # Blog.none.to_formatted_s(:db) # => "null"
+ # [1,2].to_formatted_s # => "[1, 2]"
def to_formatted_s(format = :default)
case format
when :db
diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb
new file mode 100644
index 0000000000..e8f44cc378
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb
@@ -0,0 +1,17 @@
+require 'active_support/array_inquirer'
+
+class Array
+ # Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way
+ # to check its string-like contents.
+ #
+ # pets = [:cat, :dog].inquiry
+ #
+ # pets.cat? # => true
+ # pets.ferret? # => false
+ #
+ # pets.any?(:cat, :ferret) # => true
+ # pets.any?(:ferret, :alligator) # => false
+ def inquiry
+ ActiveSupport::ArrayInquirer.new(self)
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb
index 152eb02218..b611d34c27 100644
--- a/activesupport/lib/active_support/core_ext/array/wrap.rb
+++ b/activesupport/lib/active_support/core_ext/array/wrap.rb
@@ -3,7 +3,7 @@ class Array
#
# Specifically:
#
- # * If the argument is +nil+ an empty list is returned.
+ # * If the argument is +nil+ an empty array is returned.
# * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned.
# * Otherwise, returns an array with the argument as its single element.
#
@@ -15,12 +15,13 @@ class Array
#
# * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt>
# moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns
- # +nil+ right away.
+ # an array with the argument as its single element right away.
# * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt>
# raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value.
- # * It does not call +to_a+ on the argument, but returns an empty array if argument is +nil+.
+ # * It does not call +to_a+ on the argument, if the argument does not respond to +to_ary+
+ # it returns an array with the argument as its single element.
#
- # The second point is easily explained with some enumerables:
+ # The last point is easily explained with some enumerables:
#
# Array(foo: :bar) # => [[:foo, :bar]]
# Array.wrap(foo: :bar) # => [{:foo=>:bar}]
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 234283e792..22fc7ecf92 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,15 +1,14 @@
require 'bigdecimal'
require 'bigdecimal/util'
-class BigDecimal
- DEFAULT_STRING_FORMAT = 'F'
- alias_method :to_default_s, :to_s
+module ActiveSupport
+ module BigDecimalWithDefaultFormat #:nodoc:
+ DEFAULT_STRING_FORMAT = 'F'
- def to_s(format = nil, options = nil)
- if format.is_a?(Symbol)
- to_formatted_s(format, options || {})
- else
- to_default_s(format || DEFAULT_STRING_FORMAT)
+ def to_s(format = nil)
+ super(format || DEFAULT_STRING_FORMAT)
end
end
end
+
+BigDecimal.prepend(ActiveSupport::BigDecimalWithDefaultFormat)
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index c750a10bb2..ef903d59b5 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,3 +1,2 @@
require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/subclasses'
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index f2b7bb3ef1..802d988af2 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -75,11 +75,15 @@ class Class
instance_predicate = options.fetch(:instance_predicate, true)
attrs.each do |name|
+ remove_possible_singleton_method(name)
define_singleton_method(name) { nil }
+
+ remove_possible_singleton_method("#{name}?")
define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
ivar = "@#{name}"
+ remove_possible_singleton_method("#{name}=")
define_singleton_method("#{name}=") do |val|
singleton_class.class_eval do
remove_possible_method(name)
@@ -110,10 +114,15 @@ class Class
self.class.public_send name
end
end
+
+ remove_possible_method "#{name}?"
define_method("#{name}?") { !!public_send(name) } if instance_predicate
end
- attr_writer name if instance_writer
+ if instance_writer
+ remove_possible_method "#{name}="
+ attr_writer name
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
deleted file mode 100644
index 1c305c5970..0000000000
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/module/remove_method'
-require 'active_support/core_ext/module/deprecation'
-
-
-class Class
- def superclass_delegating_accessor(name, options = {})
- # Create private _name and _name= methods that can still be used if the public
- # methods are overridden.
- _superclass_delegating_accessor("_#{name}", options)
-
- # Generate the public methods name, name=, and name?.
- # These methods dispatch to the private _name, and _name= methods, making them
- # overridable.
- singleton_class.send(:define_method, name) { send("_#{name}") }
- singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") }
- singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
-
- # If an instance_reader is needed, generate public instance methods name and name?.
- if options[:instance_reader] != false
- define_method(name) { send("_#{name}") }
- define_method("#{name}?") { !!send("#{name}") }
- end
- end
-
- deprecate superclass_delegating_accessor: :class_attribute
-
- private
- # Take the object being set and store it in a method. This gives us automatic
- # inheritance behavior, without having to store the object in an instance
- # variable and look up the superclass chain manually.
- def _stash_object_in_method(object, method, instance_reader = true)
- singleton_class.remove_possible_method(method)
- singleton_class.send(:define_method, method) { object }
- remove_possible_method(method)
- define_method(method) { object } if instance_reader
- end
-
- def _superclass_delegating_accessor(name, options = {})
- singleton_class.send(:define_method, "#{name}=") do |value|
- _stash_object_in_method(value, name, options[:instance_reader] != false)
- end
- send("#{name}=", nil)
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 3c4bfc5f1e..b0f9a8be34 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -3,7 +3,8 @@ require 'active_support/core_ext/module/reachable'
class Class
begin
- ObjectSpace.each_object(Class.new) {}
+ # Test if this Ruby supports each_object against singleton_class
+ ObjectSpace.each_object(Numeric.singleton_class) {}
def descendants # :nodoc:
descendants = []
@@ -12,7 +13,7 @@ class Class
end
descendants
end
- rescue StandardError # JRuby
+ rescue StandardError # JRuby 9.0.4.0 and earlier
def descendants # :nodoc:
descendants = []
ObjectSpace.each_object(Class) do |k|
diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb
index 465fedda80..7f0f4639a2 100644
--- a/activesupport/lib/active_support/core_ext/date.rb
+++ b/activesupport/lib/active_support/core_ext/date.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/date/acts_like'
+require 'active_support/core_ext/date/blank'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/date/zones'
-
diff --git a/activesupport/lib/active_support/core_ext/date/blank.rb b/activesupport/lib/active_support/core_ext/date/blank.rb
new file mode 100644
index 0000000000..71627b6a6f
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date/blank.rb
@@ -0,0 +1,12 @@
+require 'date'
+
+class Date #:nodoc:
+ # No Date is blank:
+ #
+ # Date.today.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index c60e833441..d589b67bf7 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -26,7 +26,7 @@ class Date
Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start)
end
- # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol.
+ # Returns week start day symbol (e.g. :monday), or raises an +ArgumentError+ for invalid day symbol.
def find_beginning_of_week!(week_start)
raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start)
week_start
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index df419a6e63..ed8bca77ac 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -35,6 +35,7 @@ class Date
# date.to_s(:db) # => "2007-11-10"
#
# date.to_formatted_s(:short) # => "10 Nov"
+ # date.to_formatted_s(:number) # => "20071110"
# date.to_formatted_s(:long) # => "November 10, 2007"
# date.to_formatted_s(:long_ordinal) # => "November 10th, 2007"
# date.to_formatted_s(:rfc822) # => "10 Nov 2007"
@@ -74,14 +75,19 @@ class Date
#
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
- # date.to_time # => Sat Nov 10 00:00:00 0800 2007
- # date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007
+ # date.to_time # => 2007-11-10 00:00:00 0800
+ # date.to_time(:local) # => 2007-11-10 00:00:00 0800
#
- # date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
+ # date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
def to_time(form = :local)
::Time.send(form, year, month, day)
end
+ # Returns a string which represents the time in used time zone as DateTime
+ # defined by XML Schema:
+ #
+ # date = Date.new(2015, 05, 23) # => Sat, 23 May 2015
+ # date.xmlschema # => "2015-05-23T00:00:00+04:00"
def xmlschema
in_time_zone.xmlschema
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 9525c10112..e079af594d 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -92,15 +92,28 @@ module DateAndTime
end
# Returns a new date/time at the start of the month.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Thu, 18 Jun 2015
+ # today.beginning_of_month # => Mon, 01 Jun 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000
+ # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000
def beginning_of_month
first_hour(change(:day => 1))
end
alias :at_beginning_of_month :beginning_of_month
# Returns a new date/time at the start of the quarter.
- # Example: 1st January, 1st July, 1st October.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_quarter # => Wed, 01 Jul 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000
def beginning_of_quarter
first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
beginning_of_month.change(:month => first_quarter_month)
@@ -108,26 +121,50 @@ module DateAndTime
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new date/time at the end of the quarter.
- # Example: 31st March, 30th June, 30th September.
- # DateTime objects will have a time set to 23:59:59.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.end_of_quarter # => Wed, 30 Sep 2015
+ #
+ # +DateTime+ objects will have a time set to 23:59:59.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000
def end_of_quarter
last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
beginning_of_month.change(:month => last_quarter_month).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
- # Return a new date/time at the beginning of the year.
- # Example: 1st January.
- # DateTime objects will have a time set to 0:00.
+ # Returns a new date/time at the beginning of the year.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_year # => Thu, 01 Jan 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000
def beginning_of_year
change(:month => 1).beginning_of_month
end
alias :at_beginning_of_year :beginning_of_year
# Returns a new date/time representing the given day in the next week.
+ #
+ # today = Date.today # => Thu, 07 May 2015
+ # today.next_week # => Mon, 11 May 2015
+ #
# The +given_day_in_next_week+ defaults to the beginning of the week
# which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+
- # when set. +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
+ # when set.
+ #
+ # today = Date.today # => Thu, 07 May 2015
+ # today.next_week(:friday) # => Fri, 15 May 2015
+ #
+ # +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
+ #
+ # now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000
+ # now.next_week # => Mon, 11 May 2015 00:00:00 +0000
def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false)
result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
same_time ? copy_time_to(result) : result
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
index 96c6df9407..d29a8db5cf 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
@@ -4,7 +4,7 @@ module DateAndTime
# if Time.zone_default is set. Otherwise, it returns the current time.
#
# Time.zone = 'Hawaii' # => 'Hawaii'
- # DateTime.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
# Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
#
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
@@ -14,7 +14,6 @@ module DateAndTime
# and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
#
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
- # DateTime.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
# Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
time_zone = ::Time.find_zone! zone
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index e8a27b9f38..bcb228b09a 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/date_time/acts_like'
+require 'active_support/core_ext/date_time/blank'
require 'active_support/core_ext/date_time/calculations'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/date_time/zones'
diff --git a/activesupport/lib/active_support/core_ext/date_time/blank.rb b/activesupport/lib/active_support/core_ext/date_time/blank.rb
new file mode 100644
index 0000000000..56981b75fb
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_time/blank.rb
@@ -0,0 +1,12 @@
+require 'date'
+
+class DateTime #:nodoc:
+ # No DateTime is ever blank:
+ #
+ # DateTime.now.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 55ad384f4f..95617fb8c2 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -168,7 +168,7 @@ class DateTime
if other.kind_of?(Infinity)
super
elsif other.respond_to? :to_datetime
- super other.to_datetime
+ super other.to_datetime rescue nil
else
nil
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index 2a9c09fc29..f59d05b214 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -40,6 +40,8 @@ class DateTime
alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
alias_method :to_s, :to_formatted_s
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
#
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
# datetime.formatted_offset # => "-06:00"
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 7a893292b3..8a74ad4d66 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -21,7 +21,7 @@ module Enumerable
if block_given?
map(&block).sum(identity)
else
- inject { |sum, element| sum + element } || identity
+ inject(:+) || identity
end
end
@@ -71,6 +71,21 @@ module Enumerable
def without(*elements)
reject { |element| elements.include?(element) }
end
+
+ # Convert an enumerable to an array based on the given key.
+ #
+ # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
+ # => ["David", "Rafael", "Aaron"]
+ #
+ # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
+ # => [[1, "David"], [2, "Rafael"]]
+ def pluck(*keys)
+ if keys.many?
+ map { |element| keys.map { |key| element[key] } }
+ else
+ map { |element| element[keys.first] }
+ end
+ end
end
class Range #:nodoc:
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index fad6fa8d9d..463fd78412 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -8,43 +8,45 @@ class File
# file.write('hello')
# end
#
- # If your temp directory is not on the same filesystem as the file you're
- # trying to write, you can provide a different temporary directory.
+ # This method needs to create a temporary file. By default it will create it
+ # in the same directory as the destination file. If you don't like this
+ # behavior you can provide a different directory but it must be on the
+ # same physical filesystem as the file you're trying to write.
#
# File.atomic_write('/data/something.important', '/data/tmp') do |file|
# file.write('hello')
# end
- def self.atomic_write(file_name, temp_dir = Dir.tmpdir)
+ def self.atomic_write(file_name, temp_dir = dirname(file_name))
require 'tempfile' unless defined?(Tempfile)
- require 'fileutils' unless defined?(FileUtils)
- temp_file = Tempfile.new(basename(file_name), temp_dir)
- temp_file.binmode
- return_val = yield temp_file
- temp_file.close
+ Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file|
+ temp_file.binmode
+ return_val = yield temp_file
+ temp_file.close
- if File.exist?(file_name)
- # Get original file permissions
- old_stat = stat(file_name)
- else
- # If not possible, probe which are the default permissions in the
- # destination directory.
- old_stat = probe_stat_in(dirname(file_name))
- end
-
- # Overwrite original file with temp file
- FileUtils.mv(temp_file.path, file_name)
+ old_stat = if exist?(file_name)
+ # Get original file permissions
+ stat(file_name)
+ elsif temp_dir != dirname(file_name)
+ # If not possible, probe which are the default permissions in the
+ # destination directory.
+ probe_stat_in(dirname(file_name))
+ end
- # Set correct permissions on new file
- begin
- chown(old_stat.uid, old_stat.gid, file_name)
- # This operation will affect filesystem ACL's
- chmod(old_stat.mode, file_name)
+ if old_stat
+ # Set correct permissions on new file
+ begin
+ chown(old_stat.uid, old_stat.gid, temp_file.path)
+ # This operation will affect filesystem ACL's
+ chmod(old_stat.mode, temp_file.path)
+ rescue Errno::EPERM, Errno::EACCES
+ # Changing file ownership failed, moving on.
+ end
+ end
- # Make sure we return the result of the yielded block
+ # Overwrite original file with temp file
+ rename(temp_file.path, file_name)
return_val
- rescue Errno::EPERM, Errno::EACCES
- # Changing file ownership failed, moving on.
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 2149d4439d..8594d9bf2e 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -106,7 +106,25 @@ class Hash
# # => {"hash"=>{"foo"=>1, "bar"=>2}}
#
# +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or
- # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML.
+ # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to
+ # parse this XML.
+ #
+ # Custom +disallowed_types+ can also be passed in the form of an
+ # array.
+ #
+ # xml = <<-XML
+ # <?xml version="1.0" encoding="UTF-8"?>
+ # <hash>
+ # <foo type="integer">1</foo>
+ # <bar type="string">"David"</bar>
+ # </hash>
+ # XML
+ #
+ # hash = Hash.from_xml(xml, ['integer'])
+ # # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer"
+ #
+ # Note that passing custom disallowed types will override the default types,
+ # which are Symbol and YAML.
def from_xml(xml, disallowed_types = nil)
ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 6e397abf51..2f6d38c1f6 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,8 +1,9 @@
class Hash
- # Returns a hash that includes everything but the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false, c: nil}
+ # Returns a hash that includes everything except given keys.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except(:c) # => { a: true, b: false }
+ # hash.except(:a, :b) # => { c: nil }
+ # hash # => { a: true, b: false, c: nil }
#
# This is useful for limiting a set of parameters to everything but a few known toggles:
# @person.update(params[:person].except(:admin))
@@ -10,10 +11,10 @@ class Hash
dup.except!(*keys)
end
- # Replaces the hash without the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except!(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false }
+ # Removes the given keys from hash and returns it.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except!(:c) # => { a: true, b: false }
+ # hash # => { a: true, b: false }
def except!(*keys)
keys.each { |key| delete(key) }
self
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index 28cb3e2a3b..6df7b4121b 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -6,7 +6,7 @@ class Hash
#
# { a: 1 }.with_indifferent_access['a'] # => 1
def with_indifferent_access
- ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
+ ActiveSupport::HashWithIndifferentAccess.new(self)
end
# Called when object is nested under an object that receives
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index c30044b9ff..8b2366c4b3 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,12 +1,16 @@
class Hash
- # Returns a new hash with all keys converted using the block operation.
+ # Returns a new hash with all keys converted using the +block+ operation.
#
# hash = { name: 'Rob', age: '28' }
#
- # hash.transform_keys{ |key| key.to_s.upcase }
- # # => {"NAME"=>"Rob", "AGE"=>"28"}
+ # hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"}
+ #
+ # If you do not provide a +block+, it will return an Enumerator
+ # for chaining with other methods:
+ #
+ # hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
def transform_keys
- return enum_for(:transform_keys) unless block_given?
+ return enum_for(:transform_keys) { size } unless block_given?
result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
@@ -14,10 +18,10 @@ class Hash
result
end
- # Destructively converts all keys using the block operations.
- # Same as transform_keys but modifies +self+.
+ # Destructively converts all keys using the +block+ operations.
+ # Same as +transform_keys+ but modifies +self+.
def transform_keys!
- return enum_for(:transform_keys!) unless block_given?
+ return enum_for(:transform_keys!) { size } unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
@@ -60,7 +64,7 @@ class Hash
alias_method :to_options!, :symbolize_keys!
# Validates all keys in a hash match <tt>*valid_keys</tt>, raising
- # ArgumentError on a mismatch.
+ # +ArgumentError+ on a mismatch.
#
# Note that keys are treated differently than HashWithIndifferentAccess,
# meaning that string and symbol keys will not match.
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
index e9bcce761f..7d507ac998 100644
--- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -2,10 +2,15 @@ class Hash
# Returns a new hash with the results of running +block+ once for every value.
# The keys are unchanged.
#
- # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 }
- # # => { a: 2, b: 4, c: 6 }
+ # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 } # => { a: 2, b: 4, c: 6 }
+ #
+ # If you do not provide a +block+, it will return an Enumerator
+ # for chaining with other methods:
+ #
+ # { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 }
def transform_values
- return enum_for(:transform_values) unless block_given?
+ return enum_for(:transform_values) { size } unless block_given?
+ return {} if empty?
result = self.class.new
each do |key, value|
result[key] = yield(value)
@@ -13,9 +18,10 @@ class Hash
result
end
- # Destructive +transform_values+
+ # Destructively converts all values using the +block+ operations.
+ # Same as +transform_values+ but modifies +self+.
def transform_values!
- return enum_for(:transform_values!) unless block_given?
+ return enum_for(:transform_values!) { size } unless block_given?
each do |key, value|
self[key] = yield(value)
end
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index 82080ffe51..87185b024f 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -17,28 +17,13 @@ class Integer
#
# # equivalent to Time.now.advance(months: 4, years: 5)
# (4.months + 5.years).from_now
- #
- # While these methods provide precise calculation when used as in the examples
- # above, care should be taken to note that this is not true if the result of
- # +months+, +years+, etc is converted before use:
- #
- # # equivalent to 30.days.to_i.from_now
- # 1.month.to_i.from_now
- #
- # # equivalent to 365.25.days.to_f.from_now
- # 1.year.to_f.from_now
- #
- # In such cases, Ruby's core
- # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
- # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
- # date and time arithmetic.
def months
ActiveSupport::Duration.new(self * 30.days, [[:months, self]])
end
alias :month :months
def years
- ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]])
+ ActiveSupport::Duration.new(self * 365.25.days.to_i, [[:years, self]])
end
alias :year :years
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 9189e6d977..8afc258df8 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,5 +1,3 @@
-require 'tempfile'
-
module Kernel
# 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/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index d9fb392752..60732eb41a 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -23,7 +23,7 @@ class LoadError
# Returns true if the given path name (except perhaps for the ".rb"
# extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
- location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
+ location.sub(/\.rb$/, ''.freeze) == path.sub(/\.rb$/, ''.freeze)
end
end
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index 56c79c04bd..e333b26133 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -1,21 +1,19 @@
-require 'active_support/core_ext/module/aliasing'
-
-module Marshal
- class << self
- def load_with_autoloading(source)
- load_without_autoloading(source)
+module ActiveSupport
+ module MarshalWithAutoloading # :nodoc:
+ def load(source)
+ super(source)
rescue ArgumentError, NameError => exc
if exc.message.match(%r|undefined class/module (.+)|)
# try loading the class/module
$1.constantize
- # if it is a IO we need to go back to read the object
+ # if it is an IO we need to go back to read the object
source.rewind if source.respond_to?(:rewind)
retry
else
raise exc
end
end
-
- alias_method_chain :load, :autoloading
end
end
+
+Marshal.singleton_class.prepend(ActiveSupport::MarshalWithAutoloading)
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/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb
index 0a6fadf928..b6934b9c54 100644
--- a/activesupport/lib/active_support/core_ext/module/aliasing.rb
+++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb
@@ -1,4 +1,7 @@
class Module
+ # NOTE: This method is deprecated. Please use <tt>Module#prepend</tt> that
+ # comes with Ruby 2.0 or newer instead.
+ #
# Encapsulates the common pattern of:
#
# alias_method :foo_without_feature, :foo
@@ -21,6 +24,8 @@ class Module
#
# so you can safely chain foo, foo?, foo! and/or foo= with the same feature.
def alias_method_chain(target, feature)
+ ActiveSupport::Deprecation.warn("alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super.")
+
# Strip out punctuation on predicates, bang or writer methods since
# e.g. target?_without_feature is not a valid method name.
aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
@@ -43,7 +48,7 @@ class Module
end
# Allows you to make aliases for attributes, which includes
- # getter, setter, and query methods.
+ # getter, setter, and a predicate.
#
# class Content < ActiveRecord::Base
# # has a title attribute
diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb
index 0ecc67a855..510c9a5430 100644
--- a/activesupport/lib/active_support/core_ext/module/anonymous.rb
+++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb
@@ -7,7 +7,7 @@ class Module
# m = Module.new
# m.name # => nil
#
- # +anonymous?+ method returns true if module does not have a name:
+ # +anonymous?+ method returns true if module does not have a name, false otherwise:
#
# Module.new.anonymous? # => true
#
@@ -18,8 +18,10 @@ class Module
# via the +module+ or +class+ keyword or by an explicit assignment:
#
# m = Module.new # creates an anonymous module
- # M = m # => m gets a name here as a side-effect
+ # m.anonymous? # => true
+ # M = m # m gets a name here as a side-effect
# m.name # => "M"
+ # m.anonymous? # => false
def anonymous?
name.nil?
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index d4e6b5a1ac..124f90dc0f 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -5,7 +5,7 @@ require 'active_support/core_ext/array/extract_options'
# attributes.
class Module
# Defines a class attribute and creates a class and instance reader methods.
- # The underlying the class variable is set to +nil+, if it is not previously
+ # The underlying class variable is set to +nil+, if it is not previously
# defined.
#
# module HairColors
@@ -19,9 +19,9 @@ class Module
# The attribute name must be a valid method name in Ruby.
#
# module Foo
- # mattr_reader :"1_Badname "
+ # mattr_reader :"1_Badname"
# end
- # # => NameError: invalid attribute name
+ # # => NameError: invalid attribute name: 1_Badname
#
# If you want to opt out the creation on the instance reader method, pass
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
@@ -53,7 +53,7 @@ class Module
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -119,7 +119,7 @@ class Module
def mattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -206,7 +206,7 @@ class Module
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def mattr_accessor(*syms, &blk)
mattr_reader(*syms, &blk)
- mattr_writer(*syms, &blk)
+ mattr_writer(*syms)
end
alias :cattr_accessor :mattr_accessor
end
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..8a7e6776da
--- /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_#{self.class.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_#{self.class.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/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index 07a392404e..65b88b9bbd 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -63,10 +63,10 @@ class Module
#
# == Mix-in noise exiled to its own file:
#
- # Once our chunk of behavior starts pushing the scroll-to-understand it's
+ # Once our chunk of behavior starts pushing the scroll-to-understand-it
# boundary, we give in and move it to a separate file. At this size, the
- # overhead feels in good proportion to the size of our extraction, despite
- # diluting our at-a-glance sense of how things really work.
+ # increased overhead can be a reasonable tradeoff even if it reduces our
+ # at-a-glance perception of how things work.
#
# class Todo
# # Other todo implementation
@@ -99,7 +99,7 @@ class Module
# end
#
# Todo.ancestors
- # # => Todo, Todo::EventTracking, Object
+ # # => [Todo, Todo::EventTracking, Object]
#
# This small step has some wonderful ripple effects. We can
# * grok the behavior of our class in one glance,
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 9b7a429db9..0d46248582 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -5,10 +5,11 @@ class Module
# option is not used.
class DelegationError < NoMethodError; end
- RUBY_RESERVED_WORDS = Set.new(
- %w(alias and BEGIN begin break case class def defined? do else elsif END
- end ensure false for if in module next nil not or redo rescue retry
- return self super then true undef unless until when while yield)
+ DELEGATION_RESERVED_METHOD_NAMES = Set.new(
+ %w(_ arg args alias and BEGIN begin block break case class def defined? do
+ else elsif END end ensure false for if in module next nil not or redo
+ rescue retry return self super then true undef unless until when while
+ yield)
).freeze
# Provides a +delegate+ class method to easily expose contained objects'
@@ -167,11 +168,11 @@ class Module
''
end
- file, line = caller(1, 1).first.split(':', 2)
+ file, line = caller(1, 1).first.split(':'.freeze, 2)
line = line.to_i
to = to.to_s
- to = "self.#{to}" if RUBY_RESERVED_WORDS.include?(to)
+ to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
methods.each do |method|
# Attribute writer methods only accept one argument. Makes sure []=
diff --git a/activesupport/lib/active_support/core_ext/module/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/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index 52632d2c6b..d5ec16d68a 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -6,10 +6,30 @@ class Module
end
end
+ # Removes the named singleton method, if it exists.
+ def remove_possible_singleton_method(method)
+ singleton_class.instance_eval do
+ remove_possible_method(method)
+ end
+ end
+
# Replaces the existing method definition, if there is one, with the passed
# block as its body.
def redefine_method(method, &block)
+ visibility = method_visibility(method)
remove_possible_method(method)
define_method(method, &block)
+ send(visibility, method)
+ end
+
+ def method_visibility(method) # :nodoc:
+ case
+ when private_method_defined?(method)
+ :private
+ when protected_method_defined?(method)
+ :protected
+ else
+ :public
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb
index a6bc0624be..bcdc3eace2 100644
--- a/activesupport/lib/active_support/core_ext/numeric.rb
+++ b/activesupport/lib/active_support/core_ext/numeric.rb
@@ -1,3 +1,4 @@
require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
+require 'active_support/core_ext/numeric/inquiry'
require 'active_support/core_ext/numeric/conversions'
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 0c8ff79237..9a3651f29a 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -1,7 +1,7 @@
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/number_helper'
-class Numeric
+module ActiveSupport::NumericWithFormat
# Provides options for converting numbers into formatted strings.
# Options are provided for phone numbers, currency, percentage,
@@ -41,7 +41,7 @@ class Numeric
# 1000.to_s(:percentage, delimiter: '.', separator: ',') # => 1.000,000%
# 302.24398923423.to_s(:percentage, precision: 5) # => 302.24399%
# 1000.to_s(:percentage, locale: :fr) # => 1 000,000%
- # 100.to_s(:percentage, format: '%n %') # => 100 %
+ # 100.to_s(:percentage, format: '%n %') # => 100.000 %
#
# Delimited:
# 12345678.to_s(:delimited) # => 12,345,678
@@ -78,7 +78,7 @@ class Numeric
# 1234567.to_s(:human_size, precision: 2) # => 1.2 MB
# 483989.to_s(:human_size, precision: 2) # => 470 KB
# 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB
- # 1234567890123.to_s(:human_size, precision: 5) # => "1.1229 TB"
+ # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
# 524288000.to_s(:human_size, precision: 5) # => "500 MB"
#
# Human-friendly format:
@@ -97,7 +97,10 @@ class Numeric
# 1234567.to_s(:human, precision: 1,
# separator: ',',
# significant: false) # => "1,2 Million"
- def to_formatted_s(format = :default, options = {})
+ def to_s(*args)
+ format, options = args
+ options ||= {}
+
case format
when :phone
return ActiveSupport::NumberHelper.number_to_phone(self, options)
@@ -114,32 +117,16 @@ class Numeric
when :human_size
return ActiveSupport::NumberHelper.number_to_human_size(self, options)
else
- self.to_default_s
- end
- end
-
- [Fixnum, Bignum].each do |klass|
- klass.class_eval do
- alias_method :to_default_s, :to_s
- def to_s(base_or_format = 10, options = nil)
- if base_or_format.is_a?(Symbol)
- to_formatted_s(base_or_format, options || {})
- else
- to_default_s(base_or_format)
- end
- end
+ super
end
end
- Float.class_eval do
- alias_method :to_default_s, :to_s
- def to_s(*args)
- if args.empty?
- to_default_s
- else
- to_formatted_s(*args)
- end
- end
+ def to_formatted_s(*args)
+ to_s(*args)
end
+ deprecate to_formatted_s: :to_s
+end
+[Fixnum, Bignum, Float, BigDecimal].each do |klass|
+ klass.prepend(ActiveSupport::NumericWithFormat)
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
new file mode 100644
index 0000000000..7e7ac1b0b2
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb
@@ -0,0 +1,26 @@
+unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3
+class Numeric
+ # Returns true if the number is positive.
+ #
+ # 1.positive? # => true
+ # 0.positive? # => false
+ # -1.positive? # => false
+ def positive?
+ self > 0
+ end
+
+ # Returns true if the number is negative.
+ #
+ # -1.negative? # => true
+ # 0.negative? # => false
+ # 1.negative? # => false
+ def negative?
+ self < 0
+ end
+end
+
+class Complex
+ undef :positive?
+ undef :negative?
+end
+end
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 98716383f4..6c4a975495 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -18,21 +18,6 @@ class Numeric
#
# # equivalent to Time.current.advance(months: 4, years: 5)
# (4.months + 5.years).from_now
- #
- # While these methods provide precise calculation when used as in the examples above, care
- # should be taken to note that this is not true if the result of `months', `years', etc is
- # converted before use:
- #
- # # equivalent to 30.days.to_i.from_now
- # 1.month.to_i.from_now
- #
- # # equivalent to 365.25.days.to_f.from_now
- # 1.year.to_f.from_now
- #
- # In such cases, Ruby's core
- # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
- # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
- # date and time arithmetic.
def seconds
ActiveSupport::Duration.new(self, [[:seconds, self]])
end
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 38e43478df..039c50a4a2 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,12 +1,10 @@
-# encoding: utf-8
-
class Object
# An object is blank if it's false, empty, or a whitespace string.
- # For example, '', ' ', +nil+, [], and {} are all blank.
+ # For example, +false+, '', ' ', +nil+, [], and {} are all blank.
#
# This simplifies
#
- # address.nil? || address.empty?
+ # !address || address.empty?
#
# to
#
@@ -129,3 +127,14 @@ class Numeric #:nodoc:
false
end
end
+
+class Time #:nodoc:
+ # No Time is blank:
+ #
+ # Time.now.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index 0191d2e973..8dfeed0066 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -39,8 +39,15 @@ class Hash
# hash[:a][:c] # => nil
# dup[:a][:c] # => "c"
def deep_dup
- each_with_object(dup) do |(key, value), hash|
- hash[key.deep_dup] = value.deep_dup
+ hash = dup
+ each_pair do |key, value|
+ if key.frozen? && ::String === key
+ hash[key] = value.deep_dup
+ else
+ hash.delete(key)
+ hash[key.deep_dup] = value.deep_dup
+ end
end
+ hash
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index 620f7b6561..befa5aee21 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbol, number objects;
+ # False for +nil+, +false+, +true+, symbol, number, method objects;
# true otherwise.
def duplicable?
true
@@ -78,6 +78,10 @@ end
require 'bigdecimal'
class BigDecimal
+ # BigDecimals are duplicable:
+ #
+ # BigDecimal.new("1.2").duplicable? # => true
+ # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index 55f281b213..d4c17dfb07 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -5,7 +5,7 @@ class Object
# characters = ["Konata", "Kagami", "Tsukasa"]
# "Konata".in?(characters) # => true
#
- # This will throw an ArgumentError if the argument doesn't respond
+ # This will throw an +ArgumentError+ if the argument doesn't respond
# to +#include?+.
def in?(another_object)
another_object.include?(self)
@@ -18,7 +18,7 @@ class Object
#
# params[:bucket_type].presence_in %w( project calendar )
#
- # This will throw an ArgumentError if the argument doesn't respond to +#include?+.
+ # This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+.
#
# @return [Object]
def presence_in(another_object)
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 698b2d1920..0db787010c 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -9,7 +9,6 @@ require 'time'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/date_time/conversions'
require 'active_support/core_ext/date/conversions'
-require 'active_support/core_ext/module/aliasing'
# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting
# their default behavior. That said, we need to define the basic to_json method in all of them,
@@ -26,22 +25,25 @@ require 'active_support/core_ext/module/aliasing'
# bypassed completely. This means that as_json won't be invoked and the JSON gem will simply
# ignore any options it does not natively understand. This also means that ::JSON.{generate,dump}
# should give exactly the same results with or without active support.
-[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].each do |klass|
- klass.class_eval do
- def to_json_with_active_support_encoder(options = nil)
+
+module ActiveSupport
+ module ToJsonWithActiveSupportEncoder # :nodoc:
+ def to_json(options = nil)
if options.is_a?(::JSON::State)
# Called from JSON.{generate,dump}, forward it to JSON gem's to_json
- self.to_json_without_active_support_encoder(options)
+ super(options)
else
# to_json is being invoked directly, use ActiveSupport's encoder
ActiveSupport::JSON.encode(self, options)
end
end
-
- alias_method_chain :to_json, :active_support_encoder
end
end
+[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].reverse_each do |klass|
+ klass.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder)
+end
+
class Object
def as_json(options = nil) #:nodoc:
if respond_to?(:to_hash)
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index e0f70b9caa..8c16d95b62 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -1,4 +1,34 @@
+require 'delegate'
+
+module ActiveSupport
+ module Tryable #:nodoc:
+ def try(*a, &b)
+ try!(*a, &b) if a.empty? || respond_to?(a.first)
+ end
+
+ def try!(*a, &b)
+ if a.empty? && block_given?
+ if b.arity == 0
+ instance_eval(&b)
+ else
+ yield self
+ end
+ else
+ public_send(*a, &b)
+ end
+ end
+ end
+end
+
class Object
+ include ActiveSupport::Tryable
+
+ ##
+ # :method: try
+ #
+ # :call-seq:
+ # try(*a, &b)
+ #
# Invokes the public method whose name goes as first argument just like
# +public_send+ does, except that if the receiver does not respond to it the
# call returns +nil+ rather than raising an exception.
@@ -56,30 +86,40 @@ class Object
#
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
# with instances of classes that do not have +Object+ among their ancestors,
- # like direct subclasses of +BasicObject+. For example, using +try+ with
- # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on
- # the delegator itself.
- def try(*a, &b)
- try!(*a, &b) if a.empty? || respond_to?(a.first)
- end
+ # like direct subclasses of +BasicObject+.
- # Same as #try, but raises a NoMethodError exception if the receiver is
+ ##
+ # :method: try!
+ #
+ # :call-seq:
+ # try!(*a, &b)
+ #
+ # Same as #try, but raises a +NoMethodError+ exception if the receiver is
# not +nil+ and does not implement the tried method.
#
# "a".try!(:upcase) # => "A"
# nil.try!(:upcase) # => nil
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
- def try!(*a, &b)
- if a.empty? && block_given?
- if b.arity.zero?
- instance_eval(&b)
- else
- yield self
- end
- else
- public_send(*a, &b)
- end
- end
+end
+
+class Delegator
+ include ActiveSupport::Tryable
+
+ ##
+ # :method: try
+ #
+ # :call-seq:
+ # try(a*, &b)
+ #
+ # See Object#try
+
+ ##
+ # :method: try!
+ #
+ # :call-seq:
+ # try!(a*, &b)
+ #
+ # See Object#try!
end
class NilClass
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index 7d38e1d134..513c8b1d55 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -7,7 +7,7 @@ class Object
# provided. Each method called on the block variable must take an options
# hash as its final argument.
#
- # Without <tt>with_options></tt>, this code contains duplication:
+ # Without <tt>with_options</tt>, this code contains duplication:
#
# class Account < ActiveRecord::Base
# has_many :customers, dependent: :destroy
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/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb
index ecef78f55f..dc6dad5ced 100644
--- a/activesupport/lib/active_support/core_ext/range/each.rb
+++ b/activesupport/lib/active_support/core_ext/range/each.rb
@@ -1,23 +1,21 @@
-require 'active_support/core_ext/module/aliasing'
+module ActiveSupport
+ module EachTimeWithZone #:nodoc:
+ def each(&block)
+ ensure_iteration_allowed
+ super
+ end
-class Range #:nodoc:
+ def step(n = 1, &block)
+ ensure_iteration_allowed
+ super
+ end
- def each_with_time_with_zone(&block)
- ensure_iteration_allowed
- each_without_time_with_zone(&block)
- end
- alias_method_chain :each, :time_with_zone
+ private
- def step_with_time_with_zone(n = 1, &block)
- ensure_iteration_allowed
- step_without_time_with_zone(n, &block)
- end
- alias_method_chain :step, :time_with_zone
-
- private
- def ensure_iteration_allowed
- if first.is_a?(Time)
- raise TypeError, "can't iterate from #{first.class}"
- end
+ def ensure_iteration_allowed
+ raise TypeError, "can't iterate from #{first.class}" if first.is_a?(Time)
+ end
end
end
+
+Range.prepend(ActiveSupport::EachTimeWithZone)
diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb
index 3a07401c8a..c69e1e3fb9 100644
--- a/activesupport/lib/active_support/core_ext/range/include_range.rb
+++ b/activesupport/lib/active_support/core_ext/range/include_range.rb
@@ -1,23 +1,23 @@
-require 'active_support/core_ext/module/aliasing'
-
-class Range
- # Extends the default Range#include? to support range comparisons.
- # (1..5).include?(1..5) # => true
- # (1..5).include?(2..3) # => true
- # (1..5).include?(2..6) # => false
- #
- # The native Range#include? behavior is untouched.
- # ('a'..'f').include?('c') # => true
- # (5..9).include?(11) # => false
- def include_with_range?(value)
- if value.is_a?(::Range)
- # 1...10 includes 1..9 but it does not include 1..10.
- operator = exclude_end? && !value.exclude_end? ? :< : :<=
- include_without_range?(value.first) && value.last.send(operator, last)
- else
- include_without_range?(value)
+module ActiveSupport
+ module IncludeWithRange #:nodoc:
+ # Extends the default Range#include? to support range comparisons.
+ # (1..5).include?(1..5) # => true
+ # (1..5).include?(2..3) # => true
+ # (1..5).include?(2..6) # => false
+ #
+ # The native Range#include? behavior is untouched.
+ # ('a'..'f').include?('c') # => true
+ # (5..9).include?(11) # => false
+ def include?(value)
+ if value.is_a?(::Range)
+ # 1...10 includes 1..9 but it does not include 1..10.
+ operator = exclude_end? && !value.exclude_end? ? :< : :<=
+ super(value.first) && value.last.send(operator, last)
+ else
+ super
+ end
end
end
-
- alias_method_chain :include?, :range
end
+
+Range.prepend(ActiveSupport::IncludeWithRange)
diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
index 6cdbea1f37..98cf7430f7 100644
--- a/activesupport/lib/active_support/core_ext/securerandom.rb
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -10,8 +10,8 @@ module SecureRandom
#
# The result may contain alphanumeric characters except 0, O, I and l
#
- # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE"
- # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7"
+ # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
+ # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
#
def self.base58(n = 16)
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 3e0cb8a7ac..fd79a40e31 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -14,7 +14,7 @@ class String
# "06:12".to_time # => 2012-12-13 06:12:00 +0100
# "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
# "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
- # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC
+ # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 06:12:00 UTC
# "12/13/2012".to_time # => ArgumentError: argument out of range
def to_time(form = :local)
parts = Date._parse(self, false)
diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb
index 7461d03acc..375ec1aef8 100644
--- a/activesupport/lib/active_support/core_ext/string/filters.rb
+++ b/activesupport/lib/active_support/core_ext/string/filters.rb
@@ -17,9 +17,8 @@ class String
# str.squish! # => "foo bar boo"
# str # => "foo bar boo"
def squish!
- gsub!(/\A[[:space:]]+/, '')
- gsub!(/[[:space:]]+\z/, '')
gsub!(/[[:space:]]+/, ' ')
+ strip!
self
end
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 97f9720b2b..cc71b8155d 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -164,15 +164,33 @@ class String
#
# <%= link_to(@person.name, person_path) %>
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
- def parameterize(sep = '-')
- ActiveSupport::Inflector.parameterize(self, sep)
+ #
+ # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ #
+ # class Person
+ # def to_param
+ # "#{id}-#{name.parameterize(preserve_case: true)}"
+ # end
+ # end
+ #
+ # @person = Person.find(1)
+ # # => #<Person id: 1, name: "Donald E. Knuth">
+ #
+ # <%= link_to(@person.name, person_path) %>
+ # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a>
+ def parameterize(sep = :unused, separator: '-', preserve_case: false)
+ unless sep == :unused
+ ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.")
+ separator = sep
+ end
+ ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case)
end
# Creates the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
#
# 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
- # 'egg_and_ham'.tableize # => "egg_and_hams"
+ # 'ham_and_egg'.tableize # => "ham_and_eggs"
# 'fancyCategory'.tableize # => "fancy_categories"
def tableize
ActiveSupport::Inflector.tableize(self)
@@ -182,7 +200,7 @@ class String
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
- # 'egg_and_hams'.classify # => "EggAndHam"
+ # 'ham_and_eggs'.classify # => "HamAndEgg"
# 'posts'.classify # => "Post"
def classify
ActiveSupport::Inflector.classify(self)
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 7055f7f699..cc6f2158e7 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -9,12 +9,10 @@ class String
# encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
# class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
#
- # name = 'Claus Müller'
- # name.reverse # => "rell??M sualC"
- # name.length # => 13
- #
- # name.mb_chars.reverse.to_s # => "rellüM sualC"
- # name.mb_chars.length # => 12
+ # >> "lj".upcase
+ # => "lj"
+ # >> "lj".mb_chars.upcase.to_s
+ # => "LJ"
#
# == Method chaining
#
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index bae4e206e6..510fa48189 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -13,7 +13,7 @@ class ERB
# This method is also aliased as <tt>h</tt>.
#
# In your ERB templates, use this method to escape any unsafe content. For example:
- # <%=h @person.name %>
+ # <%= h @person.name %>
#
# puts html_escape('is a > 0 & a < 10?')
# # => is a &gt; 0 &amp; a &lt; 10?
@@ -37,7 +37,7 @@ class ERB
if s.html_safe?
s
else
- s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
+ ActiveSupport::Multibyte::Unicode.tidy_bytes(s).gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
end
end
module_function :unwrapped_html_escape
@@ -50,7 +50,7 @@ class ERB
# html_escape_once('&lt;&lt; Accept & Checkout')
# # => "&lt;&lt; Accept &amp; Checkout"
def html_escape_once(s)
- result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
+ result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
s.html_safe? ? result.html_safe : result
end
@@ -86,7 +86,7 @@ class ERB
# use inside HTML attributes.
#
# If your JSON is being used downstream for insertion into the DOM, be aware of
- # whether or not it is being inserted via +html()+. Most JQuery plugins do this.
+ # whether or not it is being inserted via +html()+. Most jQuery plugins do this.
# If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
# content returned by your JSON.
#
diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb
index 086c610976..55b9b87352 100644
--- a/activesupport/lib/active_support/core_ext/string/strip.rb
+++ b/activesupport/lib/active_support/core_ext/string/strip.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/try'
-
class String
# Strips indentation in heredocs.
#
@@ -17,10 +15,9 @@ class String
#
# the user would see the usage message aligned against the left margin.
#
- # Technically, it looks for the least indented line in the whole string, and removes
- # that amount of leading whitespace.
+ # Technically, it looks for the least indented non-empty line
+ # in the whole string, and removes that amount of leading whitespace.
def strip_heredoc
- indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
- gsub(/^[ \t]{#{indent}}/, '')
+ gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze)
end
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 6f1b653639..768c9a1b2c 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/time/conversions'
require 'active_support/time_with_zone'
require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/date_and_time/calculations'
+require 'active_support/core_ext/date/calculations'
class Time
include DateAndTime::Calculations
@@ -15,9 +16,9 @@ class Time
super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone))
end
- # Return the number of days in the given month.
+ # Returns the number of days in the given month.
# If no year is specified, it will use the current year.
- def days_in_month(month, year = now.year)
+ def days_in_month(month, year = current.year)
if month == 2 && ::Date.gregorian_leap?(year)
29
else
@@ -25,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
@@ -50,9 +57,9 @@ class Time
# Returns the number of seconds since 00:00:00.
#
- # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0
- # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296
- # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399
+ # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0.0
+ # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296.0
+ # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399.0
def seconds_since_midnight
to_i - change(:hour => 0).to_i + (usec / 1.0e+6)
end
@@ -98,7 +105,7 @@ class Time
elsif zone
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
else
- raise ArgumentError, 'argument out of range' if new_usec > 999999
+ raise ArgumentError, 'argument out of range' if new_usec >= 1000000
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
end
end
@@ -108,6 +115,12 @@ class Time
# takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>,
# <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>,
# <tt>:seconds</tt>.
+ #
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
@@ -149,7 +162,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
@@ -246,8 +258,10 @@ class Time
# Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances
# can be chronologically compared with a Time
def compare_with_coercion(other)
- # we're avoiding Time#to_datetime cause it's expensive
- if other.is_a?(Time)
+ # we're avoiding Time#to_datetime and Time#to_time because they're expensive
+ if other.class == Time
+ compare_without_coercion(other)
+ elsif other.is_a?(Time)
compare_without_coercion(other.to_time)
else
to_datetime <=> other
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index dbf1f2f373..536c4bf525 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -6,6 +6,7 @@ class Time
:db => '%Y-%m-%d %H:%M:%S',
:number => '%Y%m%d%H%M%S',
:nsec => '%Y%m%d%H%M%S%9N',
+ :usec => '%Y%m%d%H%M%S%6N',
:time => '%H:%M',
:short => '%d %b %H:%M',
:long => '%B %d, %Y %H:%M',
@@ -24,7 +25,7 @@ class Time
#
# This method is aliased to <tt>to_s</tt>.
#
- # time = Time.now # => Thu Jan 18 06:10:17 CST 2007
+ # time = Time.now # => 2007-01-18 06:10:17 -06:00
#
# time.to_formatted_s(:time) # => "06:10"
# time.to_s(:time) # => "06:10"
@@ -55,7 +56,8 @@ class Time
alias_method :to_default_s, :to_s
alias_method :to_s, :to_formatted_s
- # Returns the UTC offset as an +HH:MM formatted string.
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
#
# Time.local(2000).formatted_offset # => "-06:00"
# Time.local(2000).formatted_offset(false) # => "-0600"
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index d683e7c777..877dc84ec8 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -53,7 +53,7 @@ class Time
# Returns a TimeZone instance matching the time zone provided.
# Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
- # Raises an ArgumentError for invalid time zones.
+ # Raises an +ArgumentError+ for invalid time zones.
#
# Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
# Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...>
@@ -65,7 +65,8 @@ class Time
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
time_zone
else
- # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
+ # Look up the timezone based on the identifier (unless we've been
+ # passed a TZInfo::Timezone)
unless time_zone.respond_to?(:period_for_local)
time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
end
diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb
index bfe0832b37..c6c183edd9 100644
--- a/activesupport/lib/active_support/core_ext/uri.rb
+++ b/activesupport/lib/active_support/core_ext/uri.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
require 'uri'
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
parser = URI::Parser.new
@@ -12,7 +10,7 @@ unless str == parser.unescape(parser.escape(str))
# YK: My initial experiments say yes, but let's be sure please
enc = str.encoding
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
- str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
+ str.gsub(escaped) { |match| [match[1, 2].hex].pack('C') }.force_encoding(enc)
end
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 664cc15a29..af18ff746f 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -1,6 +1,6 @@
require 'set'
require 'thread'
-require 'thread_safe'
+require 'concurrent/map'
require 'pathname'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
@@ -12,12 +12,40 @@ require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/core_ext/string/starts_ends_with'
+require "active_support/dependencies/interlock"
require 'active_support/inflector'
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
+ mattr_accessor :interlock
+ self.interlock = Interlock.new
+
+ # :doc:
+
+ # Execute the supplied block without interference from any
+ # concurrent loads.
+ def self.run_interlock
+ Dependencies.interlock.running { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time.
+ def self.load_interlock
+ Dependencies.interlock.loading { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time.
+ def self.unload_interlock
+ Dependencies.interlock.unloading { yield }
+ end
+
+ # :nodoc:
+
# Should we turn on Ruby warnings on the first load of dependent files?
mattr_accessor :warnings_on_first_load
self.warnings_on_first_load = false
@@ -128,7 +156,7 @@ module ActiveSupport #:nodoc:
# Normalize the list of new constants, and add them to the list we will return
new_constants.each do |suffix|
- constants << ([namespace, suffix] - ["Object"]).join("::")
+ constants << ([namespace, suffix] - ["Object"]).join("::".freeze)
end
end
constants
@@ -325,9 +353,11 @@ module ActiveSupport #:nodoc:
def clear
log_call
- loaded.clear
- loading.clear
- remove_unloadable_constants!
+ Dependencies.unload_interlock do
+ loaded.clear
+ loading.clear
+ remove_unloadable_constants!
+ end
end
def require_or_load(file_name, const_path = nil)
@@ -336,39 +366,44 @@ module ActiveSupport #:nodoc:
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
- # Record that we've seen this file *before* loading it to avoid an
- # infinite loop with mutual dependencies.
- loaded << expanded
- loading << expanded
+ Dependencies.load_interlock do
+ # Maybe it got loaded while we were waiting for our lock:
+ return if loaded.include?(expanded)
- begin
- if load?
- log "loading #{file_name}"
+ # Record that we've seen this file *before* loading it to avoid an
+ # infinite loop with mutual dependencies.
+ loaded << expanded
+ loading << expanded
- # Enable warnings if this file has not been loaded before and
- # warnings_on_first_load is set.
- load_args = ["#{file_name}.rb"]
- load_args << const_path unless const_path.nil?
-
- if !warnings_on_first_load or history.include?(expanded)
- result = load_file(*load_args)
+ 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"]
+ load_args << const_path unless const_path.nil?
+
+ if !warnings_on_first_load or history.include?(expanded)
+ result = load_file(*load_args)
+ else
+ enable_warnings { result = load_file(*load_args) }
+ end
else
- enable_warnings { result = load_file(*load_args) }
+ log "requiring #{file_name}"
+ result = require file_name
end
- else
- log "requiring #{file_name}"
- result = require file_name
+ rescue Exception
+ loaded.delete expanded
+ raise
+ ensure
+ loading.pop
end
- rescue Exception
- loaded.delete expanded
- raise
- ensure
- loading.pop
- end
- # Record history *after* loading so first load gets warnings.
- history << expanded
- result
+ # Record history *after* loading so first load gets warnings.
+ history << expanded
+ result
+ end
end
# Is the provided constant path defined?
@@ -386,13 +421,13 @@ module ActiveSupport #:nodoc:
bases.each do |root|
expanded_root = File.expand_path(root)
- next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path
+ next unless expanded_path.start_with?(expanded_root)
- nesting = expanded_path[(expanded_root.size)..-1]
- nesting = nesting[1..-1] if nesting && nesting[0] == ?/
- next if nesting.blank?
+ root_size = expanded_root.size
+ next if expanded_path[root_size] != ?/.freeze
- paths << nesting.camelize
+ nesting = expanded_path[(root_size + 1)..-1]
+ paths << nesting.camelize unless nesting.blank?
end
paths.uniq!
@@ -401,7 +436,7 @@ module ActiveSupport #:nodoc:
# Search for a file in autoload_paths matching the provided suffix.
def search_for_file(path_suffix)
- path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb")
+ path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze)
autoload_paths.each do |root|
path = File.join(root, path_suffix)
@@ -486,7 +521,7 @@ module ActiveSupport #:nodoc:
if file_path
expanded = File.expand_path(file_path)
- expanded.sub!(/\.rb\z/, '')
+ expanded.sub!(/\.rb\z/, ''.freeze)
if loading.include?(expanded)
raise "Circular dependency detected while autoloading constant #{qualified_name}"
@@ -550,7 +585,7 @@ module ActiveSupport #:nodoc:
class ClassCache
def initialize
- @store = ThreadSafe::Cache.new
+ @store = Concurrent::Map.new
end
def empty?
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
new file mode 100644
index 0000000000..fbeb904684
--- /dev/null
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -0,0 +1,47 @@
+require 'active_support/concurrency/share_lock'
+
+module ActiveSupport #:nodoc:
+ module Dependencies #:nodoc:
+ class Interlock
+ def initialize # :nodoc:
+ @lock = ActiveSupport::Concurrency::ShareLock.new
+ end
+
+ def loading
+ @lock.exclusive(purpose: :load, compatible: [:load]) do
+ yield
+ end
+ end
+
+ def unloading
+ @lock.exclusive(purpose: :unload, 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
+ end
+
+ def start_running
+ @lock.start_sharing
+ end
+
+ def done_running
+ @lock.stop_sharing
+ end
+
+ def running
+ @lock.sharing do
+ yield
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 9f9dca8453..28d2d78643 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -38,6 +38,18 @@ module ActiveSupport
silence: ->(message, callstack) {},
}
+ # Behavior module allows to determine how to display deprecation messages.
+ # You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+
+ # constant. Available behaviors are:
+ #
+ # [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>.
+ # [+stderr+] Log all deprecation warnings to +$stderr+.
+ # [+log+] Log all deprecation warnings to +Rails.logger+.
+ # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+.
+ # [+silence+] Do nothing.
+ #
+ # Setting behaviors only affects deprecations that happen after boot time.
+ # For more information you can read the documentation of the +behavior=+ method.
module Behavior
# Whether to print a backtrace along with the warning.
attr_accessor :debug
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index cab8a1b14d..32fe8025fe 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -9,35 +9,61 @@ module ActiveSupport
# module Fred
# extend self
#
- # def foo; end
- # def bar; end
- # def baz; end
+ # def aaa; end
+ # def bbb; end
+ # def ccc; end
+ # def ddd; end
+ # def eee; end
# end
#
- # ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead')
- # # => [:foo, :bar, :baz]
+ # Using the default deprecator:
+ # ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
+ # # => [:aaa, :bbb, :ccc]
#
- # Fred.foo
- # # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1."
+ # Fred.aaa
+ # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.0. (called from irb_binding at (irb):10)
+ # # => nil
#
- # Fred.bar
- # # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)."
+ # Fred.bbb
+ # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.0 (use zzz instead). (called from irb_binding at (irb):11)
+ # # => nil
#
- # Fred.baz
- # # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)."
+ # Fred.ccc
+ # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.0 (use Bar#ccc instead). (called from irb_binding at (irb):12)
+ # # => nil
+ #
+ # Passing in a custom deprecator:
+ # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem')
+ # ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator)
+ # # => [:ddd]
+ #
+ # Fred.ddd
+ # DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15)
+ # # => nil
+ #
+ # Using a custom deprecator directly:
+ # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem')
+ # custom_deprecator.deprecate_methods(Fred, eee: :zzz)
+ # # => [:eee]
+ #
+ # Fred.eee
+ # DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18)
+ # # => nil
def deprecate_methods(target_module, *method_names)
options = method_names.extract_options!
- deprecator = options.delete(:deprecator) || ActiveSupport::Deprecation.instance
+ deprecator = options.delete(:deprecator) || self
method_names += options.keys
- method_names.each do |method_name|
- target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation|
- target_module.send(:define_method, "#{target}_with_deprecation#{punctuation}") do |*args, &block|
+ mod = Module.new do
+ method_names.each do |method_name|
+ define_method(method_name) do |*args, &block|
deprecator.deprecation_warning(method_name, options[method_name])
- send(:"#{target}_without_deprecation#{punctuation}", *args, &block)
+ super(*args, &block)
end
end
end
+
+ target_module.prepend(mod)
end
end
end
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index a03a66b96b..6f0ad445fc 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -20,20 +20,22 @@ module ActiveSupport
private
def method_missing(called, *args, &block)
- warn caller, called, args
+ warn caller_locations, called, args
target.__send__(called, *args, &block)
end
end
- # This DeprecatedObjectProxy transforms object to deprecated object.
+ # DeprecatedObjectProxy transforms an object into a deprecated one. It
+ # takes an object, a deprecation message and optionally a deprecator. The
+ # deprecator defaults to +ActiveSupport::Deprecator+ if none is specified.
#
- # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!")
- # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance)
+ # deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated")
+ # # => #<Object:0x007fb9b34c34b0>
#
- # When someone executes any method except +inspect+ on proxy object this will
- # trigger +warn+ method on +deprecator_instance+.
- #
- # Default deprecator is <tt>ActiveSupport::Deprecation</tt>
+ # deprecated_object.to_s
+ # DEPRECATION WARNING: This object is now deprecated.
+ # (Backtrace)
+ # # => "#<Object:0x007fb9b34c34b0>"
class DeprecatedObjectProxy < DeprecationProxy
def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance)
@object = object
@@ -51,13 +53,16 @@ module ActiveSupport
end
end
- # This DeprecatedInstanceVariableProxy transforms instance variable to
- # deprecated instance variable.
+ # DeprecatedInstanceVariableProxy transforms an instance variable into a
+ # deprecated one. It takes an instance of a class, a method on that class
+ # and an instance variable. It optionally takes a deprecator as the last
+ # argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none
+ # is specified.
#
# class Example
- # def initialize(deprecator)
- # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator)
- # @_request = :a_request
+ # def initialize
+ # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request)
+ # @_request = :special_request
# end
#
# def request
@@ -69,12 +74,17 @@ module ActiveSupport
# end
# end
#
- # When someone execute any method on @request variable this will trigger
- # +warn+ method on +deprecator_instance+ and will fetch <tt>@_request</tt>
- # variable via +request+ method and execute the same method on non-proxy
- # instance variable.
+ # example = Example.new
+ # # => #<Example:0x007fb9b31090b8 @_request=:special_request, @request=:special_request>
+ #
+ # example.old_request.to_s
+ # # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of
+ # @request.to_s
+ # (Bactrace information…)
+ # "special_request"
#
- # Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
+ # example.request.to_s
+ # # => "special_request"
class DeprecatedInstanceVariableProxy < DeprecationProxy
def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance)
@instance = instance
@@ -93,15 +103,23 @@ module ActiveSupport
end
end
- # This DeprecatedConstantProxy transforms constant to deprecated constant.
+ # DeprecatedConstantProxy transforms a constant into a deprecated one. It
+ # takes the names of an old (deprecated) constant and of a new constant
+ # (both in string form) and optionally a deprecator. The deprecator defaults
+ # to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant
+ # now returns the value of the new one.
+ #
+ # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
#
- # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST')
- # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance)
+ # (In a later update, the original implementation of `PLANETS` has been removed.)
#
- # When someone use old constant this will trigger +warn+ method on
- # +deprecator_instance+.
+ # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
+ # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
#
- # Default deprecator is <tt>ActiveSupport::Deprecation</tt>.
+ # PLANETS.map { |planet| planet.capitalize }
+ # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
+ # (Bactrace information…)
+ # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
@old_const = old_const
@@ -109,6 +127,11 @@ module ActiveSupport
@deprecator = deprecator
end
+ # Returns the class of the new constant.
+ #
+ # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
+ # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
+ # PLANETS.class # => Array
def class
target.class
end
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index a7d265d732..f89fc0fe14 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -14,7 +14,7 @@ module ActiveSupport
def warn(message = nil, callstack = nil)
return if silenced
- callstack ||= caller(2)
+ callstack ||= caller_locations(2)
deprecation_message(callstack, message).tap do |m|
behavior.each { |b| b.call(m, callstack) }
end
@@ -37,7 +37,7 @@ module ActiveSupport
end
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
- caller_backtrace ||= caller(2)
+ caller_backtrace ||= caller_locations(2)
deprecated_method_warning(deprecated_method_name, message).tap do |msg|
warn(msg, caller_backtrace)
end
@@ -79,6 +79,17 @@ module ActiveSupport
end
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)
+ } || 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
if offending_line
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 5a64fc52cc..c63b61e97a 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -52,10 +52,38 @@ module ActiveSupport
end
end
+ # Returns the amount of seconds a duration covers as a string.
+ # For more information check to_i method.
+ #
+ # 1.day.to_s # => "86400"
def to_s
@value.to_s
end
+ # Returns the number of seconds that this Duration represents.
+ #
+ # 1.minute.to_i # => 60
+ # 1.hour.to_i # => 3600
+ # 1.day.to_i # => 86400
+ #
+ # Note that this conversion makes some assumptions about the
+ # duration of some periods, e.g. months are always 30 days
+ # and years are 365.25 days:
+ #
+ # # equivalent to 30.days.to_i
+ # 1.month.to_i # => 2592000
+ #
+ # # equivalent to 365.25.days.to_i
+ # 1.year.to_i # => 31557600
+ #
+ # In such cases, Ruby's core
+ # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and
+ # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision
+ # date and time arithmetic.
+ def to_i
+ @value.to_i
+ end
+
# Returns +true+ if +other+ is also a Duration instance, which has the
# same parts as this one.
def eql?(other)
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..315be85fb3
--- /dev/null
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -0,0 +1,150 @@
+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 { require 'listen' }
+ Listen.to(*dtw, &method(:changed)).start
+ end
+ end
+
+ def updated?
+ @updated.true?
+ end
+
+ def execute
+ @updated.make_false
+ @block.call
+ end
+
+ def execute_if_updated
+ if updated?
+ execute
+ true
+ end
+ end
+
+ private
+
+ def changed(modified, added, removed)
+ unless updated?
+ @updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
+ end
+ end
+
+ def watching?(file)
+ file = @ph.xpath(file)
+
+ if @files.member?(file)
+ true
+ elsif file.directory?
+ false
+ else
+ ext = @ph.normalize_extension(file.extname)
+
+ file.dirname.ascend do |dir|
+ if @dirs.fetch(dir, []).include?(ext)
+ break true
+ elsif dir == @lcsp || dir.root?
+ break false
+ end
+ end
+ end
+ end
+
+ def directories_to_watch
+ dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) }
+ dtw.compact!
+ dtw.uniq!
+
+ @ph.filter_out_descendants(dtw)
+ end
+
+ class PathHelper
+ using Module.new {
+ refine Pathname do
+ def ascendant_of?(other)
+ self != other && other.ascend do |ascendant|
+ break true if self == ascendant
+ end
+ end
+ end
+ }
+
+ def xpath(path)
+ Pathname.new(path).expand_path
+ end
+
+ def normalize_extension(ext)
+ ext.to_s.sub(/\A\./, '')
+ end
+
+ # Given a collection of Pathname objects returns the longest subpath
+ # common to all of them, or +nil+ if there is none.
+ def longest_common_subpath(paths)
+ return if paths.empty?
+
+ lcsp = Pathname.new(paths[0])
+
+ paths[1..-1].each do |path|
+ until lcsp.ascendant_of?(path)
+ if lcsp.root?
+ # If we get here a root directory is not an ascendant of path.
+ # This may happen if there are paths in different drives on
+ # Windows.
+ return
+ else
+ lcsp = lcsp.parent
+ end
+ end
+ end
+
+ lcsp
+ end
+
+ # Returns the deepest existing ascendant, which could be the argument itself.
+ def existing_parent(dir)
+ dir.ascend do |ascendant|
+ break ascendant if ascendant.directory?
+ end
+ end
+
+ # Filters out directories which are descendants of others in the collection (stable).
+ def filter_out_descendants(dirs)
+ return dirs if dirs.length < 2
+
+ dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
+ descendants = []
+
+ until dirs_sorted_by_nparts.empty?
+ dir = dirs_sorted_by_nparts.shift
+
+ dirs_sorted_by_nparts.reject! do |possible_descendant|
+ dir.ascendant_of?(possible_descendant) && descendants << possible_descendant
+ end
+ end
+
+ # Array#- preserves order.
+ dirs - descendants
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 78b627c286..1fa9335080 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -35,7 +35,7 @@ module ActiveSupport
# This method must also receive a block that will be called once a path
# changes. The array of files and list of directories cannot be changed
# after FileUpdateChecker has been initialized.
- def initialize(files, dirs={}, &block)
+ def initialize(files, dirs = {}, &block)
@files = files.freeze
@glob = compile_glob(dirs)
@block = block
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 7068f09d87..7790a9b2c0 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -1,5 +1,5 @@
module ActiveSupport
- # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>.
def self.gem_version
Gem::Version.new VERSION::STRING
end
@@ -8,7 +8,7 @@ module ActiveSupport
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 4f71f13971..4ff35a45a1 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -59,6 +59,10 @@ module ActiveSupport
if constructor.respond_to?(:to_hash)
super()
update(constructor)
+
+ hash = constructor.to_hash
+ self.default = hash.default if hash.default
+ self.default_proc = hash.default_proc if hash.default_proc
else
super(constructor)
end
@@ -73,11 +77,12 @@ module ActiveSupport
end
def self.new_from_hash_copying_default(hash)
- hash = hash.to_hash
- new(hash).tap do |new_hash|
- new_hash.default = hash.default
- new_hash.default_proc = hash.default_proc if hash.default_proc
- end
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`
+ has been deprecated, and will be removed in Rails 5.1. The behavior of
+ this method is now identical to the behavior of `.new`.
+ MSG
+ new(hash)
end
def self.[](*args)
@@ -92,7 +97,7 @@ module ActiveSupport
# hash = ActiveSupport::HashWithIndifferentAccess.new
# hash[:key] = 'value'
#
- # This value can be later fetched using either +:key+ or +'key'+.
+ # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
def []=(key, value)
regular_writer(convert_key(key), convert_value(value, for: :assignment))
end
@@ -188,7 +193,7 @@ module ActiveSupport
# dup[:a][:c] # => "c"
def dup
self.class.new(self).tap do |new_hash|
- new_hash.default = default
+ set_defaults(new_hash)
end
end
@@ -206,7 +211,7 @@ module ActiveSupport
# hash['a'] = nil
# hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
def reverse_merge(other_hash)
- super(self.class.new_from_hash_copying_default(other_hash))
+ super(self.class.new(other_hash))
end
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
@@ -219,7 +224,7 @@ module ActiveSupport
# h = { "a" => 100, "b" => 200 }
# h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
def replace(other_hash)
- super(self.class.new_from_hash_copying_default(other_hash))
+ super(self.class.new(other_hash))
end
# Removes the specified key from the hash.
@@ -238,16 +243,20 @@ module ActiveSupport
def to_options!; self end
def select(*args, &block)
+ return to_enum(:select) unless block_given?
dup.tap { |hash| hash.select!(*args, &block) }
end
def reject(*args, &block)
+ return to_enum(:reject) unless block_given?
dup.tap { |hash| hash.reject!(*args, &block) }
end
# Convert to a regular hash with string keys.
def to_hash
- _new_hash = Hash.new(default)
+ _new_hash = Hash.new
+ set_defaults(_new_hash)
+
each do |key, value|
_new_hash[key] = convert_value(value, for: :to_hash)
end
@@ -275,6 +284,14 @@ module ActiveSupport
value
end
end
+
+ def set_defaults(target)
+ if default_proc
+ target.default_proc = default_proc.dup
+ else
+ target.default = default
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 95f3f6255a..82aacf3b24 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -37,10 +37,12 @@ module I18n
enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
I18n.enforce_available_locales = false
+ reloadable_paths = []
app.config.i18n.each do |setting, value|
case setting
when :railties_load_path
- app.config.i18n.load_path.unshift(*value)
+ reloadable_paths = value
+ app.config.i18n.load_path.unshift(*value.map(&:existent).flatten)
when :load_path
I18n.load_path += value
else
@@ -53,7 +55,14 @@ module I18n
# Restore available locales check so it will take place from now on.
I18n.enforce_available_locales = enforce_available_locales
- reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
+ directories = watched_dirs_with_extensions(reloadable_paths)
+ 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
+
+ I18n.reload!
+ end
+
app.reloaders << reloader
ActionDispatch::Reloader.to_prepare do
reloader.execute_if_updated
@@ -96,5 +105,11 @@ module I18n
raise "Unexpected fallback type #{fallbacks.inspect}"
end
end
+
+ def self.watched_dirs_with_extensions(paths)
+ paths.each_with_object({}) do |path, result|
+ result[path.absolute_current] = path.extensions
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 486838bd15..f3e52b48ac 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,4 +1,4 @@
-require 'thread_safe'
+require 'concurrent/map'
require 'active_support/core_ext/array/prepend_and_append'
require 'active_support/i18n'
@@ -25,7 +25,38 @@ module ActiveSupport
# singularization rules that is runs. This guarantees that your rules run
# before any of the rules that may already have been loaded.
class Inflections
- @__instance__ = ThreadSafe::Cache.new
+ @__instance__ = Concurrent::Map.new
+
+ class Uncountables < Array
+ def initialize
+ @regex_array = []
+ super
+ end
+
+ def delete(entry)
+ super entry
+ @regex_array.delete(to_regex(entry))
+ end
+
+ def <<(*word)
+ add(word)
+ end
+
+ def add(words)
+ self.concat(words.flatten.map(&:downcase))
+ @regex_array += self.map {|word| to_regex(word) }
+ self
+ end
+
+ def uncountable?(str)
+ @regex_array.any? { |regex| regex === str }
+ end
+
+ private
+ def to_regex(string)
+ /\b#{::Regexp.escape(string)}\Z/i
+ end
+ end
def self.instance(locale = :en)
@__instance__[locale] ||= new
@@ -34,7 +65,7 @@ module ActiveSupport
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
def initialize
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
+ @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
end
# Private, for the test suite.
@@ -160,7 +191,7 @@ module ActiveSupport
# uncountable 'money', 'information'
# uncountable %w( money information rice )
def uncountable(*words)
- @uncountables += words.flatten.map(&:downcase)
+ @uncountables.add(words)
end
# Specifies a humanized form of a string by a regular expression rule or
@@ -185,7 +216,7 @@ module ActiveSupport
def clear(scope = :all)
case scope
when :all
- @plurals, @singulars, @uncountables, @humans = [], [], [], []
+ @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
else
instance_variable_set "@#{scope}", []
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index fe8a2ac9ba..595b0339cc 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
require 'active_support/inflections'
module ActiveSupport
@@ -68,12 +66,12 @@ module ActiveSupport
def camelize(term, uppercase_first_letter = true)
string = term.to_s
if uppercase_first_letter
- string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
+ string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
else
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
+ string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
end
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
- string.gsub!(/\//, '::')
+ string.gsub!('/'.freeze, '::'.freeze)
string
end
@@ -90,11 +88,11 @@ module ActiveSupport
# camelize(underscore('SSLError')) # => "SslError"
def underscore(camel_cased_word)
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
- word = camel_cased_word.to_s.gsub(/::/, '/')
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
- word.tr!("-", "_")
+ word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
+ word.tr!("-".freeze, "_".freeze)
word.downcase!
word
end
@@ -127,9 +125,9 @@ module ActiveSupport
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
- result.sub!(/\A_+/, '')
- result.sub!(/_id\z/, '')
- result.tr!('_', ' ')
+ result.sub!(/\A_+/, ''.freeze)
+ result.sub!(/_id\z/, ''.freeze)
+ result.tr!('_'.freeze, ' '.freeze)
result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
@@ -153,14 +151,14 @@ module ActiveSupport
# titleize('TheManWithoutAPast') # => "The Man Without A Past"
# titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
def titleize(word)
- humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
+ humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }
end
# Creates the name of a table like Rails does for models to table names.
# This method uses the #pluralize method on the last word in the string.
#
# tableize('RawScaledScorer') # => "raw_scaled_scorers"
- # tableize('egg_and_ham') # => "egg_and_hams"
+ # tableize('ham_and_egg') # => "ham_and_eggs"
# tableize('fancyCategory') # => "fancy_categories"
def tableize(class_name)
pluralize(underscore(class_name))
@@ -170,7 +168,7 @@ module ActiveSupport
# names to models. Note that this returns a string and not a Class (To
# convert to an actual class follow +classify+ with #constantize).
#
- # classify('egg_and_hams') # => "EggAndHam"
+ # classify('ham_and_eggs') # => "HamAndEgg"
# classify('posts') # => "Post"
#
# Singular names are not handled correctly:
@@ -178,14 +176,14 @@ module ActiveSupport
# classify('calculus') # => "Calculu"
def classify(table_name)
# strip out any leading schema name
- camelize(singularize(table_name.to_s.sub(/.*\./, '')))
+ camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
end
# Replaces underscores with dashes in the string.
#
# dasherize('puni_puni') # => "puni-puni"
def dasherize(underscored_word)
- underscored_word.tr('_', '-')
+ underscored_word.tr('_'.freeze, '-'.freeze)
end
# Removes the module part from the expression in the string.
@@ -231,8 +229,8 @@ module ActiveSupport
# Tries to find a constant with the name specified in the argument string.
#
- # 'Module'.constantize # => Module
- # 'Test::Unit'.constantize # => Test::Unit
+ # 'Module'.constantize # => Module
+ # 'Foo::Bar'.constantize # => Foo::Bar
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
@@ -248,7 +246,7 @@ module ActiveSupport
# NameError is raised when the name is not in CamelCase or the constant is
# unknown.
def constantize(camel_cased_word)
- names = camel_cased_word.split('::')
+ names = camel_cased_word.split('::'.freeze)
# Trigger a built-in NameError exception including the ill-formed constant in the message.
Object.const_get(camel_cased_word) if names.empty?
@@ -280,8 +278,8 @@ module ActiveSupport
# Tries to find a constant with the name specified in the argument string.
#
- # safe_constantize('Module') # => Module
- # safe_constantize('Test::Unit') # => Test::Unit
+ # safe_constantize('Module') # => Module
+ # safe_constantize('Foo::Bar') # => Foo::Bar
#
# The name is assumed to be the one of a top-level constant, no matter
# whether it starts with "::" or not. No lexical context is taken into
@@ -354,7 +352,7 @@ module ActiveSupport
# const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
# const_regexp("::") # => "::"
def const_regexp(camel_cased_word) #:nodoc:
- parts = camel_cased_word.split("::")
+ parts = camel_cased_word.split("::".freeze)
return Regexp.escape(camel_cased_word) if parts.blank?
@@ -372,7 +370,7 @@ module ActiveSupport
def apply_inflections(word, rules)
result = word.to_s.dup
- if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
+ if word.empty? || inflections.uncountables.uncountable?(result)
result
else
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index edea142e82..871cfb8a72 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/core_ext/string/multibyte'
require 'active_support/i18n'
@@ -58,7 +57,7 @@ module ActiveSupport
# I18n.locale = :de
# transliterate('Jürgen')
# # => "Juergen"
- def transliterate(string, replacement = "?")
+ def transliterate(string, replacement = "?".freeze)
I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
:replacement => replacement)
@@ -69,19 +68,45 @@ module ActiveSupport
#
# parameterize("Donald E. Knuth") # => "donald-e-knuth"
# parameterize("^trés|Jolie-- ") # => "tres-jolie"
- def parameterize(string, sep = '-')
- # replace accented chars with their ascii equivalents
+ #
+ # To use a custom separator, override the `separator` argument.
+ #
+ # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
+ # parameterize("^trés|Jolie-- ", separator: '_') # => "tres_jolie"
+ #
+ # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ #
+ # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
+ # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie"
+ #
+ def parameterize(string, sep = :unused, separator: '-', preserve_case: false)
+ unless sep == :unused
+ ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.")
+ separator = sep
+ end
+ # Replace accented chars with their ASCII equivalents.
parameterized_string = transliterate(string)
- # Turn unwanted chars into the separator
- parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep)
- unless sep.nil? || sep.empty?
- re_sep = Regexp.escape(sep)
+
+ # Turn unwanted chars into the separator.
+ parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
+
+ unless separator.nil? || separator.empty?
+ if separator == "-".freeze
+ re_duplicate_separator = /-{2,}/
+ re_leading_trailing_separator = /^-|-$/i
+ else
+ re_sep = Regexp.escape(separator)
+ re_duplicate_separator = /#{re_sep}{2,}/
+ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
+ end
# No more than one of the separator in a row.
- parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
+ parameterized_string.gsub!(re_duplicate_separator, separator)
# Remove leading/trailing separator.
- parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
+ parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze)
end
- parameterized_string.downcase
+
+ parameterized_string.downcase! unless preserve_case
+ parameterized_string
end
end
end
diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb
index 35548f3f56..2932954f03 100644
--- a/activesupport/lib/active_support/json/decoding.rb
+++ b/activesupport/lib/active_support/json/decoding.rb
@@ -9,20 +9,14 @@ module ActiveSupport
module JSON
# matches YAML-formatted dates
DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/
-
+
class << self
# Parses a JSON string (JavaScript Object Notation) into a hash.
# See http://www.json.org for more info.
#
# ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}")
# => {"team" => "rails", "players" => "36"}
- def decode(json, options = {})
- if options.present?
- raise ArgumentError, "In Rails 4.1, ActiveSupport::JSON.decode no longer " \
- "accepts an options hash for MultiJSON. MultiJSON reached its end of life " \
- "and has been removed."
- end
-
+ def decode(json)
data = ::JSON.parse(json, quirks_mode: true)
if ActiveSupport.parse_json_times
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 48f4967892..031c5e9339 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -57,6 +57,10 @@ module ActiveSupport
super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
end
end
+
+ def to_s
+ self
+ end
end
# Mark these as private so we don't leak encoding-specific constructs
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 51d2da3a79..7f73f9ddfc 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -1,8 +1,8 @@
-require 'thread_safe'
+require 'concurrent/map'
require 'openssl'
module ActiveSupport
- # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2
+ # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2.
# It can be used to derive a number of keys for various purposes from a given secret.
# This lets Rails applications have a single secure secret, but avoid reusing that
# key in multiple incompatible contexts.
@@ -24,11 +24,11 @@ module ActiveSupport
# CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
# re-executing the key generation process when it's called using the same salt and
- # key_size
+ # key_size.
class CachingKeyGenerator
def initialize(key_generator)
@key_generator = key_generator
- @cache_keys = ThreadSafe::Cache.new
+ @cache_keys = Concurrent::Map.new
end
# Returns a derived key suitable for use. The default key_size is chosen
diff --git a/activesupport/lib/active_support/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index e95dc5a866..e782cd2d4b 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -95,7 +95,7 @@ module ActiveSupport
METHOD
end
- # Set color by using a string or one of the defined constants. If a third
+ # Set color by using a symbol or one of the defined constants. If a third
# option is set to +true+, it also adds bold to the string. This is based
# on the Highline implementation and will automatically append CLEAR to the
# end of the returned String.
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 75f353f62c..588ed67c81 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -10,7 +10,7 @@ module ActiveSupport
# class SyncLogSubscriberTest < ActiveSupport::TestCase
# include ActiveSupport::LogSubscriber::TestHelper
#
- # def setup
+ # setup do
# ActiveRecord::LogSubscriber.attach_to(:active_record)
# end
#
@@ -33,7 +33,7 @@ module ActiveSupport
# you can collect them doing @logger.logged(level), where level is the level
# used in logging, like info, debug, warn and so on.
module TestHelper
- def setup
+ def setup # :nodoc:
@logger = MockLogger.new
@notifier = ActiveSupport::Notifications::Fanout.new
@@ -44,7 +44,7 @@ module ActiveSupport
ActiveSupport::Notifications.notifier = @notifier
end
- def teardown
+ def teardown # :nodoc:
set_logger(nil)
ActiveSupport::Notifications.notifier = @old_notifier
end
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 33fccdcf95..82117a64d2 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/logger_silence'
require 'logger'
@@ -6,16 +5,18 @@ module ActiveSupport
class Logger < ::Logger
include LoggerSilence
+ attr_accessor :broadcast_messages
+
# Broadcasts logs to multiple loggers.
def self.broadcast(logger) # :nodoc:
Module.new do
define_method(:add) do |*args, &block|
- logger.add(*args, &block)
+ logger.add(*args, &block) if broadcast_messages
super(*args, &block)
end
define_method(:<<) do |x|
- logger << x
+ logger << x if broadcast_messages
super(x)
end
@@ -44,6 +45,7 @@ module ActiveSupport
def initialize(*args)
super
@formatter = SimpleFormatter.new
+ @broadcast_messages = true
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..7d92256f24 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -1,8 +1,9 @@
require 'active_support/concern'
+require 'active_support/core_ext/module/attribute_accessors'
module LoggerSilence
extend ActiveSupport::Concern
-
+
included do
cattr_accessor :silencer
self.silencer = true
@@ -21,4 +22,4 @@ module LoggerSilence
yield self
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 92ab6fe648..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
@@ -82,7 +82,7 @@ module ActiveSupport
def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
+ encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)}
cipher.decrypt
cipher.key = @secret
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index eee9bbaead..854029bf83 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
#
@@ -44,9 +44,9 @@ module ActiveSupport
# tampered_message = signed_message.chop # editing the message invalidates the signature
# verifier.valid_message?(tampered_message) # => false
def valid_message?(signed_message)
- return if signed_message.blank?
+ return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
- data, digest = signed_message.split("--")
+ data, digest = signed_message.split("--".freeze)
data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
end
@@ -74,7 +74,7 @@ module ActiveSupport
def verified(signed_message)
if valid_message?(signed_message)
begin
- data = signed_message.split("--")[0]
+ data = signed_message.split("--".freeze)[0]
@serializer.load(decode(data))
rescue ArgumentError => argument_error
return if argument_error.message =~ %r{invalid base64}
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 3c0cf9f137..707cf200b5 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/json'
require 'active_support/core_ext/string/access'
require 'active_support/core_ext/string/behavior'
@@ -86,10 +85,20 @@ module ActiveSupport #:nodoc:
@wrapped_string.split(*args).map { |i| self.class.new(i) }
end
- # Works like like <tt>String#slice!</tt>, but returns an instance of
- # Chars, or nil if the string was not modified.
+ # Works like <tt>String#slice!</tt>, but returns an instance of
+ # Chars, or nil if the string was not modified. The string will not be
+ # modified if the range given is out of bounds
+ #
+ # string = 'Welcome'
+ # string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c">
+ # string # => 'Welome'
+ # string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo">
+ # string # => 'me'
def slice!(*args)
- chars(@wrapped_string.slice!(*args))
+ string_sliced = @wrapped_string.slice!(*args)
+ if string_sliced
+ chars(string_sliced)
+ end
end
# Reverses all characters in the string.
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 35efebc65f..586002b03b 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
module ActiveSupport
module Multibyte
module Unicode
@@ -11,7 +10,7 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '7.0.0'
+ UNICODE_VERSION = '8.0.0'
# The default normalization used for operations that require
# normalization. It can be set to any of the normalizations
@@ -58,7 +57,7 @@ module ActiveSupport
# Returns a regular expression pattern that matches the passed Unicode
# codepoints.
def self.codepoints_to_pattern(array_of_codepoints) #:nodoc:
- array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|')
+ array_of_codepoints.collect{ |e| [e].pack 'U*'.freeze }.join('|'.freeze)
end
TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u
LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u
@@ -257,7 +256,7 @@ module ActiveSupport
# * <tt>string</tt> - The string to perform normalization on.
# * <tt>form</tt> - The form you want to normalize in. Should be one of
# the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>.
- # Default is ActiveSupport::Multibyte.default_normalization_form.
+ # Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
def normalize(string, form=nil)
form ||= @default_normalization_form
# See http://www.unicode.org/reports/tr15, Table 1
@@ -273,7 +272,7 @@ module ActiveSupport
compose(reorder_characters(decompose(:compatibility, codepoints)))
else
raise ArgumentError, "#{form} is not a valid normalization variant", caller
- end.pack('U*')
+ end.pack('U*'.freeze)
end
def downcase(string)
@@ -338,7 +337,7 @@ module ActiveSupport
end
# Redefine the === method so we can write shorter rules for grapheme cluster breaks
- @boundary.each do |k,_|
+ @boundary.each_key do |k|
@boundary[k].instance_eval do
def ===(other)
detect { |i| i === other } ? true : false
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index b9f8e1ab2c..823d68e507 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -69,8 +69,8 @@ module ActiveSupport
# is able to take the arguments as they come and provide an object-oriented
# interface to that data.
#
- # It is also possible to pass an object as the second parameter passed to the
- # <tt>subscribe</tt> method instead of a block:
+ # It is also possible to pass an object which responds to <tt>call</tt> method
+ # as the second parameter to the <tt>subscribe</tt> method instead of a block:
#
# module ActionController
# class PageRequest
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 6bf8c7d5de..c53f9c1039 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -1,5 +1,5 @@
require 'mutex_m'
-require 'thread_safe'
+require 'concurrent/map'
module ActiveSupport
module Notifications
@@ -12,7 +12,7 @@ module ActiveSupport
def initialize
@subscribers = []
- @listeners_for = ThreadSafe::Cache.new
+ @listeners_for = Concurrent::Map.new
super
end
@@ -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)
@@ -51,7 +51,7 @@ module ActiveSupport
end
def listeners_for(name)
- # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics)
+ # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
@listeners_for[name] || synchronize do
# use synchronisation when accessing @subscribers
@listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
@@ -111,7 +111,7 @@ module ActiveSupport
end
end
- class Timed < Evented
+ class Timed < Evented # :nodoc:
def publish(name, *args)
@delegate.call name, *args
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 075ddc2382..67f2ee1a7f 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -15,14 +15,15 @@ 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]
raise e
ensure
- finish name, payload
+ finish_with_state listeners_state, name, payload
end
end
@@ -36,6 +37,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 258d9b34e1..248521e677 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -115,10 +115,10 @@ module ActiveSupport
# number_to_percentage(100, precision: 0) # => 100%
# number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
- # number_to_percentage(1000, locale: :fr) # => 1 000,000%
- # number_to_percentage:(1000, precision: nil) # => 1000%
+ # number_to_percentage(1000, locale: :fr) # => 1000,000%
+ # number_to_percentage(1000, precision: nil) # => 1000%
# number_to_percentage('98a') # => 98a%
- # number_to_percentage(100, format: '%n %') # => 100 %
+ # number_to_percentage(100, format: '%n %') # => 100.000 %
def number_to_percentage(number, options = {})
NumberToPercentageConverter.convert(number, options)
end
@@ -135,6 +135,9 @@ module ActiveSupport
# to ",").
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for
+ # deriving the placement of delimiter. Helpful when using currency formats
+ # like INR.
#
# ==== Examples
#
@@ -147,7 +150,10 @@ module ActiveSupport
# 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
def number_to_delimited(number, options = {})
NumberToDelimitedConverter.convert(number, options)
end
@@ -220,8 +226,6 @@ module ActiveSupport
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +true+)
- # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
- # prefix (defaults to :binary)
#
# ==== Examples
#
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 ce03700de1..7986eb50f0 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
@@ -13,7 +13,7 @@ module ActiveSupport
end
rounded_number = NumberToRoundedConverter.convert(number, options)
- format.gsub(/%n/, rounded_number).gsub(/%u/, options[:unit])
+ format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, options[:unit])
end
private
@@ -23,7 +23,7 @@ module ActiveSupport
end
def absolute_value(number)
- number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '')
+ number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, '')
end
def options
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 d85cc086d7..45ae8f1a93 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
@@ -3,7 +3,7 @@ module ActiveSupport
class NumberToDelimitedConverter < NumberConverter #:nodoc:
self.validate_float = true
- DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
+ DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
def convert
parts.join(options[:separator])
@@ -13,11 +13,16 @@ module ActiveSupport
def parts
left, right = number.to_s.split('.')
- left.gsub!(DELIMITED_REGEX) do |digit_to_delimit|
+ left.gsub!(delimiter_pattern) do |digit_to_delimit|
"#{digit_to_delimit}#{options[:delimiter]}"
end
[left, right].compact
end
+
+ def delimiter_pattern
+ options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX)
+ end
+
end
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
index 6940beb318..7a1f8171c0 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -20,10 +20,12 @@ module ActiveSupport
exponent = calculate_exponent(units)
@number = number / (10 ** exponent)
+ until (rounded_number = NumberToRoundedConverter.convert(number, options)) != NumberToRoundedConverter.convert(1000, options)
+ @number = number / 1000.0
+ exponent += 3
+ end
unit = determine_unit(units, exponent)
-
- rounded_number = NumberToRoundedConverter.convert(number, options)
- format.gsub(/%n/, rounded_number).gsub(/%u/, unit).strip
+ format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, unit).strip
end
private
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 78d2c9ae6e..a4a8690bcd 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
@@ -7,6 +7,10 @@ module ActiveSupport
self.validate_float = true
def convert
+ if opts.key?(:prefix)
+ ActiveSupport::Deprecation.warn('The :prefix option of `number_to_human_size` is deprecated and will be removed in Rails 5.1 with no replacement.')
+ end
+
@number = Float(number)
# for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
@@ -20,7 +24,7 @@ module ActiveSupport
human_size = number / (base ** exponent)
number_to_format = NumberToRoundedConverter.convert(human_size, options)
end
- conversion_format.gsub(/%n/, number_to_format).gsub(/%u/, unit)
+ conversion_format.gsub('%n'.freeze, number_to_format).gsub('%u'.freeze, unit)
end
private
diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
index 1af294a03e..4c04d40c19 100644
--- a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb
@@ -5,7 +5,7 @@ module ActiveSupport
def convert
rounded_number = NumberToRoundedConverter.convert(number, options)
- options[:format].gsub(/%n/, rounded_number)
+ options[:format].gsub('%n'.freeze, rounded_number)
end
end
end
diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index a33e2c58a9..53a55bd986 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -6,6 +6,7 @@ module ActiveSupport
# h[:girl] = 'Mary'
# h[:boy] # => 'John'
# h[:girl] # => 'Mary'
+ # h[:dog] # => nil
#
# Using +OrderedOptions+, the above code could be reduced to:
#
@@ -14,6 +15,13 @@ module ActiveSupport
# h.girl = 'Mary'
# h.boy # => 'John'
# h.girl # => 'Mary'
+ # h.dog # => nil
+ #
+ # To raise an exception when the value is blank, append a
+ # bang to the key name, like:
+ #
+ # h.dog! # => raises KeyError: key not found: :dog
+ #
class OrderedOptions < Hash
alias_method :_get, :[] # preserve the original #[] method
protected :_get # make it protected
@@ -31,7 +39,13 @@ module ActiveSupport
if name_string.chomp!('=')
self[name_string] = args.first
else
- self[name]
+ bangs = name_string.chomp!('!')
+
+ if bangs
+ fetch(name_string.to_sym).presence || raise(KeyError.new("#{name_string} is blank."))
+ else
+ self[name_string]
+ end
end
end
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index ca2e4d5625..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:
@@ -43,9 +48,9 @@ module ActiveSupport
protected
def method_missing(name, *args, &block) # :nodoc:
# Caches the method definition as a singleton method of the receiver.
- define_singleton_method(name) do |*a, &b|
- instance.public_send(name, *a, &b)
- end
+ #
+ # By letting #delegate handle it, we avoid an enclosure that'll capture args.
+ singleton_class.delegate name, to: :instance
send(name, *args, &block)
end
diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb
index b05c3ff126..c8e3a4bf53 100644
--- a/activesupport/lib/active_support/rails.rb
+++ b/activesupport/lib/active_support/rails.rb
@@ -1,8 +1,8 @@
# This is private interface.
#
# Rails components cherry pick from Active Support as needed, but there are a
-# few features that are used for sure some way or another and it is not worth
-# to put individual requires absolutely everywhere. Think blank? for example.
+# few features that are used for sure in some way or another and it is not worth
+# putting individual requires absolutely everywhere. Think blank? for example.
#
# This file is loaded by every Rails component except Active Support itself,
# but it does not belong to the Rails public interface. It is internal to
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index ef22433491..845788b669 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -13,13 +13,6 @@ module ActiveSupport
end
end
- initializer "active_support.halt_callback_chains_on_return_false", after: :load_config_initializers do |app|
- if app.config.active_support.key? :halt_callback_chains_on_return_false
- ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = \
- app.config.active_support.halt_callback_chains_on_return_false
- end
- end
-
# Sets the default value for Time.zone
# If assigned value cannot be matched to a TimeZone, an exception will be raised.
initializer "active_support.initialize_time_zone" do |app|
@@ -33,7 +26,7 @@ module ActiveSupport
unless zone_default
raise 'Value assigned to config.time_zone not recognized. ' \
- 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
+ 'Run "rake time:zones:all" for a time zone names list.'
end
Time.zone_default = zone_default
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index 67aac32742..fcf5553061 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -68,7 +68,7 @@ module ActiveSupport
raise ArgumentError, "#{klass} is neither an Exception nor a String"
end
- # put the new handler at the end because the list is read in reverse
+ # Put the new handler at the end because the list is read in reverse.
self.rescue_handlers += [[key, options[:with]]]
end
end
diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb
index cd40284660..1cd4b807ad 100644
--- a/activesupport/lib/active_support/subscriber.rb
+++ b/activesupport/lib/active_support/subscriber.rb
@@ -5,24 +5,19 @@ module ActiveSupport
# ActiveSupport::Notifications. The subscriber dispatches notifications to
# a registered object based on its given namespace.
#
- # An example would be Active Record subscriber responsible for collecting
+ # An example would be an Active Record subscriber responsible for collecting
# statistics about queries:
#
# module ActiveRecord
# class StatsSubscriber < ActiveSupport::Subscriber
+ # attach_to :active_record
+ #
# def sql(event)
# Statsd.timing("sql.#{event.payload[:name]}", event.duration)
# end
# end
# end
#
- # And it's finally registered as:
- #
- # ActiveRecord::StatsSubscriber.attach_to :active_record
- #
- # Since we need to know all instance methods before attaching the log
- # subscriber, the line above should be called after your subscriber definition.
- #
# After configured, whenever a "sql.active_record" notification is published,
# it will properly dispatch the event (ActiveSupport::Notifications::Event) to
# the +sql+ method.
@@ -66,7 +61,7 @@ module ActiveSupport
pattern = "#{event}.#{namespace}"
- # don't add multiple subscribers (eg. if methods are redefined)
+ # Don't add multiple subscribers (eg. if methods are redefined).
return if subscriber.patterns.include?(pattern)
subscriber.patterns << pattern
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index 739823bd56..ae6f00b861 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -9,6 +9,7 @@ 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
@@ -36,17 +37,17 @@ module ActiveSupport
# Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+.
# Defaults to +:random+.
def test_order
- test_order = ActiveSupport.test_order
+ ActiveSupport.test_order ||= :random
+ end
- if test_order.nil?
- test_order = :random
- self.test_order = test_order
+ def run(reporter, options = {})
+ if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ }
+ options[:filter] = \
+ Testing::CompositeFilter.new(self, options[:filter], options[:patterns])
end
- test_order
+ super
end
-
- alias :my_tests_are_order_dependent! :i_suck_and_my_tests_are_order_dependent!
end
alias_method :method_name, :name
@@ -75,7 +76,7 @@ module ActiveSupport
alias :assert_not_respond_to :refute_respond_to
alias :assert_not_same :refute_same
- # Fails if the block raises an exception.
+ # Reveals the intention that the block should not raise any exception.
#
# assert_nothing_raised do
# ...
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 8b649c193f..29305e0082 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/object/blank'
module ActiveSupport
module Testing
module Assertions
- # Assert that an expression is not truthy. Passes if <tt>object</tt> is
+ # Asserts that an expression is not truthy. Passes if <tt>object</tt> is
# +nil+ or +false+. "Truthy" means "considered true in a conditional"
# like <tt>if foo</tt>.
#
@@ -23,42 +23,42 @@ module ActiveSupport
# result of what is evaluated in the yielded block.
#
# assert_difference 'Article.count' do
- # post :create, article: {...}
+ # post :create, params: { article: {...} }
# end
#
# An arbitrary expression is passed in and evaluated.
#
- # assert_difference 'assigns(:article).comments(:reload).size' do
- # post :create, comment: {...}
+ # assert_difference 'Article.last.comments(:reload).size' do
+ # post :create, params: { comment: {...} }
# end
#
# An arbitrary positive or negative difference can be specified.
# The default is <tt>1</tt>.
#
# assert_difference 'Article.count', -1 do
- # post :delete, id: ...
+ # post :delete, params: { id: ... }
# end
#
# An array of expressions can also be passed in and evaluated.
#
# assert_difference [ 'Article.count', 'Post.count' ], 2 do
- # post :create, article: {...}
+ # post :create, params: { article: {...} }
# end
#
# A lambda or a list of lambdas can be passed in and evaluated:
#
# assert_difference ->{ Article.count }, 2 do
- # post :create, article: {...}
+ # post :create, params: { article: {...} }
# end
#
# assert_difference [->{ Article.count }, ->{ Post.count }], 2 do
- # post :create, article: {...}
+ # post :create, params: { article: {...} }
# end
#
# An error message can be specified.
#
# assert_difference 'Article.count', -1, 'An Article should be destroyed' do
- # post :delete, id: ...
+ # post :delete, params: { id: ... }
# end
def assert_difference(expression, difference = 1, message = nil, &block)
expressions = Array(expression)
@@ -68,26 +68,28 @@ module ActiveSupport
}
before = exps.map(&:call)
- yield
+ retval = yield
expressions.zip(exps).each_with_index do |(code, e), i|
error = "#{code.inspect} didn't change by #{difference}"
error = "#{message}.\n#{error}" if message
assert_equal(before[i] + difference, e.call, error)
end
+
+ retval
end
# Assertion that the numeric result of evaluating an expression is not
# changed before and after invoking the passed in block.
#
# assert_no_difference 'Article.count' do
- # post :create, article: invalid_attributes
+ # post :create, params: { article: invalid_attributes }
# end
#
# An error message can be specified.
#
# assert_no_difference 'Article.count', 'An Article should not be created' do
- # post :create, article: invalid_attributes
+ # post :create, params: { article: invalid_attributes }
# end
def assert_no_difference(expression, message = nil, &block)
assert_difference expression, 0, message, &block
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
index 5aa5f46310..84c6b89340 100644
--- a/activesupport/lib/active_support/testing/autorun.rb
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -2,4 +2,11 @@ gem 'minitest'
require 'minitest'
-Minitest.autorun
+if Minitest.respond_to?(:run_with_rails_extension)
+ unless Minitest.run_with_rails_extension
+ Minitest.run_with_autorun = true
+ Minitest.autorun
+ end
+else
+ Minitest.autorun
+end
diff --git a/activesupport/lib/active_support/testing/composite_filter.rb b/activesupport/lib/active_support/testing/composite_filter.rb
new file mode 100644
index 0000000000..bde723e30b
--- /dev/null
+++ b/activesupport/lib/active_support/testing/composite_filter.rb
@@ -0,0 +1,54 @@
+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/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb
index 6c94c611b6..5dfa14eeba 100644
--- a/activesupport/lib/active_support/testing/deprecation.rb
+++ b/activesupport/lib/active_support/testing/deprecation.rb
@@ -3,8 +3,8 @@ require 'active_support/deprecation'
module ActiveSupport
module Testing
module Deprecation #:nodoc:
- def assert_deprecated(match = nil, &block)
- result, warnings = collect_deprecations(&block)
+ def assert_deprecated(match = nil, deprecator = nil, &block)
+ result, warnings = collect_deprecations(deprecator, &block)
assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
if match
match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
@@ -13,22 +13,23 @@ module ActiveSupport
result
end
- def assert_not_deprecated(&block)
- result, deprecations = collect_deprecations(&block)
+ def assert_not_deprecated(deprecator = nil, &block)
+ result, deprecations = collect_deprecations(deprecator, &block)
assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
result
end
- def collect_deprecations
- old_behavior = ActiveSupport::Deprecation.behavior
+ def collect_deprecations(deprecator = nil)
+ deprecator ||= ActiveSupport::Deprecation
+ old_behavior = deprecator.behavior
deprecations = []
- ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack|
+ deprecator.behavior = Proc.new do |message, callstack|
deprecations << message
end
result = yield
[result, deprecations]
ensure
- ActiveSupport::Deprecation.behavior = old_behavior
+ deprecator.behavior = old_behavior
end
end
end
diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb
index 4c6a0801b8..affb84cda5 100644
--- a/activesupport/lib/active_support/testing/file_fixtures.rb
+++ b/activesupport/lib/active_support/testing/file_fixtures.rb
@@ -18,7 +18,7 @@ module ActiveSupport
# Returns a +Pathname+ to the fixture file named +fixture_name+.
#
- # Raises ArgumentError if +fixture_name+ can't be found.
+ # Raises +ArgumentError+ if +fixture_name+ can't be found.
def file_fixture(fixture_name)
path = Pathname.new(File.join(file_fixture_path, fixture_name))
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 247df7423b..edf8b30a0a 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -41,7 +41,23 @@ module ActiveSupport
pid = fork do
read.close
yield
- write.puts [Marshal.dump(self.dup)].pack("m")
+ begin
+ if error?
+ failures.map! { |e|
+ begin
+ Marshal.dump e
+ e
+ rescue TypeError
+ ex = Exception.new e.message
+ ex.set_backtrace e.backtrace
+ Minitest::UnexpectedError.new ex
+ end
+ }
+ end
+ result = Marshal.dump(self.dup)
+ end
+
+ write.puts [result].pack("m")
exit!
end
@@ -69,17 +85,17 @@ module ActiveSupport
else
Tempfile.open("isolation") do |tmpfile|
env = {
- ISOLATION_TEST: self.class.name,
- ISOLATION_OUTPUT: tmpfile.path
+ 'ISOLATION_TEST' => self.class.name,
+ 'ISOLATION_OUTPUT' => tmpfile.path
}
load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ")
orig_args = ORIG_ARGV.join(" ")
test_opts = "-n#{self.class.name}##{self.name}"
- command = "#{Gem.ruby} #{load_paths} #{$0} #{orig_args} #{test_opts}"
+ command = "#{Gem.ruby} #{load_paths} #{$0} '#{orig_args}' #{test_opts}"
# IO.popen lets us pass env in a cross-platform way
- child = IO.popen([env, command])
+ child = IO.popen(env, command)
begin
Process.wait(child.pid)
diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb
new file mode 100644
index 0000000000..fccaa54f40
--- /dev/null
+++ b/activesupport/lib/active_support/testing/method_call_assertions.rb
@@ -0,0 +1,41 @@
+require 'minitest/mock'
+
+module ActiveSupport
+ module Testing
+ module MethodCallAssertions # :nodoc:
+ private
+ def assert_called(object, method_name, message = nil, times: 1, returns: nil)
+ times_called = 0
+
+ object.stub(method_name, proc { times_called += 1; returns }) { yield }
+
+ error = "Expected #{method_name} to be called #{times} times, " \
+ "but was called #{times_called} times"
+ error = "#{message}.\n#{error}" if message
+ assert_equal times, times_called, error
+ end
+
+ def assert_called_with(object, method_name, args = [], returns: nil)
+ mock = Minitest::Mock.new
+
+ if args.all? { |arg| arg.is_a?(Array) }
+ args.each { |arg| mock.expect(:call, returns, arg) }
+ else
+ mock.expect(:call, returns, args)
+ end
+
+ object.stub(method_name, mock) { yield }
+
+ mock.verify
+ end
+
+ def assert_not_called(object, method_name, message = nil, &block)
+ assert_called(object, method_name, message, times: 0, &block)
+ end
+
+ def stub_any_instance(klass, instance: klass.new)
+ klass.stub(:new, instance) { yield instance }
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index c9d20cd837..fca0947c5b 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -39,7 +39,7 @@ module ActiveSupport
end
end
- # Containing helpers that helps you test passage of time.
+ # Contains helpers that help you test passage of time.
module TimeHelpers
# Changes current time to the time in the future or in the past by a given time difference by
# stubbing +Time.now+, +Date.today+, and +DateTime.now+.
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index c28de4e21c..79cc748cf5 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,3 +1,4 @@
+require 'active_support/duration'
require 'active_support/values/time_zone'
require 'active_support/core_ext/object/acts_like'
@@ -13,7 +14,7 @@ module ActiveSupport
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
# Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45 EST -05:00
- # Time.zone.at(1170361845) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
+ # Time.zone.at(1171139445) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
# Time.zone.now # => Sun, 18 May 2008 13:07:55 EDT -04:00
# Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -05:00
#
@@ -40,6 +41,9 @@ module ActiveSupport
'Time'
end
+ PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze }
+ PRECISIONS[0] = '%FT%T'.freeze
+
include Comparable
attr_reader :time_zone
@@ -98,7 +102,7 @@ module ActiveSupport
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.now.utc? # => false
def utc?
- time_zone.name == 'UTC'
+ period.offset.abbreviation == :UTC || period.offset.abbreviation == :UCT
end
alias_method :gmt?, :utc?
@@ -131,7 +135,7 @@ module ActiveSupport
# Returns a string of the object's date, time, zone and offset from UTC.
#
- # Time.zone.now.httpdate # => "Thu, 04 Dec 2014 11:00:25 EST -05:00"
+ # Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25 EST -05:00"
def inspect
"#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
end
@@ -141,11 +145,7 @@ module ActiveSupport
#
# Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00"
def xmlschema(fraction_digits = 0)
- fraction = if fraction_digits.to_i > 0
- (".%06i" % time.usec)[0, fraction_digits.to_i + 1]
- end
-
- "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}"
+ "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z'.freeze)}"
end
alias_method :iso8601, :xmlschema
@@ -169,12 +169,13 @@ module ActiveSupport
end
end
- def encode_with(coder)
- if coder.respond_to?(:represent_object)
- coder.represent_object(nil, utc)
- else
- coder.represent_scalar(nil, utc.strftime("%Y-%m-%d %H:%M:%S.%9NZ"))
- end
+ def init_with(coder) #:nodoc:
+ initialize(coder['utc'], coder['zone'], coder['time'])
+ end
+
+ def encode_with(coder) #:nodoc:
+ coder.tag = '!ruby/object:ActiveSupport::TimeWithZone'
+ coder.map = { 'utc' => utc, 'zone' => time_zone, 'time' => time }
end
# Returns a string of the object's date and time in the format used by
@@ -244,8 +245,9 @@ module ActiveSupport
utc.future?
end
+ # Returns +true+ if +other+ is equal to current object.
def eql?(other)
- utc.eql?(other)
+ other.eql?(utc)
end
def hash
@@ -282,8 +284,8 @@ module ActiveSupport
# the current object's time and the +other+ time.
#
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
- # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EST -05:00
- # now - 1000 # => Sun, 02 Nov 2014 01:09:48 EST -05:00
+ # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00
+ # now - 1000 # => Mon, 03 Nov 2014 00:09:48 EST -05:00
#
# If subtracting a Duration of variable length (i.e., years, months, days),
# move backward from #time, otherwise move backward from #utc, for accuracy
@@ -292,8 +294,8 @@ module ActiveSupport
# For instance, a time - 24.hours will go subtract exactly 24 hours, while a
# time - 1.day will subtract 23-25 hours, depending on the day.
#
- # now - 24.hours # => Sat, 01 Nov 2014 02:26:28 EDT -04:00
- # now - 1.day # => Sat, 01 Nov 2014 01:26:28 EDT -04:00
+ # now - 24.hours # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now - 1.day # => Sun, 02 Nov 2014 00:26:28 EDT -04:00
def -(other)
if other.acts_like?(:time)
to_time - other.to_time
@@ -305,10 +307,48 @@ module ActiveSupport
end
end
+ # Subtracts an interval of time from the current object's time and returns
+ # the result as a new TimeWithZone object.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00
+ # now.ago(1000) # => Mon, 03 Nov 2014 00:09:48 EST -05:00
+ #
+ # If we're subtracting a Duration of variable length (i.e., years, months,
+ # days), move backward from #time, otherwise move backward from #utc, for
+ # accuracy when moving across DST boundaries.
+ #
+ # For instance, <tt>time.ago(24.hours)</tt> will move back exactly 24 hours,
+ # while <tt>time.ago(1.day)</tt> will move back 23-25 hours, depending on
+ # the day.
+ #
+ # now.ago(24.hours) # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now.ago(1.day) # => Sun, 02 Nov 2014 00:26:28 EDT -04:00
def ago(other)
since(-other)
end
+ # Uses Date to provide precise Time calculations for years, months, and days
+ # according to the proleptic Gregorian calendar. The result is returned as a
+ # new TimeWithZone object.
+ #
+ # The +options+ parameter takes a hash with any of these keys:
+ # <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>,
+ # <tt>:hours</tt>, <tt>:minutes</tt>, <tt>:seconds</tt>.
+ #
+ # If advancing by a value of variable length (i.e., years, weeks, months,
+ # days), move forward from #time, otherwise move forward from #utc, for
+ # accuracy when moving across DST boundaries.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now.advance(seconds: 1) # => Sun, 02 Nov 2014 01:26:29 EDT -04:00
+ # now.advance(minutes: 1) # => Sun, 02 Nov 2014 01:27:28 EDT -04:00
+ # now.advance(hours: 1) # => Sun, 02 Nov 2014 01:26:28 EST -05:00
+ # now.advance(days: 1) # => Mon, 03 Nov 2014 01:26:28 EST -05:00
+ # now.advance(weeks: 1) # => Sun, 09 Nov 2014 01:26:28 EST -05:00
+ # now.advance(months: 1) # => Tue, 02 Dec 2014 01:26:28 EST -05:00
+ # now.advance(years: 1) # => Mon, 02 Nov 2015 01:26:28 EST -05:00
def advance(options)
# If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time,
# otherwise advance from #utc, for accuracy when moving across DST boundaries
@@ -327,6 +367,11 @@ module ActiveSupport
EOV
end
+ # Returns Array of parts of Time in sequence of
+ # [seconds, minutes, hours, day, month, year, weekday, yearday, dst?, zone].
+ #
+ # now = Time.zone.now # => Tue, 18 Aug 2015 02:29:27 UTC +00:00
+ # now.to_a # => [27, 29, 2, 18, 8, 2015, 2, 230, false, "UTC"]
def to_a
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
end
@@ -356,11 +401,15 @@ module ActiveSupport
utc.to_r
end
- # Return an instance of Time in the system timezone.
+ # 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
+ # Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000
def to_datetime
utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
end
@@ -376,6 +425,11 @@ module ActiveSupport
end
alias_method :kind_of?, :is_a?
+ # An instance of ActiveSupport::TimeWithZone is never blank
+ def blank?
+ false
+ end
+
def freeze
period; utc; time # preload instance variables before freezing
super
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index da39f0d245..7ca3592520 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,5 +1,5 @@
require 'tzinfo'
-require 'thread_safe'
+require 'concurrent/map'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
@@ -23,15 +23,9 @@ module ActiveSupport
# config.time_zone = 'Eastern Time (US & Canada)'
# end
#
- # Time.zone # => #<TimeZone:0x514834...>
+ # Time.zone # => #<ActiveSupport::TimeZone:0x514834...>
# Time.zone.name # => "Eastern Time (US & Canada)"
# Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
- #
- # The version of TZInfo bundled with Active Support only includes the
- # definitions necessary to support the zones defined by the TimeZone class.
- # If you need to use zones that aren't defined by TimeZone, you'll need to
- # install the TZInfo gem (if a recent version of the gem is installed locally,
- # this will be used instead of the bundled version.)
class TimeZone
# Keys are Rails TimeZone names, values are TZInfo identifiers.
MAPPING = {
@@ -92,7 +86,8 @@ module ActiveSupport
"Paris" => "Europe/Paris",
"Amsterdam" => "Europe/Amsterdam",
"Berlin" => "Europe/Berlin",
- "Bern" => "Europe/Berlin",
+ "Bern" => "Europe/Zurich",
+ "Zurich" => "Europe/Zurich",
"Rome" => "Europe/Rome",
"Stockholm" => "Europe/Stockholm",
"Vienna" => "Europe/Vienna",
@@ -189,13 +184,13 @@ module ActiveSupport
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '')
- @lazy_zones_map = ThreadSafe::Cache.new
+ @lazy_zones_map = Concurrent::Map.new
class << self
# Assumes self represents an offset from UTC in seconds (as returned from
# Time#utc_offset) and turns this into an +HH:MM formatted string.
#
- # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
+ # ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
def seconds_to_utc_offset(seconds, colon = true)
format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
sign = (seconds < 0 ? '-' : '+')
@@ -285,8 +280,12 @@ module ActiveSupport
end
end
- # Returns the offset of this time zone as a formatted string, of the
- # format "+HH:MM".
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
+ #
+ # zone = ActiveSupport::TimeZone['Central Time (US & Canada)']
+ # zone.formatted_offset # => "-06:00"
+ # zone.formatted_offset(false) # => "-0600"
def formatted_offset(colon=true, alternate_utc_string = nil)
utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon)
end
@@ -348,24 +347,31 @@ module ActiveSupport
#
# Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
def parse(str, now=now())
- parts = Date._parse(str, false)
- return if parts.empty?
-
- time = Time.new(
- parts.fetch(:year, now.year),
- parts.fetch(:mon, now.month),
- parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day),
- parts.fetch(:hour, 0),
- parts.fetch(:min, 0),
- parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
- parts.fetch(:offset, 0)
- )
-
- if parts[:offset]
- TimeWithZone.new(time.utc, self)
- else
- TimeWithZone.new(nil, self, time)
- end
+ parts_to_time(Date._parse(str, false), now)
+ end
+
+ # Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone.
+ #
+ # Assumes that +str+ is a time in the time zone +self+,
+ # unless +format+ includes an explicit time zone.
+ # (This is the same behavior as +parse+.)
+ # In either case, the returned TimeWithZone has the timezone of +self+.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.strptime('1999-12-31 14:00:00', '%Y-%m-%d %H:%M:%S') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ #
+ # If upper components are missing from the string, they are supplied from
+ # TimeZone#now:
+ #
+ # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ # Time.zone.strptime('22:30:00', '%H:%M:%S') # => Fri, 31 Dec 1999 22:30:00 HST -10:00
+ #
+ # However, if the date component is not provided, but any other upper
+ # components are supplied, then the day of the month defaults to 1:
+ #
+ # Time.zone.strptime('Mar 2000', '%b %Y') # => Wed, 01 Mar 2000 00:00:00 HST -10:00
+ def strptime(str, format, now=now())
+ parts_to_time(DateTime._strptime(str, format), now)
end
# Returns an ActiveSupport::TimeWithZone instance representing the current
@@ -377,7 +383,7 @@ module ActiveSupport
time_now.utc.in_time_zone(self)
end
- # Return the current date in this time zone.
+ # Returns the current date in this time zone.
def today
tzinfo.now.to_date
end
@@ -421,7 +427,36 @@ module ActiveSupport
tzinfo.periods_for_local(time)
end
+ def init_with(coder) #:nodoc:
+ initialize(coder['name'])
+ end
+
+ def encode_with(coder) #:nodoc:
+ coder.tag ="!ruby/object:#{self.class}"
+ coder.map = { 'name' => tzinfo.name }
+ end
+
private
+ def parts_to_time(parts, now)
+ return if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year, now.year),
+ parts.fetch(:mon, now.month),
+ parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day),
+ parts.fetch(:hour, 0),
+ parts.fetch(:min, 0),
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, 0)
+ )
+
+ if parts[:offset]
+ TimeWithZone.new(time.utc, self)
+ else
+ TimeWithZone.new(nil, self, time)
+ end
+ end
+
def time_now
Time.now
end
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 760be4c07a..dd2c178fb6 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 009ee4db90..df7b081993 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -78,6 +78,9 @@ module ActiveSupport
)
end
+ attr_accessor :depth
+ self.depth = 100
+
delegate :parse, :to => :backend
def backend
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index f303daa1a7..94751bbc04 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -46,7 +46,7 @@ module ActiveSupport
xml_string_reader = StringReader.new(data)
xml_input_source = InputSource.new(xml_string_reader)
doc = @dbf.new_document_builder.parse(xml_input_source)
- merge_element!({CONTENT_KEY => ''}, doc.document_element)
+ merge_element!({CONTENT_KEY => ''}, doc.document_element, XmlMini.depth)
end
end
@@ -58,9 +58,10 @@ module ActiveSupport
# Hash to merge the converted element into.
# element::
# XML element to merge into hash
- def merge_element!(hash, element)
+ def merge_element!(hash, element, depth)
+ raise 'Document too deep!' if depth == 0
delete_empty(hash)
- merge!(hash, element.tag_name, collapse(element))
+ merge!(hash, element.tag_name, collapse(element, depth))
end
def delete_empty(hash)
@@ -71,14 +72,14 @@ module ActiveSupport
#
# element::
# The document element to be collapsed.
- def collapse(element)
+ def collapse(element, depth)
hash = get_attributes(element)
child_nodes = element.child_nodes
if child_nodes.length > 0
(0...child_nodes.length).each do |i|
child = child_nodes.item(i)
- merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE
+ merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE
end
merge_texts!(hash, element) unless empty_content?(element)
hash
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 5c7c78bf70..924ed72345 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -29,7 +29,7 @@ module ActiveSupport
doc = REXML::Document.new(data)
if doc.root
- merge_element!({}, doc.root)
+ merge_element!({}, doc.root, XmlMini.depth)
else
raise REXML::ParseException,
"The document #{doc.to_s.inspect} does not have a valid root"
@@ -44,19 +44,20 @@ module ActiveSupport
# Hash to merge the converted element into.
# element::
# XML element to merge into hash
- def merge_element!(hash, element)
- merge!(hash, element.name, collapse(element))
+ def merge_element!(hash, element, depth)
+ raise REXML::ParseException, "The document is too deep" if depth == 0
+ merge!(hash, element.name, collapse(element, depth))
end
# Actually converts an XML document element into a data structure.
#
# element::
# The document element to be collapsed.
- def collapse(element)
+ def collapse(element, depth)
hash = get_attributes(element)
if element.has_elements?
- element.each_element {|child| merge_element!(hash, child) }
+ element.each_element {|child| merge_element!(hash, child, depth - 1) }
merge_texts!(hash, element) unless empty_content?(element)
hash
else