diff options
Diffstat (limited to 'activesupport')
46 files changed, 660 insertions, 229 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 7feb820d4f..237d1d71ae 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,108 @@ +* Fixed confusing `DelegationError` in `Module#delegate`. + + See #15186. + + *Vladimir Yarotsky* + +* Fixed `ActiveSupport::Subscriber` so that no duplicate subscriber is created + when a subscriber method is redefined. + + *Dennis Schön* + +* Remove deprecated string based terminators for `ActiveSupport::Callbacks`. + + *Eileen M. Uchitelle* + +* Fixed an issue when using + `ActiveSupport::NumberHelper::NumberToDelimitedConverter` to + convert a value that is an `ActiveSupport::SafeBuffer` introduced + in 2da9d67. + + See #15064. + + *Mark J. Titorenko* + +* `TimeZone#parse` defaults the day of the month to '1' if any other date + components are specified. This is more consistent with the behavior of + `Time#parse`. + + *Ulysse Carion* + +* `humanize` strips leading underscores, if any. + + Before: + + '_id'.humanize # => "" + + After: + + '_id'.humanize # => "Id" + + *Xavier Noria* + +* Fixed backward compatibility isues introduced in 326e652. + + Empty Hash or Array should not present in serialization result. + + {a: []}.to_query # => "" + {a: {}}.to_query # => "" + + For more info see #14948. + + *Bogdan Gusiev* + +* Add `SecureRandom::uuid_v3` and `SecureRandom::uuid_v5` to support stable + UUID fixtures on PostgreSQL. + + *Roderick van Domburg* + +* Fixed `ActiveSupport::Duration#eql?` so that `1.second.eql?(1.second)` is + true. + + This fixes the current situation of: + + 1.second.eql?(1.second) #=> false + + `eql?` also requires that the other object is an `ActiveSupport::Duration`. + This requirement makes `ActiveSupport::Duration`'s behavior consistent with + the behavior of Ruby's numeric types: + + 1.eql?(1.0) #=> false + 1.0.eql?(1) #=> false + + 1.second.eql?(1) #=> false (was true) + 1.eql?(1.second) #=> false + + { 1 => "foo", 1.0 => "bar" } + #=> { 1 => "foo", 1.0 => "bar" } + + { 1 => "foo", 1.second => "bar" } + # now => { 1 => "foo", 1.second => "bar" } + # was => { 1 => "bar" } + + And though the behavior of these hasn't changed, for reference: + + 1 == 1.0 #=> true + 1.0 == 1 #=> true + + 1 == 1.second #=> true + 1.second == 1 #=> true + + *Emily Dobervich* + +* `ActiveSupport::SafeBuffer#prepend` acts like `String#prepend` and modifies + instance in-place, returning self. `ActiveSupport::SafeBuffer#prepend!` is + deprecated. + + *Pavel Pravosud* + +* `HashWithIndifferentAccess` better respects `#to_hash` on objects it's + given. In particular, `.new`, `#update`, `#merge`, `#replace` all accept + objects which respond to `#to_hash`, even if those objects are not Hashes + directly. + + *Peter Jaros* + * Deprecate `Class#superclass_delegating_accessor`, use `Class#class_attribute` instead. *Akshay Vishnoi* diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index d58578b7bc..1fec1bea0d 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -65,14 +65,14 @@ module ActiveSupport @silencers << block end - # Will remove all silencers, but leave in the filters. This is useful if - # your context of debugging suddenly expands as you suspect a bug in one of + # Removes all silencers, but leaves in the filters. Useful if your + # context of debugging suddenly expands as you suspect a bug in one of # the libraries you use. def remove_silencers! @silencers = [] end - # Removes all filters, but leaves in silencers. Useful if you suddenly + # Removes all filters, but leaves in the silencers. Useful if you suddenly # need to see entire filepaths in the backtrace that you had already # filtered out. def remove_filters! diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index e9ee98a128..73c6b3cb88 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -1,5 +1,6 @@ require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/string/inflections' +require 'active_support/per_thread_registry' module ActiveSupport module Cache diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index e14ece7f35..06505bddf9 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -131,8 +131,6 @@ module ActiveSupport end end - private - def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) lambda { |env| target = env.target @@ -149,6 +147,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :halting_and_conditional def self.halting(next_callback, user_callback, halted_lambda, filter) lambda { |env| @@ -166,6 +165,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :halting def self.conditional(next_callback, user_callback, user_conditions) lambda { |env| @@ -178,6 +178,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :conditional def self.simple(next_callback, user_callback) lambda { |env| @@ -185,6 +186,7 @@ module ActiveSupport next_callback.call env } end + private_class_method :simple end class After @@ -208,8 +210,6 @@ module ActiveSupport end end - private - def self.halting_and_conditional(next_callback, user_callback, user_conditions) lambda { |env| env = next_callback.call env @@ -223,6 +223,7 @@ module ActiveSupport env } end + private_class_method :halting_and_conditional def self.halting(next_callback, user_callback) lambda { |env| @@ -233,6 +234,7 @@ module ActiveSupport env } end + private_class_method :halting def self.conditional(next_callback, user_callback, user_conditions) lambda { |env| @@ -246,6 +248,7 @@ module ActiveSupport env } end + private_class_method :conditional def self.simple(next_callback, user_callback) lambda { |env| @@ -254,6 +257,7 @@ module ActiveSupport env } end + private_class_method :simple end class Around @@ -269,8 +273,6 @@ module ActiveSupport end end - private - def self.halting_and_conditional(next_callback, user_callback, user_conditions) lambda { |env| target = env.target @@ -288,23 +290,25 @@ module ActiveSupport end } end + private_class_method :halting_and_conditional def self.halting(next_callback, user_callback) lambda { |env| target = env.target value = env.value - unless env.halted + if env.halted + next_callback.call env + else user_callback.call(target, value) { env = next_callback.call env env.value } env - else - next_callback.call env end } end + private_class_method :halting def self.conditional(next_callback, user_callback, user_conditions) lambda { |env| @@ -322,6 +326,7 @@ module ActiveSupport end } end + private_class_method :conditional def self.simple(next_callback, user_callback) lambda { |env| @@ -332,6 +337,7 @@ module ActiveSupport env } end + private_class_method :simple end end @@ -718,12 +724,6 @@ module ActiveSupport # would call <tt>Audit#save</tt>. def define_callbacks(*names) options = names.extract_options! - if options.key?(:terminator) && String === options[:terminator] - ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda" - value = options[:terminator] - line = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__ - options[:terminator] = lambda { |target, result| target.instance_exec(result, &line) } - end names.each do |name| class_attribute "_#{name}_callbacks" diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 6c3e48a3ca..2149d4439d 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -221,7 +221,7 @@ module ActiveSupport def garbage?(value) # If the type is the only element which makes it then # this still makes the value nil, except if type is - # a XML node(where type['value'] is a Hash) + # an XML node(where type['value'] is a Hash) value['type'] && !value['type'].is_a?(::Hash) && value.size == 1 end diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb index dc86c92003..763d563231 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb @@ -1,27 +1,38 @@ class Hash # Returns a new hash with +self+ and +other_hash+ merged recursively. # - # h1 = { x: { y: [4, 5, 6] }, z: [7, 8, 9] } - # h2 = { x: { y: [7, 8, 9] }, z: 'xyz' } + # h1 = { a: true, b: { c: [1, 2, 3] } } + # h2 = { a: false, b: { x: [3, 4, 5] } } # - # h1.deep_merge(h2) # => {x: {y: [7, 8, 9]}, z: "xyz"} - # h2.deep_merge(h1) # => {x: {y: [4, 5, 6]}, z: [7, 8, 9]} - # h1.deep_merge(h2) { |key, old, new| Array.wrap(old) + Array.wrap(new) } - # # => {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]} + # h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } + # + # Like with Hash#merge in the standard library, a block can be provided + # to merge values: + # + # h1 = { a: 100, b: 200, c: { c1: 100 } } + # h2 = { b: 250, c: { c1: 200 } } + # h1.deep_merge(h2) { |key, this_val, other_val| this_val + other_val } + # # => { a: 100, b: 450, c: { c1: 300 } } def deep_merge(other_hash, &block) dup.deep_merge!(other_hash, &block) end # Same as +deep_merge+, but modifies +self+. def deep_merge!(other_hash, &block) - other_hash.each_pair do |k,v| - tv = self[k] - if tv.is_a?(Hash) && v.is_a?(Hash) - self[k] = tv.deep_merge(v, &block) + other_hash.each_pair do |current_key, other_value| + this_value = self[current_key] + + self[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash) + this_value.deep_merge(other_value, &block) else - self[k] = block && tv ? block.call(k, tv, v) : v + if block_given? && key?(current_key) + block.call(current_key, this_value, other_value) + else + other_value + end end end + self end end diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb index aa19aed43b..293a3b2619 100644 --- a/activesupport/lib/active_support/core_ext/kernel.rb +++ b/activesupport/lib/active_support/core_ext/kernel.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/kernel/agnostics' require 'active_support/core_ext/kernel/concern' -require 'active_support/core_ext/kernel/debugger' +require 'active_support/core_ext/kernel/debugger' if RUBY_VERSION < '2.0.0' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index fe24f3716d..768b980f21 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -7,6 +7,7 @@ class LoadError ] unless method_defined?(:path) + # Returns the path which was unable to be loaded. def path @path ||= begin REGEXPS.find do |regex| @@ -17,9 +18,11 @@ class LoadError end end + # 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$/, '') end end -MissingSourceFile = LoadError
\ No newline at end of file +MissingSourceFile = LoadError diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index f855833a24..e926392952 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -170,38 +170,26 @@ class Module # methods still accept two arguments. definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block' - # The following generated methods call the target exactly once, storing + # The following generated method calls the target exactly once, storing # the returned value in a dummy variable. # # Reason is twofold: On one hand doing less calls is in general better. # On the other hand it could be that the target has side-effects, # whereas conceptually, from the user point of view, the delegator should # be doing one call. - if allow_nil - method_def = [ - "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block) - "_ = #{to}", # _ = client - "if !_.nil? || nil.respond_to?(:#{method})", # if !_.nil? || nil.respond_to?(:name) - " _.#{method}(#{definition})", # _.name(*args, &block) - "end", # end - "end" # end - ].join ';' - else - exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") - method_def = [ - "def #{method_prefix}#{method}(#{definition})", # def customer_name(*args, &block) - " _ = #{to}", # _ = client - " _.#{method}(#{definition})", # _.name(*args, &block) - "rescue NoMethodError => e", # rescue NoMethodError => e - " if _.nil? && e.name == :#{method}", # if _.nil? && e.name == :name - " #{exception}", # # add helpful message to the exception - " else", # else - " raise", # raise - " end", # end - "end" # end - ].join ';' - end + exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") + + method_def = [ + "def #{method_prefix}#{method}(#{definition})", + " _ = #{to}", + " if !_.nil? || nil.respond_to?(:#{method})", + " _.#{method}(#{definition})", + " else", + " #{exception unless allow_nil}", + " end", + "end" + ].join ';' module_eval(method_def, file, line) end diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9cd7485e2e..3d2c809c9f 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -78,6 +78,9 @@ end require 'bigdecimal' class BigDecimal + # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead + # raises TypeError exception. Checking here on the runtime whether BigDecimal + # will allow dup or not. begin BigDecimal.new('4.56').dup diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb deleted file mode 100644 index f58364f9c6..0000000000 --- a/activesupport/lib/active_support/core_ext/object/to_json.rb +++ /dev/null @@ -1,5 +0,0 @@ -ActiveSupport::Deprecation.warn 'You have required `active_support/core_ext/object/to_json`. ' \ - 'This file will be removed in Rails 4.2. You should require `active_support/core_ext/object/json` ' \ - 'instead.' - -require 'active_support/core_ext/object/json'
\ No newline at end of file diff --git a/activesupport/lib/active_support/core_ext/object/to_param.rb b/activesupport/lib/active_support/core_ext/object/to_param.rb index 13be0038c2..e65fc5bac1 100644 --- a/activesupport/lib/active_support/core_ext/object/to_param.rb +++ b/activesupport/lib/active_support/core_ext/object/to_param.rb @@ -51,12 +51,10 @@ class Hash # # This method is also aliased as +to_query+. def to_param(namespace = nil) - if empty? - namespace ? nil.to_query(namespace) : '' - else - collect do |key, value| + collect do |key, value| + unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty? value.to_query(namespace ? "#{namespace}[#{key}]" : key) - end.sort! * '&' - end + end + end.compact.sort! * '&' end end diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index 37352fa608..172f06ed64 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -1,4 +1,5 @@ require 'active_support/core_ext/object/to_param' +require 'cgi' class Object # Converts an object into a string suitable for use as a URL query string, using the given <tt>key</tt> as the @@ -6,7 +7,6 @@ class Object # # Note: This method is defined as a default implementation for all Objects for Hash#to_query to work. def to_query(key) - require 'cgi' unless defined?(CGI) && defined?(CGI::escape) "#{CGI.escape(key.to_param)}=#{CGI.escape(to_param.to_s)}" end end diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 0000000000..fec8f7c0ec --- /dev/null +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,47 @@ +module SecureRandom + UUID_DNS_NAMESPACE = "k\xA7\xB8\x10\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + UUID_URL_NAMESPACE = "k\xA7\xB8\x11\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + UUID_OID_NAMESPACE = "k\xA7\xB8\x12\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + UUID_X500_NAMESPACE = "k\xA7\xB8\x14\x9D\xAD\x11\xD1\x80\xB4\x00\xC0O\xD40\xC8" #:nodoc: + + # Generates a v5 non-random UUID (Universally Unique IDentifier). + # + # Using Digest::MD5 generates version 3 UUIDs; Digest::SHA1 generates version 5 UUIDs. + # ::uuid_from_hash always generates the same UUID for a given name and namespace combination. + # + # See RFC 4122 for details of UUID at: http://www.ietf.org/rfc/rfc4122.txt + def self.uuid_from_hash(hash_class, uuid_namespace, name) + if hash_class == Digest::MD5 + version = 3 + elsif hash_class == Digest::SHA1 + version = 5 + else + raise ArgumentError, "Expected Digest::SHA1 or Digest::MD5, got #{hash_class.name}." + end + + hash = hash_class.new + hash.update(uuid_namespace) + hash.update(name) + + ary = hash.digest.unpack('NnnnnN') + ary[2] = (ary[2] & 0x0FFF) | (version << 12) + ary[3] = (ary[3] & 0x3FFF) | 0x8000 + + "%08x-%04x-%04x-%04x-%04x%08x" % ary + end + + # Convenience method for ::uuid_from_hash using Digest::MD5. + def self.uuid_v3(uuid_namespace, name) + self.uuid_from_hash(Digest::MD5, uuid_namespace, name) + end + + # Convenience method for ::uuid_from_hash using Digest::SHA1. + def self.uuid_v5(uuid_namespace, name) + self.uuid_from_hash(Digest::SHA1, uuid_namespace, name) + end + + class << self + # Alias for ::uuid. + alias_method :uuid_v4, :uuid + end +end diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb index 6018fd9641..ebd0dd3fc7 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -64,7 +64,7 @@ class String # Returns the first character. If a limit is supplied, returns a substring # from the beginning of the string until it reaches the limit value. If the - # given limit is greater than or equal to the string length, returns self. + # given limit is greater than or equal to the string length, returns a copy of self. # # str = "hello" # str.first # => "h" @@ -76,7 +76,7 @@ class String if limit == 0 '' elsif limit >= size - self + self.dup else to(limit - 1) end @@ -84,7 +84,7 @@ class String # Returns the last character of the string. If a limit is supplied, returns a substring # from the end of the string until it reaches the limit value (counting backwards). If - # the given limit is greater than or equal to the string length, returns self. + # the given limit is greater than or equal to the string length, returns a copy of self. # # str = "hello" # str.last # => "o" @@ -96,7 +96,7 @@ class String if limit == 0 '' elsif limit >= size - self + self.dup else from(-limit) end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index cf9b1a4ec0..a943752f17 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -31,7 +31,7 @@ class String def pluralize(count = nil, locale = :en) locale = count if count.is_a?(Symbol) if count == 1 - self + self.dup else ActiveSupport::Inflector.pluralize(self, locale) end @@ -130,6 +130,8 @@ class String # # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => '' # # See also +deconstantize+. def demodulize 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 eb02b6a442..2c8995be9a 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,5 +1,6 @@ require 'erb' require 'active_support/core_ext/kernel/singleton_class' +require 'active_support/deprecation' class ERB module Util @@ -124,7 +125,7 @@ module ActiveSupport #:nodoc: class SafeBuffer < String UNSAFE_STRING_METHODS = %w( capitalize chomp chop delete downcase gsub lstrip next reverse rstrip - slice squeeze strip sub succ swapcase tr tr_s upcase prepend + slice squeeze strip sub succ swapcase tr tr_s upcase ) alias_method :original_concat, :concat @@ -169,15 +170,18 @@ module ActiveSupport #:nodoc: self[0, 0] end - def concat(value) - if !html_safe? || value.html_safe? - super(value) - else - super(ERB::Util.h(value)) + %w[concat prepend].each do |method_name| + define_method method_name do |value| + super(html_escape_interpolated_argument(value)) end end alias << concat + def prepend!(value) + ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend + prepend value + end + def +(other) dup.concat(other) end diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb index ac1ffa4128..4cd6634558 100644 --- a/activesupport/lib/active_support/core_ext/thread.rb +++ b/activesupport/lib/active_support/core_ext/thread.rb @@ -62,6 +62,13 @@ class Thread _locals.has_key?(key.to_sym) end + # Freezes the thread so that thread local variables cannot be set via + # Thread#thread_variable_set, nor can fiber local variables be set. + # + # me = Thread.current + # me.freeze + # me.thread_variable_set(:oliver, "a") #=> RuntimeError: can't modify frozen thread locals + # me[:oliver] = "a" #=> RuntimeError: can't modify frozen thread locals def freeze _locals.freeze super diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 9fd26156c7..dbf1f2f373 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -20,7 +20,7 @@ class Time :iso8601 => lambda { |time| time.iso8601 } } - # Converts to a formatted string. See DATE_FORMATS for builtin formats. + # Converts to a formatted string. See DATE_FORMATS for built-in formats. # # This method is aliased to <tt>to_s</tt>. # diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 7df4857c25..09eb732ef7 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -49,6 +49,10 @@ module ActiveSupport end end + def eql?(other) + other.is_a?(Duration) && self == other + end + def self.===(other) #:nodoc: other.is_a?(Duration) rescue ::NoMethodError diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 594a4ca938..a4ebdea598 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -55,9 +55,9 @@ module ActiveSupport end def initialize(constructor = {}) - if constructor.is_a?(Hash) + if constructor.respond_to?(:to_hash) super() - update(constructor) + update(constructor.to_hash) else super(constructor) end @@ -72,6 +72,7 @@ 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 end @@ -125,7 +126,7 @@ module ActiveSupport if other_hash.is_a? HashWithIndifferentAccess super(other_hash) else - other_hash.each_pair do |key, value| + other_hash.to_hash.each_pair do |key, value| if block_given? && key?(key) value = yield(convert_key(key), self[key], value) end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index a270c4452f..51720d0192 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -99,26 +99,46 @@ module ActiveSupport word end - # Capitalizes the first word, turns underscores into spaces, and strips a - # trailing '_id' if present. - # Like +titleize+, this is meant for creating pretty output. + # Tweaks an attribute name for display to end users. + # + # Specifically, +humanize+ performs these transformations: + # + # * Applies human inflection rules to the argument. + # * Deletes leading underscores, if any. + # * Removes a "_id" suffix if present. + # * Replaces underscores with spaces, if any. + # * Downcases all words except acronyms. + # * Capitalizes the first word. # # The capitalization of the first word can be turned off by setting the - # optional parameter +capitalize+ to false. - # By default, this parameter is true. + # +:capitalize+ option to false (default is true). # # humanize('employee_salary') # => "Employee salary" # humanize('author_id') # => "Author" # humanize('author_id', capitalize: false) # => "author" + # humanize('_id') # => "Id" + # + # If "SSL" was defined to be an acronym: + # + # humanize('ssl_error') # => "SSL error" + # def humanize(lower_case_and_underscored_word, options = {}) result = lower_case_and_underscored_word.to_s.dup + inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } - result.gsub!(/_id$/, "") + + result.sub!(/\A_+/, '') + result.sub!(/_id\z/, '') result.tr!('_', ' ') - result.gsub!(/([a-z\d]*)/i) { |match| + + result.gsub!(/([a-z\d]*)/i) do |match| "#{inflections.acronyms[match] || match.downcase}" - } - result.gsub!(/^\w/) { |match| match.upcase } if options.fetch(:capitalize, true) + end + + if options.fetch(:capitalize, true) + result.sub!(/\A\w/) { |match| match.upcase } + end + result end @@ -155,7 +175,7 @@ module ActiveSupport # # Singular names are not handled correctly: # - # 'business'.classify # => "Busines" + # 'calculus'.classify # => "Calculu" def classify(table_name) # strip out any leading schema name camelize(singularize(table_name.to_s.sub(/.*\./, ''))) @@ -172,6 +192,8 @@ module ActiveSupport # # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" # 'Inflections'.demodulize # => "Inflections" + # '::Inflections'.demodulize # => "Inflections" + # ''.demodulize # => "" # # See also +deconstantize+. def demodulize(path) @@ -228,7 +250,7 @@ module ActiveSupport def constantize(camel_cased_word) names = camel_cased_word.split('::') - # Trigger a builtin NameError exception including the ill-formed constant in the message. + # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(camel_cased_word) if names.empty? # Remove the first blank element in case of '::ClassName' notation. @@ -242,8 +264,8 @@ module ActiveSupport next candidate if constant.const_defined?(name, false) next candidate unless Object.const_defined?(name) - # Go down the ancestors to check it it's owned - # directly before we reach Object or the end of ancestors. + # Go down the ancestors to check if it is owned directly. The check + # stops when we reach Object or the end of ancestors tree. constant = constant.ancestors.inject do |const, ancestor| break const if ancestor == Object break ancestor if ancestor.const_defined?(name, false) 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 6405afc9a6..d85cc086d7 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 @@ -13,7 +13,9 @@ module ActiveSupport def parts left, right = number.to_s.split('.') - left.gsub!(DELIMITED_REGEX) { "#{$1}#{options[:delimiter]}" } + left.gsub!(DELIMITED_REGEX) do |digit_to_delimit| + "#{digit_to_delimit}#{options[:delimiter]}" + end [left, right].compact end end diff --git a/activesupport/lib/active_support/option_merger.rb b/activesupport/lib/active_support/option_merger.rb index e55ffd12c3..dea84e437f 100644 --- a/activesupport/lib/active_support/option_merger.rb +++ b/activesupport/lib/active_support/option_merger.rb @@ -12,7 +12,7 @@ module ActiveSupport private def method_missing(method, *arguments, &block) - if arguments.last.is_a?(Proc) + if arguments.first.is_a?(Proc) proc = arguments.pop arguments << lambda { |*args| @options.deep_merge(proc.call(*args)) } else diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index 4b9b48539f..98be78b41b 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -64,12 +64,21 @@ module ActiveSupport def add_event_subscriber(event) return if %w{ start finish }.include?(event.to_s) - notifier.subscribe("#{event}.#{namespace}", subscriber) + pattern = "#{event}.#{namespace}" + + # don't add multiple subscribers (eg. if methods are redefined) + return if subscriber.patterns.include?(pattern) + + subscriber.patterns << pattern + notifier.subscribe(pattern, subscriber) end end + attr_reader :patterns # :nodoc: + def initialize @queue_key = [self.class.name, object_id].join "-" + @patterns = [] super end diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index c25c97cfa8..f2a2f3c3db 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -185,8 +185,11 @@ module ActiveSupport end alias_method :rfc822, :rfc2822 - # <tt>:db</tt> format outputs time in UTC; all others output time in local. - # Uses TimeWithZone's +strftime+, so <tt>%Z</tt> and <tt>%z</tt> work correctly. + # Returns a string of the object's date and time. + # Accepts an optional <tt>format</tt>: + # * <tt>:default</tt> - default value, mimics Ruby 1.9 Time#to_s format. + # * <tt>:db</tt> - format outputs time in UTC :db time. See Time#to_formatted_s(:db). + # * Any key in <tt>Time::DATE_FORMATS</tt> can be used. See active_support/core_ext/time/conversions.rb. def to_s(format = :default) if format == :db utc.to_s(format) diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 38f0d268f4..ee62523824 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -188,16 +188,72 @@ module ActiveSupport @lazy_zones_map = ThreadSafe::Cache.new - # 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" - def self.seconds_to_utc_offset(seconds, colon = true) - format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON - sign = (seconds < 0 ? '-' : '+') - hours = seconds.abs / 3600 - minutes = (seconds.abs % 3600) / 60 - format % [sign, hours, minutes] + 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" + def seconds_to_utc_offset(seconds, colon = true) + format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON + sign = (seconds < 0 ? '-' : '+') + hours = seconds.abs / 3600 + minutes = (seconds.abs % 3600) / 60 + format % [sign, hours, minutes] + end + + def find_tzinfo(name) + TZInfo::TimezoneProxy.new(MAPPING[name] || name) + end + + alias_method :create, :new + + # Returns a TimeZone instance with the given name, or +nil+ if no + # such TimeZone instance exists. (This exists to support the use of + # this class with the +composed_of+ macro.) + def new(name) + self[name] + end + + # Returns an array of all TimeZone objects. There are multiple + # TimeZone objects per time zone, in many cases, to make it easier + # for users to find their own time zone. + def all + @zones ||= zones_map.values.sort + end + + def zones_map + @zones_map ||= begin + MAPPING.each_key {|place| self[place]} # load all the zones + @lazy_zones_map + end + end + + # Locate a specific time zone object. If the argument is a string, it + # is interpreted to mean the name of the timezone to locate. If it is a + # numeric value it is either the hour offset, or the second offset, of the + # timezone to find. (The first one with that offset will be returned.) + # Returns +nil+ if no such time zone is known to the system. + def [](arg) + case arg + when String + begin + @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset } + rescue TZInfo::InvalidTimezoneIdentifier + nil + end + when Numeric, ActiveSupport::Duration + arg *= 3600 if arg.abs <= 13 + all.find { |z| z.utc_offset == arg.to_i } + else + raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" + end + end + + # A convenience method for returning a collection of TimeZone objects + # for time zones in the USA. + def us_zones + @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } + end end include Comparable @@ -282,6 +338,11 @@ module ActiveSupport # # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 # Time.zone.parse('22:30:00') # => 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.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? @@ -289,7 +350,7 @@ module ActiveSupport time = Time.new( parts.fetch(:year, now.year), parts.fetch(:mon, now.month), - parts.fetch(:mday, now.day), + 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), @@ -356,66 +417,9 @@ module ActiveSupport tzinfo.periods_for_local(time) end - def self.find_tzinfo(name) - TZInfo::TimezoneProxy.new(MAPPING[name] || name) - end - - class << self - alias_method :create, :new - - # Returns a TimeZone instance with the given name, or +nil+ if no - # such TimeZone instance exists. (This exists to support the use of - # this class with the +composed_of+ macro.) - def new(name) - self[name] - end - - # Returns an array of all TimeZone objects. There are multiple - # TimeZone objects per time zone, in many cases, to make it easier - # for users to find their own time zone. - def all - @zones ||= zones_map.values.sort - end - - def zones_map - @zones_map ||= begin - MAPPING.each_key {|place| self[place]} # load all the zones - @lazy_zones_map - end - end - - # Locate a specific time zone object. If the argument is a string, it - # is interpreted to mean the name of the timezone to locate. If it is a - # numeric value it is either the hour offset, or the second offset, of the - # timezone to find. (The first one with that offset will be returned.) - # Returns +nil+ if no such time zone is known to the system. - def [](arg) - case arg - when String - begin - @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset } - rescue TZInfo::InvalidTimezoneIdentifier - nil - end - when Numeric, ActiveSupport::Duration - arg *= 3600 if arg.abs <= 13 - all.find { |z| z.utc_offset == arg.to_i } - else - raise ArgumentError, "invalid argument to TimeZone[]: #{arg.inspect}" - end - end - - # A convenience method for returning a collection of TimeZone objects - # for time zones in the USA. - def us_zones - @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } - end - end - private - - def time_now - Time.now - end + def time_now + Time.now + end end end diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 57722fd52a..e0e54f47e4 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -1,10 +1,9 @@ require 'abstract_unit' require 'active_support/core_ext/array' require 'active_support/core_ext/big_decimal' +require 'active_support/core_ext/hash' require 'active_support/core_ext/object/conversions' - -require 'active_support/core_ext' # FIXME: pulling in all to_xml extensions -require 'active_support/hash_with_indifferent_access' +require 'active_support/core_ext/string' class ArrayExtAccessTests < ActiveSupport::TestCase def test_from @@ -234,7 +233,7 @@ class ArraySplitTests < ActiveSupport::TestCase end class ArrayToXmlTests < ActiveSupport::TestCase - def test_to_xml + def test_to_xml_with_hash_elements xml = [ { :name => "David", :age => 26, :age_in_millis => 820497600000 }, { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') } @@ -249,6 +248,22 @@ class ArrayToXmlTests < ActiveSupport::TestCase assert xml.include?(%(<name>Jason</name>)), xml end + def test_to_xml_with_non_hash_elements + xml = [1, 2, 3].to_xml(:skip_instruct => true, :indent => 0) + + assert_equal '<fixnums type="array"><fixnum', xml.first(29) + assert xml.include?(%(<fixnum type="integer">2</fixnum>)), xml + end + + def test_to_xml_with_non_hash_different_type_elements + xml = [1, 2.0, '3'].to_xml(:skip_instruct => true, :indent => 0) + + assert_equal '<objects type="array"><object', xml.first(29) + assert xml.include?(%(<object type="integer">1</object>)), xml + assert xml.include?(%(<object type="float">2.0</object>)), xml + assert xml.include?(%(object>3</object>)), xml + end + def test_to_xml_with_dedicated_name xml = [ { :name => "David", :age => 26, :age_in_millis => 820497600000 }, { :name => "Jason", :age => 31 } @@ -269,6 +284,18 @@ class ArrayToXmlTests < ActiveSupport::TestCase assert xml.include?(%(<name>Jason</name>)) end + def test_to_xml_with_indent_set + xml = [ + { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } + ].to_xml(:skip_instruct => true, :skip_types => true, :indent => 4) + + assert_equal "<objects>\n <object>", xml.first(22) + assert xml.include?(%(\n <street-address>Paulina</street-address>)) + assert xml.include?(%(\n <name>David</name>)) + assert xml.include?(%(\n <street-address>Evergreen</street-address>)) + assert xml.include?(%(\n <name>Jason</name>)) + end + def test_to_xml_with_dasherize_false xml = [ { :name => "David", :street_address => "Paulina" }, { :name => "Jason", :street_address => "Evergreen" } @@ -289,7 +316,7 @@ class ArrayToXmlTests < ActiveSupport::TestCase assert xml.include?(%(<street-address>Evergreen</street-address>)) end - def test_to_with_instruct + def test_to_xml_with_instruct xml = [ { :name => "David", :age => 26, :age_in_millis => 820497600000 }, { :name => "Jason", :age => 31, :age_in_millis => BigDecimal.new('1.0') } diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb index 86193c2b07..447b1d10ad 100644 --- a/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb @@ -33,7 +33,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase end def test_simple_accessor_declaration - ActiveSupport::Deprecation.silence do + assert_deprecated do single_class.superclass_delegating_accessor :both end @@ -48,7 +48,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase def test_simple_accessor_declaration_with_instance_reader_false _instance_methods = single_class.public_instance_methods - ActiveSupport::Deprecation.silence do + assert_deprecated do single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false end @@ -60,7 +60,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase end def test_working_with_simple_attributes - ActiveSupport::Deprecation.silence do + assert_deprecated do single_class.superclass_delegating_accessor :both end @@ -79,7 +79,7 @@ class DelegatingAttributesTest < ActiveSupport::TestCase def test_child_class_delegates_to_parent_but_can_be_overridden parent = Class.new - ActiveSupport::Deprecation.silence do + assert_deprecated do parent.superclass_delegating_accessor :both end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 28ba33331e..c8f17f4618 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -31,6 +31,13 @@ class DurationTest < ActiveSupport::TestCase assert !(1.day == 'foo') end + def test_eql + assert 1.minute.eql?(1.minute) + assert 2.days.eql?(48.hours) + assert !1.second.eql?(1) + assert !1.eql?(1.second) + end + def test_inspect assert_equal '0 seconds', 0.seconds.inspect assert_equal '1 month', 1.month.inspect diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 40c8f03374..ad354a4c30 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -23,6 +23,16 @@ class HashExtTest < ActiveSupport::TestCase end end + class HashByConversion + def initialize(hash) + @hash = hash + end + + def to_hash + @hash + end + end + def setup @strings = { 'a' => 1, 'b' => 2 } @nested_strings = { 'a' => { 'b' => { 'c' => 3 } } } @@ -411,6 +421,12 @@ class HashExtTest < ActiveSupport::TestCase assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 } end + def test_update_with_to_hash_conversion + hash = HashWithIndifferentAccess.new + hash.update HashByConversion.new({ :a => 1 }) + assert_equal hash['a'], 1 + end + def test_indifferent_merging hash = HashWithIndifferentAccess.new hash[:a] = 'failure' @@ -430,6 +446,12 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 2, hash['b'] end + def test_merge_with_to_hash_conversion + hash = HashWithIndifferentAccess.new + merged = hash.merge HashByConversion.new({ :a => 1 }) + assert_equal merged['a'], 1 + end + def test_indifferent_replace hash = HashWithIndifferentAccess.new hash[:a] = 42 @@ -442,6 +464,18 @@ class HashExtTest < ActiveSupport::TestCase assert_same hash, replaced end + def test_replace_with_to_hash_conversion + hash = HashWithIndifferentAccess.new + hash[:a] = 42 + + replaced = hash.replace(HashByConversion.new(b: 12)) + + assert hash.key?('b') + assert !hash.key?(:a) + assert_equal 12, hash[:b] + assert_same hash, replaced + end + def test_indifferent_merging_with_block hash = HashWithIndifferentAccess.new hash[:a] = 1 @@ -601,7 +635,7 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 1, h['first'] end - def test_indifferent_subhashes + def test_indifferent_sub_hashes h = {'user' => {'id' => 5}}.with_indifferent_access ['user', :user].each {|user| [:id, 'id'].each {|id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5"}} @@ -663,6 +697,16 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, hash_1 end + def test_deep_merge_with_falsey_values + hash_1 = { e: false } + hash_2 = { e: 'e' } + expected = { e: [:e, false, 'e'] } + assert_equal(expected, hash_1.deep_merge(hash_2) { |k, o, n| [k, o, n] }) + + hash_1.deep_merge!(hash_2) { |k, o, n| [k, o, n] } + assert_equal expected, hash_1 + end + def test_deep_merge_on_indifferent_access hash_1 = HashWithIndifferentAccess.new({ :a => "a", :b => "b", :c => { :c1 => "c1", :c2 => "c2", :c3 => { :d1 => "d1" } } }) hash_2 = HashWithIndifferentAccess.new({ :a => 1, :c => { :c1 => 2, :c3 => { :d2 => "d2" } } }) @@ -871,11 +915,11 @@ class HashExtTest < ActiveSupport::TestCase def test_compact hash_contain_nil_value = @symbols.merge(z: nil) hash_with_only_nil_values = { a: nil, b: nil } - + h = hash_contain_nil_value.dup assert_equal(@symbols, h.compact) assert_equal(hash_contain_nil_value, h) - + h = hash_with_only_nil_values.dup assert_equal({}, h.compact) assert_equal(hash_with_only_nil_values, h) @@ -884,15 +928,21 @@ class HashExtTest < ActiveSupport::TestCase def test_compact! hash_contain_nil_value = @symbols.merge(z: nil) hash_with_only_nil_values = { a: nil, b: nil } - + h = hash_contain_nil_value.dup assert_equal(@symbols, h.compact!) assert_equal(@symbols, h) - + h = hash_with_only_nil_values.dup assert_equal({}, h.compact!) assert_equal({}, h) end + + def test_new_with_to_hash_conversion + hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1)) + assert hash.key?('a') + assert_equal 1, hash[:a] + end end class IWriteMyOwnXML diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index 18b251173f..d8bf81d02b 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -137,4 +137,4 @@ class KernelDebuggerTest < ActiveSupport::TestCase ensure Object.send(:remove_const, :Rails) end -end +end if RUBY_VERSION < '2.0.0' diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index ff6e21854e..380f5ad42b 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -72,7 +72,7 @@ Product = Struct.new(:name) do def type @type ||= begin - nil.type_name + :thing_without_same_method_name_as_delegated.name end end end diff --git a/activesupport/test/core_ext/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb index 91d558dbb5..91d558dbb5 100644 --- a/activesupport/test/core_ext/deep_dup_test.rb +++ b/activesupport/test/core_ext/object/deep_dup_test.rb diff --git a/activesupport/test/core_ext/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index e0566e012c..84512380cf 100644 --- a/activesupport/test/core_ext/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -5,34 +5,27 @@ require 'active_support/core_ext/numeric/time' class DuplicableTest < ActiveSupport::TestCase RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, 5.seconds] - YES = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] - NO = [] + ALLOW_DUP = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] + # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead + # raises TypeError exception. Checking here on the runtime whether BigDecimal + # will allow dup or not. begin bd = BigDecimal.new('4.56') - YES << bd.dup + ALLOW_DUP << bd.dup rescue TypeError RAISE_DUP << bd end - def test_duplicable - (RAISE_DUP + NO).each do |v| + RAISE_DUP.each do |v| assert !v.duplicable? + assert_raises(TypeError, v.class.name) { v.dup } end - YES.each do |v| - assert v.duplicable?, "#{v.class} should be duplicable" - end - - (YES + NO).each do |v| + ALLOW_DUP.each do |v| + assert v.duplicable?, "#{ v.class } should be duplicable" assert_nothing_raised { v.dup } end - - RAISE_DUP.each do |v| - assert_raises(TypeError, v.class.name) do - v.dup - end - end end end diff --git a/activesupport/test/core_ext/object/json_test.rb b/activesupport/test/core_ext/object/json_test.rb deleted file mode 100644 index d3d31530df..0000000000 --- a/activesupport/test/core_ext/object/json_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'abstract_unit' - -class JsonTest < ActiveSupport::TestCase - # See activesupport/test/json/encoding_test.rb for JSON encoding tests - - def test_deprecated_require_to_json_rb - assert_deprecated { require 'active_support/core_ext/object/to_json' } - end -end diff --git a/activesupport/test/core_ext/object/to_query_test.rb b/activesupport/test/core_ext/object/to_query_test.rb index f887a9e613..7457c4655a 100644 --- a/activesupport/test/core_ext/object/to_query_test.rb +++ b/activesupport/test/core_ext/object/to_query_test.rb @@ -49,13 +49,15 @@ class ToQueryTest < ActiveSupport::TestCase def test_nested_empty_hash assert_equal '', {}.to_query - assert_query_equal 'a=1&b%5Bc%5D=3&b%5Bd%5D=', + assert_query_equal 'a=1&b%5Bc%5D=3', { a: 1, b: { c: 3, d: {} } } + assert_query_equal '', + { a: {b: {c: {}}} } assert_query_equal 'b%5Bc%5D=false&b%5Be%5D=&b%5Bf%5D=&p=12', { p: 12, b: { c: false, e: nil, f: '' } } - assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=&b%5Bk%5D=', + assert_query_equal 'b%5Bc%5D=3&b%5Bf%5D=', { b: { c: 3, k: {}, f: '' } } - assert_query_equal 'a%5B%5D=&b=3', + assert_query_equal 'b=3', {a: [], b: 3} end diff --git a/activesupport/test/core_ext/securerandom_test.rb b/activesupport/test/core_ext/securerandom_test.rb new file mode 100644 index 0000000000..71980f6910 --- /dev/null +++ b/activesupport/test/core_ext/securerandom_test.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' +require 'active_support/core_ext/securerandom' + +class SecureRandomExt < ActiveSupport::TestCase + def test_v3_uuids + assert_equal "3d813cbb-47fb-32ba-91df-831e1593ac29", SecureRandom.uuid_v3(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com") + assert_equal "86df55fb-428e-3843-8583-ba3c05f290bc", SecureRandom.uuid_v3(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com") + assert_equal "8c29ab0e-a2dc-3482-b5eb-20cb2e2387a1", SecureRandom.uuid_v3(SecureRandom::UUID_OID_NAMESPACE, "1.2.3") + assert_equal "ee49149d-53a4-304a-890b-468229f6afc3", SecureRandom.uuid_v3(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US") + end + + def test_v5_uuids + assert_equal "21f7f8de-8051-5b89-8680-0195ef798b6a", SecureRandom.uuid_v5(SecureRandom::UUID_DNS_NAMESPACE, "www.widgets.com") + assert_equal "4e570fd8-186d-5a74-90f0-4d28e34673a1", SecureRandom.uuid_v5(SecureRandom::UUID_URL_NAMESPACE, "http://www.widgets.com") + assert_equal "42d5e23b-3a02-5135-85c6-52d1102f1f00", SecureRandom.uuid_v5(SecureRandom::UUID_OID_NAMESPACE, "1.2.3") + assert_equal "fd5b2ddf-bcfe-58b6-90d6-db50f74db527", SecureRandom.uuid_v5(SecureRandom::UUID_X500_NAMESPACE, "cn=John Doe, ou=People, o=Acme, Inc., c=US") + end + + def test_uuid_v4_alias + assert_equal SecureRandom.method(:uuid_v4), SecureRandom.method(:uuid) + end + + def test_invalid_hash_class + assert_raise ArgumentError do + SecureRandom.uuid_from_hash(Digest::SHA2, SecureRandom::UUID_OID_NAMESPACE, '1.2.3') + end + end +end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 072b970a2d..95df173880 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -58,6 +58,11 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal("blargles", "blargle".pluralize(2)) end + test 'pluralize with count = 1 still returns new string' do + name = "Kuldeep" + assert_not_same name.pluralize(1), name + end + def test_singularize SingularToPlural.each do |singular, plural| assert_equal(singular, plural.singularize) @@ -296,6 +301,12 @@ class StringAccessTest < ActiveSupport::TestCase assert_equal 'x', 'x'.first(4) end + test "#first with Fixnum >= string length still returns a new string" do + string = "hello" + different_string = string.first(5) + assert_not_same different_string, string + end + test "#last returns the last character" do assert_equal "o", "hello".last assert_equal 'x', 'x'.last @@ -308,6 +319,12 @@ class StringAccessTest < ActiveSupport::TestCase assert_equal 'x', 'x'.last(4) end + test "#last with Fixnum >= string length still returns a new string" do + string = "hello" + different_string = string.last(5) + assert_not_same different_string, string + end + test "access returns a real string" do hash = {} hash["h"] = true @@ -608,6 +625,29 @@ class OutputSafetyTest < ActiveSupport::TestCase assert !@other_combination.html_safe? end + test "Prepending safe onto unsafe yields unsafe" do + @string.prepend "other".html_safe + assert !@string.html_safe? + assert_equal @string, "otherhello" + end + + test "Prepending unsafe onto safe yields escaped safe" do + other = "other".html_safe + other.prepend "<foo>" + assert other.html_safe? + assert_equal other, "<foo>other" + end + + test "Deprecated #prepend! method is still present" do + other = "other".html_safe + + assert_deprecated do + other.prepend! "<foo>" + end + + assert_equal other, "<foo>other" + end + test "Concatting safe onto unsafe yields unsafe" do @other_string = "other" diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 35967ba656..b0b4738eb3 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -200,6 +200,7 @@ class InflectorTest < ActiveSupport::TestCase def test_demodulize assert_equal "Account", ActiveSupport::Inflector.demodulize("MyApplication::Billing::Account") assert_equal "Account", ActiveSupport::Inflector.demodulize("Account") + assert_equal "Account", ActiveSupport::Inflector.demodulize("::Account") assert_equal "", ActiveSupport::Inflector.demodulize("") end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index dd03a61176..b556da0046 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -208,9 +208,11 @@ module InflectorTestCases } UnderscoreToHuman = { - "employee_salary" => "Employee salary", - "employee_id" => "Employee", - "underground" => "Underground" + 'employee_salary' => 'Employee salary', + 'employee_id' => 'Employee', + 'underground' => 'Underground', + '_id' => 'Id', + '_external_id' => 'External' } UnderscoreToHumanWithoutCapitalize = { diff --git a/activesupport/test/multibyte_conformance.rb b/activesupport/test/multibyte_conformance_test.rb index 2baf724da4..6ab8fa28ee 100644 --- a/activesupport/test/multibyte_conformance.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -20,8 +20,8 @@ class Downloader target.write l end end - end - end + end + end end end diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index 9bdb92024e..a7a0ae02e7 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -1,5 +1,6 @@ require 'abstract_unit' require 'active_support/number_helper' +require 'active_support/core_ext/string/output_safety' module ActiveSupport module NumberHelper @@ -97,6 +98,7 @@ module ActiveSupport assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901)) assert_equal("0.78901", number_helper.number_to_delimited(0.78901)) assert_equal("123,456.78", number_helper.number_to_delimited("123456.78")) + assert_equal("123,456.78", number_helper.number_to_delimited("123456.78".html_safe)) end end diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb index 253411aa3d..8be8c51f07 100644 --- a/activesupport/test/subscriber_test.rb +++ b/activesupport/test/subscriber_test.rb @@ -4,20 +4,27 @@ require 'active_support/subscriber' class TestSubscriber < ActiveSupport::Subscriber attach_to :doodle - cattr_reader :event + cattr_reader :events def self.clear - @@event = nil + @@events = [] end def open_party(event) - @@event = event + events << event end private def private_party(event) - @@event = event + events << event + end +end + +# Monkey patch subscriber to test that only one subscriber per method is added. +class TestSubscriber + def open_party(event) + events << event end end @@ -29,12 +36,18 @@ class SubscriberTest < ActiveSupport::TestCase def test_attaches_subscribers ActiveSupport::Notifications.instrument("open_party.doodle") - assert_equal "open_party.doodle", TestSubscriber.event.name + assert_equal "open_party.doodle", TestSubscriber.events.first.name + end + + def test_attaches_only_one_subscriber + ActiveSupport::Notifications.instrument("open_party.doodle") + + assert_equal 1, TestSubscriber.events.size end def test_does_not_attach_private_methods ActiveSupport::Notifications.instrument("private_party.doodle") - assert_nil TestSubscriber.event + assert_equal TestSubscriber.events, [] end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 79ec57af2b..127bcc2b4d 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -254,6 +254,15 @@ class TimeZoneTest < ActiveSupport::TestCase assert_equal Time.utc(1999,12,31,19), twz.time end + def test_parse_with_day_omitted + with_env_tz 'US/Eastern' do + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + assert_equal Time.local(2000, 2, 1), zone.parse('Feb', Time.local(2000, 1, 1)) + assert_equal Time.local(2005, 2, 1), zone.parse('Feb 2005', Time.local(2000, 1, 1)) + assert_equal Time.local(2005, 2, 2), zone.parse('2 Feb 2005', Time.local(2000, 1, 1)) + end + end + def test_parse_should_not_black_out_system_timezone_dst_jump with_env_tz('EET') do zone = ActiveSupport::TimeZone['Pacific Time (US & Canada)'] diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index 753effb54e..f49431cbbf 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -1,6 +1,9 @@ require 'abstract_unit' require 'active_support/xml_mini' require 'active_support/builder' +require 'active_support/core_ext/array' +require 'active_support/core_ext/hash' +require 'active_support/core_ext/big_decimal' module XmlMiniTest class RenameKeyTest < ActiveSupport::TestCase @@ -88,6 +91,61 @@ module XmlMiniTest assert_xml "<b>Howdy</b>" end + test "#to_tag should use the type value in the options hash" do + @xml.to_tag(:b, "blue", @options.merge(type: 'color')) + assert_xml( "<b type=\"color\">blue</b>" ) + end + + test "#to_tag accepts symbol types" do + @xml.to_tag(:b, :name, @options) + assert_xml( "<b type=\"symbol\">name</b>" ) + end + + test "#to_tag accepts boolean types" do + @xml.to_tag(:b, true, @options) + assert_xml( "<b type=\"boolean\">true</b>") + end + + test "#to_tag accepts float types" do + @xml.to_tag(:b, 3.14, @options) + assert_xml( "<b type=\"float\">3.14</b>") + end + + test "#to_tag accepts decimal types" do + @xml.to_tag(:b, ::BigDecimal.new("1.2"), @options) + assert_xml( "<b type=\"decimal\">1.2</b>") + end + + test "#to_tag accepts date types" do + @xml.to_tag(:b, Date.new(2001,2,3), @options) + assert_xml( "<b type=\"date\">2001-02-03</b>") + end + + test "#to_tag accepts datetime types" do + @xml.to_tag(:b, DateTime.new(2001,2,3,4,5,6,'+7'), @options) + assert_xml( "<b type=\"dateTime\">2001-02-03T04:05:06+07:00</b>") + end + + test "#to_tag accepts time types" do + @xml.to_tag(:b, Time.new(1993, 02, 24, 12, 0, 0, "+09:00"), @options) + assert_xml( "<b type=\"dateTime\">1993-02-24T12:00:00+09:00</b>") + end + + test "#to_tag accepts array types" do + @xml.to_tag(:b, ["first_name", "last_name"], @options) + assert_xml( "<b type=\"array\"><b>first_name</b><b>last_name</b></b>" ) + end + + test "#to_tag accepts hash types" do + @xml.to_tag(:b, { first_name: "Bob", last_name: "Marley" }, @options) + assert_xml( "<b><first-name>Bob</first-name><last-name>Marley</last-name></b>" ) + end + + test "#to_tag should not add type when skip types option is set" do + @xml.to_tag(:b, "Bob", @options.merge(skip_types: 1)) + assert_xml( "<b>Bob</b>" ) + end + test "#to_tag should dasherize the space when passed a string with spaces as a key" do @xml.to_tag("New York", 33, @options) assert_xml "<New---York type=\"integer\">33</New---York>" @@ -97,7 +155,6 @@ module XmlMiniTest @xml.to_tag(:"New York", 33, @options) assert_xml "<New---York type=\"integer\">33</New---York>" end - # TODO: test the remaining functions hidden in #to_tag. end class WithBackendTest < ActiveSupport::TestCase |