diff options
Diffstat (limited to 'activesupport')
98 files changed, 1341 insertions, 682 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index 221ceb532a..ee05ea3255 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,3 +1,151 @@ +* Fix Active Support `Time#to_json` and `DateTime#to_json` to return 3 decimal + places worth of fractional seconds, similar to `TimeWithZone`. + + *Ryan Glover* + +* Removed circular reference protection in JSON encoder, deprecated + `ActiveSupport::JSON::Encoding::CircularReferenceError`. + + *Godfrey Chan*, *Sergio Campamá* + +* Add `capitalize` option to `Inflector.humanize`, so strings can be humanized without being capitalized: + + 'employee_salary'.humanize # => "Employee salary" + 'employee_salary'.humanize(capitalize: false) # => "employee salary" + + *claudiob* + +* Fixed `Object#as_json` and `Struct#as_json` not working properly with options. They now take + the same options as `Hash#as_json`: + + struct = Struct.new(:foo, :bar).new + struct.foo = "hello" + struct.bar = "world" + json = struct.as_json(only: [:foo]) # => {foo: "hello"} + + *Sergio Campamá*, *Godfrey Chan* + +* Added `Numeric#in_milliseconds`, like `1.hour.in_milliseconds`, so we can feed them to JavaScript functions like `getTime()`. + + *DHH* + +* Calling `ActiveSupport::JSON.decode` with unsupported options now raises an error. + + *Godfrey Chan* + +* Support `:unless_exist` in `FileStore`. + + *Michael Grosser* + +* Fix `slice!` deleting the default value of the hash. + + *Antonio Santos* + +* `require_dependency` accepts objects that respond to `to_path`, in + particular `Pathname` instances. + + *Benjamin Fleischer* + +* Disable the ability to iterate over Range of AS::TimeWithZone + due to significant performance issues. + + *Bogdan Gusiev* + +* Allow attaching event subscribers to ActiveSupport::Notifications namespaces + before they're defined. Essentially, this means instead of this: + + class JokeSubscriber < ActiveSupport::Subscriber + def sql(event) + puts "A rabbi and a priest walk into a bar..." + end + + # This call needs to happen *after* defining the methods. + attach_to "active_record" + end + + You can do this: + + class JokeSubscriber < ActiveSupport::Subscriber + # This is much easier to read! + attach_to "active_record" + + def sql(event) + puts "A rabbi and a priest walk into a bar..." + end + end + + This should make it easier to read and understand these subscribers. + + *Daniel Schierbeck* + +* Add `Date#middle_of_day`, `DateTime#middle_of_day` and `Time#middle_of_day` methods. + + Also added `midday`, `noon`, `at_midday`, `at_noon` and `at_middle_of_day` as aliases. + + *Anatoli Makarevich* + +* Fix ActiveSupport::Cache::FileStore#cleanup to no longer rely on missing each_key method. + + *Murray Steele* + +* Ensure that autoloaded constants in all-caps nestings are marked as + autoloaded. + + *Simon Coffey* + +* Add String#remove(pattern) as a short-hand for the common pattern of String#gsub(pattern, '') + + *DHH* + +* Adds a new deprecation behaviour that raises an exception. Throwing this + line into +config/environments/development.rb+ + + ActiveSupport::Deprecation.behavior = :raise + + will cause the application to raise an +ActiveSupport::DeprecationException+ + on deprecations. + + Use this for aggressive deprecation cleanups. + + *Xavier Noria* + +* Remove 'cow' => 'kine' irregular inflection from default inflections. + + *Andrew White* + +* Add `DateTime#to_s(:iso8601)` and `Date#to_s(:iso8601)` for consistency. + + *Andrew White* + +* Add `Time#to_s(:iso8601)` for easy conversion of times to the iso8601 format for easy Javascript date parsing. + + *DHH* + +* Improve `ActiveSupport::Cache::MemoryStore` cache size calculation. + The memory used by a key/entry pair is calculated via `#cached_size`: + + def cached_size(key, entry) + key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD + end + + The value of `PER_ENTRY_OVERHEAD` is 240 bytes based on an [empirical + estimation](https://gist.github.com/ssimeonov/6047200) for 64-bit MRI on + 1.9.3 and 2.0. GH#11512 + + *Simeon Simeonov* + +* Only raise `Module::DelegationError` if it's the source of the exception. + + Fixes #10559 + + *Andrew White* + +* Make `Time.at_with_coercion` retain the second fraction and return local time. + + Fixes #11350 + + *Neer Friedman*, *Andrew White* + * Make `HashWithIndifferentAccess#select` always return the hash, even when `Hash#select!` returns `nil`, to allow further chaining. diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 99770bace9..5ba153662a 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -12,9 +12,8 @@ end namespace :test do task :isolated do - ruby = File.join(*RbConfig::CONFIG.values_at('bindir', 'RUBY_INSTALL_NAME')) Dir.glob("test/**/*_test.rb").all? do |file| - sh(ruby, '-w', '-Ilib:test', file) + sh(Gem.ruby, '-w', '-Ilib:test', file) end or raise "Failures" end end diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index ffc2c2074e..c27c50e47b 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -22,7 +22,7 @@ Gem::Specification.new do |s| s.add_dependency('i18n', '~> 0.6', '>= 0.6.4') s.add_dependency 'json', '~> 1.7' - s.add_dependency 'tzinfo', '~> 0.3.37' + s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.0' s.add_dependency 'thread_safe','~> 0.1' end diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables index 5fefa429df..f39e89b7d0 100755 --- a/activesupport/bin/generate_tables +++ b/activesupport/bin/generate_tables @@ -28,12 +28,6 @@ module ActiveSupport def initialize @ucd = Unicode::UnicodeDatabase.new - - default = Codepoint.new - default.combining_class = 0 - default.uppercase_mapping = 0 - default.lowercase_mapping = 0 - @ucd.codepoints = Hash.new(default) end def parse_codepoints(line) diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index c539048a85..29e2440288 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -12,10 +12,10 @@ require 'active_support/core_ext/string/inflections' module ActiveSupport # See ActiveSupport::Cache::Store for documentation. module Cache - autoload :FileStore, 'active_support/cache/file_store' - autoload :MemoryStore, 'active_support/cache/memory_store' + autoload :FileStore, 'active_support/cache/file_store' + autoload :MemoryStore, 'active_support/cache/memory_store' autoload :MemCacheStore, 'active_support/cache/mem_cache_store' - autoload :NullStore, 'active_support/cache/null_store' + autoload :NullStore, 'active_support/cache/null_store' # These options mean something to all cache implementations. Individual cache # implementations may support additional options. @@ -88,25 +88,24 @@ module ActiveSupport end private + def retrieve_cache_key(key) + case + when key.respond_to?(:cache_key) then key.cache_key + when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param + when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) + else key.to_param + end.to_s + end - def retrieve_cache_key(key) - case - when key.respond_to?(:cache_key) then key.cache_key - when key.is_a?(Array) then key.map { |element| retrieve_cache_key(element) }.to_param - when key.respond_to?(:to_a) then retrieve_cache_key(key.to_a) - else key.to_param - end.to_s - end - - # Obtains the specified cache store class, given the name of the +store+. - # Raises an error when the store class cannot be found. - def retrieve_store_class(store) - require "active_support/cache/#{store}" - rescue LoadError => e - raise "Could not find cache store adapter for #{store} (#{e})" - else - ActiveSupport::Cache.const_get(store.to_s.camelize) - end + # Obtains the specified cache store class, given the name of the +store+. + # Raises an error when the store class cannot be found. + def retrieve_store_class(store) + require "active_support/cache/#{store}" + rescue LoadError => e + raise "Could not find cache store adapter for #{store} (#{e})" + else + ActiveSupport::Cache.const_get(store.to_s.camelize) + end end # An abstract cache store class. There are multiple cache store @@ -153,7 +152,6 @@ module ActiveSupport # or +write+. To specify the threshold at which to compress values, set the # <tt>:compress_threshold</tt> option. The default threshold is 16K. class Store - cattr_accessor :logger, :instance_writer => true attr_reader :silence, :options @@ -234,7 +232,7 @@ module ActiveSupport # bump the cache expiration time by the value set in <tt>:race_condition_ttl</tt>. # Yes, this process is extending the time for a stale value by another few # seconds. Because of extended life of the previous cache, other processes - # will continue to use slightly stale data for a just a big longer. In the + # will continue to use slightly stale data for a just a bit longer. In the # meantime that first process will go ahead and will write into cache the # new value. After that all the processes will start getting new value. # The key is to keep <tt>:race_condition_ttl</tt> small. @@ -385,6 +383,7 @@ module ActiveSupport # Options are passed to the underlying cache implementation. def write(name, value, options = nil) options = merged_options(options) + instrument(:write, name, options) do entry = Entry.new(value, options) write_entry(namespaced_key(name, options), entry, options) @@ -396,6 +395,7 @@ module ActiveSupport # Options are passed to the underlying cache implementation. def delete(name, options = nil) options = merged_options(options) + instrument(:delete, name) do delete_entry(namespaced_key(name, options), options) end @@ -406,9 +406,10 @@ module ActiveSupport # Options are passed to the underlying cache implementation. def exist?(name, options = nil) options = merged_options(options) + instrument(:exist?, name) do entry = read_entry(namespaced_key(name, options), options) - entry && !entry.expired? + (entry && !entry.expired?) || false end end @@ -585,6 +586,7 @@ module ActiveSupport result = instrument(:generate, name, options) do |payload| yield(name) end + write(name, result, options) result end @@ -608,6 +610,7 @@ module ActiveSupport else @value = value end + @created_at = Time.now.to_f @expires_in = options[:expires_in] @expires_in = @expires_in.to_f if @expires_in @@ -658,6 +661,7 @@ module ActiveSupport # serialize entries to protect against accidental cache modifications. def dup_value! convert_version_4beta1_entry! if defined?(@v) + if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false) if @value.is_a?(String) @value = @value.dup @@ -672,8 +676,10 @@ module ActiveSupport if value && options[:compress] compress_threshold = options[:compress_threshold] || DEFAULT_COMPRESS_LIMIT serialized_value_size = (value.is_a?(String) ? value : Marshal.dump(value)).bytesize + return true if serialized_value_size >= compress_threshold end + false end @@ -696,10 +702,12 @@ module ActiveSupport @value = @v remove_instance_variable(:@v) end + if defined?(@c) @compressed = @c remove_instance_variable(:@c) end + if defined?(@x) && @x @created_at ||= Time.now.to_f @expires_in = @x - @created_at diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index 0c55aa8a32..5cd6065077 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -22,19 +22,26 @@ module ActiveSupport extend Strategy::LocalCache end + # Deletes all items from the cache. In this case it deletes all the entries in the specified + # file store directory except for .gitkeep. Be careful which directory is specified in your + # config file when using +FileStore+ because everything in that directory will be deleted. def clear(options = nil) root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)} FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) end + # Premptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) - each_key(options) do |key| + search_dir(cache_path) do |fname| + key = file_path_key(fname) entry = read_entry(key, options) delete_entry(key, options) if entry && entry.expired? end end + # Increments an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. def increment(name, amount = 1, options = nil) file_name = key_file_path(namespaced_key(name, options)) lock_file(file_name) do @@ -49,6 +56,8 @@ module ActiveSupport end end + # Decrements an already existing integer value that is stored in the cache. + # If the key is not found nothing is done. def decrement(name, amount = 1, options = nil) file_name = key_file_path(namespaced_key(name, options)) lock_file(file_name) do @@ -88,6 +97,7 @@ module ActiveSupport 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)} true diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 4d26fb7e42..34ac91334a 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -36,6 +36,7 @@ module ActiveSupport end end + # Premptively iterates through all stored keys and removes the ones which have expired. def cleanup(options = nil) options = merged_options(options) instrument(:cleanup, :size => @data.size) do @@ -122,6 +123,13 @@ module ActiveSupport end protected + + PER_ENTRY_OVERHEAD = 240 + + def cached_size(key, entry) + key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD + end + def read_entry(key, options) # :nodoc: entry = @data[key] synchronize do @@ -139,8 +147,11 @@ module ActiveSupport synchronize do old_entry = @data[key] return false if @data.key?(key) && options[:unless_exist] - @cache_size -= old_entry.size if old_entry - @cache_size += entry.size + if old_entry + @cache_size -= (old_entry.size - entry.size) + else + @cache_size += cached_size(key, entry) + end @key_access[key] = Time.now.to_f @data[key] = entry prune(@max_size * 0.75, @max_prune_time) if @cache_size > @max_size @@ -152,7 +163,7 @@ module ActiveSupport synchronize do @key_access.delete(key) entry = @data.delete(key) - @cache_size -= entry.size if entry + @cache_size -= cached_size(key, entry) if entry !!entry end end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index fb42c4a41e..cea7eee924 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -23,6 +23,9 @@ module ActiveSupport def set_cache_for(local_cache_key, value) @registry[local_cache_key] = value end + + def self.set_cache_for(l, v); instance.set_cache_for l, v; end + def self.cache_for(l); instance.cache_for l; end end # Simple memory backed cache. This cache is not thread safe and is intended only diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 5c738572a8..c3aac31323 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -574,7 +574,7 @@ module ActiveSupport # # set_callback :save, :before_meth # - # The callback can specified as a symbol naming an instance method; as a + # 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 # object that responds to a certain method determined by the <tt>:scope</tt> # argument to +define_callback+. diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index 4f1e432b61..67f58bc0fe 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -48,6 +48,8 @@ class Array end # Equal to <tt>self[41]</tt>. Also known as accessing "the reddit". + # + # (1..42).to_a.forty_two # => 42 def forty_two self[41] end diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 3807ee63b1..76ffd23ed1 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -82,23 +82,8 @@ class Array end end - # Converts a collection of elements into a formatted string by calling - # <tt>to_s</tt> on all elements and joining them. Having this model: - # - # class Blog < ActiveRecord::Base - # def to_s - # title - # end - # end - # - # Blog.all.map(&:title) #=> ["First Post", "Second Post", "Third post"] - # - # <tt>to_formatted_s</tt> shows us: - # - # Blog.all.to_formatted_s # => "First PostSecond PostThird Post" - # - # Adding in the <tt>:db</tt> argument as the format yields a comma separated - # id list: + # 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" def to_formatted_s(format = :default) diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb index dbddc7a7b4..3529d57174 100644 --- a/activesupport/lib/active_support/core_ext/array/grouping.rb +++ b/activesupport/lib/active_support/core_ext/array/grouping.rb @@ -31,9 +31,7 @@ class Array if block_given? collection.each_slice(number) { |slice| yield(slice) } else - groups = [] - collection.each_slice(number) { |group| groups << group } - groups + collection.each_slice(number).to_a end end @@ -55,7 +53,7 @@ class Array # ["4", "5"] # ["6", "7"] def in_groups(number, fill_with = nil) - # size / number gives minor group size; + # size.div number gives minor group size; # size % number gives how many objects need extra accommodation; # each group hold either division or division + 1 items. division = size.div number @@ -85,14 +83,28 @@ class Array # # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]] # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]] - def split(value = nil, &block) - inject([[]]) do |results, element| - if block && block.call(element) || value == element - results << [] - else - results.last << element - end + def split(value = nil) + if block_given? + inject([[]]) do |results, element| + if yield(element) + results << [] + else + results.last << element + end + results + end + else + results, arr = [[]], self.dup + until arr.empty? + if (idx = arr.index(value)) + results.last.concat(arr.shift(idx)) + arr.shift + results << [] + else + results.last.concat(arr.shift(arr.size)) + end + end results end end diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index 83038f9da5..f2a221c396 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -118,7 +118,10 @@ class Class end private - def singleton_class? - ancestors.first != self + + unless respond_to?(:singleton_class?) + def singleton_class? + ancestors.first != self + end 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 06e4847e82..c60e833441 100644 --- a/activesupport/lib/active_support/core_ext/date/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date/calculations.rb @@ -69,6 +69,16 @@ class Date alias :at_midnight :beginning_of_day alias :at_beginning_of_day :beginning_of_day + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the middle of the day (12:00) + def middle_of_day + in_time_zone.middle_of_day + end + alias :midday :middle_of_day + alias :noon :middle_of_day + alias :at_midday :middle_of_day + alias :at_noon :middle_of_day + alias :at_middle_of_day :middle_of_day + # Converts Date to a Time (or DateTime if necessary) with the time portion set to the end of the day (23:59:59) def end_of_day in_time_zone.end_of_day diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index 0637fe4929..6bc8f12176 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -12,7 +12,8 @@ class Date day_format = ActiveSupport::Inflector.ordinalize(date.day) date.strftime("%B #{day_format}, %Y") # => "April 25th, 2007" }, - :rfc822 => '%e %b %Y' + :rfc822 => '%e %b %Y', + :iso8601 => lambda { |date| date.iso8601 } } # Ruby 1.9 has Date#to_time which converts to localtime only. @@ -34,6 +35,7 @@ class Date # 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" + # date.to_formatted_s(:iso8601) # => "2007-11-10" # # == Adding your own time formats to to_formatted_s # You can add your own formats to the Date::DATE_FORMATS hash. diff --git a/activesupport/lib/active_support/core_ext/date/zones.rb b/activesupport/lib/active_support/core_ext/date/zones.rb index 538ed00406..d109b430db 100644 --- a/activesupport/lib/active_support/core_ext/date/zones.rb +++ b/activesupport/lib/active_support/core_ext/date/zones.rb @@ -1,22 +1,6 @@ require 'date' -require 'active_support/core_ext/time/zones' +require 'active_support/core_ext/date_and_time/zones' class Date - # Converts Date to a TimeWithZone in the current zone if Time.zone or Time.zone_default - # is set, otherwise converts Date to a Time via Date#to_time - # - # Time.zone = 'Hawaii' # => 'Hawaii' - # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 - # - # You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument, - # and the conversion will be based on that zone instead of <tt>Time.zone</tt>. - # - # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00 - def in_time_zone(zone = ::Time.zone) - if zone - ::Time.find_zone!(zone).local(year, month, day) - else - to_time - end - end + include DateAndTime::Zones 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 0d14cba7cc..c869a0e210 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 @@ -78,7 +78,7 @@ module DateAndTime # Returns a new date/time at the start of the month. # DateTime objects will have a time set to 0:00. def beginning_of_month - first_hour{ change(:day => 1) } + first_hour(change(:day => 1)) end alias :at_beginning_of_month :beginning_of_month @@ -113,7 +113,7 @@ module DateAndTime # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+ # when set. +DateTime+ objects have their time set to 0:00. def next_week(given_day_in_next_week = Date.beginning_of_week) - first_hour{ weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)) } + first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) end # Short-hand for months_since(1). @@ -136,7 +136,7 @@ module DateAndTime # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. # DateTime objects have their time set to 0:00. def prev_week(start_day = Date.beginning_of_week) - first_hour{ weeks_ago(1).beginning_of_week.days_since(days_span(start_day)) } + first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) end alias_method :last_week, :prev_week @@ -188,7 +188,7 @@ module DateAndTime # +Date.beginning_of_week+ or +config.beginning_of_week+ when set. # DateTime objects have their time set to 23:59:59. def end_of_week(start_day = Date.beginning_of_week) - last_hour{ days_since(6 - days_to_week_start(start_day)) } + last_hour(days_since(6 - days_to_week_start(start_day))) end alias :at_end_of_week :end_of_week @@ -202,7 +202,7 @@ module DateAndTime # DateTime objects will have a time set to 23:59:59. def end_of_month last_day = ::Time.days_in_month(month, year) - last_hour{ days_since(last_day - day) } + last_hour(days_since(last_day - day)) end alias :at_end_of_month :end_of_month @@ -215,14 +215,12 @@ module DateAndTime private - def first_hour - result = yield - acts_like?(:time) ? result.change(:hour => 0) : result + def first_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time end - def last_hour - result = yield - acts_like?(:time) ? result.end_of_day : result + def last_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time end def days_span(day) 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 new file mode 100644 index 0000000000..96c6df9407 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb @@ -0,0 +1,41 @@ +module DateAndTime + module Zones + # Returns the simultaneous time in <tt>Time.zone</tt> if a zone is given or + # 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 + # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00 + # + # This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone + # instead of the operating system's time zone. + # + # You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument, + # 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 + time = acts_like?(:time) ? self : nil + + if time_zone + time_with_zone(time, time_zone) + else + time || self.to_time + end + end + + private + + def time_with_zone(time, zone) + if time + ActiveSupport::TimeWithZone.new(time.utc? ? time : time.getutc, zone) + else + ActiveSupport::TimeWithZone.new(nil, zone, to_time(:utc)) + end + end + end +end + diff --git a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb index c79745c5aa..8fbbe0d3e9 100644 --- a/activesupport/lib/active_support/core_ext/date_time/acts_like.rb +++ b/activesupport/lib/active_support/core_ext/date_time/acts_like.rb @@ -1,3 +1,4 @@ +require 'date' require 'active_support/core_ext/object/acts_like' class DateTime 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 7d4f716bb6..8e5d723074 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -10,16 +10,6 @@ class DateTime end end - # Tells whether the DateTime object's datetime lies in the past. - def past? - self < ::DateTime.current - end - - # Tells whether the DateTime object's datetime lies in the future. - def future? - self > ::DateTime.current - end - # Seconds since midnight: DateTime.now.seconds_since_midnight. def seconds_since_midnight sec + (min * 60) + (hour * 3600) @@ -99,6 +89,16 @@ class DateTime alias :at_midnight :beginning_of_day alias :at_beginning_of_day :beginning_of_day + # Returns a new DateTime representing the middle of the day (12:00) + def middle_of_day + change(:hour => 12) + end + alias :midday :middle_of_day + alias :noon :middle_of_day + alias :at_midday :middle_of_day + alias :at_noon :middle_of_day + alias :at_middle_of_day :middle_of_day + # Returns a new DateTime representing the end of the day (23:59:59). def end_of_day change(:hour => 23, :min => 59, :sec => 59) 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 c44626aed9..6ddfb72a0d 100644 --- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb @@ -19,6 +19,7 @@ class DateTime # datetime.to_formatted_s(:long) # => "December 04, 2007 00:00" # datetime.to_formatted_s(:long_ordinal) # => "December 4th, 2007 00:00" # datetime.to_formatted_s(:rfc822) # => "Tue, 04 Dec 2007 00:00:00 +0000" + # datetime.to_formatted_s(:iso8601) # => "2007-12-04T00:00:00+00:00" # # == Adding your own datetime formats to to_formatted_s # DateTime formats are shared with Time. You can add your own to the diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb index 01a627f8af..c39f358395 100644 --- a/activesupport/lib/active_support/core_ext/date_time/zones.rb +++ b/activesupport/lib/active_support/core_ext/date_time/zones.rb @@ -1,25 +1,6 @@ require 'date' -require 'active_support/core_ext/time/zones' +require 'active_support/core_ext/date_and_time/zones' class DateTime - # Returns the simultaneous time in <tt>Time.zone</tt>. - # - # Time.zone = 'Hawaii' # => 'Hawaii' - # DateTime.new(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - # - # This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> - # as the local zone instead of the operating system's time zone. - # - # You can also pass in a TimeZone instance or string that identifies a TimeZone - # as an argument, and the conversion will be based on that zone instead of - # <tt>Time.zone</tt>. - # - # DateTime.new(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00 - def in_time_zone(zone = ::Time.zone) - if zone - ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) - else - self - end - end + include DateAndTime::Zones end diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb index c3e6124a57..0e7e3ba378 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -23,7 +23,7 @@ class File yield temp_file temp_file.close - if File.exists?(file_name) + if File.exist?(file_name) # Get original file permissions old_stat = stat(file_name) else diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 8930376ac8..2684c772ea 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -10,7 +10,7 @@ require 'active_support/core_ext/string/inflections' class Hash # Returns a string containing an XML representation of its receiver: # - # {'foo' => 1, 'bar' => 2}.to_xml + # { foo: 1, bar: 2 }.to_xml # # => # # <?xml version="1.0" encoding="UTF-8"?> # # <hash> @@ -43,7 +43,10 @@ class Hash # end # # { foo: Foo.new }.to_xml(skip_instruct: true) - # # => "<hash><bar>fooing!</bar></hash>" + # # => + # # <hash> + # # <bar>fooing!</bar> + # # </hash> # # * Otherwise, a node with +key+ as tag is created with a string representation of # +value+ as text node. If +value+ is +nil+ an attribute "nil" set to "true" is added. @@ -201,7 +204,7 @@ module ActiveSupport end def become_empty_string?(value) - # {"string" => true} + # { "string" => true } # No tests fail when the second term is removed. value['type'] == 'string' && value['nil'] != 'true' 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 e07db50b77..42fece6c28 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb @@ -1,13 +1,13 @@ 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 = { x: { y: [4, 5, 6] }, z: [7, 8, 9] } + # h2 = { x: { y: [7, 8, 9] }, z: 'xyz' } # - # 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) # => {: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"]} + # # => {:x=>{:y=>[4, 5, 6, 7, 8, 9]}, :z=>[7, 8, 9, "xyz"]} def deep_merge(other_hash, &block) dup.deep_merge!(other_hash, &block) end diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb index 981e8436bf..970d6faa1d 100644 --- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb +++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb @@ -18,5 +18,6 @@ class Hash # # b = { b: 1 } # { a: b }.with_indifferent_access['a'] # calls b.nested_under_indifferent_access + # # => {"b"=>32} alias nested_under_indifferent_access with_indifferent_access end diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index b4c451ace4..1845e74c45 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -4,7 +4,7 @@ class Hash # hash = { name: 'Rob', age: '28' } # # hash.transform_keys{ |key| key.to_s.upcase } - # # => { "NAME" => "Rob", "AGE" => "28" } + # # => {"NAME"=>"Rob", "AGE"=>"28"} def transform_keys result = {} each_key do |key| @@ -27,7 +27,7 @@ class Hash # hash = { name: 'Rob', age: '28' } # # hash.stringify_keys - # #=> { "name" => "Rob", "age" => "28" } + # # => {"name"=>"Rob", "age"=>"28"} def stringify_keys transform_keys{ |key| key.to_s } end @@ -44,7 +44,7 @@ class Hash # hash = { 'name' => 'Rob', 'age' => '28' } # # hash.symbolize_keys - # #=> { name: "Rob", age: "28" } + # # => {"name"=>"Rob", "age"=>"28"} def symbolize_keys transform_keys{ |key| key.to_sym rescue key } end @@ -78,7 +78,7 @@ class Hash # hash = { person: { name: 'Rob', age: '28' } } # # hash.deep_transform_keys{ |key| key.to_s.upcase } - # # => { "PERSON" => { "NAME" => "Rob", "AGE" => "28" } } + # # => {"PERSON"=>{"NAME"=>"Rob", "AGE"=>"28"}} def deep_transform_keys(&block) result = {} each do |key, value| @@ -105,7 +105,7 @@ class Hash # hash = { person: { name: 'Rob', age: '28' } } # # hash.deep_stringify_keys - # # => { "person" => { "name" => "Rob", "age" => "28" } } + # # => {"person"=>{"name"=>"Rob", "age"=>"28"}} def deep_stringify_keys deep_transform_keys{ |key| key.to_s } end @@ -124,7 +124,7 @@ class Hash # hash = { 'person' => { 'name' => 'Rob', 'age' => '28' } } # # hash.deep_symbolize_keys - # # => { person: { name: "Rob", age: "28" } } + # # => {:person=>{:name=>"Rob", :age=>"28"}} def deep_symbolize_keys deep_transform_keys{ |key| key.to_sym rescue key } end diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 9fa9b3dac4..8ad600b171 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -26,6 +26,8 @@ class Hash keys.map! { |key| convert_key(key) } if respond_to?(:convert_key, true) omit = slice(*self.keys - keys) hash = slice(*keys) + hash.default = default + hash.default_proc = default_proc if default_proc replace(hash) omit end diff --git a/activesupport/lib/active_support/core_ext/integer/multiple.rb b/activesupport/lib/active_support/core_ext/integer/multiple.rb index 7c6c2f1ca7..c668c7c2eb 100644 --- a/activesupport/lib/active_support/core_ext/integer/multiple.rb +++ b/activesupport/lib/active_support/core_ext/integer/multiple.rb @@ -1,9 +1,9 @@ class Integer # Check whether the integer is evenly divisible by the argument. # - # 0.multiple_of?(0) #=> true - # 6.multiple_of?(5) #=> false - # 10.multiple_of?(2) #=> true + # 0.multiple_of?(0) # => true + # 6.multiple_of?(5) # => false + # 10.multiple_of?(2) # => true def multiple_of?(number) number != 0 ? self % number == 0 : zero? end diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 79d3303b41..df11737a6b 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -60,8 +60,7 @@ module Kernel # puts 'This code gets executed and nothing related to ZeroDivisionError was seen' def suppress(*exception_classes) yield - rescue Exception => e - raise unless exception_classes.any? { |cls| e.kind_of?(cls) } + rescue *exception_classes end # Captures the given stream and returns it: @@ -91,6 +90,7 @@ module Kernel stream_io.rewind return captured_stream.read ensure + captured_stream.close captured_stream.unlink stream_io.reopen(origin_stream) end diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 1aa72da743..182e74d9e1 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -6,6 +6,11 @@ class Module # Provides a +delegate+ class method to easily expose contained objects' # public methods as your own. # + # ==== Options + # * <tt>:to</tt> - Specifies the target object + # * <tt>:prefix</tt> - Prefixes the new method with the target name or a custom prefix + # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ to be raised + # # The macro receives one or more method names (specified as symbols or # strings) and the name of the target object via the <tt>:to</tt> option # (also a symbol or string). @@ -183,16 +188,16 @@ class Module exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") module_eval(<<-EOS, file, line - 2) - def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block) - _ = #{to} # _ = client - _.#{method}(#{definition}) # _.name(*args, &block) - rescue NoMethodError # rescue NoMethodError - if _.nil? # if _.nil? - #{exception} # # add helpful message to the exception - else # else - raise # raise - end # end - end # end + 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 EOS 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 87b9a23aef..d97ce938c1 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -76,4 +76,10 @@ class Numeric # Reads best without arguments: 10.minutes.from_now alias :from_now :since + + # Used with the standard time durations, like 1.hour.in_milliseconds -- + # so we can feed them to JavaScript functions like getTime(). + def in_milliseconds + self * 1000 + end end diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index ec2157221f..f4f9152d6a 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -8,7 +8,7 @@ require 'active_support/core_ext/object/inclusion' require 'active_support/core_ext/object/conversions' require 'active_support/core_ext/object/instance_variables' -require 'active_support/core_ext/object/to_json' +require 'active_support/core_ext/object/json' require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/object/with_options' diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb new file mode 100644 index 0000000000..898c3f4307 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/object/json.rb @@ -0,0 +1,214 @@ +# Hack to load json gem first so we can overwrite its to_json. +require 'json' +require 'bigdecimal' +require 'active_support/core_ext/big_decimal/conversions' # for #to_s +require 'active_support/core_ext/hash/except' +require 'active_support/core_ext/hash/slice' +require 'active_support/core_ext/object/instance_variables' +require 'time' +require 'active_support/core_ext/time/conversions' +require 'active_support/core_ext/date_time/conversions' +require 'active_support/core_ext/date/conversions' + +# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting +# their default behavior. That said, we need to define the basic to_json method in all of them, +# otherwise they will always use to_json gem implementation, which is backwards incompatible in +# several cases (for instance, the JSON implementation for Hash does not work) with inheritance +# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. +[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass| + klass.class_eval do + # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info. + def to_json(options = nil) + ActiveSupport::JSON.encode(self, options) + end + end +end + +class Object + def as_json(options = nil) #:nodoc: + if respond_to?(:to_hash) + to_hash.as_json(options) + else + instance_values.as_json(options) + end + end +end + +class Struct #:nodoc: + def as_json(options = nil) + Hash[members.zip(values)].as_json(options) + end +end + +class TrueClass + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + to_s + end +end + +class FalseClass + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + to_s + end +end + +class NilClass + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + 'null' + end +end + +class String + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + encoder.escape(self) + end +end + +class Symbol + def as_json(options = nil) #:nodoc: + to_s + end +end + +class Numeric + def as_json(options = nil) #:nodoc: + self + end + + def encode_json(encoder) #:nodoc: + to_s + end +end + +class Float + # Encoding Infinity or NaN to JSON should return "null". The default returns + # "Infinity" or "NaN" which are not valid JSON. + def as_json(options = nil) #:nodoc: + finite? ? self : nil + end +end + +class BigDecimal + # A BigDecimal would be naturally represented as a JSON number. Most libraries, + # however, parse non-integer JSON numbers directly as floats. Clients using + # those libraries would get in general a wrong number and no way to recover + # other than manually inspecting the string with the JSON code itself. + # + # That's why a JSON string is returned. The JSON literal is not numeric, but + # if the other end knows by contract that the data is supposed to be a + # BigDecimal, it still has the chance to post-process the string and get the + # real value. + # + # Use <tt>ActiveSupport.use_standard_json_big_decimal_format = true</tt> to + # override this behavior. + def as_json(options = nil) #:nodoc: + if finite? + ActiveSupport.encode_big_decimal_as_string ? to_s : self + else + nil + end + end +end + +class Regexp + def as_json(options = nil) #:nodoc: + to_s + end +end + +module Enumerable + def as_json(options = nil) #:nodoc: + to_a.as_json(options) + end +end + +class Range + def as_json(options = nil) #:nodoc: + to_s + end +end + +class Array + def as_json(options = nil) #:nodoc: + map { |v| v.as_json(options && options.dup) } + end + + def encode_json(encoder) #:nodoc: + "[#{map { |v| v.as_json.encode_json(encoder) } * ','}]" + end +end + +class Hash + def as_json(options = nil) #:nodoc: + # create a subset of the hash by applying :only or :except + subset = if options + if attrs = options[:only] + slice(*Array(attrs)) + elsif attrs = options[:except] + except(*Array(attrs)) + else + self + end + else + self + end + + Hash[subset.map { |k, v| [k.to_s, v.as_json(options && options.dup)] }] + end + + def encode_json(encoder) #:nodoc: + "{#{map { |k,v| "#{k.as_json.encode_json(encoder)}:#{v.as_json.encode_json(encoder)}" } * ','}}" + end +end + +class Time + def as_json(options = nil) #:nodoc: + if ActiveSupport.use_standard_json_time_format + xmlschema(3) + else + %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) + end + end +end + +class Date + def as_json(options = nil) #:nodoc: + if ActiveSupport.use_standard_json_time_format + strftime("%Y-%m-%d") + else + strftime("%Y/%m/%d") + end + end +end + +class DateTime + def as_json(options = nil) #:nodoc: + if ActiveSupport.use_standard_json_time_format + xmlschema(3) + else + strftime('%Y/%m/%d %H:%M:%S %z') + end + end +end + +class Process::Status + def as_json(options = nil) + { :exitstatus => exitstatus, :pid => pid } + end +end diff --git a/activesupport/lib/active_support/core_ext/object/to_json.rb b/activesupport/lib/active_support/core_ext/object/to_json.rb index 83cc8066e7..3dcae6fc7f 100644 --- a/activesupport/lib/active_support/core_ext/object/to_json.rb +++ b/activesupport/lib/active_support/core_ext/object/to_json.rb @@ -1,27 +1,5 @@ -# Hack to load json gem first so we can overwrite its to_json. -begin - require 'json' -rescue LoadError -end +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.' -# The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting -# their default behavior. That said, we need to define the basic to_json method in all of them, -# otherwise they will always use to_json gem implementation, which is backwards incompatible in -# several cases (for instance, the JSON implementation for Hash does not work) with inheritance -# and consequently classes as ActiveSupport::OrderedHash cannot be serialized to json. -[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass].each do |klass| - klass.class_eval do - # Dumps object in JSON (JavaScript Object Notation). See www.json.org for more info. - def to_json(options = nil) - ActiveSupport::JSON.encode(self, options) - end - end -end - -module Process - class Status - def as_json(options = nil) - { :exitstatus => exitstatus, :pid => pid } - end - end -end +require 'active_support/core_ext/object/json' diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 534bbe3c42..48190e1e66 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -47,7 +47,7 @@ class Object end # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and - # does not implemented the tried method. + # does not implement the tried method. def try!(*a, &b) if a.empty? && block_given? yield self diff --git a/activesupport/lib/active_support/core_ext/range.rb b/activesupport/lib/active_support/core_ext/range.rb index 1d8b1ede5a..9368e81235 100644 --- a/activesupport/lib/active_support/core_ext/range.rb +++ b/activesupport/lib/active_support/core_ext/range.rb @@ -1,3 +1,4 @@ require 'active_support/core_ext/range/conversions' require 'active_support/core_ext/range/include_range' require 'active_support/core_ext/range/overlaps' +require 'active_support/core_ext/range/each' diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb new file mode 100644 index 0000000000..d51ea2e944 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/range/each.rb @@ -0,0 +1,24 @@ +require 'active_support/core_ext/module/aliasing' +require 'active_support/core_ext/object/acts_like' + +class Range #:nodoc: + + def each_with_time_with_zone(&block) + ensure_iteration_allowed + each_without_time_with_zone(&block) + end + alias_method_chain :each, :time_with_zone + + 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.acts_like?(:time) + raise TypeError, "can't iterate from #{first.class}" + end + 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 8fa8157d65..ee3b6d2b3f 100644 --- a/activesupport/lib/active_support/core_ext/string/access.rb +++ b/activesupport/lib/active_support/core_ext/string/access.rb @@ -59,7 +59,7 @@ class String # str.from(0).to(-1) #=> "hello" # str.from(1).to(-2) #=> "ell" def to(position) - self[0..position] + self[0, position + 1] end # Returns the first character. If a limit is supplied, returns a substring diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index c62bb41416..49c0df6026 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -20,6 +20,16 @@ class String self end + # Returns a new string with all occurrences of the pattern removed. Short-hand for String#gsub(pattern, ''). + def remove(pattern) + gsub pattern, '' + end + + # Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, ''). + def remove!(pattern) + gsub! pattern, '' + end + # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>: # # 'Once upon a time in a world far far away'.truncate(27) @@ -41,8 +51,8 @@ class String def truncate(truncate_at, options = {}) return dup unless length > truncate_at - options[:omission] ||= '...' - length_with_room_for_omission = truncate_at - options[:omission].length + omission = options[:omission] || '...' + length_with_room_for_omission = truncate_at - omission.length stop = \ if options[:separator] rindex(options[:separator], length_with_room_for_omission) || length_with_room_for_omission @@ -50,6 +60,6 @@ class String length_with_room_for_omission end - "#{self[0...stop]}#{options[:omission]}" + "#{self[0, stop]}#{omission}" end end diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 56e8a5f98d..b7b750c77b 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -190,13 +190,19 @@ class String ActiveSupport::Inflector.classify(self) end - # Capitalizes the first word, turns underscores into spaces, and strips '_id'. + # Capitalizes the first word, turns underscores into spaces, and strips a + # trailing '_id' if present. # Like +titleize+, this is meant for creating pretty output. # - # 'employee_salary'.humanize # => "Employee salary" - # 'author_id'.humanize # => "Author" - def humanize - ActiveSupport::Inflector.humanize(self) + # The capitalization of the first word can be turned off by setting the + # optional parameter +capitalize+ to false. + # By default, this parameter is true. + # + # 'employee_salary'.humanize # => "Employee salary" + # 'author_id'.humanize # => "Author" + # 'author_id'.humanize(capitalize: false) # => "author" + def humanize(options = {}) + ActiveSupport::Inflector.humanize(self, options) end # Creates a foreign key name from a class name. diff --git a/activesupport/lib/active_support/core_ext/string/zones.rb b/activesupport/lib/active_support/core_ext/string/zones.rb index e3f20eee29..510c884c18 100644 --- a/activesupport/lib/active_support/core_ext/string/zones.rb +++ b/activesupport/lib/active_support/core_ext/string/zones.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/string/conversions' require 'active_support/core_ext/time/zones' class String diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb index 5481766f10..e80f442973 100644 --- a/activesupport/lib/active_support/core_ext/thread.rb +++ b/activesupport/lib/active_support/core_ext/thread.rb @@ -23,14 +23,14 @@ class Thread # for the fiber local. The fiber is executed in the same thread, so the # thread local values are available. def thread_variable_get(key) - locals[key.to_sym] + _locals[key.to_sym] end # Sets a thread local with +key+ to +value+. Note that these are local to # threads, and not to fibers. Please see Thread#thread_variable_get for # more information. def thread_variable_set(key, value) - locals[key.to_sym] = value + _locals[key.to_sym] = value end # Returns an an array of the names of the thread-local variables (as Symbols). @@ -45,7 +45,7 @@ class Thread # Note that these are not fiber local variables. Please see Thread#thread_variable_get # for more details. def thread_variables - locals.keys + _locals.keys end # Returns <tt>true</tt> if the given string (or symbol) exists as a @@ -59,16 +59,21 @@ class Thread # Note that these are not fiber local variables. Please see Thread#thread_variable_get # for more details. def thread_variable?(key) - locals.has_key?(key.to_sym) + _locals.has_key?(key.to_sym) + end + + def freeze + _locals.freeze + super end private - def locals - if defined?(@locals) - @locals + def _locals + if defined?(@_locals) + @_locals else - LOCK.synchronize { @locals ||= {} } + LOCK.synchronize { @_locals ||= {} } end end end unless Thread.instance_methods.include?(:thread_variable_set) diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index fa74fee78a..6e0af0db4d 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -33,10 +33,15 @@ class Time # Layers additional behavior on Time.at so that ActiveSupport::TimeWithZone and DateTime # instances can be used when called with a single argument def at_with_coercion(*args) - if args.size == 1 && args.first.acts_like?(:time) - at_without_coercion(args.first.to_i) + return at_without_coercion(*args) if args.size != 1 + + # Time.at can be called with a time or numerical value + time_or_number = args.first + + if time_or_number.is_a?(ActiveSupport::TimeWithZone) || time_or_number.is_a?(DateTime) + at_without_coercion(time_or_number.to_f).getlocal else - at_without_coercion(*args) + at_without_coercion(time_or_number) end end alias_method :at_without_coercion, :at @@ -137,6 +142,16 @@ class Time alias :at_midnight :beginning_of_day alias :at_beginning_of_day :beginning_of_day + # Returns a new Time representing the middle of the day (12:00) + def middle_of_day + change(:hour => 12) + end + alias :midday :middle_of_day + alias :noon :middle_of_day + alias :at_midday :middle_of_day + alias :at_noon :middle_of_day + alias :at_middle_of_day :middle_of_day + # Returns a new Time representing the end of the day, 23:59:59.999999 (.999999999 in ruby1.9) def end_of_day change( diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb index 48654eb1cc..9fd26156c7 100644 --- a/activesupport/lib/active_support/core_ext/time/conversions.rb +++ b/activesupport/lib/active_support/core_ext/time/conversions.rb @@ -16,7 +16,8 @@ class Time :rfc822 => lambda { |time| offset_format = time.formatted_offset(false) time.strftime("%a, %d %b %Y %H:%M:%S #{offset_format}") - } + }, + :iso8601 => lambda { |time| time.iso8601 } } # Converts to a formatted string. See DATE_FORMATS for builtin formats. @@ -34,6 +35,7 @@ class Time # time.to_formatted_s(:long) # => "January 18, 2007 06:10" # time.to_formatted_s(:long_ordinal) # => "January 18th, 2007 06:10" # time.to_formatted_s(:rfc822) # => "Thu, 18 Jan 2007 06:10:17 -0600" + # time.to_formatted_s(:iso8601) # => "2007-01-18T06:10:17-06:00" # # == Adding your own time formats to +to_formatted_s+ # You can add your own formats to the Time::DATE_FORMATS hash. diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index 139d48f59c..bbda04d60c 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,6 +1,8 @@ require 'active_support/time_with_zone' +require 'active_support/core_ext/date_and_time/zones' class Time + include DateAndTime::Zones class << self attr_accessor :zone_default @@ -73,24 +75,4 @@ class Time find_zone!(time_zone) rescue nil end end - - # Returns the simultaneous time in <tt>Time.zone</tt>. - # - # Time.zone = 'Hawaii' # => 'Hawaii' - # Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00 - # - # This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone - # instead of the operating system's time zone. - # - # You can also pass in a TimeZone instance or string that identifies a TimeZone as an argument, - # 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 - def in_time_zone(zone = ::Time.zone) - if zone - ActiveSupport::TimeWithZone.new(utc? ? self : getutc, ::Time.find_zone!(zone)) - else - self - end - end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 73559bfe0e..19d4ff51d7 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -8,6 +8,7 @@ require 'active_support/core_ext/module/introspection' require 'active_support/core_ext/module/anonymous' require 'active_support/core_ext/module/qualified_const' require 'active_support/core_ext/object/blank' +require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/load_error' require 'active_support/core_ext/name_error' require 'active_support/core_ext/string/starts_ends_with' @@ -198,9 +199,19 @@ module ActiveSupport #:nodoc: Dependencies.require_or_load(file_name) end + # Interprets a file using <tt>mechanism</tt> and marks its defined + # constants as autoloaded. <tt>file_name</tt> can be either a string or + # respond to <tt>to_path</tt>. + # + # Use this method in code that absolutely needs a certain constant to be + # defined at that point. A typical use case is to make constant name + # resolution deterministic for constants with the same relative name in + # different namespaces whose evaluation would depend on load order + # otherwise. def require_dependency(file_name, message = "No such file to load -- %s") + file_name = file_name.to_path if file_name.respond_to?(:to_path) unless file_name.is_a?(String) - raise ArgumentError, "the file name must be a String -- you passed #{file_name.inspect}" + raise ArgumentError, "the file name must either be a String or implement #to_path -- you passed #{file_name.inspect}" end Dependencies.depend_on(file_name, message) @@ -459,7 +470,7 @@ module ActiveSupport #:nodoc: if loaded.include?(expanded) raise "Circular dependency detected while autoloading constant #{qualified_name}" else - require_or_load(expanded) + require_or_load(expanded, qualified_name) raise LoadError, "Unable to autoload constant #{qualified_name}, expected #{file_path} to define it" unless from_mod.const_defined?(const_name, false) return from_mod.const_get(const_name) end diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb index 0281c9222e..ab16977bda 100644 --- a/activesupport/lib/active_support/deprecation.rb +++ b/activesupport/lib/active_support/deprecation.rb @@ -32,7 +32,7 @@ module ActiveSupport # and the second is a library name # # ActiveSupport::Deprecation.new('2.0', 'MyLibrary') - def initialize(deprecation_horizon = '4.1', gem_name = 'Rails') + def initialize(deprecation_horizon = '4.2', gem_name = 'Rails') self.gem_name = gem_name self.deprecation_horizon = deprecation_horizon # By default, warnings are not silenced and debugging is off. diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 90db180124..328b8c320a 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -1,14 +1,24 @@ require "active_support/notifications" module ActiveSupport + class DeprecationException < StandardError + end + class Deprecation # Default warning behaviors per Rails.env. DEFAULT_BEHAVIORS = { - :stderr => Proc.new { |message, callstack| + raise: ->(message, callstack) { + e = DeprecationException.new(message) + e.set_backtrace(callstack) + raise e + }, + + stderr: ->(message, callstack) { $stderr.puts(message) $stderr.puts callstack.join("\n ") if debug }, - :log => Proc.new { |message, callstack| + + log: ->(message, callstack) { logger = if defined?(Rails) && Rails.logger Rails.logger @@ -19,11 +29,13 @@ module ActiveSupport logger.warn message logger.debug callstack.join("\n ") if debug }, - :notify => Proc.new { |message, callstack| + + notify: ->(message, callstack) { ActiveSupport::Notifications.instrument("deprecation.rails", :message => message, :callstack => callstack) }, - :silence => Proc.new { |message, callstack| } + + silence: ->(message, callstack) {}, } module Behavior @@ -40,6 +52,7 @@ module ActiveSupport # # Available behaviors: # + # [+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+. @@ -52,7 +65,7 @@ module ActiveSupport # ActiveSupport::Deprecation.behavior = :stderr # ActiveSupport::Deprecation.behavior = [:stderr, :log] # ActiveSupport::Deprecation.behavior = MyCustomHandler - # ActiveSupport::Deprecation.behavior = proc { |message, callstack| + # ActiveSupport::Deprecation.behavior = ->(message, callstack) { # # custom stuff # } def behavior=(behavior) diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 2cb1f408b6..87b6407038 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -70,13 +70,11 @@ module ActiveSupport alias :until :ago def inspect #:nodoc: - consolidated = parts.inject(::Hash.new(0)) { |h,(l,r)| h[l] += r; h } - parts = [:years, :months, :days, :minutes, :seconds].map do |length| - n = consolidated[length] - "#{n} #{n == 1 ? length.to_s.singularize : length.to_s}" if n.nonzero? - end.compact - parts = ["0 seconds"] if parts.empty? - parts.to_sentence(:locale => :en) + parts. + reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }. + sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}. + map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}. + to_sentence(:locale => :en) end def as_json(options = nil) #:nodoc: diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb index d6918bede2..78b627c286 100644 --- a/activesupport/lib/active_support/file_update_checker.rb +++ b/activesupport/lib/active_support/file_update_checker.rb @@ -92,7 +92,7 @@ module ActiveSupport def watched @watched || begin - all = @files.select { |f| File.exists?(f) } + all = @files.select { |f| File.exist?(f) } all.concat(Dir[@glob]) if @glob all end diff --git a/activesupport/lib/active_support/inflections.rb b/activesupport/lib/active_support/inflections.rb index ef882ebd09..4ea6abfa12 100644 --- a/activesupport/lib/active_support/inflections.rb +++ b/activesupport/lib/active_support/inflections.rb @@ -57,7 +57,6 @@ module ActiveSupport inflect.irregular('child', 'children') inflect.irregular('sex', 'sexes') inflect.irregular('move', 'moves') - inflect.irregular('cow', 'kine') inflect.irregular('zombie', 'zombies') inflect.uncountable(%w(equipment information rice money species series fish sheep jeans police)) diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 9ca5a388f8..0f7ae98a8a 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -36,7 +36,7 @@ module ActiveSupport # string. # # If passed an optional +locale+ parameter, the word will be - # pluralized using rules defined for that language. By default, + # singularized using rules defined for that language. By default, # this parameter is set to <tt>:en</tt>. # # 'posts'.singularize # => "post" @@ -72,7 +72,9 @@ module ActiveSupport else string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } end - string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }.gsub('/', '::') + string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } + string.gsub!('/', '::') + string end # Makes an underscored, lowercase form from the expression in the string. @@ -87,8 +89,7 @@ module ActiveSupport # # 'SSLError'.underscore.camelize # => "SslError" def underscore(camel_cased_word) - word = camel_cased_word.to_s.dup - word.gsub!('::', '/') + word = camel_cased_word.to_s.gsub('::', '/') word.gsub!(/(?:([A-Za-z\d])|^)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1}#{$1 && '_'}#{$2.downcase}" } word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') @@ -97,20 +98,26 @@ module ActiveSupport word end - # Capitalizes the first word and turns underscores into spaces and strips a - # trailing "_id", if any. Like +titleize+, this is meant for creating pretty - # output. - # - # 'employee_salary'.humanize # => "Employee salary" - # 'author_id'.humanize # => "Author" - def humanize(lower_case_and_underscored_word) + # Capitalizes the first word, turns underscores into spaces, and strips a + # trailing '_id' if present. + # Like +titleize+, this is meant for creating pretty output. + # + # The capitalization of the first word can be turned off by setting the + # optional parameter +capitalize+ to false. + # By default, this parameter is true. + # + # humanize('employee_salary') # => "Employee salary" + # humanize('author_id') # => "Author" + # humanize('author_id', capitalize: false) # => "author" + 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.tr!('_', ' ') - result.gsub(/([a-z\d]*)/i) { |match| + result.gsub!(/([a-z\d]*)/i) { |match| "#{inflections.acronyms[match] || match.downcase}" - }.gsub(/^\w/) { $&.upcase } + } + options.fetch(:capitalize, true) ? result.gsub(/^\w/) { $&.upcase } : result end # Capitalizes all the words and replaces some characters in the string to @@ -184,7 +191,7 @@ module ActiveSupport # # See also +demodulize+. def deconstantize(path) - path.to_s[0...(path.rindex('::') || 0)] # implementation based on the one in facets' Module#spacename + path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename end # Creates a foreign key name from a class name. diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index 30833a4cb1..8b5fc70dee 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -7,14 +7,24 @@ module ActiveSupport mattr_accessor :parse_json_times 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 www.json.org for more info. # # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}") # => {"team" => "rails", "players" => "36"} - def decode(json, proc = nil, options = {}) - data = ::JSON.load(json, proc, options) + 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 + + data = ::JSON.parse(json, quirks_mode: true) + if ActiveSupport.parse_json_times convert_dates_from(data) else diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index 77b5d8d227..0e1c379b5b 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -1,19 +1,8 @@ #encoding: us-ascii -require 'active_support/core_ext/object/to_json' +require 'active_support/core_ext/object/json' require 'active_support/core_ext/module/delegation' -require 'bigdecimal' -require 'active_support/core_ext/big_decimal/conversions' # for #to_s -require 'active_support/core_ext/hash/except' -require 'active_support/core_ext/hash/slice' -require 'active_support/core_ext/object/instance_variables' -require 'time' -require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/date_time/conversions' -require 'active_support/core_ext/date/conversions' -require 'set' - module ActiveSupport class << self delegate :use_standard_json_time_format, :use_standard_json_time_format=, @@ -23,9 +12,6 @@ module ActiveSupport end 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})?))$/ - # Dumps objects in JSON (JavaScript Object Notation). # See www.json.org for more info. # @@ -36,56 +22,22 @@ module ActiveSupport end module Encoding #:nodoc: - class CircularReferenceError < StandardError; end - class Encoder attr_reader :options def initialize(options = nil) @options = options || {} - @seen = Set.new end - def encode(value, use_options = true) - check_for_circular_references(value) do - jsonified = use_options ? value.as_json(options_for(value)) : value.as_json - jsonified.encode_json(self) - end - end - - # like encode, but only calls as_json, without encoding to string. - def as_json(value, use_options = true) - check_for_circular_references(value) do - use_options ? value.as_json(options_for(value)) : value.as_json - end - end - - def options_for(value) - if value.is_a?(Array) || value.is_a?(Hash) - # hashes and arrays need to get encoder in the options, so that - # they can detect circular references. - options.merge(:encoder => self) - else - options.dup - end + def encode(value) + value.as_json(options.dup).encode_json(self) end def escape(string) Encoding.escape(string) end - - private - def check_for_circular_references(value) - unless @seen.add?(value.__id__) - raise CircularReferenceError, 'object references itself' - end - yield - ensure - @seen.delete(value.__id__) - end end - ESCAPED_CHARS = { "\x00" => '\u0000', "\x01" => '\u0001', "\x02" => '\u0002', "\x03" => '\u0003', "\x04" => '\u0004', "\x05" => '\u0005', @@ -140,6 +92,28 @@ module ActiveSupport json.force_encoding(::Encoding::UTF_8) json end + + # Deprecate CircularReferenceError + def const_missing(name) + if name == :CircularReferenceError + message = "The JSON encoder in Rails 4.1 no longer offers protection from circular references. " \ + "You are seeing this warning because you are rescuing from (or otherwise referencing) " \ + "ActiveSupport::Encoding::CircularReferenceError. In the future, this error will be " \ + "removed from Rails. You should remove these rescue blocks from your code and ensure " \ + "that your data structures are free of circular references so they can be properly " \ + "serialized into JSON.\n\n" \ + "For example, the following Hash contains a circular reference to itself:\n" \ + " h = {}\n" \ + " h['circular'] = h\n" \ + "In this case, calling h.to_json would not work properly." + + ActiveSupport::Deprecation.warn message + + SystemStackError + else + super + end + end end self.use_standard_json_time_format = true @@ -148,197 +122,3 @@ module ActiveSupport end end end - -class Object - def as_json(options = nil) #:nodoc: - if respond_to?(:to_hash) - to_hash - else - instance_values - end - end -end - -class Struct #:nodoc: - def as_json(options = nil) - Hash[members.zip(values)] - end -end - -class TrueClass - def as_json(options = nil) #:nodoc: - self - end - - def encode_json(encoder) #:nodoc: - to_s - end -end - -class FalseClass - def as_json(options = nil) #:nodoc: - self - end - - def encode_json(encoder) #:nodoc: - to_s - end -end - -class NilClass - def as_json(options = nil) #:nodoc: - self - end - - def encode_json(encoder) #:nodoc: - 'null' - end -end - -class String - def as_json(options = nil) #:nodoc: - self - end - - def encode_json(encoder) #:nodoc: - encoder.escape(self) - end -end - -class Symbol - def as_json(options = nil) #:nodoc: - to_s - end -end - -class Numeric - def as_json(options = nil) #:nodoc: - self - end - - def encode_json(encoder) #:nodoc: - to_s - end -end - -class Float - # Encoding Infinity or NaN to JSON should return "null". The default returns - # "Infinity" or "NaN" which breaks parsing the JSON. E.g. JSON.parse('[NaN]'). - def as_json(options = nil) #:nodoc: - finite? ? self : nil - end -end - -class BigDecimal - # A BigDecimal would be naturally represented as a JSON number. Most libraries, - # however, parse non-integer JSON numbers directly as floats. Clients using - # those libraries would get in general a wrong number and no way to recover - # other than manually inspecting the string with the JSON code itself. - # - # That's why a JSON string is returned. The JSON literal is not numeric, but - # if the other end knows by contract that the data is supposed to be a - # BigDecimal, it still has the chance to post-process the string and get the - # real value. - # - # Use <tt>ActiveSupport.use_standard_json_big_decimal_format = true</tt> to - # override this behavior. - def as_json(options = nil) #:nodoc: - if finite? - ActiveSupport.encode_big_decimal_as_string ? to_s : self - else - nil - end - end -end - -class Regexp - def as_json(options = nil) #:nodoc: - to_s - end -end - -module Enumerable - def as_json(options = nil) #:nodoc: - to_a.as_json(options) - end -end - -class Range - def as_json(options = nil) #:nodoc: - to_s - end -end - -class Array - def as_json(options = nil) #:nodoc: - # use encoder as a proxy to call as_json on all elements, to protect from circular references - encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options) - map { |v| encoder.as_json(v, options) } - end - - def encode_json(encoder) #:nodoc: - # we assume here that the encoder has already run as_json on self and the elements, so we run encode_json directly - "[#{map { |v| v.encode_json(encoder) } * ','}]" - end -end - -class Hash - def as_json(options = nil) #:nodoc: - # create a subset of the hash by applying :only or :except - subset = if options - if attrs = options[:only] - slice(*Array(attrs)) - elsif attrs = options[:except] - except(*Array(attrs)) - else - self - end - else - self - end - - # use encoder as a proxy to call as_json on all values in the subset, to protect from circular references - encoder = options && options[:encoder] || ActiveSupport::JSON::Encoding::Encoder.new(options) - Hash[subset.map { |k, v| [k.to_s, encoder.as_json(v, options)] }] - end - - def encode_json(encoder) #:nodoc: - # values are encoded with use_options = false, because we don't want hash representations from ActiveModel to be - # processed once again with as_json with options, as this could cause unexpected results (i.e. missing fields); - - # on the other hand, we need to run as_json on the elements, because the model representation may contain fields - # like Time/Date in their original (not jsonified) form, etc. - - "{#{map { |k,v| "#{encoder.encode(k.to_s)}:#{encoder.encode(v, false)}" } * ','}}" - end -end - -class Time - def as_json(options = nil) #:nodoc: - if ActiveSupport.use_standard_json_time_format - xmlschema - else - %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)}) - end - end -end - -class Date - def as_json(options = nil) #:nodoc: - if ActiveSupport.use_standard_json_time_format - strftime("%Y-%m-%d") - else - strftime("%Y/%m/%d") - end - end -end - -class DateTime - def as_json(options = nil) #:nodoc: - if ActiveSupport.use_standard_json_time_format - xmlschema - else - strftime('%Y/%m/%d %H:%M:%S %z') - end - end -end diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index a42e7f6542..3c0cf9f137 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -56,11 +56,10 @@ module ActiveSupport #:nodoc: # Forward all undefined methods to the wrapped string. def method_missing(method, *args, &block) + result = @wrapped_string.__send__(method, *args, &block) if method.to_s =~ /!$/ - result = @wrapped_string.__send__(method, *args, &block) self if result else - result = @wrapped_string.__send__(method, *args, &block) result.kind_of?(String) ? chars(result) : result end end diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 04e6b71580..1845c6ae38 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -287,6 +287,13 @@ module ActiveSupport class Codepoint attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping + # Initializing Codepoint object with default values + def initialize + @combining_class = 0 + @uppercase_mapping = 0 + @lowercase_mapping = 0 + end + def swapcase_mapping uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping end diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index b32aa75e59..7a96c66626 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -178,7 +178,7 @@ module ActiveSupport end def instrumenter - InstrumentationRegistry.instrumenter_for(notifier) + InstrumentationRegistry.instance.instrumenter_for(notifier) end end diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 99fe03e6d0..8f5fa646e8 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -107,21 +107,18 @@ module ActiveSupport end class Timed < Evented - def initialize(pattern, delegate) - @timestack = [] - super - end - def publish(name, *args) @delegate.call name, *args end def start(name, id, payload) - @timestack.push Time.now + timestack = Thread.current[:_timestack] ||= [] + timestack.push Time.now end def finish(name, id, payload) - started = @timestack.pop + timestack = Thread.current[:_timestack] + started = timestack.pop @delegate.call(name, started, Time.now, id, payload) end end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 0c9a729ce5..3a244b34b5 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -54,10 +54,11 @@ module ActiveSupport @transaction_id = transaction_id @end = ending @children = [] + @duration = nil end def duration - 1000.0 * (self.end - time) + @duration ||= 1000.0 * (self.end - time) end def <<(event) diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 414960d2b1..e0151baa36 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -108,7 +108,7 @@ module ActiveSupport DECIMAL_UNITS = { 0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto } - + INVERTED_DECIMAL_UNITS = DECIMAL_UNITS.invert STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb] # Formats a +number+ into a US phone number (e.g., (555) @@ -244,14 +244,14 @@ module ActiveSupport # # ==== Examples # - # number_to_percentage(100) # => 100.000% - # number_to_percentage('98') # => 98.000% - # number_to_percentage(100, precision: 0) # => 100% - # number_to_percentage(1000, delimiter: '.', separator: ,') # => 1.000,000% - # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% - # number_to_percentage(1000, locale: :fr) # => 1 000,000% - # number_to_percentage('98a') # => 98a% - # number_to_percentage(100, format: '%n %') # => 100 % + # number_to_percentage(100) # => 100.000% + # number_to_percentage('98') # => 98.000% + # number_to_percentage(100, precision: 0) # => 100% + # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000% + # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% + # number_to_percentage(1000, locale: :fr) # => 1 000,000% + # number_to_percentage('98a') # => 98a% + # number_to_percentage(100, format: '%n %') # => 100 % def number_to_percentage(number, options = {}) return unless number options = options.symbolize_keys @@ -460,7 +460,7 @@ module ActiveSupport # See <tt>number_to_human_size</tt> if you want to print a file # size. # - # You can also define you own unit-quantifier names if you want + # You can also define your own unit-quantifier names if you want # to use other decimal units (eg.: 1500 becomes "1.5 # kilometers", 0.150 becomes "150 milliliters", etc). You may # define a wide range of unit quantifiers, even fractional ones @@ -561,8 +561,6 @@ module ActiveSupport #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) - inverted_du = DECIMAL_UNITS.invert - units = options.delete :units unit_exponents = case units when Hash @@ -573,7 +571,7 @@ module ActiveSupport translate_number_value_with_default("human.decimal_units.units", :locale => options[:locale], :raise => true) else raise ArgumentError, ":units must be a Hash or String translation scope." - end.keys.map{|e_name| inverted_du[e_name] }.sort_by{|e| -e} + end.keys.map!{|e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by!{|e| -e} number_exponent = number != 0 ? Math.log10(number.abs).floor : 0 display_exponent = unit_exponents.find{ |e| number_exponent >= e } || 0 diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index e03bb4ca0f..a33e2c58a9 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -41,7 +41,7 @@ module ActiveSupport end # +InheritableOptions+ provides a constructor to build an +OrderedOptions+ - # hash inherited from the another hash. + # hash inherited from another hash. # # Use this if you already have some hash and you want to create a new one based on it. # diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb index aa682fb36c..a5e7389d16 100644 --- a/activesupport/lib/active_support/per_thread_registry.rb +++ b/activesupport/lib/active_support/per_thread_registry.rb @@ -32,21 +32,19 @@ module ActiveSupport # # If the class has an initializer, it must accept no arguments. module PerThreadRegistry + def instance + Thread.current[name] ||= new + end + 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| - per_thread_registry_instance.public_send(name, *a, &b) + instance.public_send(name, *a, &b) end send(name, *args, &block) end - - private - - def per_thread_registry_instance - Thread.current[name] ||= new - end end end diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index 34c6f900c1..4b9b48539f 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -31,18 +31,41 @@ module ActiveSupport # Attach the subscriber to a namespace. def attach_to(namespace, subscriber=new, notifier=ActiveSupport::Notifications) + @namespace = namespace + @subscriber = subscriber + @notifier = notifier + subscribers << subscriber + # Add event subscribers for all existing methods on the class. subscriber.public_methods(false).each do |event| - next if %w{ start finish }.include?(event.to_s) + add_event_subscriber(event) + end + end - notifier.subscribe("#{event}.#{namespace}", subscriber) + # Adds event subscribers for all new methods added to the class. + def method_added(event) + # Only public methods are added as subscribers, and only if a notifier + # has been set up. This means that subscribers will only be set up for + # classes that call #attach_to. + if public_method_defined?(event) && notifier + add_event_subscriber(event) end end def subscribers @@subscribers ||= [] end + + protected + + attr_reader :subscriber, :notifier, :namespace + + def add_event_subscriber(event) + return if %w{ start finish }.include?(event.to_s) + + notifier.subscribe("#{event}.#{namespace}", subscriber) + end end def initialize @@ -71,7 +94,7 @@ module ActiveSupport private def event_stack - SubscriberQueueRegistry.get_queue(@queue_key) + SubscriberQueueRegistry.instance.get_queue(@queue_key) end end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 2f27aff2bd..d687d69603 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -22,7 +22,7 @@ module Minitest # :nodoc: def self.__run reporter, options # :nodoc: # FIXME: MT5's runnables is not ordered. This is needed because - # we have have tests have cross-class order-dependent bugs. + # we have tests with cross-class order-dependent bugs. suites = Runnable.runnables.sort_by { |ts| ts.name.to_s } parallel, serial = suites.partition { |s| s.test_order == :parallel } @@ -44,10 +44,8 @@ module ActiveSupport end # FIXME: we have tests that depend on run order, we should fix that and - # remove this method. - def self.test_order # :nodoc: - :sorted - end + # remove this method call. + self.i_suck_and_my_tests_are_order_dependent! include ActiveSupport::Testing::TaggedLogging include ActiveSupport::Testing::SetupAndTeardown diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 9c52ae7768..d5d31cecbe 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -3,49 +3,6 @@ require 'minitest/parallel_each' module ActiveSupport module Testing - class RemoteError < StandardError - - attr_reader :message, :backtrace - - def initialize(exception) - @message = "caught #{exception.class.name}: #{exception.message}" - @backtrace = exception.backtrace - end - end - - class ProxyTestResult - def initialize(calls = []) - @calls = calls - end - - def add_error(e) - e = Test::Unit::Error.new(e.test_name, RemoteError.new(e.exception)) - @calls << [:add_error, e] - end - - def __replay__(result) - @calls.each do |name, args| - result.send(name, *args) - end - end - - def marshal_dump - @calls - end - - def marshal_load(calls) - initialize(calls) - end - - def method_missing(name, *args) - @calls << [name, args] - end - - def info_signal - Signal.list['INFO'] - end - end - module Isolation require 'thread' @@ -107,19 +64,18 @@ module ActiveSupport require "tempfile" if ENV["ISOLATION_TEST"] - proxy = ProxyTestResult.new - retval = yield proxy + yield File.open(ENV["ISOLATION_OUTPUT"], "w") do |file| - file.puts [Marshal.dump([retval, proxy])].pack("m") + file.puts [Marshal.dump(self.dup)].pack("m") end exit! else Tempfile.open("isolation") do |tmpfile| - ENV["ISOLATION_TEST"] = @method_name + ENV["ISOLATION_TEST"] = self.class.name ENV["ISOLATION_OUTPUT"] = tmpfile.path load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ") - `#{Gem.ruby} #{load_paths} #{$0} #{ORIG_ARGV.join(" ")} -t\"#{self.class}\"` + `#{Gem.ruby} #{load_paths} #{$0} #{ORIG_ARGV.join(" ")}` ENV.delete("ISOLATION_TEST") ENV.delete("ISOLATION_OUTPUT") diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 95b9b8e5ae..50db7da9d9 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -146,12 +146,12 @@ module ActiveSupport # to +false+. # # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = true - # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json - # # => "2005-02-01T15:15:10Z" + # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json + # # => "2005-02-01T05:15:10.000-10:00" # # # With ActiveSupport::JSON::Encoding.use_standard_json_time_format = false - # Time.utc(2005,2,1,15,15,10).in_time_zone.to_json - # # => "2005/02/01 15:15:10 +0000" + # Time.utc(2005,2,1,15,15,10).in_time_zone("Hawaii").to_json + # # => "2005/02/01 05:15:10 -1000" def as_json(options = nil) if ActiveSupport::JSON::Encoding.use_standard_json_time_format xmlschema(3) diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index 939bd7bfa8..65de48a7f6 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -24,3 +24,13 @@ Thread.abort_on_exception = true # Show backtraces for deprecated behavior for quicker cleanup. ActiveSupport::Deprecation.debug = true + +# Skips the current run on Rubinius using Minitest::Assertions#skip +def rubinius_skip(message = '') + skip message if RUBY_ENGINE == 'rbx' +end + +# Skips the current run on JRuby using Minitest::Assertions#skip +def jruby_skip(message = '') + skip message if RUBY_ENGINE == 'jruby' +end diff --git a/activesupport/test/autoloading_fixtures/html/some_class.rb b/activesupport/test/autoloading_fixtures/html/some_class.rb new file mode 100644 index 0000000000..b43d15d891 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/html/some_class.rb @@ -0,0 +1,4 @@ +module HTML + class SomeClass + end +end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index ae6eaa4b60..51007402a1 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -327,8 +327,8 @@ module CacheStoreBehavior def test_exist @cache.write('foo', 'bar') - assert @cache.exist?('foo') - assert !@cache.exist?('bar') + assert_equal true, @cache.exist?('foo') + assert_equal false, @cache.exist?('bar') end def test_nil_exist @@ -709,12 +709,31 @@ class FileStoreTest < ActiveSupport::TestCase @cache.send(:read_entry, "winston", {}) assert @buffer.string.present? end + + def test_cleanup_removes_all_expired_entries + time = Time.now + @cache.write('foo', 'bar', expires_in: 10) + @cache.write('baz', 'qux') + @cache.write('quux', 'corge', expires_in: 20) + Time.stubs(:now).returns(time + 15) + @cache.cleanup + assert_not @cache.exist?('foo') + assert @cache.exist?('baz') + assert @cache.exist?('quux') + end + + def test_write_with_unless_exist + assert_equal true, @cache.write(1, "aaaaaaaaaa") + assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) + @cache.write(1, nil) + assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true) + end end class MemoryStoreTest < ActiveSupport::TestCase def setup - @record_size = ActiveSupport::Cache::Entry.new("aaaaaaaaaa").size - @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10) + @record_size = ActiveSupport::Cache.lookup_store(:memory_store).send(:cached_size, 1, ActiveSupport::Cache::Entry.new("aaaaaaaaaa")) + @cache = ActiveSupport::Cache.lookup_store(:memory_store, :expires_in => 60, :size => @record_size * 10 + 1) end include CacheStoreBehavior @@ -764,6 +783,30 @@ class MemoryStoreTest < ActiveSupport::TestCase assert !@cache.exist?(1), "no entry" end + def test_prune_size_on_write_based_on_key_length + @cache.write(1, "aaaaaaaaaa") && sleep(0.001) + @cache.write(2, "bbbbbbbbbb") && sleep(0.001) + @cache.write(3, "cccccccccc") && sleep(0.001) + @cache.write(4, "dddddddddd") && sleep(0.001) + @cache.write(5, "eeeeeeeeee") && sleep(0.001) + @cache.write(6, "ffffffffff") && sleep(0.001) + @cache.write(7, "gggggggggg") && sleep(0.001) + @cache.write(8, "hhhhhhhhhh") && sleep(0.001) + @cache.write(9, "iiiiiiiiii") && sleep(0.001) + long_key = '*' * 2 * @record_size + @cache.write(long_key, "llllllllll") + assert @cache.exist?(long_key) + assert @cache.exist?(9) + assert @cache.exist?(8) + assert @cache.exist?(7) + assert @cache.exist?(6) + assert !@cache.exist?(5), "no entry" + assert !@cache.exist?(4), "no entry" + assert !@cache.exist?(3), "no entry" + assert !@cache.exist?(2), "no entry" + assert !@cache.exist?(1), "no entry" + end + def test_pruning_is_capped_at_a_max_time def @cache.delete_entry (*args) sleep(0.01) diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb index 8e2c298fc6..a74ee880b2 100644 --- a/activesupport/test/concern_test.rb +++ b/activesupport/test/concern_test.rb @@ -56,10 +56,6 @@ class ConcernTest < ActiveSupport::TestCase @klass.send(:include, Baz) assert_equal "baz", @klass.new.baz assert @klass.included_modules.include?(ConcernTest::Baz) - - @klass.send(:include, Baz) - assert_equal "baz", @klass.new.baz - assert @klass.included_modules.include?(ConcernTest::Baz) end def test_class_methods_are_extended @@ -68,12 +64,6 @@ class ConcernTest < ActiveSupport::TestCase assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0] end - def test_instance_methods_are_included - @klass.send(:include, Baz) - assert_equal "baz", @klass.new.baz - assert @klass.included_modules.include?(ConcernTest::Baz) - end - def test_included_block_is_ran @klass.send(:include, Baz) assert_equal true, @klass.included_ran diff --git a/activesupport/test/core_ext/array_ext_test.rb b/activesupport/test/core_ext/array_ext_test.rb index 6cd0eb39b7..57722fd52a 100644 --- a/activesupport/test/core_ext/array_ext_test.rb +++ b/activesupport/test/core_ext/array_ext_test.rb @@ -212,18 +212,24 @@ class ArraySplitTests < ActiveSupport::TestCase end def test_split_with_argument - assert_equal [[1, 2], [4, 5]], [1, 2, 3, 4, 5].split(3) - assert_equal [[1, 2, 3, 4, 5]], [1, 2, 3, 4, 5].split(0) + a = [1, 2, 3, 4, 5] + assert_equal [[1, 2], [4, 5]], a.split(3) + assert_equal [[1, 2, 3, 4, 5]], a.split(0) + assert_equal [1, 2, 3, 4, 5], a end def test_split_with_block - assert_equal [[1, 2], [4, 5], [7, 8], [10]], (1..10).to_a.split { |i| i % 3 == 0 } + a = (1..10).to_a + assert_equal [[1, 2], [4, 5], [7, 8], [10]], a.split { |i| i % 3 == 0 } + assert_equal [1, 2, 3, 4, 5, 6, 7, 8, 9 ,10], a end def test_split_with_edge_values - assert_equal [[], [2, 3, 4, 5]], [1, 2, 3, 4, 5].split(1) - assert_equal [[1, 2, 3, 4], []], [1, 2, 3, 4, 5].split(5) - assert_equal [[], [2, 3, 4], []], [1, 2, 3, 4, 5].split { |i| i == 1 || i == 5 } + a = [1, 2, 3, 4, 5] + assert_equal [[], [2, 3, 4, 5]], a.split(1) + assert_equal [[1, 2, 3, 4], []], a.split(5) + assert_equal [[], [2, 3, 4], []], a.split { |i| i == 1 || i == 5 } + assert_equal [1, 2, 3, 4, 5], a end end diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb index a5987044b9..b386e55d6c 100644 --- a/activesupport/test/core_ext/bigdecimal_test.rb +++ b/activesupport/test/core_ext/bigdecimal_test.rb @@ -1,5 +1,4 @@ require 'abstract_unit' -require 'bigdecimal' require 'active_support/core_ext/big_decimal' class BigDecimalTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index c32056f672..eab3aa7a6e 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -25,6 +25,7 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal "February 21st, 2005", date.to_s(:long_ordinal) assert_equal "2005-02-21", date.to_s(:db) assert_equal "21 Feb 2005", date.to_s(:rfc822) + assert_equal "2005-02-21", date.to_s(:iso8601) end def test_readable_inspect @@ -247,6 +248,10 @@ class DateExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,2,21,0,0,0), Date.new(2005,2,21).beginning_of_day end + def test_middle_of_day + assert_equal Time.local(2005,2,21,12,0,0), Date.new(2005,2,21).middle_of_day + end + def test_beginning_of_day_when_zone_is_set zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'UTC' do diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 571344b728..0a40aeb96c 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -18,6 +18,12 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal "Mon, 21 Feb 2005 14:30:00 +0000", datetime.to_s(:rfc822) assert_equal "February 21st, 2005 14:30", datetime.to_s(:long_ordinal) assert_match(/^2005-02-21T14:30:00(Z|\+00:00)$/, datetime.to_s) + + with_env_tz "US/Central" do + assert_equal "2009-02-05T14:30:05-06:00", DateTime.civil(2009, 2, 5, 14, 30, 5, Rational(-21600, 86400)).to_s(:iso8601) + assert_equal "2008-06-09T04:05:01-05:00", DateTime.civil(2008, 6, 9, 4, 5, 1, Rational(-18000, 86400)).to_s(:iso8601) + assert_equal "2009-02-05T14:30:05+00:00", DateTime.civil(2009, 2, 5, 14, 30, 5).to_s(:iso8601) + end end def test_readable_inspect @@ -76,6 +82,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal DateTime.civil(2005,2,4,0,0,0), DateTime.civil(2005,2,4,10,10,10).beginning_of_day end + def test_middle_of_day + assert_equal DateTime.civil(2005,2,4,12,0,0), DateTime.civil(2005,2,4,10,10,10).middle_of_day + end + def test_end_of_day assert_equal DateTime.civil(2005,2,4,23,59,59), DateTime.civil(2005,2,4,10,10,10).end_of_day end diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 5e3987265b..ed267cf4b9 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -27,6 +27,7 @@ class DurationTest < ActiveSupport::TestCase def test_equals assert 1.day == 1.day assert 1.day == 1.day.to_i + assert 1.day.to_i == 1.day assert !(1.day == 'foo') end @@ -37,6 +38,8 @@ class DurationTest < ActiveSupport::TestCase assert_equal '6 months and -2 days', (6.months - 2.days).inspect assert_equal '10 seconds', 10.seconds.inspect assert_equal '10 years, 2 months, and 1 day', (10.years + 2.months + 1.day).inspect + assert_equal '10 years, 2 months, and 1 day', (10.years + 1.month + 1.day + 1.month).inspect + assert_equal '10 years, 2 months, and 1 day', (1.day + 10.years + 2.months).inspect assert_equal '7 days', 1.week.inspect assert_equal '14 days', 1.fortnight.inspect end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 2d0c56bef5..b059bc3e89 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -781,6 +781,24 @@ class HashExtTest < ActiveSupport::TestCase assert_equal 'bender', slice['login'] end + def test_slice_bang_does_not_override_default + hash = Hash.new(0) + hash.update(a: 1, b: 2) + + hash.slice!(:a) + + assert_equal 0, hash[:c] + end + + def test_slice_bang_does_not_override_default_proc + hash = Hash.new { |h, k| h[k] = [] } + hash.update(a: 1, b: 2) + + hash.slice!(:a) + + assert_equal [], hash[:c] + end + def test_extract original = {:a => 1, :b => 2, :c => 3, :d => 4} expected = {:a => 1, :b => 2} diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index ccf537e075..283b13ff8b 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -66,6 +66,23 @@ Tester = Struct.new(:client) do delegate :name, :to => :client, :prefix => false end +Product = Struct.new(:name) do + delegate :name, :to => :manufacturer, :prefix => true + delegate :name, :to => :type, :prefix => true + + def manufacturer + @manufacturer ||= begin + nil.unknown_method + end + end + + def type + @type ||= begin + nil.type_name + end + end +end + class ParameterSet delegate :[], :[]=, :to => :@params @@ -264,6 +281,16 @@ class ModuleTest < ActiveSupport::TestCase assert_equal [3], se.ints end + def test_delegation_doesnt_mask_nested_no_method_error_on_nil_receiver + product = Product.new('Widget') + + # Nested NoMethodError is a different name from the delegation + assert_raise(NoMethodError) { product.manufacturer_name } + + # Nested NoMethodError is the same name as the delegation + assert_raise(NoMethodError) { product.type_name } + end + def test_parent assert_equal Yz::Zy, Yz::Zy::Cd.parent assert_equal Yz, Yz::Zy.parent diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index 1da72eb17d..23bbb8d7d2 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -440,4 +440,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase assert_equal BigDecimal, BigDecimal("1000010").class assert_equal '1 Million', BigDecimal("1000010").to_s(:human) end + + def test_in_milliseconds + assert_equal 10_000, 10.seconds.in_milliseconds + end end diff --git a/activesupport/test/core_ext/object/json_test.rb b/activesupport/test/core_ext/object/json_test.rb new file mode 100644 index 0000000000..d3d31530df --- /dev/null +++ b/activesupport/test/core_ext/object/json_test.rb @@ -0,0 +1,9 @@ +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/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 2f8723a074..854b0a38bd 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -90,4 +90,26 @@ class RangeTest < ActiveSupport::TestCase time_range_2 = Time.utc(2005, 12, 10, 17, 31)..Time.utc(2005, 12, 10, 18, 00) assert !time_range_1.overlaps?(time_range_2) end + + def test_each_on_time_with_zone + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30)) + assert_raises TypeError do + ((twz - 1.hour)..twz).each {} + end + end + + def test_step_on_time_with_zone + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30)) + assert_raises TypeError do + ((twz - 1.hour)..twz).step(1) {} + end + end + + def test_include_on_time_with_zone + twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Eastern Time (US & Canada)'] , Time.utc(2006,11,28,10,30)) + assert_raises TypeError do + ((twz - 1.hour)..twz).include?(twz) + end + end + end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 12126fb9ef..20e3d4802e 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -11,13 +11,6 @@ require 'active_support/core_ext/string/strip' require 'active_support/core_ext/string/output_safety' require 'active_support/core_ext/string/indent' -module Ace - module Base - class Case - end - end -end - class StringInflectionsTest < ActiveSupport::TestCase include InflectorTestCases include ConstantizeTestCases @@ -162,6 +155,12 @@ class StringInflectionsTest < ActiveSupport::TestCase end end + def test_humanize_without_capitalize + UnderscoreToHumanWithoutCapitalize.each do |underscore, human| + assert_equal(human, underscore.humanize(capitalize: false)) + end + end + def test_ord assert_equal 97, 'a'.ord assert_equal 97, 'abc'.ord @@ -278,6 +277,11 @@ class StringInflectionsTest < ActiveSupport::TestCase assert !"Hello World!".truncate(12).html_safe? end + def test_remove + assert_equal "Summer", "Fast Summer".remove(/Fast /) + assert_equal "Summer", "Fast Summer".remove!(/Fast /) + end + def test_constantize run_constantize_tests_on do |string| string.constantize diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb index 230c1203ad..6a7c6e0604 100644 --- a/activesupport/test/core_ext/thread_test.rb +++ b/activesupport/test/core_ext/thread_test.rb @@ -63,15 +63,13 @@ class ThreadExt < ActiveSupport::TestCase end end - def test_thread_variable_security - t = Thread.new { sleep } - - assert_raises(SecurityError) do - Thread.new { $SAFE = 4; t.thread_variable_get(:foo) }.join - end - - assert_raises(SecurityError) do - Thread.new { $SAFE = 4; t.thread_variable_set(:foo, :baz) }.join + def test_thread_variable_frozen_after_set + t = Thread.new { }.join + t.thread_variable_set :foo, "bar" + t.freeze + assert_raises(RuntimeError) do + t.thread_variable_set(:baz, "qux") end end + end diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb index d43cf41201..41a1df084e 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -117,6 +117,18 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end end + def test_middle_of_day + assert_equal Time.local(2005,2,4,12,0,0), Time.local(2005,2,4,10,10,10).middle_of_day + with_env_tz 'US/Eastern' do + assert_equal Time.local(2006,4,2,12,0,0), Time.local(2006,4,2,10,10,10).middle_of_day, 'start DST' + assert_equal Time.local(2006,10,29,12,0,0), Time.local(2006,10,29,10,10,10).middle_of_day, 'ends DST' + end + with_env_tz 'NZ' do + assert_equal Time.local(2006,3,19,12,0,0), Time.local(2006,3,19,10,10,10).middle_of_day, 'ends DST' + assert_equal Time.local(2006,10,1,12,0,0), Time.local(2006,10,1,10,10,10).middle_of_day, 'start DST' + end + end + def test_beginning_of_hour assert_equal Time.local(2005,2,4,19,0,0), Time.local(2005,2,4,19,30,10).beginning_of_hour end @@ -509,6 +521,9 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase with_env_tz "US/Central" do assert_equal "Thu, 05 Feb 2009 14:30:05 -0600", Time.local(2009, 2, 5, 14, 30, 5).to_s(:rfc822) assert_equal "Mon, 09 Jun 2008 04:05:01 -0500", Time.local(2008, 6, 9, 4, 5, 1).to_s(:rfc822) + assert_equal "2009-02-05T14:30:05-06:00", Time.local(2009, 2, 5, 14, 30, 5).to_s(:iso8601) + assert_equal "2008-06-09T04:05:01-05:00", Time.local(2008, 6, 9, 4, 5, 1).to_s(:iso8601) + assert_equal "2009-02-05T14:30:05Z", Time.utc(2009, 2, 5, 14, 30, 5).to_s(:iso8601) end end @@ -700,6 +715,21 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end end + def test_at_with_datetime_returns_local_time + with_env_tz 'US/Eastern' do + dt = DateTime.civil(2000, 1, 1, 0, 0, 0, '+0') + assert_equal Time.local(1999, 12, 31, 19, 0, 0), Time.at(dt) + assert_equal 'EST', Time.at(dt).zone + assert_equal(-18000, Time.at(dt).utc_offset) + + # Daylight savings + dt = DateTime.civil(2000, 7, 1, 1, 0, 0, '+1') + assert_equal Time.local(2000, 6, 30, 20, 0, 0), Time.at(dt) + assert_equal 'EDT', Time.at(dt).zone + assert_equal(-14400, Time.at(dt).utc_offset) + end + end + def test_at_with_time_with_zone assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['UTC'])) @@ -711,6 +741,45 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end end + def test_at_with_time_with_zone_returns_local_time + with_env_tz 'US/Eastern' do + twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 1, 1, 0, 0, 0), ActiveSupport::TimeZone['London']) + assert_equal Time.local(1999, 12, 31, 19, 0, 0), Time.at(twz) + assert_equal 'EST', Time.at(twz).zone + assert_equal(-18000, Time.at(twz).utc_offset) + + # Daylight savings + twz = ActiveSupport::TimeWithZone.new(Time.utc(2000, 7, 1, 0, 0, 0), ActiveSupport::TimeZone['London']) + assert_equal Time.local(2000, 6, 30, 20, 0, 0), Time.at(twz) + assert_equal 'EDT', Time.at(twz).zone + assert_equal(-14400, Time.at(twz).utc_offset) + end + end + + def test_at_with_time_microsecond_precision + assert_equal Time.at(Time.utc(2000, 1, 1, 0, 0, 0, 111)).to_f, Time.utc(2000, 1, 1, 0, 0, 0, 111).to_f + end + + def test_at_with_utc_time + with_env_tz 'US/Eastern' do + assert_equal Time.utc(2000), Time.at(Time.utc(2000)) + assert_equal 'UTC', Time.at(Time.utc(2000)).zone + assert_equal(0, Time.at(Time.utc(2000)).utc_offset) + end + end + + def test_at_with_local_time + with_env_tz 'US/Eastern' do + assert_equal Time.local(2000), Time.at(Time.local(2000)) + assert_equal 'EST', Time.at(Time.local(2000)).zone + assert_equal(-18000, Time.at(Time.local(2000)).utc_offset) + + assert_equal Time.local(2000, 7, 1), Time.at(Time.local(2000, 7, 1)) + assert_equal 'EDT', Time.at(Time.local(2000, 7, 1)).zone + assert_equal(-14400, Time.at(Time.local(2000, 7, 1)).utc_offset) + end + end + def test_eql? assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']) ) assert_equal true, Time.utc(2000).eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index ddcfcc491f..5494824a40 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -80,6 +80,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase ActiveSupport.use_standard_json_time_format = old end + def test_to_json_when_wrapping_a_date_time + twz = ActiveSupport::TimeWithZone.new(DateTime.civil(2000), @time_zone) + assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(twz) + end + def test_nsec local = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000)) with_zone = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Hawaii"], local) @@ -992,6 +997,15 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase Time.zone = nil end + def test_time_in_time_zone_doesnt_affect_receiver + with_env_tz 'Europe/London' do + time = Time.local(2000, 7, 1) + time_with_zone = time.in_time_zone('Eastern Time (US & Canada)') + assert_equal Time.utc(2000, 6, 30, 23, 0, 0), time_with_zone + assert_not time.utc?, 'time expected to be local, but is UTC' + end + end + protected def with_env_tz(new_tz = 'US/Eastern') old_tz, ENV['TZ'] = ENV['TZ'], new_tz diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 68b6cc6e8c..2392b71960 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -35,6 +35,17 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal expected.path, e.path end + def test_require_dependency_accepts_an_object_which_implements_to_path + o = Object.new + def o.to_path; 'dependencies/service_one'; end + assert_nothing_raised { + require_dependency o + } + assert defined?(ServiceOne) + ensure + remove_constants(:ServiceOne) + end + def test_tracking_loaded_files require_dependency 'dependencies/service_one' require_dependency 'dependencies/service_two' @@ -647,6 +658,14 @@ class DependenciesTest < ActiveSupport::TestCase Object.class_eval { remove_const :E } end + def test_constants_in_capitalized_nesting_marked_as_autoloaded + with_autoloading_fixtures do + ActiveSupport::Dependencies.load_missing_constant(HTML, "SomeClass") + + assert ActiveSupport::Dependencies.autoloaded?("HTML::SomeClass") + end + end + def test_unloadable with_autoloading_fixtures do Object.const_set :M, Module.new diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 9616e42f44..9674851b9d 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -98,6 +98,22 @@ class DeprecationTest < ActiveSupport::TestCase assert_match(/foo=nil/, @b) end + def test_raise_behaviour + ActiveSupport::Deprecation.behavior = :raise + + message = 'Revise this deprecated stuff now!' + callstack = %w(foo bar baz) + + begin + ActiveSupport::Deprecation.behavior.first.call(message, callstack) + rescue ActiveSupport::DeprecationException => e + assert_equal message, e.message + assert_equal callstack, e.backtrace + else + flunk 'the :raise deprecation behaviour should raise the expected exception' + end + end + def test_default_stderr_behavior ActiveSupport::Deprecation.behavior = :stderr behavior = ActiveSupport::Deprecation.behavior.first diff --git a/activesupport/test/descendants_tracker_without_autoloading_test.rb b/activesupport/test/descendants_tracker_without_autoloading_test.rb index 74669aaca1..00b449af51 100644 --- a/activesupport/test/descendants_tracker_without_autoloading_test.rb +++ b/activesupport/test/descendants_tracker_without_autoloading_test.rb @@ -4,4 +4,14 @@ require 'descendants_tracker_test_cases' class DescendantsTrackerWithoutAutoloadingTest < ActiveSupport::TestCase include DescendantsTrackerTestCases + + # Regression test for #8422. https://github.com/rails/rails/issues/8442 + def test_clear_without_autoloaded_singleton_parent + mark_as_autoloaded do + parent_instance = Parent.new + parent_instance.singleton_class.descendants + ActiveSupport::DescendantsTracker.clear + assert !ActiveSupport::DescendantsTracker.class_variable_get(:@@direct_descendants).key?(parent_instance.singleton_class) + end + end end diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index 22cb61ffd6..6184df481f 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -76,9 +76,10 @@ class InflectorTest < ActiveSupport::TestCase ActiveSupport::Inflector.inflections.uncountable "series" # Return to normal end - MixtureToTitleCase.each do |before, titleized| - define_method "test_titleize_#{before}" do - assert_equal(titleized, ActiveSupport::Inflector.titleize(before)) + MixtureToTitleCase.each_with_index do |(before, titleized), index| + define_method "test_titleize_mixture_to_title_case_#{index}" do + assert_equal(titleized, ActiveSupport::Inflector.titleize(before), "mixture \ + to TitleCase failed for #{before}") end end @@ -229,25 +230,35 @@ class InflectorTest < ActiveSupport::TestCase end end +# FIXME: get following tests to pass on jruby, currently skipped +# +# Currently this fails because ActiveSupport::Multibyte::Unicode#tidy_bytes +# required a specific Encoding::Converter(UTF-8 to UTF8-MAC) which unavailable on JRuby +# causing our tests to error out. +# related bug http://jira.codehaus.org/browse/JRUBY-7194 def test_parameterize + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterized.each do |some_string, parameterized_string| assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string)) end end def test_parameterize_and_normalize + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterizedAndNormalized.each do |some_string, parameterized_string| assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string)) end end def test_parameterize_with_custom_separator + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterizeWithUnderscore.each do |some_string, parameterized_string| assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_')) end end def test_parameterize_with_multi_character_separator + jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable" StringToParameterized.each do |some_string, parameterized_string| assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__')) end @@ -276,6 +287,12 @@ class InflectorTest < ActiveSupport::TestCase end end + def test_humanize_without_capitalize + UnderscoreToHumanWithoutCapitalize.each do |underscore, human| + assert_equal(human, ActiveSupport::Inflector.humanize(underscore, capitalize: false)) + end + end + def test_humanize_by_rule ActiveSupport::Inflector.inflections do |inflect| inflect.human(/_cnt$/i, '\1_count') @@ -419,33 +436,36 @@ class InflectorTest < ActiveSupport::TestCase end end - Irregularities.each do |irregularity| - singular, plural = *irregularity - ActiveSupport::Inflector.inflections do |inflect| - define_method("test_irregularity_between_#{singular}_and_#{plural}") do - inflect.irregular(singular, plural) - assert_equal singular, ActiveSupport::Inflector.singularize(plural) - assert_equal plural, ActiveSupport::Inflector.pluralize(singular) + Irregularities.each do |singular, plural| + define_method("test_irregularity_between_#{singular}_and_#{plural}") do + with_dup do + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular(singular, plural) + assert_equal singular, ActiveSupport::Inflector.singularize(plural) + assert_equal plural, ActiveSupport::Inflector.pluralize(singular) + end end end end - Irregularities.each do |irregularity| - singular, plural = *irregularity - ActiveSupport::Inflector.inflections do |inflect| - define_method("test_pluralize_of_irregularity_#{plural}_should_be_the_same") do - inflect.irregular(singular, plural) - assert_equal plural, ActiveSupport::Inflector.pluralize(plural) + Irregularities.each do |singular, plural| + define_method("test_pluralize_of_irregularity_#{plural}_should_be_the_same") do + with_dup do + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular(singular, plural) + assert_equal plural, ActiveSupport::Inflector.pluralize(plural) + end end end end - Irregularities.each do |irregularity| - singular, plural = *irregularity - ActiveSupport::Inflector.inflections do |inflect| - define_method("test_singularize_of_irregularity_#{singular}_should_be_the_same") do - inflect.irregular(singular, plural) - assert_equal singular, ActiveSupport::Inflector.singularize(singular) + Irregularities.each do |singular, plural| + define_method("test_singularize_of_irregularity_#{singular}_should_be_the_same") do + with_dup do + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular(singular, plural) + assert_equal singular, ActiveSupport::Inflector.singularize(singular) + end end end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 7704300938..b34a946baf 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -105,7 +105,6 @@ module InflectorTestCases "prize" => "prizes", "edge" => "edges", - "cow" => "kine", "database" => "databases", # regression tests against improper inflection regexes @@ -213,6 +212,12 @@ module InflectorTestCases "underground" => "Underground" } + UnderscoreToHumanWithoutCapitalize = { + "employee_salary" => "employee salary", + "employee_id" => "employee", + "underground" => "underground" + } + MixtureToTitleCase = { 'active_record' => 'Active Record', 'ActiveRecord' => 'Active Record', diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 34ed866848..07d7e530ca 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -4,6 +4,12 @@ require 'active_support/json' require 'active_support/time' class TestJSONDecoding < ActiveSupport::TestCase + class Foo + def self.json_create(object) + "Foo" + end + end + TESTS = { %q({"returnTo":{"\/categories":"\/"}}) => {"returnTo" => {"/categories" => "/"}}, %q({"return\\"To\\":":{"\/categories":"\/"}}) => {"return\"To\":" => {"/categories" => "/"}}, @@ -52,15 +58,26 @@ class TestJSONDecoding < ActiveSupport::TestCase # tests escaping of "\n" char with Yaml backend %q({"a":"\n"}) => {"a"=>"\n"}, %q({"a":"\u000a"}) => {"a"=>"\n"}, - %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"} + %q({"a":"Line1\u000aLine2"}) => {"a"=>"Line1\nLine2"}, + # prevent json unmarshalling + %q({"json_class":"TestJSONDecoding::Foo"}) => {"json_class"=>"TestJSONDecoding::Foo"}, + # json "fragments" - these are invalid JSON, but ActionPack relies on this + %q("a string") => "a string", + %q(1.1) => 1.1, + %q(1) => 1, + %q(-1) => -1, + %q(true) => true, + %q(false) => false, + %q(null) => nil } - TESTS.each do |json, expected| - test "json decodes #{json}" do + TESTS.each_with_index do |(json, expected), index| + test "json decodes #{index}" do prev = ActiveSupport.parse_json_times ActiveSupport.parse_json_times = true silence_warnings do - assert_equal expected, ActiveSupport::JSON.decode(json) + assert_equal expected, ActiveSupport::JSON.decode(json), "JSON decoding \ + failed for #{json}" end ActiveSupport.parse_json_times = prev end @@ -75,7 +92,14 @@ class TestJSONDecoding < ActiveSupport::TestCase end def test_failed_json_decoding + assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%(undefined)) } + assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({a: 1})) } assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%({: 1})) } + assert_raise(ActiveSupport::JSON.parse_error) { ActiveSupport::JSON.decode(%()) } + end + + def test_cannot_pass_unsupported_options + assert_raise(ArgumentError) { ActiveSupport::JSON.decode("", create_additions: true) } end end diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index ed1326705c..856ca75cbc 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -1,4 +1,5 @@ # encoding: utf-8 +require 'securerandom' require 'abstract_unit' require 'active_support/core_ext/string/inflections' require 'active_support/json' @@ -12,7 +13,7 @@ class TestJSONEncoding < ActiveSupport::TestCase class Hashlike def to_hash - { :a => 1 } + { :foo => "hello", :bar => "world" } end end @@ -60,7 +61,7 @@ class TestJSONEncoding < ActiveSupport::TestCase [ :"a b", %("a b") ]] ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]] - HashlikeTests = [[ Hashlike.new, %({\"a\":1}) ]] + HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]] CustomTests = [[ Custom.new, '"custom"' ]] RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']] @@ -70,8 +71,8 @@ class TestJSONEncoding < ActiveSupport::TestCase DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]] StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]] - StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10Z") ]] - StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10+00:00") ]] + StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]] + StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]] StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]] def sorted_json(json) @@ -96,6 +97,13 @@ class TestJSONEncoding < ActiveSupport::TestCase end end + def test_process_status + # There doesn't seem to be a good way to get a handle on a Process::Status object without actually + # creating a child process, hence this to populate $? + system("not_a_real_program_#{SecureRandom.hex}") + assert_equal %({"exitstatus":#{$?.exitstatus},"pid":#{$?.pid}}), ActiveSupport::JSON.encode($?) + end + def test_hash_encoding assert_equal %({\"a\":\"b\"}), ActiveSupport::JSON.encode(:a => :b) assert_equal %({\"a\":1}), ActiveSupport::JSON.encode('a' => 1) @@ -138,19 +146,25 @@ class TestJSONEncoding < ActiveSupport::TestCase def test_exception_raised_when_encoding_circular_reference_in_array a = [1] a << a - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } + assert_deprecated do + assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } + end end def test_exception_raised_when_encoding_circular_reference_in_hash a = { :name => 'foo' } a[:next] = a - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } + assert_deprecated do + assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } + end end def test_exception_raised_when_encoding_circular_reference_in_hash_inside_array a = { :name => 'foo', :sub => [] } a[:sub] << a - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } + assert_deprecated do + assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } + end end def test_hash_key_identifiers_are_always_quoted @@ -170,7 +184,7 @@ class TestJSONEncoding < ActiveSupport::TestCase prev = ActiveSupport.use_standard_json_time_format ActiveSupport.use_standard_json_time_format = true with_env_tz 'US/Eastern' do - assert_equal %("2005-02-01T15:15:10-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10)) + assert_equal %("2005-02-01T15:15:10.000-05:00"), ActiveSupport::JSON.encode(Time.local(2005,2,1,15,15,10)) end ensure ActiveSupport.use_standard_json_time_format = prev @@ -196,6 +210,31 @@ class TestJSONEncoding < ActiveSupport::TestCase end end + def test_hash_like_with_options + h = Hashlike.new + json = h.to_json :only => [:foo] + + assert_equal({"foo"=>"hello"}, JSON.parse(json)) + end + + def test_object_to_json_with_options + obj = Object.new + obj.instance_variable_set :@foo, "hello" + obj.instance_variable_set :@bar, "world" + json = obj.to_json :only => ["foo"] + + assert_equal({"foo"=>"hello"}, JSON.parse(json)) + end + + def test_struct_to_json_with_options + struct = Struct.new(:foo, :bar).new + struct.foo = "hello" + struct.bar = "world" + json = struct.to_json :only => [:foo] + + assert_equal({"foo"=>"hello"}, JSON.parse(json)) + end + def test_hash_should_pass_encoding_options_to_children_in_as_json person = { :name => 'John', @@ -277,7 +316,7 @@ class TestJSONEncoding < ActiveSupport::TestCase hash = {"foo" => f, "other_hash" => {"foo" => "other_foo", "test" => "other_test"}} assert_equal({"foo"=>{"foo"=>"hello","bar"=>"world"}, - "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, JSON.parse(hash.to_json)) + "other_hash" => {"foo"=>"other_foo","test"=>"other_test"}}, ActiveSupport::JSON.decode(hash.to_json)) end def test_struct_encoding @@ -302,13 +341,13 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal({"name" => "David", "sub" => { "name" => "David", - "date" => "2010-01-01" }}, JSON.parse(json_custom)) + "date" => "2010-01-01" }}, ActiveSupport::JSON.decode(json_custom)) assert_equal({"name" => "David", "email" => "sample@example.com"}, - JSON.parse(json_strings)) + ActiveSupport::JSON.decode(json_strings)) assert_equal({"name" => "David", "date" => "2010-01-01"}, - JSON.parse(json_string_and_date)) + ActiveSupport::JSON.decode(json_string_and_date)) end def test_opt_out_big_decimal_string_serialization diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index 509c453b5c..203156baa1 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -60,7 +60,7 @@ class MessageEncryptorTest < ActiveSupport::TestCase ActiveSupport.use_standard_json_time_format = true encryptor = ActiveSupport::MessageEncryptor.new(SecureRandom.hex(64), SecureRandom.hex(64), :serializer => JSONSerializer.new) message = encryptor.encrypt_and_sign({ :foo => 123, 'bar' => Time.utc(2010) }) - exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" } assert_equal exp, encryptor.decrypt_and_verify(message) ensure ActiveSupport.use_standard_json_time_format = prev diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index a8633f7299..f0f261d710 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -49,7 +49,7 @@ class MessageVerifierTest < ActiveSupport::TestCase ActiveSupport.use_standard_json_time_format = true verifier = ActiveSupport::MessageVerifier.new("Hey, I'm a secret!", :serializer => JSONSerializer.new) message = verifier.generate({ :foo => 123, 'bar' => Time.utc(2010) }) - exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00Z" } + exp = { "foo" => 123, "bar" => "2010-01-01T00:00:00.000Z" } assert_equal exp, verifier.verify(message) ensure ActiveSupport.use_standard_json_time_format = prev diff --git a/activesupport/test/ordered_hash_test.rb b/activesupport/test/ordered_hash_test.rb index c3fe89de4b..0b54026c64 100644 --- a/activesupport/test/ordered_hash_test.rb +++ b/activesupport/test/ordered_hash_test.rb @@ -1,6 +1,6 @@ require 'abstract_unit' require 'active_support/json' -require 'active_support/core_ext/object/to_json' +require 'active_support/core_ext/object/json' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/extract_options' diff --git a/activesupport/test/subscriber_test.rb b/activesupport/test/subscriber_test.rb new file mode 100644 index 0000000000..253411aa3d --- /dev/null +++ b/activesupport/test/subscriber_test.rb @@ -0,0 +1,40 @@ +require 'abstract_unit' +require 'active_support/subscriber' + +class TestSubscriber < ActiveSupport::Subscriber + attach_to :doodle + + cattr_reader :event + + def self.clear + @@event = nil + end + + def open_party(event) + @@event = event + end + + private + + def private_party(event) + @@event = event + end +end + +class SubscriberTest < ActiveSupport::TestCase + def setup + TestSubscriber.clear + end + + def test_attaches_subscribers + ActiveSupport::Notifications.instrument("open_party.doodle") + + assert_equal "open_party.doodle", TestSubscriber.event.name + end + + def test_does_not_attach_private_methods + ActiveSupport::Notifications.instrument("private_party.doodle") + + assert_nil TestSubscriber.event + end +end diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index ce91c443e1..e0f85f4e7c 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -3,7 +3,6 @@ require 'abstract_unit' require 'active_support/inflector/transliterate' class TransliterateTest < ActiveSupport::TestCase - def test_transliterate_should_not_change_ascii_chars (0..127).each do |byte| char = [byte].pack("U") @@ -24,12 +23,13 @@ class TransliterateTest < ActiveSupport::TestCase def test_transliterate_should_work_with_custom_i18n_rules_and_uncomposed_utf8 char = [117, 776].pack("U*") # "ü" as ASCII "u" plus COMBINING DIAERESIS I18n.backend.store_translations(:de, :i18n => {:transliterate => {:rule => {"ü" => "ue"}}}) - I18n.locale = :de + default_locale, I18n.locale = I18n.locale, :de assert_equal "ue", ActiveSupport::Inflector.transliterate(char) + ensure + I18n.locale = default_locale end def test_transliterate_should_allow_a_custom_replacement_char assert_equal "a*b", ActiveSupport::Inflector.transliterate("a索b", "*") end - end diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index a025279e16..d992028323 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -106,7 +106,11 @@ module XmlMiniTest module Nokogiri end setup do - @xml = ActiveSupport::XmlMini + @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend + end + + teardown do + ActiveSupport::XmlMini.backend = @default_backend end test "#with_backend should switch backend and then switch back" do @@ -135,7 +139,11 @@ module XmlMiniTest module LibXML end setup do - @xml = ActiveSupport::XmlMini + @xml, @default_backend = ActiveSupport::XmlMini, ActiveSupport::XmlMini.backend + end + + teardown do + ActiveSupport::XmlMini.backend = @default_backend end test "#with_backend should be thread-safe" do |