diff options
Diffstat (limited to 'activesupport')
222 files changed, 5011 insertions, 2991 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index a935d33686..aec8b1efab 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,373 +1,340 @@ -* Corrected Inflector#underscore handling of multiple successive acroynms. +* `number_to_currency` and `number_with_delimiter` now accept custom `delimiter_pattern` option + to handle placement of delimiter, to support currency formats like INR - *James Le Cuirot* + Example: -* Delegation now works with ruby reserved words passed to `:to` option. + number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n") + # => '₹ 12,30,000.00' - Fixes #16956. + *Vipul A M* - *Agis Anastasopoulos* +* Deprecate `:prefix` option of `number_to_human_size` with no replacement. -* Added method `#eql?` to `ActiveSupport::Duration`, in addition to `#==`. + *Jean Boussier* - Currently, the following returns `false`, contrary to expectation: +* Fix `TimeWithZone#eql?` to properly handle `TimeWithZone` created from `DateTime`: + twz = DateTime.now.in_time_zone + twz.eql?(twz.dup) => true - 1.minute.eql?(1.minute) + Fixes #14178. - Adding method `#eql?` will make this behave like expected. Method `#eql?` is - just a bit stricter than `#==`, as it checks whether the argument is also a duration. Their - parts may be different though. + *Roque Pinel* - 1.minute.eql?(60.seconds) # => true - 1.minute.eql?(60) # => false +* ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return + enumerator if called without block. - *Joost Lubach* + Fixes #20095 -* `Time#change` can now change nanoseconds (`:nsec`) as a higher-precision - alternative to microseconds (`:usec`). + *Bernard Potocki* - *Agis Anastasooulos* +* Removed `ActiveSupport::Concurrency::Latch`, superseded by `Concurrent::CountDownLatch` + from the concurrent-ruby gem. -* `MessageVerifier.new` raises an appropriate exception if the secret is `nil`. - This prevents `MessageVerifier#generate` from raising a cryptic error later on. + *Jerry D'Antonio* - *Kostiantyn Kahanskyi* +* Fix not calling `#default` on `HashWithIndifferentAccess#to_hash` when only + `default_proc` is set, which could raise. -* Introduced new configuration option `active_support.test_order` for - specifying the order in which test cases are executed. This option currently defaults - to `:sorted` but will be changed to `:random` in Rails 5.0. + *Simon Eskildsen* - *Akira Matsuda*, *Godfrey Chan* +* Fix setting `default_proc` on `HashWithIndifferentAccess#dup` -* Fixed a bug in `Inflector#underscore` where acroynms in nested constant names - are incorrectly parsed as camelCase. + *Simon Eskildsen* - Fixes #8015. +* Fix a range of values for parameters of the Time#change - *Fred Wu*, *Matthew Draper* + *Nikolay Kondratyev* -* Make `Time#change` throw an exception if the `:usec` option is out of range and - the time has an offset other than UTC or local. +* Add `Enumerable#pluck` to get the same values from arrays as from ActiveRecord + associations. - *Agis Anastasopoulos* + Fixes #20339. -* `Method` objects now report themselves as not `duplicable?`. This allows - hashes and arrays containing `Method` objects to be `deep_dup`ed. + *Kevin Deisz* - *Peter Jaros* +* Add a bang version to `ActiveSupport::OrderedOptions` get methods which will raise + an `KeyError` if the value is `.blank?` -* `determine_constant_from_test_name` does no longer shadow `NameError`s - which happens during constant autoloading. - - Fixes #9933. - - *Guo Xiang Tan* - -* Added instance_eval version to Object#try, so you can do this: - - person.try { name.first } + Before: - instead of: + if (slack_url = Rails.application.secrets.slack_url).present? + # Do something worthwhile + else + # Raise as important secret password is not specified + end - person.try { |person| person.name.first } + After: - *DHH* + slack_url = Rails.application.secrets.slack_url! -* Fix the `ActiveSupport::Duration#instance_of?` method to return the right - value with the class itself since it was previously delegated to the - internal value. + *Aditya Sanghi*, *Gaurish Sharma* - *Robin Dupret* +* Remove deprecated `Class#superclass_delegating_accessor`. + Use `Class#class_attribute` instead. -* Fix rounding errors with `#travel_to` by resetting the usec on any passed time to zero, so we only travel - with per-second precision, not anything deeper than that. + *Akshay Vishnoi* - *DHH* +* Patch `Delegator` to work with `#try`. -* Fix DateTime comparison with `DateTime::Infinity` object. + Fixes #5790. - *Rafael Mendonça França* + *Nate Smith* -* Added Object#itself which returns the object itself. Useful when dealing with a chaining scenario, like Active Record scopes: +* Add `Integer#positive?` and `Integer#negative?` query methods + in the vein of `Fixnum#zero?`. - Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at) + This makes it nicer to do things like `bunch_of_numbers.select(&:positive?)`. *DHH* -* `Object#with_options` executes block in merging option context when - explicit receiver in not passed. +* Encoding `ActiveSupport::TimeWithZone` to YAML now preserves the timezone information. - *Pavel Pravosud* + Fixes #9183. -* Fixed a compatibility issue with the `Oj` gem when cherry-picking the file - `active_support/core_ext/object/json` without requiring `active_support/json`. - - Fixes #16131. - - *Godfrey Chan* + *Andrew White* -* Make `Hash#with_indifferent_access` copy the default proc too. +* Added `ActiveSupport::TimeZone#strptime` to allow parsing times as if + from a given timezone. - *arthurnn*, *Xanders* + *Paul A Jungwirth* -* Add `String#truncate_words` to truncate a string by a number of words. +* `ActiveSupport::Callbacks#skip_callback` now raises an `ArgumentError` if + an unrecognized callback is removed. - *Mohamed Osama* + *Iain Beeston* -* Deprecate `capture` and `quietly`. +* Added `ActiveSupport::ArrayInquirer` and `Array#inquiry`. - These methods are not thread safe and may cause issues when used in threaded environments. - To avoid problems we are deprecating them. + Wrapping an array in an `ArrayInquirer` gives a friendlier way to check its + contents: - *Tom Meier* + variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) -* `DateTime#to_f` now preserves the fractional seconds instead of always - rounding to `.0`. + variants.phone? # => true + variants.tablet? # => true + variants.desktop? # => false - Fixes #15994. + variants.any?(:phone, :tablet) # => true + variants.any?(:phone, :desktop) # => true + variants.any?(:desktop, :watch) # => false - *John Paul Ashenfelter* + `Array#inquiry` is a shortcut for wrapping the receiving array in an + `ArrayInquirer`. -* Add `Hash#transform_values` to simplify a common pattern where the values of a - hash must change, but the keys are left the same. + *George Claghorn* - *Sean Griffin* +* Deprecate `alias_method_chain` in favour of `Module#prepend` introduced in + Ruby 2.0. -* Always instrument `ActiveSupport::Cache`. + *Kir Shatrov* - Since `ActiveSupport::Notifications` only instruments items when there - are attached subscribers, we don't need to disable instrumentation. +* Added `#without` on `Enumerable` and `Array` to return a copy of an + enumerable without the specified elements. - *Peter Wagenet* + *Todd Bealmear* -* Make the `apply_inflections` method case-insensitive when checking - whether a word is uncountable or not. +* Fixed a problem where `String#truncate_words` would get stuck with a complex + string. - *Robin Dupret* + *Henrik Nygren* -* Make Dependencies pass a name to NameError error. - - *arthurnn* - -* Fixed `ActiveSupport::Cache::FileStore` exploding with long paths. - - *Adam Panzer / Michael Grosser* - -* Fixed `ActiveSupport::TimeWithZone#-` so precision is not unnecessarily lost - when working with objects with a nanosecond component. - - `ActiveSupport::TimeWithZone#-` should return the same result as if we were - using `Time#-`: - - Time.now.end_of_day - Time.now.beginning_of_day # => 86399.999999999 +* Fixed a roundtrip problem with `AS::SafeBuffer` where primitive-like strings + will be dumped as primitives: Before: - Time.zone.now.end_of_day.nsec # => 999999999 - Time.zone.now.end_of_day - Time.zone.now.beginning_of_day # => 86400.0 + YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello" + YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => true + YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => false + YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => 1 + YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => 1.1 After: - Time.zone.now.end_of_day - Time.zone.now.beginning_of_day - # => 86399.999999999 - - *Gordon Chan* - -* Fixed precision error in NumberHelper when using Rationals. - - Before: - - ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2 - # => "330.00" - - After: - - ActiveSupport::NumberHelper.number_to_rounded Rational(1000, 3), precision: 2 - # => "333.33" - - See #15379. - - *Juanjo Bazán* - -* Removed deprecated `Numeric#ago` and friends - - Replacements: - - 5.ago => 5.seconds.ago - 5.until => 5.seconds.until - 5.since => 5.seconds.since - 5.from_now => 5.seconds.from_now - - See #12389 for the history and rationale behind this. + YAML.load ActiveSupport::SafeBuffer.new("Hello").to_yaml # => "Hello" + YAML.load ActiveSupport::SafeBuffer.new("true").to_yaml # => "true" + YAML.load ActiveSupport::SafeBuffer.new("false").to_yaml # => "false" + YAML.load ActiveSupport::SafeBuffer.new("1").to_yaml # => "1" + YAML.load ActiveSupport::SafeBuffer.new("1.1").to_yaml # => "1.1" *Godfrey Chan* -* DateTime `advance` now supports partial days. +* Enable `number_to_percentage` to keep the number's precision by allowing + `:precision` to be `nil`. - Before: + *Jack Xu* - DateTime.now.advance(days: 1, hours: 12) +* `config_accessor` became a private method, as with Ruby's `attr_accessor`. - After: + *Akira Matsuda* - DateTime.now.advance(days: 1.5) +* `AS::Testing::TimeHelpers#travel_to` now changes `DateTime.now` as well as + `Time.now` and `Date.today`. - Fixes #12005. + *Yuki Nishijima* - *Shay Davidson* +* Add `file_fixture` to `ActiveSupport::TestCase`. + It provides a simple mechanism to access sample files in your test cases. -* `Hash#deep_transform_keys` and `Hash#deep_transform_keys!` now transform hashes - in nested arrays. This change also applies to `Hash#deep_stringify_keys`, - `Hash#deep_stringify_keys!`, `Hash#deep_symbolize_keys` and - `Hash#deep_symbolize_keys!`. + By default file fixtures are stored in `test/fixtures/files`. This can be + configured per test-case using the `file_fixture_path` class attribute. - *OZAWA Sakuro* + *Yves Senn* -* Fixed confusing `DelegationError` in `Module#delegate`. +* Return value of yielded block in `File.atomic_write`. - See #15186. + *Ian Ker-Seymer* - *Vladimir Yarotsky* +* Duplicate frozen array when assigning it to a `HashWithIndifferentAccess` so + that it doesn't raise a `RuntimeError` when calling `map!` on it in `convert_value`. -* Fixed `ActiveSupport::Subscriber` so that no duplicate subscriber is created - when a subscriber method is redefined. + Fixes #18550. - *Dennis Schön* + *Aditya Kapoor* -* Remove deprecated string based terminators for `ActiveSupport::Callbacks`. +* Add missing time zone definitions for Russian Federation and sync them + with `zone.tab` file from tzdata version 2014j (latest). - *Eileen M. Uchitelle* + *Andrey Novikov* -* Fixed an issue when using - `ActiveSupport::NumberHelper::NumberToDelimitedConverter` to - convert a value that is an `ActiveSupport::SafeBuffer` introduced - in 2da9d67. +* Add `SecureRandom.base58` for generation of random base58 strings. - See #15064. + *Matthew Draper*, *Guillermo Iguaran* - *Mark J. Titorenko* +* Add `#prev_day` and `#next_day` counterparts to `#yesterday` and + `#tomorrow` for `Date`, `Time`, and `DateTime`. -* `TimeZone#parse` defaults the day of the month to '1' if any other date - components are specified. This is more consistent with the behavior of - `Time#parse`. + *George Claghorn* - *Ulysse Carion* +* Add `same_time` option to `#next_week` and `#prev_week` for `Date`, `Time`, + and `DateTime`. -* `humanize` strips leading underscores, if any. + *George Claghorn* - Before: +* Add `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`, + `Time`, and `DateTime`. - '_id'.humanize # => "" + `#on_weekend?` returns `true` if the receiving date/time falls on a Saturday + or Sunday. - After: + `#next_weekday` returns a new date/time representing the next day that does + not fall on a Saturday or Sunday. + + `#prev_weekday` returns a new date/time representing the previous day that + does not fall on a Saturday or Sunday. - '_id'.humanize # => "Id" + *George Claghorn* - *Xavier Noria* +* Change the default test order from `:sorted` to `:random`. -* Fixed backward compatibility issues introduced in 326e652. + *Rafael Mendonça França* - Empty Hash or Array should not be present in serialization result. +* Remove deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`. - {a: []}.to_query # => "" - {a: {}}.to_query # => "" + *Rafael Mendonça França* - For more info see #14948. +* Remove deprecated methods `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string=` + and `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string`. - *Bogdan Gusiev* + *Rafael Mendonça França* -* Add `Digest::UUID::uuid_v3` and `Digest::UUID::uuid_v5` to support stable - UUID fixtures on PostgreSQL. +* Remove deprecated `ActiveSupport::SafeBuffer#prepend`. - *Roderick van Domburg* + *Rafael Mendonça França* -* Fixed `ActiveSupport::Duration#eql?` so that `1.second.eql?(1.second)` is - true. +* Remove deprecated methods at `Kernel`. - This fixes the current situation of: + `silence_stderr`, `silence_stream`, `capture` and `quietly`. - 1.second.eql?(1.second) # => false + *Rafael Mendonça França* - `eql?` also requires that the other object is an `ActiveSupport::Duration`. - This requirement makes `ActiveSupport::Duration`'s behavior consistent with - the behavior of Ruby's numeric types: +* Remove deprecated `active_support/core_ext/big_decimal/yaml_conversions` + file. - 1.eql?(1.0) # => false - 1.0.eql?(1) # => false + *Rafael Mendonça França* - 1.second.eql?(1) # => false (was true) - 1.eql?(1.second) # => false +* Remove deprecated methods `ActiveSupport::Cache::Store.instrument` and + `ActiveSupport::Cache::Store.instrument=`. - { 1 => "foo", 1.0 => "bar" } - # => { 1 => "foo", 1.0 => "bar" } + *Rafael Mendonça França* - { 1 => "foo", 1.second => "bar" } - # now => { 1 => "foo", 1.second => "bar" } - # was => { 1 => "bar" } +* Change the way in which callback chains can be halted. - And though the behavior of these hasn't changed, for reference: + The preferred method to halt a callback chain from now on is to explicitly + `throw(:abort)`. + In the past, returning `false` in an ActiveSupport callback had the side + effect of halting the callback chain. This is not recommended anymore and, + depending on the value of + `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will + either not work at all or display a deprecation warning. - 1 == 1.0 # => true - 1.0 == 1 # => true +* Add `Callbacks::CallbackChain.halt_and_display_warning_on_return_false` - 1 == 1.second # => true - 1.second == 1 # => true + Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false` + to `true` will let an app support the deprecated way of halting callback + chains by returning `false`. - *Emily Dobervich* + Setting the value to `false` will tell the app to ignore any `false` value + returned by callbacks, and only halt the chain upon `throw(:abort)`. -* `ActiveSupport::SafeBuffer#prepend` acts like `String#prepend` and modifies - instance in-place, returning self. `ActiveSupport::SafeBuffer#prepend!` is - deprecated. + The value can also be set with the Rails configuration option + `config.active_support.halt_callback_chains_on_return_false`. - *Pavel Pravosud* + When the configuration option is missing, its value is `true`, so older apps + ported to Rails 5.0 will not break (but display a deprecation warning). + For new Rails 5.0 apps, its value is set to `false` in an initializer, so + these apps will support the new behavior by default. -* `HashWithIndifferentAccess` better respects `#to_hash` on objects it - receives. In particular, `.new`, `#update`, `#merge`, and `#replace` accept - objects which respond to `#to_hash`, even if those objects are not hashes - directly. + *claudiob* - *Peter Jaros* +* Changes arguments and default value of CallbackChain's `:terminator` option -* Deprecate `Class#superclass_delegating_accessor`, use `Class#class_attribute` instead. + Chains of callbacks defined without an explicit `:terminator` option will + now be halted as soon as a `before_` callback throws `:abort`. - *Akshay Vishnoi* + Chains of callbacks defined with a `:terminator` option will maintain their + existing behavior of halting as soon as a `before_` callback matches the + terminator's expectation. -* Ensure classes which `include Enumerable` get `#to_json` in addition to - `#as_json`. + *claudiob* - *Sammy Larbi* +* Deprecate `MissingSourceFile` in favor of `LoadError`. -* Change the signature of `fetch_multi` to return a hash rather than an - array. This makes it consistent with the output of `read_multi`. + `MissingSourceFile` was just an alias to `LoadError` and was not being + raised inside the framework. - *Parker Selbert* + *Rafael Mendonça França* -* Introduce `Concern#class_methods` as a sleek alternative to clunky - `module ClassMethods`. Add `Kernel#concern` to define at the toplevel - without chunky `module Foo; extend ActiveSupport::Concern` boilerplate. +* Add support for error dispatcher classes in `ActiveSupport::Rescuable`. + Now it acts closer to Ruby's rescue. - # app/models/concerns/authentication.rb - concern :Authentication do - included do - after_create :generate_private_key - end + Example: - class_methods do - def authenticate(credentials) - # ... + class BaseController < ApplicationController + module ErrorDispatcher + def self.===(other) + Exception === other && other.respond_to?(:status) end end - def generate_private_key - # ... + rescue_from ErrorDispatcher do |error| + render status: error.status, json: { error: error.to_s } end end - # app/models/user.rb - class User < ActiveRecord::Base - include Authentication - end + *Genadi Samokovarov* + +* Add `#verified` and `#valid_message?` methods to `ActiveSupport::MessageVerifier` + + Previously, the only way to decode a message with `ActiveSupport::MessageVerifier` + was to use `#verify`, which would raise an exception on invalid messages. Now + `#verified` can also be used, which returns `nil` on messages that cannot be + decoded. + + Previously, there was no way to check if a message's format was valid without + attempting to decode it. `#valid_message?` is a boolean convenience method that + checks whether the message is valid without actually decoding it. - *Jeremy Kemper* + *Logan Leger* -Please check [4-1-stable](https://github.com/rails/rails/blob/4-1-stable/activesupport/CHANGELOG.md) for previous changes. +Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/MIT-LICENSE b/activesupport/MIT-LICENSE index d06d4f3b2d..7bffebb076 100644 --- a/activesupport/MIT-LICENSE +++ b/activesupport/MIT-LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2005-2014 David Heinemeier Hansson +Copyright (c) 2005-2015 David Heinemeier Hansson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc index a6424a353a..cd72f53821 100644 --- a/activesupport/README.rdoc +++ b/activesupport/README.rdoc @@ -10,7 +10,7 @@ outside of Rails. The latest version of Active Support can be installed with RubyGems: - % [sudo] gem install activesupport + % gem install activesupport Source code can be downloaded as part of the Rails project on GitHub: diff --git a/activesupport/Rakefile b/activesupport/Rakefile index 7c40df8dc8..81c242d4b1 100644 --- a/activesupport/Rakefile +++ b/activesupport/Rakefile @@ -1,5 +1,4 @@ require 'rake/testtask' -require 'rubygems/package_task' task :default => :test Rake::TestTask.new do |t| @@ -17,16 +16,3 @@ namespace :test do end or raise "Failures" end end - -spec = eval(File.read('activesupport.gemspec')) - -Gem::PackageTask.new(spec) do |p| - p.gem_spec = spec -end - -desc "Release to rubygems" -task :release => :package do - require 'rake/gemcutter' - Rake::Gemcutter::Tasks.new(spec).define - Rake::Task['gem:push'].invoke -end diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 80d1d35888..67b6663915 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.summary = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.' s.description = 'A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing.' - s.required_ruby_version = '>= 1.9.3' + s.required_ruby_version = '>= 2.2.2' s.license = 'MIT' @@ -20,9 +20,11 @@ Gem::Specification.new do |s| s.rdoc_options.concat ['--encoding', 'UTF-8'] - s.add_dependency 'i18n', '>= 0.7.0.beta1', '< 0.8' + s.add_dependency 'i18n', '~> 0.7' s.add_dependency 'json', '~> 1.7', '>= 1.7.7' s.add_dependency 'tzinfo', '~> 1.1' s.add_dependency 'minitest', '~> 5.1' - s.add_dependency 'thread_safe','~> 0.1' + s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4' + s.add_dependency 'concurrent-ruby', '~> 0.9.1' + s.add_dependency 'method_source' end diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables index f39e89b7d0..71a6b78652 100755 --- a/activesupport/bin/generate_tables +++ b/activesupport/bin/generate_tables @@ -55,7 +55,7 @@ module ActiveSupport codepoint.combining_class = Integer($4) #codepoint.bidi_class = $5 codepoint.decomp_type = $7 - codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect { |element| element.hex } + codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect(&:hex) #codepoint.bidi_mirrored = ($13=='Y') ? true : false codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex diff --git a/activesupport/bin/test b/activesupport/bin/test new file mode 100755 index 0000000000..404cabba51 --- /dev/null +++ b/activesupport/bin/test @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +COMPONENT_ROOT = File.expand_path("../../", __FILE__) +require File.expand_path("../tools/test", COMPONENT_ROOT) +exit Minitest.run(ARGV) diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 94468240a4..588d6c49f9 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -1,5 +1,5 @@ #-- -# Copyright (c) 2005-2014 David Heinemeier Hansson +# Copyright (c) 2005-2015 David Heinemeier Hansson # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the @@ -59,6 +59,7 @@ module ActiveSupport autoload :StringInquirer autoload :TaggedLogging autoload :XmlMini + autoload :ArrayInquirer end autoload :Rescuable @@ -71,14 +72,14 @@ module ActiveSupport NumberHelper.eager_load! end - @@test_order = nil + cattr_accessor :test_order # :nodoc: - def self.test_order=(new_order) - @@test_order = new_order + def self.halt_callback_chains_on_return_false + Callbacks::CallbackChain.halt_and_display_warning_on_return_false end - def self.test_order - @@test_order + def self.halt_callback_chains_on_return_false=(value) + Callbacks::CallbackChain.halt_and_display_warning_on_return_false = value end end diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb new file mode 100644 index 0000000000..f59ddf5403 --- /dev/null +++ b/activesupport/lib/active_support/array_inquirer.rb @@ -0,0 +1,44 @@ +module ActiveSupport + # Wrapping an array in an +ArrayInquirer+ gives a friendlier way to check + # its string-like contents: + # + # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) + # + # variants.phone? # => true + # variants.tablet? # => true + # variants.desktop? # => false + class ArrayInquirer < Array + # Passes each element of +candidates+ collection to ArrayInquirer collection. + # The method returns true if at least one element is the same. If +candidates+ + # collection is not given, method returns true. + # + # variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet]) + # + # variants.any? # => true + # variants.any?(:phone, :tablet) # => true + # variants.any?('phone', 'desktop') # => true + # variants.any?(:desktop, :watch) # => false + def any?(*candidates, &block) + if candidates.none? + super + else + candidates.any? do |candidate| + include?(candidate.to_sym) || include?(candidate.to_s) + end + end + end + + private + def respond_to_missing?(name, include_private = false) + name[-1] == '?' + end + + def method_missing(name, *args) + if name[-1] == '?' + any?(name[0..-2]) + else + super + end + end + end +end diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb index d06f22ad5c..e161ec4cca 100644 --- a/activesupport/lib/active_support/backtrace_cleaner.rb +++ b/activesupport/lib/active_support/backtrace_cleaner.rb @@ -25,7 +25,7 @@ module ActiveSupport # of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt> # These two methods will give you a completely untouched backtrace. # - # Inspired by the Quiet Backtrace gem by Thoughtbot. + # Inspired by the Quiet Backtrace gem by thoughtbot. class BacktraceCleaner def initialize @filters, @silencers = [], [] diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb index ff67a6828c..8253a76383 100644 --- a/activesupport/lib/active_support/cache.rb +++ b/activesupport/lib/active_support/cache.rb @@ -8,7 +8,6 @@ require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' require 'active_support/core_ext/object/to_param' require 'active_support/core_ext/string/inflections' -require 'active_support/deprecation' module ActiveSupport # See ActiveSupport::Cache::Store for documentation. @@ -27,7 +26,7 @@ module ActiveSupport end class << self - # Creates a new CacheStore object according to the given options. + # Creates a new Store object according to the given options. # # If no arguments are passed to this method, then a new # ActiveSupport::Cache::MemoryStore object will be returned. @@ -179,18 +178,6 @@ module ActiveSupport @silence = previous_silence end - # :deprecated: - def self.instrument=(boolean) - ActiveSupport::Deprecation.warn "ActiveSupport::Cache.instrument= is deprecated and will be removed in Rails 5. Instrumentation is now always on so you can safely stop using it." - true - end - - # :deprecated: - def self.instrument - ActiveSupport::Deprecation.warn "ActiveSupport::Cache.instrument is deprecated and will be removed in Rails 5. Instrumentation is now always on so you can safely stop using it." - true - end - # Fetches data from the cache, using the given key. If there is data in # the cache with the given key, then that data is returned. # @@ -338,19 +325,22 @@ module ActiveSupport def read_multi(*names) options = names.extract_options! options = merged_options(options) - results = {} - names.each do |name| - key = namespaced_key(name, options) - entry = read_entry(key, options) - if entry - if entry.expired? - delete_entry(key, options) - else - results[name] = entry.value + + instrument_multi(:read, names, options) do |payload| + results = {} + names.each do |name| + key = namespaced_key(name, options) + entry = read_entry(key, options) + if entry + if entry.expired? + delete_entry(key, options) + else + results[name] = entry.value + end end end + results end - results end # Fetches data from the cache, using the given keys. If there is data in @@ -363,8 +353,11 @@ module ActiveSupport # Returns a hash with the data for each of the names. For example: # # cache.write("bim", "bam") - # cache.fetch_multi("bim", "boom") { |key| key * 2 } - # # => { "bam" => "bam", "boom" => "boomboom" } + # cache.fetch_multi("bim", "unknown_key") do |key| + # "Fallback value for key: #{key}" + # end + # # => { "bim" => "bam", + # # "unknown_key" => "Fallback value for key: unknown_key" } # def fetch_multi(*names) options = names.extract_options! @@ -540,16 +533,27 @@ module ActiveSupport end def instrument(operation, key, options = nil) - log(operation, key, options) + log { "Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}" } payload = { :key => key } payload.merge!(options) if options.is_a?(Hash) ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) } end - def log(operation, key, options = nil) + def instrument_multi(operation, keys, options = nil) + log do + formatted_keys = keys.map { |k| "- #{k}" }.join("\n") + "Caches multi #{operation}:\n#{formatted_keys}#{options.blank? ? "" : " (#{options.inspect})"}" + end + + payload = { key: keys } + payload.merge!(options) if options.is_a?(Hash) + ActiveSupport::Notifications.instrument("cache_#{operation}_multi.active_support", payload) { yield(payload) } + end + + def log return unless logger && logger.debug? && !silence? - logger.debug("Cache #{operation}: #{key}#{options.blank? ? "" : " (#{options.inspect})"}") + logger.debug(yield) end def find_cached_entry(key, name, options) @@ -562,9 +566,9 @@ module ActiveSupport def handle_expired_entry(entry, key, options) if entry && entry.expired? race_ttl = options[:race_condition_ttl].to_i - if race_ttl && (Time.now.to_f - entry.expires_at <= race_ttl) - # When an entry has :race_condition_ttl defined, put the stale entry back into the cache - # for a brief period while the entry is begin recalculated. + if (race_ttl > 0) && (Time.now.to_f - entry.expires_at <= race_ttl) + # When an entry has a positive :race_condition_ttl defined, put the stale entry back into the cache + # for a brief period while the entry is being recalculated. entry.expires_at = Time.now + race_ttl write_entry(key, entry, :expires_in => race_ttl * 2) else @@ -615,14 +619,12 @@ module ActiveSupport end def value - convert_version_4beta1_entry! if defined?(@v) compressed? ? uncompress(@value) : @value end # Check if the entry is expired. The +expires_in+ parameter can override # the value set when the entry was created. def expired? - convert_version_4beta1_entry! if defined?(@value) @expires_in && @created_at + @expires_in <= Time.now.to_f end @@ -658,8 +660,6 @@ module ActiveSupport # Duplicate the value in a class. This is used by cache implementations that don't natively # serialize entries to protect against accidental cache modifications. def dup_value! - 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 @@ -692,26 +692,6 @@ module ActiveSupport def uncompress(value) Marshal.load(Zlib::Inflate.inflate(value)) end - - # The internals of this method changed between Rails 3.x and 4.0. This method provides the glue - # to ensure that cache entries created under the old version still work with the new class definition. - def convert_version_4beta1_entry! - if defined?(@v) - @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 - remove_instance_variable(:@x) - end - end end end end diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index d08ecd2f7d..e6a8b84214 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -29,6 +29,7 @@ module ActiveSupport def clear(options = nil) root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)} FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) + rescue Errno::ENOENT end # Preemptively iterates through all stored keys and removes the ones which have expired. diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb index 61b4f0b8b0..47133bf550 100644 --- a/activesupport/lib/active_support/cache/mem_cache_store.rb +++ b/activesupport/lib/active_support/cache/mem_cache_store.rb @@ -26,7 +26,14 @@ module ActiveSupport class MemCacheStore < Store ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n - def self.build_mem_cache(*addresses) + # Creates a new Dalli::Client instance with specified addresses and options. + # By default address is equal localhost:11211. + # + # ActiveSupport::Cache::MemCacheStore.build_mem_cache + # # => #<Dalli::Client:0x007f98a47d2028 @servers=["localhost:11211"], @options={}, @ring=nil> + # ActiveSupport::Cache::MemCacheStore.build_mem_cache('localhost:10290') + # # => #<Dalli::Client:0x007f98a47b3a60 @servers=["localhost:10290"], @options={}, @ring=nil> + def self.build_mem_cache(*addresses) # :nodoc: addresses = addresses.flatten options = addresses.extract_options! addresses = ["localhost:11211"] if addresses.empty? @@ -66,14 +73,17 @@ module ActiveSupport def read_multi(*names) options = names.extract_options! options = merged_options(options) - keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}] - raw_values = @data.get_multi(keys_to_names.keys, :raw => true) - values = {} - raw_values.each do |key, value| - entry = deserialize_entry(value) - values[keys_to_names[key]] = entry.value unless entry.expired? + + instrument_multi(:read, names, options) do + keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}] + raw_values = @data.get_multi(keys_to_names.keys, :raw => true) + values = {} + raw_values.each do |key, value| + entry = deserialize_entry(value) + values[keys_to_names[key]] = entry.value unless entry.expired? + end + values end - values end # Increment a cached value. This method uses the memcached incr atomic diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb index 8a0523d0e2..90bb2c38c3 100644 --- a/activesupport/lib/active_support/cache/memory_store.rb +++ b/activesupport/lib/active_support/cache/memory_store.rb @@ -126,7 +126,7 @@ module ActiveSupport PER_ENTRY_OVERHEAD = 240 - def cached_size(key, entry) + def cached_size(key, entry) # :nodoc: key.to_s.bytesize + entry.size + PER_ENTRY_OVERHEAD end diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb index 73c6b3cb88..fe5bc82c30 100644 --- a/activesupport/lib/active_support/cache/strategy/local_cache.rb +++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb @@ -39,7 +39,7 @@ module ActiveSupport @data = {} end - # Don't allow synchronizing since it isn't thread safe, + # Don't allow synchronizing since it isn't thread safe. def synchronize # :nodoc: yield end @@ -120,7 +120,7 @@ module ActiveSupport super end - def set_cache_value(value, name, amount, options) + def set_cache_value(value, name, amount, options) # :nodoc: if local_cache local_cache.mute do if value diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb index 45231bc101..fefba5b0fd 100644 --- a/activesupport/lib/active_support/callbacks.rb +++ b/activesupport/lib/active_support/callbacks.rb @@ -4,6 +4,8 @@ require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/class/attribute' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' +require 'active_support/core_ext/string/filters' +require 'active_support/deprecation' require 'thread' module ActiveSupport @@ -78,14 +80,14 @@ module ActiveSupport # save # end def run_callbacks(kind, &block) - send "run_#{kind}_callbacks", &block + send "_run_#{kind}_callbacks", &block end private - def _run_callbacks(callbacks, &block) + def __run_callbacks__(callbacks, &block) if callbacks.empty? - block.call if block + yield if block_given? else runner = callbacks.compile e = Filters::Environment.new(self, false, nil, block) @@ -121,102 +123,106 @@ module ActiveSupport ENDING = End.new class Before - def self.build(next_callback, user_callback, user_conditions, chain_config, filter) + def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter) halted_lambda = chain_config[:terminator] if chain_config.key?(:terminator) && user_conditions.any? - halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) + halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) elsif chain_config.key? :terminator - halting(next_callback, user_callback, halted_lambda, filter) + halting(callback_sequence, user_callback, halted_lambda, filter) elsif user_conditions.any? - conditional(next_callback, user_callback, user_conditions) + conditional(callback_sequence, user_callback, user_conditions) else - simple next_callback, user_callback + simple callback_sequence, user_callback end end - def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter) - lambda { |env| + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter) + callback_sequence.before do |env| target = env.target value = env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } - result = user_callback.call target, value - env.halted = halted_lambda.call(target, result) + result_lambda = -> { user_callback.call target, value } + env.halted = halted_lambda.call(target, result_lambda) if env.halted target.send :halted_callback_hook, filter end end - next_callback.call env - } + + env + end end private_class_method :halting_and_conditional - def self.halting(next_callback, user_callback, halted_lambda, filter) - lambda { |env| + def self.halting(callback_sequence, user_callback, halted_lambda, filter) + callback_sequence.before do |env| target = env.target value = env.value halted = env.halted unless halted - result = user_callback.call target, value - env.halted = halted_lambda.call(target, result) + result_lambda = -> { user_callback.call target, value } + env.halted = halted_lambda.call(target, result_lambda) + if env.halted target.send :halted_callback_hook, filter end end - next_callback.call env - } + + env + end end private_class_method :halting - def self.conditional(next_callback, user_callback, user_conditions) - lambda { |env| + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.before do |env| target = env.target value = env.value if user_conditions.all? { |c| c.call(target, value) } user_callback.call target, value end - next_callback.call env - } + + env + end end private_class_method :conditional - def self.simple(next_callback, user_callback) - lambda { |env| + def self.simple(callback_sequence, user_callback) + callback_sequence.before do |env| user_callback.call env.target, env.value - next_callback.call env - } + + env + end end private_class_method :simple end class After - def self.build(next_callback, user_callback, user_conditions, chain_config) + def self.build(callback_sequence, user_callback, user_conditions, chain_config) if chain_config[:skip_after_callbacks_if_terminated] if chain_config.key?(:terminator) && user_conditions.any? - halting_and_conditional(next_callback, user_callback, user_conditions) + halting_and_conditional(callback_sequence, user_callback, user_conditions) elsif chain_config.key?(:terminator) - halting(next_callback, user_callback) + halting(callback_sequence, user_callback) elsif user_conditions.any? - conditional next_callback, user_callback, user_conditions + conditional callback_sequence, user_callback, user_conditions else - simple next_callback, user_callback + simple callback_sequence, user_callback end else if user_conditions.any? - conditional next_callback, user_callback, user_conditions + conditional callback_sequence, user_callback, user_conditions else - simple next_callback, user_callback + simple callback_sequence, user_callback end end end - def self.halting_and_conditional(next_callback, user_callback, user_conditions) - lambda { |env| - env = next_callback.call env + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| target = env.target value = env.value halted = env.halted @@ -224,122 +230,119 @@ module ActiveSupport if !halted && user_conditions.all? { |c| c.call(target, value) } user_callback.call target, value end + env - } + end end private_class_method :halting_and_conditional - def self.halting(next_callback, user_callback) - lambda { |env| - env = next_callback.call env + def self.halting(callback_sequence, user_callback) + callback_sequence.after do |env| unless env.halted user_callback.call env.target, env.value end + env - } + end end private_class_method :halting - def self.conditional(next_callback, user_callback, user_conditions) - lambda { |env| - env = next_callback.call env + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.after do |env| target = env.target value = env.value if user_conditions.all? { |c| c.call(target, value) } user_callback.call target, value end + env - } + end end private_class_method :conditional - def self.simple(next_callback, user_callback) - lambda { |env| - env = next_callback.call env + def self.simple(callback_sequence, user_callback) + callback_sequence.after do |env| user_callback.call env.target, env.value + env - } + end end private_class_method :simple end class Around - def self.build(next_callback, user_callback, user_conditions, chain_config) + def self.build(callback_sequence, user_callback, user_conditions, chain_config) if chain_config.key?(:terminator) && user_conditions.any? - halting_and_conditional(next_callback, user_callback, user_conditions) + halting_and_conditional(callback_sequence, user_callback, user_conditions) elsif chain_config.key? :terminator - halting(next_callback, user_callback) + halting(callback_sequence, user_callback) elsif user_conditions.any? - conditional(next_callback, user_callback, user_conditions) + conditional(callback_sequence, user_callback, user_conditions) else - simple(next_callback, user_callback) + simple(callback_sequence, user_callback) end end - def self.halting_and_conditional(next_callback, user_callback, user_conditions) - lambda { |env| + def self.halting_and_conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.around do |env, &run| target = env.target value = env.value halted = env.halted if !halted && user_conditions.all? { |c| c.call(target, value) } user_callback.call(target, value) { - env = next_callback.call env - env.value + run.call.value } env else - next_callback.call env + run.call end - } + end end private_class_method :halting_and_conditional - def self.halting(next_callback, user_callback) - lambda { |env| + def self.halting(callback_sequence, user_callback) + callback_sequence.around do |env, &run| target = env.target value = env.value if env.halted - next_callback.call env + run.call else user_callback.call(target, value) { - env = next_callback.call env - env.value + run.call.value } env end - } + end end private_class_method :halting - def self.conditional(next_callback, user_callback, user_conditions) - lambda { |env| + def self.conditional(callback_sequence, user_callback, user_conditions) + callback_sequence.around do |env, &run| target = env.target value = env.value if user_conditions.all? { |c| c.call(target, value) } user_callback.call(target, value) { - env = next_callback.call env - env.value + run.call.value } env else - next_callback.call env + run.call end - } + end end private_class_method :conditional - def self.simple(next_callback, user_callback) - lambda { |env| + def self.simple(callback_sequence, user_callback) + callback_sequence.around do |env, &run| user_callback.call(env.target, env.value) { - env = next_callback.call env - env.value + run.call.value } env - } + end end private_class_method :simple end @@ -366,14 +369,14 @@ module ActiveSupport def filter; @key; end def raw_filter; @filter; end - def merge(chain, new_options) + def merge_conditional_options(chain, if_option:, unless_option:) options = { :if => @if.dup, :unless => @unless.dup } - options[:if].concat Array(new_options.fetch(:unless, [])) - options[:unless].concat Array(new_options.fetch(:if, [])) + options[:if].concat Array(unless_option) + options[:unless].concat Array(if_option) self.class.build chain, @filter, @kind, options end @@ -392,17 +395,17 @@ module ActiveSupport end # Wraps code with filter - def apply(next_callback) + def apply(callback_sequence) user_conditions = conditions_lambdas user_callback = make_lambda @filter case kind when :before - Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter) + Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter) when :after - Filters::After.build(next_callback, user_callback, user_conditions, chain_config) + Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config) when :around - Filters::Around.build(next_callback, user_callback, user_conditions, chain_config) + Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config) end end @@ -467,16 +470,59 @@ module ActiveSupport end end + # Execute before and after filters in a sequence instead of + # chaining them with nested lambda calls, see: + # https://github.com/rails/rails/issues/18011 + class CallbackSequence + def initialize(&call) + @call = call + @before = [] + @after = [] + end + + def before(&before) + @before.unshift(before) + self + end + + def after(&after) + @after.push(after) + self + end + + def around(&around) + CallbackSequence.new do |arg| + around.call(arg) { + self.call(arg) + } + end + end + + def call(arg) + @before.each { |b| b.call(arg) } + value = @call.call(arg) + @after.each { |a| a.call(arg) } + value + end + end + # An Array with a compile method. class CallbackChain #:nodoc:# include Enumerable attr_reader :name, :config + # If true, any callback returning +false+ will halt the entire callback + # chain and display a deprecation message. If false, callback chains will + # only be halted by calling +throw :abort+. Defaults to +true+. + class_attribute :halt_and_display_warning_on_return_false + self.halt_and_display_warning_on_return_false = true + def initialize(name, config) @name = name @config = { - :scope => [ :kind ] + scope: [:kind], + terminator: default_terminator }.merge!(config) @chain = [] @callbacks = nil @@ -511,8 +557,9 @@ module ActiveSupport def compile @callbacks || @mutex.synchronize do - @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback| - callback.apply chain + final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) } + @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback| + callback.apply callback_sequence end end end @@ -546,6 +593,28 @@ module ActiveSupport @callbacks = nil @chain.delete_if { |c| callback.duplicates?(c) } end + + def default_terminator + Proc.new do |target, result_lambda| + terminate = true + catch(:abort) do + result = result_lambda.call if result_lambda.is_a?(Proc) + if halt_and_display_warning_on_return_false && result == false + display_deprecation_warning_for_false_terminator + else + terminate = false + end + end + terminate + end + end + + def display_deprecation_warning_for_false_terminator + ActiveSupport::Deprecation.warn(<<-MSG.squish) + Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails. + To explicitly halt a callback chain, please use `throw :abort` instead. + MSG + end end module ClassMethods @@ -571,7 +640,7 @@ module ActiveSupport # set_callback :save, :after, :after_meth, if: :condition # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff } # - # The second arguments indicates whether the callback is to be run +:before+, + # The second argument indicates whether the callback is to be run +:before+, # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This # means the first example above can also be written as: # @@ -594,10 +663,12 @@ module ActiveSupport # # ===== Options # - # * <tt>:if</tt> - A symbol naming an instance method or a proc; the - # callback will be called only when it returns a +true+ value. - # * <tt>:unless</tt> - A symbol naming an instance method or a proc; the - # callback will be called only when it returns a +false+ value. + # * <tt>:if</tt> - A symbol, a string or an array of symbols and strings, + # each naming an instance method or a proc; the callback will be called + # only when they all return a true value. + # * <tt>:unless</tt> - A symbol, a string or an array of symbols and + # strings, each naming an instance method or a proc; the callback will + # be called only when they all return a false value. # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the # existing chain rather than appended. def set_callback(name, *filter_list, &block) @@ -620,19 +691,27 @@ module ActiveSupport # class Writer < Person # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 } # end + # + # An <tt>ArgumentError</tt> will be raised if the callback has not + # already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>). def skip_callback(name, *filter_list, &block) type, filters, options = normalize_callback_params(filter_list, block) + options[:raise] = true unless options.key?(:raise) __update_callbacks(name) do |target, chain| filters.each do |filter| - filter = chain.find {|c| c.matches?(type, filter) } + callback = chain.find {|c| c.matches?(type, filter) } + + if !callback && options[:raise] + raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined" + end - if filter && options.any? - new_filter = filter.merge(chain, options) - chain.insert(chain.index(filter), new_filter) + if callback && (options.key?(:if) || options.key?(:unless)) + new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless]) + chain.insert(chain.index(callback), new_callback) end - chain.delete(filter) + chain.delete(callback) end target.set_callbacks name, chain end @@ -659,21 +738,22 @@ module ActiveSupport # ===== Options # # * <tt>:terminator</tt> - Determines when a before filter will halt the - # callback chain, preventing following callbacks from being called and - # the event from being triggered. This should be a lambda to be executed. - # The current object and the return result of the callback will be called - # with the lambda. + # callback chain, preventing following before and around callbacks from + # being called and the event from being triggered. + # This should be a lambda to be executed. + # The current object and the result lambda of the callback will be provided + # to the terminator lambda. # - # define_callbacks :validate, terminator: ->(target, result) { result == false } + # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false } # # In this example, if any before validate callbacks returns +false+, - # other callbacks are not executed. Defaults to +false+, meaning no value - # halts the chain. + # any successive before and around callback is not executed. + # Defaults to +false+, meaning no value halts the chain. # # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after # callbacks should be terminated by the <tt>:terminator</tt> option. By - # default after callbacks executed no matter if callback chain was - # terminated or not. Option makes sense only when <tt>:terminator</tt> + # default after callbacks are executed no matter if callback chain was + # terminated or not. This option makes sense only when <tt>:terminator</tt> # option is specified. # # * <tt>:scope</tt> - Indicates which methods should be executed when an @@ -730,8 +810,8 @@ module ActiveSupport set_callbacks name, CallbackChain.new(name, options) module_eval <<-RUBY, __FILE__, __LINE__ + 1 - def run_#{name}_callbacks(&block) - _run_callbacks(_#{name}_callbacks, &block) + def _run_#{name}_callbacks(&block) + __run_callbacks__(_#{name}_callbacks, &block) end RUBY end diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb index 342d3a9d52..0403eb70ca 100644 --- a/activesupport/lib/active_support/concern.rb +++ b/activesupport/lib/active_support/concern.rb @@ -114,7 +114,7 @@ module ActiveSupport return false else return false if base < self - @_dependencies.each { |dep| base.send(:include, dep) } + @_dependencies.each { |dep| base.include(dep) } super base.extend const_get(:ClassMethods) if const_defined?(:ClassMethods) base.class_eval(&@_included_block) if instance_variable_defined?(:@_included_block) @@ -132,7 +132,7 @@ module ActiveSupport end def class_methods(&class_methods_module_definition) - mod = const_defined?(:ClassMethods) ? + mod = const_defined?(:ClassMethods, false) ? const_get(:ClassMethods) : const_set(:ClassMethods, Module.new) diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb index 1507de433e..7b8df0df04 100644 --- a/activesupport/lib/active_support/concurrency/latch.rb +++ b/activesupport/lib/active_support/concurrency/latch.rb @@ -1,26 +1,18 @@ -require 'thread' -require 'monitor' +require 'concurrent/atomics' module ActiveSupport module Concurrency - class Latch - def initialize(count = 1) - @count = count - @lock = Monitor.new - @cv = @lock.new_cond - end + class Latch < Concurrent::CountDownLatch - def release - @lock.synchronize do - @count -= 1 if @count > 0 - @cv.broadcast if @count.zero? - end + def initialize(count = 1) + ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.") + super(count) end + + alias_method :release, :count_down def await - @lock.synchronize do - @cv.wait_while { @count > 0 } - end + wait(nil) end end end diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb new file mode 100644 index 0000000000..ca48164c54 --- /dev/null +++ b/activesupport/lib/active_support/concurrency/share_lock.rb @@ -0,0 +1,142 @@ +require 'thread' +require 'monitor' + +module ActiveSupport + module Concurrency + # A share/exclusive lock, otherwise known as a read/write lock. + # + # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock + #-- + # Note that a pending Exclusive lock attempt does not block incoming + # Share requests (i.e., we are "read-preferring"). That seems + # consistent with the behavior of "loose" upgrades, but may be the + # wrong choice otherwise: it nominally reduces the possibility of + # deadlock by risking starvation instead. + class ShareLock + include MonitorMixin + + # We track Thread objects, instead of just using counters, because + # we need exclusive locks to be reentrant, and we need to be able + # to upgrade share locks to exclusive. + + + def initialize + super() + + @cv = new_cond + + @sharing = Hash.new(0) + @waiting = {} + @exclusive_thread = nil + @exclusive_depth = 0 + end + + # Returns false if +no_wait+ is set and the lock is not + # immediately available. Otherwise, returns true after the lock + # has been acquired. + # + # +purpose+ and +compatible+ work together; while this thread is + # waiting for the exclusive lock, it will yield its share (if any) + # to any other attempt whose +purpose+ appears in this attempt's + # +compatible+ list. This allows a "loose" upgrade, which, being + # less strict, prevents some classes of deadlocks. + # + # For many resources, loose upgrades are sufficient: if a thread + # is awaiting a lock, it is not running any other code. With + # +purpose+ matching, it is possible to yield only to other + # threads whose activity will not interfere. + def start_exclusive(purpose: nil, compatible: [], no_wait: false) + synchronize do + unless @exclusive_thread == Thread.current + if busy?(purpose) + return false if no_wait + + loose_shares = @sharing.delete(Thread.current) + @waiting[Thread.current] = compatible if loose_shares + + begin + @cv.wait_while { busy?(purpose) } + ensure + @waiting.delete Thread.current + @sharing[Thread.current] = loose_shares if loose_shares + end + end + @exclusive_thread = Thread.current + end + @exclusive_depth += 1 + + true + end + end + + # Relinquish the exclusive lock. Must only be called by the thread + # that called start_exclusive (and currently holds the lock). + def stop_exclusive + synchronize do + raise "invalid unlock" if @exclusive_thread != Thread.current + + @exclusive_depth -= 1 + if @exclusive_depth == 0 + @exclusive_thread = nil + @cv.broadcast + end + end + end + + def start_sharing + synchronize do + if @exclusive_thread && @exclusive_thread != Thread.current + @cv.wait_while { @exclusive_thread } + end + @sharing[Thread.current] += 1 + end + end + + def stop_sharing + synchronize do + if @sharing[Thread.current] > 1 + @sharing[Thread.current] -= 1 + else + @sharing.delete Thread.current + @cv.broadcast + end + end + end + + # Execute the supplied block while holding the Exclusive lock. If + # +no_wait+ is set and the lock is not immediately available, + # returns +nil+ without yielding. Otherwise, returns the result of + # the block. + # + # See +start_exclusive+ for other options. + def exclusive(purpose: nil, compatible: [], no_wait: false) + if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait) + begin + yield + ensure + stop_exclusive + end + end + end + + # Execute the supplied block while holding the Share lock. + def sharing + start_sharing + begin + yield + ensure + stop_sharing + end + end + + private + + # Must be called within synchronize + def busy?(purpose) + (@exclusive_thread && @exclusive_thread != Thread.current) || + @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } || + @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0) + end + end + end +end diff --git a/activesupport/lib/active_support/configurable.rb b/activesupport/lib/active_support/configurable.rb index 3dd44e32d8..8256c325af 100644 --- a/activesupport/lib/active_support/configurable.rb +++ b/activesupport/lib/active_support/configurable.rb @@ -122,6 +122,7 @@ module ActiveSupport send("#{name}=", yield) if block_given? end end + private :config_accessor end # Reads and writes attributes from a configuration <tt>OrderedHash</tt>. diff --git a/activesupport/lib/active_support/core_ext.rb b/activesupport/lib/active_support/core_ext.rb index 199aa91020..52706c3d7a 100644 --- a/activesupport/lib/active_support/core_ext.rb +++ b/activesupport/lib/active_support/core_ext.rb @@ -1,3 +1,4 @@ -Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"].each do |path| +DEPRECATED_FILES = ["#{File.dirname(__FILE__)}/core_ext/struct.rb"] +(Dir["#{File.dirname(__FILE__)}/core_ext/*.rb"] - DEPRECATED_FILES).each do |path| require path end diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb index 7d0c1e4c8d..7551551bd7 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -4,3 +4,4 @@ require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/array/extract_options' require 'active_support/core_ext/array/grouping' require 'active_support/core_ext/array/prepend_and_append' +require 'active_support/core_ext/array/inquiry' diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb index caa499dfa2..3177d8498e 100644 --- a/activesupport/lib/active_support/core_ext/array/access.rb +++ b/activesupport/lib/active_support/core_ext/array/access.rb @@ -20,7 +20,23 @@ class Array # %w( a b c d ).to(-2) # => ["a", "b", "c"] # %w( a b c ).to(-10) # => [] def to(position) - self[0..position] + if position >= 0 + take position + 1 + else + self[0..position] + end + end + + # Returns a copy of the Array without the specified elements. + # + # people = ["David", "Rafael", "Aaron", "Todd"] + # people.without "Aaron", "Todd" + # => ["David", "Rafael"] + # + # Note: This is an optimization of `Enumerable#without` that uses `Array#-` + # instead of `Array#reject` for performance reasons. + def without(*elements) + self - elements end # Equal to <tt>self[1]</tt>. diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb index 76ffd23ed1..d80df21e7d 100644 --- a/activesupport/lib/active_support/core_ext/array/conversions.rb +++ b/activesupport/lib/active_support/core_ext/array/conversions.rb @@ -74,7 +74,7 @@ class Array when 0 '' when 1 - self[0].to_s.dup + "#{self[0]}" when 2 "#{self[0]}#{options[:two_words_connector]}#{self[1]}" else @@ -92,7 +92,7 @@ class Array if empty? 'null' else - collect { |element| element.id }.join(',') + collect(&:id).join(',') end else to_default_s diff --git a/activesupport/lib/active_support/core_ext/array/inquiry.rb b/activesupport/lib/active_support/core_ext/array/inquiry.rb new file mode 100644 index 0000000000..e8f44cc378 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/array/inquiry.rb @@ -0,0 +1,17 @@ +require 'active_support/array_inquirer' + +class Array + # Wraps the array in an +ArrayInquirer+ object, which gives a friendlier way + # to check its string-like contents. + # + # pets = [:cat, :dog].inquiry + # + # pets.cat? # => true + # pets.ferret? # => false + # + # pets.any?(:cat, :ferret) # => true + # pets.any?(:ferret, :alligator) # => false + def inquiry + ActiveSupport::ArrayInquirer.new(self) + end +end diff --git a/activesupport/lib/active_support/core_ext/array/wrap.rb b/activesupport/lib/active_support/core_ext/array/wrap.rb index 152eb02218..b611d34c27 100644 --- a/activesupport/lib/active_support/core_ext/array/wrap.rb +++ b/activesupport/lib/active_support/core_ext/array/wrap.rb @@ -3,7 +3,7 @@ class Array # # Specifically: # - # * If the argument is +nil+ an empty list is returned. + # * If the argument is +nil+ an empty array is returned. # * Otherwise, if the argument responds to +to_ary+ it is invoked, and its result returned. # * Otherwise, returns an array with the argument as its single element. # @@ -15,12 +15,13 @@ class Array # # * If the argument responds to +to_ary+ the method is invoked. <tt>Kernel#Array</tt> # moves on to try +to_a+ if the returned value is +nil+, but <tt>Array.wrap</tt> returns - # +nil+ right away. + # an array with the argument as its single element right away. # * If the returned value from +to_ary+ is neither +nil+ nor an +Array+ object, <tt>Kernel#Array</tt> # raises an exception, while <tt>Array.wrap</tt> does not, it just returns the value. - # * It does not call +to_a+ on the argument, but returns an empty array if argument is +nil+. + # * It does not call +to_a+ on the argument, if the argument does not respond to +to_ary+ + # it returns an array with the argument as its single element. # - # The second point is easily explained with some enumerables: + # The last point is easily explained with some enumerables: # # Array(foo: :bar) # => [[:foo, :bar]] # Array.wrap(foo: :bar) # => [{:foo=>:bar}] diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb index 843c592669..234283e792 100644 --- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb +++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb @@ -3,14 +3,13 @@ require 'bigdecimal/util' class BigDecimal DEFAULT_STRING_FORMAT = 'F' - def to_formatted_s(*args) - if args[0].is_a?(Symbol) - super + alias_method :to_default_s, :to_s + + def to_s(format = nil, options = nil) + if format.is_a?(Symbol) + to_formatted_s(format, options || {}) else - format = args[0] || DEFAULT_STRING_FORMAT - _original_to_s(format) + to_default_s(format || DEFAULT_STRING_FORMAT) end end - alias_method :_original_to_s, :to_s - alias_method :to_s, :to_formatted_s end diff --git a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb deleted file mode 100644 index 46ba93ead4..0000000000 --- a/activesupport/lib/active_support/core_ext/big_decimal/yaml_conversions.rb +++ /dev/null @@ -1,14 +0,0 @@ -ActiveSupport::Deprecation.warn 'core_ext/big_decimal/yaml_conversions is deprecated and will be removed in the future.' - -require 'bigdecimal' -require 'yaml' -require 'active_support/core_ext/big_decimal/conversions' - -class BigDecimal - YAML_MAPPING = { 'Infinity' => '.Inf', '-Infinity' => '-.Inf', 'NaN' => '.NaN' } - - def encode_with(coder) - string = to_s - coder.represent_scalar(nil, YAML_MAPPING[string] || string) - end -end diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb index c750a10bb2..ef903d59b5 100644 --- a/activesupport/lib/active_support/core_ext/class.rb +++ b/activesupport/lib/active_support/core_ext/class.rb @@ -1,3 +1,2 @@ require 'active_support/core_ext/class/attribute' -require 'active_support/core_ext/class/delegating_attributes' require 'active_support/core_ext/class/subclasses' diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb index f2a221c396..f2b7bb3ef1 100644 --- a/activesupport/lib/active_support/core_ext/class/attribute.rb +++ b/activesupport/lib/active_support/core_ext/class/attribute.rb @@ -116,12 +116,4 @@ class Class attr_writer name if instance_writer end end - - private - - unless respond_to?(:singleton_class?) - def singleton_class? - ancestors.first != self - end - end end diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb deleted file mode 100644 index 1c305c5970..0000000000 --- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/core_ext/module/remove_method' -require 'active_support/core_ext/module/deprecation' - - -class Class - def superclass_delegating_accessor(name, options = {}) - # Create private _name and _name= methods that can still be used if the public - # methods are overridden. - _superclass_delegating_accessor("_#{name}", options) - - # Generate the public methods name, name=, and name?. - # These methods dispatch to the private _name, and _name= methods, making them - # overridable. - singleton_class.send(:define_method, name) { send("_#{name}") } - singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") } - singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) } - - # If an instance_reader is needed, generate public instance methods name and name?. - if options[:instance_reader] != false - define_method(name) { send("_#{name}") } - define_method("#{name}?") { !!send("#{name}") } - end - end - - deprecate superclass_delegating_accessor: :class_attribute - - private - # Take the object being set and store it in a method. This gives us automatic - # inheritance behavior, without having to store the object in an instance - # variable and look up the superclass chain manually. - def _stash_object_in_method(object, method, instance_reader = true) - singleton_class.remove_possible_method(method) - singleton_class.send(:define_method, method) { object } - remove_possible_method(method) - define_method(method) { object } if instance_reader - end - - def _superclass_delegating_accessor(name, options = {}) - singleton_class.send(:define_method, "#{name}=") do |value| - _stash_object_in_method(value, name, options[:instance_reader] != false) - end - send("#{name}=", nil) - end -end diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb index df419a6e63..31479a1269 100644 --- a/activesupport/lib/active_support/core_ext/date/conversions.rb +++ b/activesupport/lib/active_support/core_ext/date/conversions.rb @@ -35,6 +35,7 @@ class Date # date.to_s(:db) # => "2007-11-10" # # date.to_formatted_s(:short) # => "10 Nov" + # date.to_formatted_s(:number) # => "20071110" # date.to_formatted_s(:long) # => "November 10, 2007" # date.to_formatted_s(:long_ordinal) # => "November 10th, 2007" # date.to_formatted_s(:rfc822) # => "10 Nov 2007" @@ -82,6 +83,11 @@ class Date ::Time.send(form, year, month, day) end + # Returns a string which represents the time in used time zone as DateTime + # defined by XML Schema: + # + # date = Date.new(2015, 05, 23) # => Sat, 23 May 2015 + # date.xmlschema # => "2015-05-23T00:00:00+04:00" def xmlschema in_time_zone.xmlschema end diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb index b85e49aca5..40811dafc0 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 @@ -9,15 +9,26 @@ module DateAndTime :saturday => 5, :sunday => 6 } + WEEKEND_DAYS = [ 6, 0 ] # Returns a new date/time representing yesterday. def yesterday - advance(:days => -1) + advance(days: -1) + end + + # Returns a new date/time representing the previous day. + def prev_day + advance(days: -1) end # Returns a new date/time representing tomorrow. def tomorrow - advance(:days => 1) + advance(days: 1) + end + + # Returns a new date/time representing the next day. + def next_day + advance(days: 1) end # Returns true if the date/time is today. @@ -35,6 +46,11 @@ module DateAndTime self > self.class.current end + # Returns true if the date/time falls on a Saturday or Sunday. + def on_weekend? + WEEKEND_DAYS.include?(wday) + end + # Returns a new date/time the specified number of days ago. def days_ago(days) advance(:days => -days) @@ -76,15 +92,28 @@ module DateAndTime end # Returns a new date/time at the start of the month. - # DateTime objects will have a time set to 0:00. + # + # today = Date.today # => Thu, 18 Jun 2015 + # today.beginning_of_month # => Mon, 01 Jun 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000 + # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000 def beginning_of_month first_hour(change(:day => 1)) end alias :at_beginning_of_month :beginning_of_month # Returns a new date/time at the start of the quarter. - # Example: 1st January, 1st July, 1st October. - # DateTime objects will have a time set to 0:00. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.beginning_of_quarter # => Wed, 01 Jul 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000 def beginning_of_quarter first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month } beginning_of_month.change(:month => first_quarter_month) @@ -92,8 +121,14 @@ module DateAndTime alias :at_beginning_of_quarter :beginning_of_quarter # Returns a new date/time at the end of the quarter. - # Example: 31st March, 30th June, 30th September. - # DateTime objects will have a time set to 23:59:59. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.end_of_quarter # => Wed, 30 Sep 2015 + # + # +DateTime+ objects will have a time set to 23:59:59. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000 def end_of_quarter last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month } beginning_of_month.change(:month => last_quarter_month).end_of_month @@ -101,19 +136,47 @@ module DateAndTime alias :at_end_of_quarter :end_of_quarter # Return a new date/time at the beginning of the year. - # Example: 1st January. - # DateTime objects will have a time set to 0:00. + # + # today = Date.today # => Fri, 10 Jul 2015 + # today.beginning_of_year # => Thu, 01 Jan 2015 + # + # +DateTime+ objects will have a time set to 0:00. + # + # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000 + # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000 def beginning_of_year change(:month => 1).beginning_of_month end alias :at_beginning_of_year :beginning_of_year # Returns a new date/time representing the given day in the next week. + # + # today = Date.today # => Thu, 07 May 2015 + # today.next_week # => Mon, 11 May 2015 + # # The +given_day_in_next_week+ defaults to the beginning of the week # which is determined by +Date.beginning_of_week+ or +config.beginning_of_week+ - # when set. +DateTime+ objects have their time set to 0:00. - 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))) + # when set. + # + # today = Date.today # => Thu, 07 May 2015 + # today.next_week(:friday) # => Fri, 15 May 2015 + # + # +DateTime+ objects have their time set to 0:00 unless +same_time+ is true. + # + # now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000 + # now.next_week # => Mon, 11 May 2015 00:00:00 +0000 + def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week))) + same_time ? copy_time_to(result) : result + end + + # Returns a new date/time representing the next weekday. + def next_weekday + if next_day.on_weekend? + next_week(:monday, same_time: true) + else + next_day + end end # Short-hand for months_since(1). @@ -134,12 +197,23 @@ module DateAndTime # Returns a new date/time representing the given day in the previous week. # Week is assumed to start on +start_day+, default is # +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))) + # DateTime objects have their time set to 0:00 unless +same_time+ is true. + def prev_week(start_day = Date.beginning_of_week, same_time: false) + result = first_hour(weeks_ago(1).beginning_of_week.days_since(days_span(start_day))) + same_time ? copy_time_to(result) : result end alias_method :last_week, :prev_week + # Returns a new date/time representing the previous weekday. + def prev_weekday + if prev_day.on_weekend? + copy_time_to(beginning_of_week(:friday)) + else + prev_day + end + end + alias_method :last_weekday, :prev_weekday + # Short-hand for months_ago(1). def prev_month months_ago(1) @@ -235,17 +309,20 @@ module DateAndTime end private + def first_hour(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.beginning_of_day : date_or_time + end - 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(date_or_time) + date_or_time.acts_like?(:time) ? date_or_time.end_of_day : date_or_time + end - 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) + (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 + end - def days_span(day) - (DAYS_INTO_WEEK[day] - DAYS_INTO_WEEK[Date.beginning_of_week]) % 7 - end + def copy_time_to(other) + other.change(hour: hour, min: min, sec: sec, usec: try(:usec)) + end end end diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb index dc4e767e9d..95617fb8c2 100644 --- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb @@ -10,7 +10,11 @@ class DateTime end end - # Seconds since midnight: DateTime.now.seconds_since_midnight. + # Returns the number of seconds since 00:00:00. + # + # DateTime.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0 + # DateTime.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296 + # DateTime.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399 def seconds_since_midnight sec + (min * 60) + (hour * 3600) end @@ -164,7 +168,7 @@ class DateTime if other.kind_of?(Infinity) super elsif other.respond_to? :to_datetime - super other.to_datetime + super other.to_datetime rescue nil else nil end diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb index 1343beb87a..fc7531d088 100644 --- a/activesupport/lib/active_support/core_ext/enumerable.rb +++ b/activesupport/lib/active_support/core_ext/enumerable.rb @@ -60,6 +60,32 @@ module Enumerable def exclude?(object) !include?(object) end + + # Returns a copy of the enumerable without the specified elements. + # + # ["David", "Rafael", "Aaron", "Todd"].without "Aaron", "Todd" + # => ["David", "Rafael"] + # + # {foo: 1, bar: 2, baz: 3}.without :bar + # => {foo: 1, baz: 3} + def without(*elements) + reject { |element| elements.include?(element) } + end + + # Convert an enumerable to an array based on the given key. + # + # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name) + # => ["David", "Rafael", "Aaron"] + # + # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name) + # => [[1, "David"], [2, "Rafael"]] + def pluck(*keys) + if keys.many? + map { |element| keys.map { |key| element[key] } } + else + map { |element| element[keys.first] } + end + end end class Range #:nodoc: diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb index 38374af388..ab635f6db8 100644 --- a/activesupport/lib/active_support/core_ext/file/atomic.rb +++ b/activesupport/lib/active_support/core_ext/file/atomic.rb @@ -8,40 +8,45 @@ class File # file.write('hello') # end # - # If your temp directory is not on the same filesystem as the file you're - # trying to write, you can provide a different temporary directory. + # This method needs to create a temporary file. By default it will create it + # in the same directory as the destination file. If you don't like this + # behaviour you can provide a different directory but it must be on the + # same physical filesystem as the the file you're trying to write. # # File.atomic_write('/data/something.important', '/data/tmp') do |file| # file.write('hello') # end - def self.atomic_write(file_name, temp_dir = Dir.tmpdir) + def self.atomic_write(file_name, temp_dir = dirname(file_name)) require 'tempfile' unless defined?(Tempfile) - require 'fileutils' unless defined?(FileUtils) - temp_file = Tempfile.new(basename(file_name), temp_dir) - temp_file.binmode - yield temp_file - temp_file.close + Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file| + temp_file.binmode + return_val = yield temp_file + temp_file.close - if File.exist?(file_name) - # Get original file permissions - old_stat = stat(file_name) - else - # If not possible, probe which are the default permissions in the - # destination directory. - old_stat = probe_stat_in(dirname(file_name)) - end + old_stat = if exist?(file_name) + # Get original file permissions + stat(file_name) + elsif temp_dir != dirname(file_name) + # If not possible, probe which are the default permissions in the + # destination directory. + probe_stat_in(dirname(file_name)) + end - # Overwrite original file with temp file - FileUtils.mv(temp_file.path, file_name) + if old_stat + # Set correct permissions on new file + begin + chown(old_stat.uid, old_stat.gid, temp_file.path) + # This operation will affect filesystem ACL's + chmod(old_stat.mode, temp_file.path) + rescue Errno::EPERM, Errno::EACCES + # Changing file ownership failed, moving on. + end + end - # Set correct permissions on new file - begin - chown(old_stat.uid, old_stat.gid, file_name) - # This operation will affect filesystem ACL's - chmod(old_stat.mode, file_name) - rescue Errno::EPERM, Errno::EACCES - # Changing file ownership failed, moving on. + # Overwrite original file with temp file + rename(temp_file.path, file_name) + return_val end end diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb index 6566215a4d..5dc9a05ec7 100644 --- a/activesupport/lib/active_support/core_ext/hash/compact.rb +++ b/activesupport/lib/active_support/core_ext/hash/compact.rb @@ -1,6 +1,6 @@ class Hash # Returns a hash with non +nil+ values. - # + # # hash = { a: true, b: false, c: nil} # hash.compact # => { a: true, b: false} # hash # => { a: true, b: false, c: nil} @@ -8,9 +8,9 @@ class Hash def compact self.select { |_, value| !value.nil? } end - + # Replaces current hash with non +nil+ values. - # + # # hash = { a: true, b: false, c: nil} # hash.compact! # => { a: true, b: false} # hash # => { a: true, b: false} diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb index 2149d4439d..8594d9bf2e 100644 --- a/activesupport/lib/active_support/core_ext/hash/conversions.rb +++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb @@ -106,7 +106,25 @@ class Hash # # => {"hash"=>{"foo"=>1, "bar"=>2}} # # +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or - # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML. + # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to + # parse this XML. + # + # Custom +disallowed_types+ can also be passed in the form of an + # array. + # + # xml = <<-XML + # <?xml version="1.0" encoding="UTF-8"?> + # <hash> + # <foo type="integer">1</foo> + # <bar type="string">"David"</bar> + # </hash> + # XML + # + # hash = Hash.from_xml(xml, ['integer']) + # # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer" + # + # Note that passing custom disallowed types will override the default types, + # which are Symbol and YAML. def from_xml(xml, disallowed_types = nil) ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h end diff --git a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb index 763d563231..9c9faf67ea 100644 --- a/activesupport/lib/active_support/core_ext/hash/deep_merge.rb +++ b/activesupport/lib/active_support/core_ext/hash/deep_merge.rb @@ -4,7 +4,7 @@ class Hash # h1 = { a: true, b: { c: [1, 2, 3] } } # h2 = { a: false, b: { x: [3, 4, 5] } } # - # h1.deep_merge(h2) #=> { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } + # h1.deep_merge(h2) # => { a: false, b: { c: [1, 2, 3], x: [3, 4, 5] } } # # Like with Hash#merge in the standard library, a block can be provided # to merge values: diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb index 682d089881..2f6d38c1f6 100644 --- a/activesupport/lib/active_support/core_ext/hash/except.rb +++ b/activesupport/lib/active_support/core_ext/hash/except.rb @@ -1,13 +1,20 @@ class Hash - # Returns a hash that includes everything but the given keys. This is useful for - # limiting a set of parameters to everything but a few known toggles: + # Returns a hash that includes everything except given keys. + # hash = { a: true, b: false, c: nil } + # hash.except(:c) # => { a: true, b: false } + # hash.except(:a, :b) # => { c: nil } + # hash # => { a: true, b: false, c: nil } # + # This is useful for limiting a set of parameters to everything but a few known toggles: # @person.update(params[:person].except(:admin)) def except(*keys) dup.except!(*keys) end - # Replaces the hash without the given keys. + # Removes the given keys from hash and returns it. + # hash = { a: true, b: false, c: nil } + # hash.except!(:c) # => { a: true, b: false } + # hash # => { a: true, b: false } def except!(*keys) keys.each { |key| delete(key) } self diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index f4105f66b0..c30044b9ff 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -14,7 +14,7 @@ class Hash result end - # Destructively convert all keys using the block operations. + # Destructively converts all keys using the block operations. # Same as transform_keys but modifies +self+. def transform_keys! return enum_for(:transform_keys!) unless block_given? @@ -31,13 +31,13 @@ class Hash # hash.stringify_keys # # => {"name"=>"Rob", "age"=>"28"} def stringify_keys - transform_keys{ |key| key.to_s } + transform_keys(&:to_s) end - # Destructively convert all keys to strings. Same as + # Destructively converts all keys to strings. Same as # +stringify_keys+, but modifies +self+. def stringify_keys! - transform_keys!{ |key| key.to_s } + transform_keys!(&:to_s) end # Returns a new hash with all keys converted to symbols, as long as @@ -52,14 +52,14 @@ class Hash end alias_method :to_options, :symbolize_keys - # Destructively convert all keys to symbols, as long as they respond + # Destructively converts all keys to symbols, as long as they respond # to +to_sym+. Same as +symbolize_keys+, but modifies +self+. def symbolize_keys! transform_keys!{ |key| key.to_sym rescue key } end alias_method :to_options!, :symbolize_keys! - # Validate all keys in a hash match <tt>*valid_keys</tt>, raising + # Validates all keys in a hash match <tt>*valid_keys</tt>, raising # ArgumentError on a mismatch. # # Note that keys are treated differently than HashWithIndifferentAccess, @@ -89,7 +89,7 @@ class Hash _deep_transform_keys_in_object(self, &block) end - # Destructively convert all keys by using the block operation. + # Destructively converts all keys by using the block operation. # This includes the keys from the root hash and from all # nested hashes and arrays. def deep_transform_keys!(&block) @@ -105,14 +105,14 @@ class Hash # hash.deep_stringify_keys # # => {"person"=>{"name"=>"Rob", "age"=>"28"}} def deep_stringify_keys - deep_transform_keys{ |key| key.to_s } + deep_transform_keys(&:to_s) end - # Destructively convert all keys to strings. + # Destructively converts all keys to strings. # This includes the keys from the root hash and from all # nested hashes and arrays. def deep_stringify_keys! - deep_transform_keys!{ |key| key.to_s } + deep_transform_keys!(&:to_s) end # Returns a new hash with all keys converted to symbols, as long as @@ -127,7 +127,7 @@ class Hash deep_transform_keys{ |key| key.to_sym rescue key } end - # Destructively convert all keys to symbols, as long as they respond + # Destructively converts all keys to symbols, as long as they respond # to +to_sym+. This includes the keys from the root hash and from all # nested hashes and arrays. def deep_symbolize_keys! diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index 8ad600b171..1d5f38231a 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -1,6 +1,12 @@ class Hash - # Slice a hash to include only the given keys. This is useful for - # limiting an options hash to valid keys before passing to a method: + # Slices a hash to include only the given keys. Returns a hash containing + # the given keys. + # + # { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b) + # # => {:a=>1, :b=>2} + # + # This is useful for limiting an options hash to valid keys before + # passing to a method: # # def search(criteria = {}) # criteria.assert_valid_keys(:mass, :velocity, :time) diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb index 82080ffe51..f0b7382ef3 100644 --- a/activesupport/lib/active_support/core_ext/integer/time.rb +++ b/activesupport/lib/active_support/core_ext/integer/time.rb @@ -17,21 +17,6 @@ class Integer # # # equivalent to Time.now.advance(months: 4, years: 5) # (4.months + 5.years).from_now - # - # While these methods provide precise calculation when used as in the examples - # above, care should be taken to note that this is not true if the result of - # +months+, +years+, etc is converted before use: - # - # # equivalent to 30.days.to_i.from_now - # 1.month.to_i.from_now - # - # # equivalent to 365.25.days.to_f.from_now - # 1.year.to_f.from_now - # - # In such cases, Ruby's core - # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and - # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision - # date and time arithmetic. def months ActiveSupport::Duration.new(self * 30.days, [[:months, self]]) end diff --git a/activesupport/lib/active_support/core_ext/kernel.rb b/activesupport/lib/active_support/core_ext/kernel.rb index 293a3b2619..364ed9d65f 100644 --- a/activesupport/lib/active_support/core_ext/kernel.rb +++ b/activesupport/lib/active_support/core_ext/kernel.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/kernel/agnostics' require 'active_support/core_ext/kernel/concern' -require 'active_support/core_ext/kernel/debugger' if RUBY_VERSION < '2.0.0' require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/kernel/singleton_class' diff --git a/activesupport/lib/active_support/core_ext/kernel/debugger.rb b/activesupport/lib/active_support/core_ext/kernel/debugger.rb index 2073cac98d..1fde3db070 100644 --- a/activesupport/lib/active_support/core_ext/kernel/debugger.rb +++ b/activesupport/lib/active_support/core_ext/kernel/debugger.rb @@ -1,10 +1,3 @@ -module Kernel - unless respond_to?(:debugger) - # Starts a debugging session if the +debugger+ gem has been loaded (call rails server --debugger to do load it). - def debugger - message = "\n***** Debugger requested, but was not available (ensure the debugger gem is listed in Gemfile/installed as gem): Start server with --debugger to enable *****\n" - defined?(Rails) ? Rails.logger.info(message) : $stderr.puts(message) - end - alias breakpoint debugger unless respond_to?(:breakpoint) - end -end +require 'active_support/deprecation' + +ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb index 80c531b694..8afc258df8 100644 --- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb +++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb @@ -1,6 +1,3 @@ -require 'rbconfig' -require 'tempfile' - module Kernel # Sets $VERBOSE to nil for the duration of the block and back to its original # value afterwards. @@ -29,34 +26,6 @@ module Kernel $VERBOSE = old_verbose end - # For compatibility - def silence_stderr #:nodoc: - ActiveSupport::Deprecation.warn( - "#silence_stderr is deprecated and will be removed in the next release" - ) #not thread-safe - silence_stream(STDERR) { yield } - end - - # Deprecated : this method is not thread safe - # Silences any stream for the duration of the block. - # - # silence_stream(STDOUT) do - # puts 'This will never be seen' - # end - # - # puts 'But this will' - # - # This method is not thread-safe. - def silence_stream(stream) - old_stream = stream.dup - stream.reopen(RbConfig::CONFIG['host_os'] =~ /mswin|mingw/ ? 'NUL:' : '/dev/null') - stream.sync = true - yield - ensure - stream.reopen(old_stream) - old_stream.close - end - # Blocks and ignores any exception passed as argument if raised within the block. # # suppress(ZeroDivisionError) do @@ -69,56 +38,4 @@ module Kernel yield rescue *exception_classes end - - # Captures the given stream and returns it: - # - # stream = capture(:stdout) { puts 'notice' } - # stream # => "notice\n" - # - # stream = capture(:stderr) { warn 'error' } - # stream # => "error\n" - # - # even for subprocesses: - # - # stream = capture(:stdout) { system('echo notice') } - # stream # => "notice\n" - # - # stream = capture(:stderr) { system('echo error 1>&2') } - # stream # => "error\n" - def capture(stream) - ActiveSupport::Deprecation.warn( - "#capture(stream) is deprecated and will be removed in the next release" - ) #not thread-safe - stream = stream.to_s - captured_stream = Tempfile.new(stream) - stream_io = eval("$#{stream}") - origin_stream = stream_io.dup - stream_io.reopen(captured_stream) - - yield - - stream_io.rewind - return captured_stream.read - ensure - captured_stream.close - captured_stream.unlink - stream_io.reopen(origin_stream) - end - alias :silence :capture - - # Silences both STDOUT and STDERR, even for subprocesses. - # - # quietly { system 'bundle install' } - # - # This method is not thread-safe. - def quietly - ActiveSupport::Deprecation.warn( - "#quietly is deprecated and will be removed in the next release" - ) #not thread-safe - silence_stream(STDOUT) do - silence_stream(STDERR) do - yield - end - end - end end diff --git a/activesupport/lib/active_support/core_ext/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb index 768b980f21..60732eb41a 100644 --- a/activesupport/lib/active_support/core_ext/load_error.rb +++ b/activesupport/lib/active_support/core_ext/load_error.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation/proxy_wrappers' + class LoadError REGEXPS = [ /^no such file to load -- (.+)$/i, @@ -21,8 +23,8 @@ class LoadError # Returns true if the given path name (except perhaps for the ".rb" # extension) is the missing file which caused the exception to be raised. def is_missing?(location) - location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '') + location.sub(/\.rb$/, ''.freeze) == path.sub(/\.rb$/, ''.freeze) end end -MissingSourceFile = LoadError +MissingSourceFile = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('MissingSourceFile', 'LoadError') diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb index 56c79c04bd..20a0856e71 100644 --- a/activesupport/lib/active_support/core_ext/marshal.rb +++ b/activesupport/lib/active_support/core_ext/marshal.rb @@ -1,9 +1,7 @@ -require 'active_support/core_ext/module/aliasing' - -module Marshal - class << self - def load_with_autoloading(source) - load_without_autoloading(source) +module ActiveSupport + module MarshalWithAutoloading # :nodoc: + def load(source) + super(source) rescue ArgumentError, NameError => exc if exc.message.match(%r|undefined class/module (.+)|) # try loading the class/module @@ -15,7 +13,7 @@ module Marshal raise exc end end - - alias_method_chain :load, :autoloading end end + +Marshal.singleton_class.prepend(ActiveSupport::MarshalWithAutoloading) diff --git a/activesupport/lib/active_support/core_ext/module/aliasing.rb b/activesupport/lib/active_support/core_ext/module/aliasing.rb index 0a6fadf928..b6934b9c54 100644 --- a/activesupport/lib/active_support/core_ext/module/aliasing.rb +++ b/activesupport/lib/active_support/core_ext/module/aliasing.rb @@ -1,4 +1,7 @@ class Module + # NOTE: This method is deprecated. Please use <tt>Module#prepend</tt> that + # comes with Ruby 2.0 or newer instead. + # # Encapsulates the common pattern of: # # alias_method :foo_without_feature, :foo @@ -21,6 +24,8 @@ class Module # # so you can safely chain foo, foo?, foo! and/or foo= with the same feature. def alias_method_chain(target, feature) + ActiveSupport::Deprecation.warn("alias_method_chain is deprecated. Please, use Module#prepend instead. From module, you can access the original method using super.") + # Strip out punctuation on predicates, bang or writer methods since # e.g. target?_without_feature is not a valid method name. aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1 @@ -43,7 +48,7 @@ class Module end # Allows you to make aliases for attributes, which includes - # getter, setter, and query methods. + # getter, setter, and a predicate. # # class Content < ActiveRecord::Base # # has a title attribute diff --git a/activesupport/lib/active_support/core_ext/module/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb index b0c7b021db..0ecc67a855 100644 --- a/activesupport/lib/active_support/core_ext/module/anonymous.rb +++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb @@ -7,6 +7,13 @@ class Module # m = Module.new # m.name # => nil # + # +anonymous?+ method returns true if module does not have a name: + # + # Module.new.anonymous? # => true + # + # module M; end + # M.anonymous? # => false + # # A module gets a name when it is first assigned to a constant. Either # via the +module+ or +class+ keyword or by an explicit assignment: # diff --git a/activesupport/lib/active_support/core_ext/module/attr_internal.rb b/activesupport/lib/active_support/core_ext/module/attr_internal.rb index 67f0e0335d..93fb598650 100644 --- a/activesupport/lib/active_support/core_ext/module/attr_internal.rb +++ b/activesupport/lib/active_support/core_ext/module/attr_internal.rb @@ -27,11 +27,8 @@ class Module def attr_internal_define(attr_name, type) internal_name = attr_internal_ivar_name(attr_name).sub(/\A@/, '') - # class_eval is necessary on 1.9 or else the methods are made private - class_eval do - # use native attr_* methods as they are faster on some Ruby implementations - send("attr_#{type}", internal_name) - end + # use native attr_* methods as they are faster on some Ruby implementations + send("attr_#{type}", internal_name) attr_name, internal_name = "#{attr_name}=", "#{internal_name}=" if type == :writer alias_method attr_name, internal_name remove_method internal_name diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb index d317df5079..a77da573fe 100644 --- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb +++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb @@ -203,10 +203,10 @@ class Module # include HairColors # end # - # Person.class_variable_get("@@hair_colors") #=> [:brown, :black, :blonde, :red] + # Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red] def mattr_accessor(*syms, &blk) mattr_reader(*syms, &blk) - mattr_writer(*syms, &blk) + mattr_writer(*syms) end alias :cattr_accessor :mattr_accessor end diff --git a/activesupport/lib/active_support/core_ext/module/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb index 07a392404e..65b88b9bbd 100644 --- a/activesupport/lib/active_support/core_ext/module/concerning.rb +++ b/activesupport/lib/active_support/core_ext/module/concerning.rb @@ -63,10 +63,10 @@ class Module # # == Mix-in noise exiled to its own file: # - # Once our chunk of behavior starts pushing the scroll-to-understand it's + # Once our chunk of behavior starts pushing the scroll-to-understand-it # boundary, we give in and move it to a separate file. At this size, the - # overhead feels in good proportion to the size of our extraction, despite - # diluting our at-a-glance sense of how things really work. + # increased overhead can be a reasonable tradeoff even if it reduces our + # at-a-glance perception of how things work. # # class Todo # # Other todo implementation @@ -99,7 +99,7 @@ class Module # end # # Todo.ancestors - # # => Todo, Todo::EventTracking, Object + # # => [Todo, Todo::EventTracking, Object] # # This small step has some wonderful ripple effects. We can # * grok the behavior of our class in one glance, diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb index 570585b89a..9dc0dee1bd 100644 --- a/activesupport/lib/active_support/core_ext/module/delegation.rb +++ b/activesupport/lib/active_support/core_ext/module/delegation.rb @@ -17,7 +17,7 @@ class Module # ==== 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 + # * <tt>:allow_nil</tt> - if set to true, prevents a +NoMethodError+ from being 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 @@ -167,7 +167,7 @@ class Module '' end - file, line = caller.first.split(':', 2) + file, line = caller(1, 1).first.split(':'.freeze, 2) line = line.to_i to = to.to_s @@ -185,19 +185,31 @@ class Module # On the other hand it could be that the target has side-effects, # whereas conceptually, from the user point of view, the delegator should # be doing one call. - - exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") - - method_def = [ - "def #{method_prefix}#{method}(#{definition})", - " _ = #{to}", - " if !_.nil? || nil.respond_to?(:#{method})", - " _.#{method}(#{definition})", - " else", - " #{exception unless allow_nil}", - " end", + if allow_nil + method_def = [ + "def #{method_prefix}#{method}(#{definition})", + "_ = #{to}", + "if !_.nil? || nil.respond_to?(:#{method})", + " _.#{method}(#{definition})", + "end", "end" - ].join ';' + ].join ';' + else + exception = %(raise DelegationError, "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}") + + method_def = [ + "def #{method_prefix}#{method}(#{definition})", + " _ = #{to}", + " _.#{method}(#{definition})", + "rescue NoMethodError => e", + " if _.nil? && e.name == :#{method}", + " #{exception}", + " else", + " raise", + " end", + "end" + ].join ';' + end module_eval(method_def, file, line) end diff --git a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb index b1097cc83b..1fde3db070 100644 --- a/activesupport/lib/active_support/core_ext/module/method_transplanting.rb +++ b/activesupport/lib/active_support/core_ext/module/method_transplanting.rb @@ -1,11 +1,3 @@ -class Module - ### - # TODO: remove this after 1.9 support is dropped - def methods_transplantable? # :nodoc: - x = Module.new { def foo; end } - Module.new { define_method :bar, x.instance_method(:foo) } - true - rescue TypeError - false - end -end +require 'active_support/deprecation' + +ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb index 719071d1c2..52632d2c6b 100644 --- a/activesupport/lib/active_support/core_ext/module/remove_method.rb +++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb @@ -1,10 +1,13 @@ class Module + # Removes the named method, if it exists. def remove_possible_method(method) if method_defined?(method) || private_method_defined?(method) undef_method(method) end end + # Replaces the existing method definition, if there is one, with the passed + # block as its body. def redefine_method(method, &block) remove_possible_method(method) define_method(method, &block) diff --git a/activesupport/lib/active_support/core_ext/name_error.rb b/activesupport/lib/active_support/core_ext/name_error.rb index e1ebd4f91c..6b447d772b 100644 --- a/activesupport/lib/active_support/core_ext/name_error.rb +++ b/activesupport/lib/active_support/core_ext/name_error.rb @@ -1,5 +1,12 @@ class NameError # Extract the name of the missing constant from the exception message. + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name + # end + # # => "HelloWorld" def missing_name if /undefined local variable or method/ !~ message $1 if /((::)?([A-Z]\w*)(::[A-Z]\w*)*)$/ =~ message @@ -7,10 +14,16 @@ class NameError end # Was this exception raised because the given name was missing? + # + # begin + # HelloWorld + # rescue NameError => e + # e.missing_name?("HelloWorld") + # end + # # => true def missing_name?(name) if name.is_a? Symbol - last_name = (missing_name || '').split('::').last - last_name == name.to_s + self.name == name else missing_name == name.to_s end diff --git a/activesupport/lib/active_support/core_ext/numeric.rb b/activesupport/lib/active_support/core_ext/numeric.rb index a6bc0624be..bcdc3eace2 100644 --- a/activesupport/lib/active_support/core_ext/numeric.rb +++ b/activesupport/lib/active_support/core_ext/numeric.rb @@ -1,3 +1,4 @@ require 'active_support/core_ext/numeric/bytes' require 'active_support/core_ext/numeric/time' +require 'active_support/core_ext/numeric/inquiry' require 'active_support/core_ext/numeric/conversions' diff --git a/activesupport/lib/active_support/core_ext/numeric/bytes.rb b/activesupport/lib/active_support/core_ext/numeric/bytes.rb index deea8e9358..dfbca32474 100644 --- a/activesupport/lib/active_support/core_ext/numeric/bytes.rb +++ b/activesupport/lib/active_support/core_ext/numeric/bytes.rb @@ -7,36 +7,56 @@ class Numeric EXABYTE = PETABYTE * 1024 # Enables the use of byte calculations and declarations, like 45.bytes + 2.6.megabytes + # + # 2.bytes # => 2 def bytes self end alias :byte :bytes + # Returns the number of bytes equivalent to the kilobytes provided. + # + # 2.kilobytes # => 2048 def kilobytes self * KILOBYTE end alias :kilobyte :kilobytes + # Returns the number of bytes equivalent to the megabytes provided. + # + # 2.megabytes # => 2_097_152 def megabytes self * MEGABYTE end alias :megabyte :megabytes + # Returns the number of bytes equivalent to the gigabytes provided. + # + # 2.gigabytes # => 2_147_483_648 def gigabytes self * GIGABYTE end alias :gigabyte :gigabytes + # Returns the number of bytes equivalent to the terabytes provided. + # + # 2.terabytes # => 2_199_023_255_552 def terabytes self * TERABYTE end alias :terabyte :terabytes + # Returns the number of bytes equivalent to the petabytes provided. + # + # 2.petabytes # => 2_251_799_813_685_248 def petabytes self * PETABYTE end alias :petabyte :petabytes + # Returns the number of bytes equivalent to the exabytes provided. + # + # 2.exabytes # => 2_305_843_009_213_693_952 def exabytes self * EXABYTE end diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index 6d3635c69a..0c8ff79237 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -118,18 +118,28 @@ class Numeric end end - [Float, Fixnum, Bignum, BigDecimal].each do |klass| - klass.send(:alias_method, :to_default_s, :to_s) - - klass.send(:define_method, :to_s) do |*args| - if args[0].is_a?(Symbol) - format = args[0] - options = args[1] || {} + [Fixnum, Bignum].each do |klass| + klass.class_eval do + alias_method :to_default_s, :to_s + def to_s(base_or_format = 10, options = nil) + if base_or_format.is_a?(Symbol) + to_formatted_s(base_or_format, options || {}) + else + to_default_s(base_or_format) + end + end + end + end - self.to_formatted_s(format, options) + Float.class_eval do + alias_method :to_default_s, :to_s + def to_s(*args) + if args.empty? + to_default_s else - to_default_s(*args) + to_formatted_s(*args) end end end + end diff --git a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb new file mode 100644 index 0000000000..7e7ac1b0b2 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb @@ -0,0 +1,26 @@ +unless 1.respond_to?(:positive?) # TODO: Remove this file when we drop support to ruby < 2.3 +class Numeric + # Returns true if the number is positive. + # + # 1.positive? # => true + # 0.positive? # => false + # -1.positive? # => false + def positive? + self > 0 + end + + # Returns true if the number is negative. + # + # -1.negative? # => true + # 0.negative? # => false + # 1.negative? # => false + def negative? + self < 0 + end +end + +class Complex + undef :positive? + undef :negative? +end +end diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb index 689fae4830..6c4a975495 100644 --- a/activesupport/lib/active_support/core_ext/numeric/time.rb +++ b/activesupport/lib/active_support/core_ext/numeric/time.rb @@ -1,6 +1,8 @@ require 'active_support/duration' require 'active_support/core_ext/time/calculations' require 'active_support/core_ext/time/acts_like' +require 'active_support/core_ext/date/calculations' +require 'active_support/core_ext/date/acts_like' class Numeric # Enables the use of time calculations and declarations, like 45.minutes + 2.hours + 4.years. @@ -16,53 +18,56 @@ class Numeric # # # equivalent to Time.current.advance(months: 4, years: 5) # (4.months + 5.years).from_now - # - # While these methods provide precise calculation when used as in the examples above, care - # should be taken to note that this is not true if the result of `months', `years', etc is - # converted before use: - # - # # equivalent to 30.days.to_i.from_now - # 1.month.to_i.from_now - # - # # equivalent to 365.25.days.to_f.from_now - # 1.year.to_f.from_now - # - # In such cases, Ruby's core - # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and - # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision - # date and time arithmetic. def seconds ActiveSupport::Duration.new(self, [[:seconds, self]]) end alias :second :seconds + # Returns a Duration instance matching the number of minutes provided. + # + # 2.minutes # => 120 seconds def minutes ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]]) end alias :minute :minutes + # Returns a Duration instance matching the number of hours provided. + # + # 2.hours # => 7_200 seconds def hours ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]]) end alias :hour :hours + # Returns a Duration instance matching the number of days provided. + # + # 2.days # => 2 days def days ActiveSupport::Duration.new(self * 24.hours, [[:days, self]]) end alias :day :days + # Returns a Duration instance matching the number of weeks provided. + # + # 2.weeks # => 14 days def weeks ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]]) end alias :week :weeks + # Returns a Duration instance matching the number of fortnights provided. + # + # 2.fortnights # => 28 days def fortnights ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]]) end alias :fortnight :fortnights + # Returns the number of milliseconds equivalent to the seconds provided. # Used with the standard time durations, like 1.hour.in_milliseconds -- # so we can feed them to JavaScript functions like getTime(). + # + # 2.in_milliseconds # => 2_000 def in_milliseconds self * 1000 end diff --git a/activesupport/lib/active_support/core_ext/object.rb b/activesupport/lib/active_support/core_ext/object.rb index f1106cca9b..f4f9152d6a 100644 --- a/activesupport/lib/active_support/core_ext/object.rb +++ b/activesupport/lib/active_support/core_ext/object.rb @@ -2,7 +2,6 @@ require 'active_support/core_ext/object/acts_like' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/duplicable' require 'active_support/core_ext/object/deep_dup' -require 'active_support/core_ext/object/itself' require 'active_support/core_ext/object/try' require 'active_support/core_ext/object/inclusion' diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb index 2e99f4a1b8..ad5b2af161 100644 --- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb +++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb @@ -25,7 +25,7 @@ class Array # array[1][2] # => nil # dup[1][2] # => 4 def deep_dup - map { |it| it.deep_dup } + map(&:deep_dup) end end @@ -40,6 +40,7 @@ class Hash # dup[:a][:c] # => "c" def deep_dup each_with_object(dup) do |(key, value), hash| + hash.delete(key) hash[key.deep_dup] = value.deep_dup end end diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 665cb0f96d..befa5aee21 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -19,7 +19,7 @@ class Object # Can you safely dup this object? # - # False for +nil+, +false+, +true+, symbol, number and BigDecimal(in 1.9.x) objects; + # False for +nil+, +false+, +true+, symbol, number, method objects; # true otherwise. def duplicable? true @@ -78,17 +78,12 @@ end require 'bigdecimal' class BigDecimal - # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead - # raises TypeError exception. Checking here on the runtime whether BigDecimal - # will allow dup or not. - begin - BigDecimal.new('4.56').dup - - def duplicable? - true - end - rescue TypeError - # can't dup, so use superclass implementation + # BigDecimals are duplicable: + # + # BigDecimal.new("1.2").duplicable? # => true + # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)> + def duplicable? + true end end diff --git a/activesupport/lib/active_support/core_ext/object/instance_variables.rb b/activesupport/lib/active_support/core_ext/object/instance_variables.rb index 755e1c6b16..593a7a4940 100644 --- a/activesupport/lib/active_support/core_ext/object/instance_variables.rb +++ b/activesupport/lib/active_support/core_ext/object/instance_variables.rb @@ -23,6 +23,6 @@ class Object # # C.new(0, 1).instance_variable_names # => ["@y", "@x"] def instance_variable_names - instance_variables.map { |var| var.to_s } + instance_variables.map(&:to_s) end end diff --git a/activesupport/lib/active_support/core_ext/object/itself.rb b/activesupport/lib/active_support/core_ext/object/itself.rb deleted file mode 100644 index d71cea6674..0000000000 --- a/activesupport/lib/active_support/core_ext/object/itself.rb +++ /dev/null @@ -1,15 +0,0 @@ -class Object - # TODO: Remove this file when we drop support for Ruby < 2.2 - unless respond_to?(:itself) - # Returns the object itself. - # - # Useful for chaining methods, such as Active Record scopes: - # - # Event.public_send(state.presence_in([ :trashed, :drafted ]) || :itself).order(:created_at) - # - # @return Object - def itself - self - end - end -end diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb index 698b2d1920..0db787010c 100644 --- a/activesupport/lib/active_support/core_ext/object/json.rb +++ b/activesupport/lib/active_support/core_ext/object/json.rb @@ -9,7 +9,6 @@ require 'time' require 'active_support/core_ext/time/conversions' require 'active_support/core_ext/date_time/conversions' require 'active_support/core_ext/date/conversions' -require 'active_support/core_ext/module/aliasing' # The JSON gem adds a few modules to Ruby core classes containing :to_json definition, overwriting # their default behavior. That said, we need to define the basic to_json method in all of them, @@ -26,22 +25,25 @@ require 'active_support/core_ext/module/aliasing' # bypassed completely. This means that as_json won't be invoked and the JSON gem will simply # ignore any options it does not natively understand. This also means that ::JSON.{generate,dump} # should give exactly the same results with or without active support. -[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].each do |klass| - klass.class_eval do - def to_json_with_active_support_encoder(options = nil) + +module ActiveSupport + module ToJsonWithActiveSupportEncoder # :nodoc: + def to_json(options = nil) if options.is_a?(::JSON::State) # Called from JSON.{generate,dump}, forward it to JSON gem's to_json - self.to_json_without_active_support_encoder(options) + super(options) else # to_json is being invoked directly, use ActiveSupport's encoder ActiveSupport::JSON.encode(self, options) end end - - alias_method_chain :to_json, :active_support_encoder end end +[Object, Array, FalseClass, Float, Hash, Integer, NilClass, String, TrueClass, Enumerable].reverse_each do |klass| + klass.prepend(ActiveSupport::ToJsonWithActiveSupportEncoder) +end + class Object def as_json(options = nil) #:nodoc: if respond_to?(:to_hash) diff --git a/activesupport/lib/active_support/core_ext/object/to_query.rb b/activesupport/lib/active_support/core_ext/object/to_query.rb index ccd568bbf5..ec5ace4e16 100644 --- a/activesupport/lib/active_support/core_ext/object/to_query.rb +++ b/activesupport/lib/active_support/core_ext/object/to_query.rb @@ -38,7 +38,7 @@ class Array # Calls <tt>to_param</tt> on all its elements and joins the result with # slashes. This is used by <tt>url_for</tt> in Action Pack. def to_param - collect { |e| e.to_param }.join '/' + collect(&:to_param).join '/' end # Converts an array into a string suitable for use as a URL query string, diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb index 31919474ed..c67eb25b68 100644 --- a/activesupport/lib/active_support/core_ext/object/try.rb +++ b/activesupport/lib/active_support/core_ext/object/try.rb @@ -1,4 +1,34 @@ +require 'delegate' + +module ActiveSupport + module Tryable #:nodoc: + def try(*a, &b) + try!(*a, &b) if a.empty? || respond_to?(a.first) + end + + def try!(*a, &b) + if a.empty? && block_given? + if b.arity == 0 + instance_eval(&b) + else + yield self + end + else + public_send(*a, &b) + end + end + end +end + class Object + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(*a, &b) + # # Invokes the public method whose name goes as first argument just like # +public_send+ does, except that if the receiver does not respond to it the # call returns +nil+ rather than raising an exception. @@ -9,7 +39,23 @@ class Object # # instead of # - # @person ? @person.name : nil + # @person.name if @person + # + # +try+ calls can be chained: + # + # @person.try(:spouse).try(:name) + # + # instead of + # + # @person.spouse.name if @person && @person.spouse + # + # +try+ will also return +nil+ if the receiver does not respond to the method: + # + # @person.try(:non_existing_method) # => nil + # + # instead of + # + # @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil # # +try+ returns +nil+ when called on +nil+ regardless of whether it responds # to the method: @@ -24,7 +70,7 @@ class Object # # The number of arguments in the signature must match. If the object responds # to the method the call is attempted and +ArgumentError+ is still raised - # otherwise. + # in case of argument mismatch. # # If +try+ is called without arguments it yields the receiver to a given # block unless it is +nil+: @@ -38,42 +84,52 @@ class Object # # @person.try { upcase.truncate(50) } # - # Please also note that +try+ is defined on +Object+, therefore it won't work + # Please also note that +try+ is defined on +Object+. Therefore, it won't work # with instances of classes that do not have +Object+ among their ancestors, - # like direct subclasses of +BasicObject+. For example, using +try+ with - # +SimpleDelegator+ will delegate +try+ to the target instead of calling it on - # delegator itself. - def try(*a, &b) - if a.empty? && block_given? - if b.arity.zero? - instance_eval(&b) - else - yield self - end - else - public_send(*a, &b) if respond_to?(a.first) - end - end + # like direct subclasses of +BasicObject+. - # Same as #try, but will raise a NoMethodError exception if the receiving is not nil and - # does not implement the tried method. - def try!(*a, &b) - if a.empty? && block_given? - yield self - else - public_send(*a, &b) - end - end + ## + # :method: try! + # + # :call-seq: + # try!(*a, &b) + # + # Same as #try, but raises a NoMethodError exception if the receiver is + # not +nil+ and does not implement the tried method. + # + # "a".try!(:upcase) # => "A" + # nil.try!(:upcase) # => nil + # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum +end + +class Delegator + include ActiveSupport::Tryable + + ## + # :method: try + # + # :call-seq: + # try(a*, &b) + # + # See Object#try + + ## + # :method: try! + # + # :call-seq: + # try!(a*, &b) + # + # See Object#try! end class NilClass # Calling +try+ on +nil+ always returns +nil+. - # It becomes specially helpful when navigating through associations that may return +nil+. + # It becomes especially helpful when navigating through associations that may return +nil+. # # nil.try(:name) # => nil # # Without +try+ - # @person && !@person.children.blank? && @person.children.first.name + # @person && @person.children.any? && @person.children.first.name # # With +try+ # @person.try(:children).try(:first).try(:name) @@ -81,6 +137,9 @@ class NilClass nil end + # Calling +try!+ on +nil+ always returns +nil+. + # + # nil.try!(:name) # => nil def try!(*args) nil end diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb index 7d38e1d134..513c8b1d55 100644 --- a/activesupport/lib/active_support/core_ext/object/with_options.rb +++ b/activesupport/lib/active_support/core_ext/object/with_options.rb @@ -7,7 +7,7 @@ class Object # provided. Each method called on the block variable must take an options # hash as its final argument. # - # Without <tt>with_options></tt>, this code contains duplication: + # Without <tt>with_options</tt>, this code contains duplication: # # class Account < ActiveRecord::Base # has_many :customers, dependent: :destroy diff --git a/activesupport/lib/active_support/core_ext/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb index b1a12781f3..83eced50bf 100644 --- a/activesupport/lib/active_support/core_ext/range/conversions.rb +++ b/activesupport/lib/active_support/core_ext/range/conversions.rb @@ -3,9 +3,24 @@ class Range :db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" } } - # Gives a human readable format of the range. + # Convert range to a formatted string. See RANGE_FORMATS for predefined formats. # - # (1..100).to_formatted_s # => "1..100" + # This method is aliased to <tt>to_s</tt>. + # + # range = (1..100) # => 1..100 + # + # range.to_formatted_s # => "1..100" + # range.to_s # => "1..100" + # + # range.to_formatted_s(:db) # => "BETWEEN '1' AND '100'" + # range.to_s(:db) # => "BETWEEN '1' AND '100'" + # + # == Adding your own range formats to to_formatted_s + # You can add your own formats to the Range::RANGE_FORMATS hash. + # Use the format name as the hash key and a Proc instance. + # + # # config/initializers/range_formats.rb + # Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" } def to_formatted_s(format = :default) if formatter = RANGE_FORMATS[format] formatter.call(first, last) diff --git a/activesupport/lib/active_support/core_ext/range/each.rb b/activesupport/lib/active_support/core_ext/range/each.rb index ecef78f55f..dc6dad5ced 100644 --- a/activesupport/lib/active_support/core_ext/range/each.rb +++ b/activesupport/lib/active_support/core_ext/range/each.rb @@ -1,23 +1,21 @@ -require 'active_support/core_ext/module/aliasing' +module ActiveSupport + module EachTimeWithZone #:nodoc: + def each(&block) + ensure_iteration_allowed + super + end -class Range #:nodoc: + def step(n = 1, &block) + ensure_iteration_allowed + super + end - def each_with_time_with_zone(&block) - ensure_iteration_allowed - each_without_time_with_zone(&block) - end - alias_method_chain :each, :time_with_zone + private - def step_with_time_with_zone(n = 1, &block) - ensure_iteration_allowed - step_without_time_with_zone(n, &block) - end - alias_method_chain :step, :time_with_zone - - private - def ensure_iteration_allowed - if first.is_a?(Time) - raise TypeError, "can't iterate from #{first.class}" - end + def ensure_iteration_allowed + raise TypeError, "can't iterate from #{first.class}" if first.is_a?(Time) + end end end + +Range.prepend(ActiveSupport::EachTimeWithZone) diff --git a/activesupport/lib/active_support/core_ext/range/include_range.rb b/activesupport/lib/active_support/core_ext/range/include_range.rb index 3a07401c8a..c69e1e3fb9 100644 --- a/activesupport/lib/active_support/core_ext/range/include_range.rb +++ b/activesupport/lib/active_support/core_ext/range/include_range.rb @@ -1,23 +1,23 @@ -require 'active_support/core_ext/module/aliasing' - -class Range - # Extends the default Range#include? to support range comparisons. - # (1..5).include?(1..5) # => true - # (1..5).include?(2..3) # => true - # (1..5).include?(2..6) # => false - # - # The native Range#include? behavior is untouched. - # ('a'..'f').include?('c') # => true - # (5..9).include?(11) # => false - def include_with_range?(value) - if value.is_a?(::Range) - # 1...10 includes 1..9 but it does not include 1..10. - operator = exclude_end? && !value.exclude_end? ? :< : :<= - include_without_range?(value.first) && value.last.send(operator, last) - else - include_without_range?(value) +module ActiveSupport + module IncludeWithRange #:nodoc: + # Extends the default Range#include? to support range comparisons. + # (1..5).include?(1..5) # => true + # (1..5).include?(2..3) # => true + # (1..5).include?(2..6) # => false + # + # The native Range#include? behavior is untouched. + # ('a'..'f').include?('c') # => true + # (5..9).include?(11) # => false + def include?(value) + if value.is_a?(::Range) + # 1...10 includes 1..9 but it does not include 1..10. + operator = exclude_end? && !value.exclude_end? ? :< : :<= + super(value.first) && value.last.send(operator, last) + else + super + end end end - - alias_method_chain :include?, :range end + +Range.prepend(ActiveSupport::IncludeWithRange) diff --git a/activesupport/lib/active_support/core_ext/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb new file mode 100644 index 0000000000..6cdbea1f37 --- /dev/null +++ b/activesupport/lib/active_support/core_ext/securerandom.rb @@ -0,0 +1,23 @@ +require 'securerandom' + +module SecureRandom + BASE58_ALPHABET = ('0'..'9').to_a + ('A'..'Z').to_a + ('a'..'z').to_a - ['0', 'O', 'I', 'l'] + # SecureRandom.base58 generates a random base58 string. + # + # The argument _n_ specifies the length, of the random string to be generated. + # + # If _n_ is not specified or is nil, 16 is assumed. It may be larger in the future. + # + # The result may contain alphanumeric characters except 0, O, I and l + # + # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE" + # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7" + # + def self.base58(n = 16) + SecureRandom.random_bytes(n).unpack("C*").map do |byte| + idx = byte % 64 + idx = SecureRandom.random_number(58) if idx >= 58 + BASE58_ALPHABET[idx] + end.join + end +end diff --git a/activesupport/lib/active_support/core_ext/string/behavior.rb b/activesupport/lib/active_support/core_ext/string/behavior.rb index 4aa960039b..710f1f4670 100644 --- a/activesupport/lib/active_support/core_ext/string/behavior.rb +++ b/activesupport/lib/active_support/core_ext/string/behavior.rb @@ -1,5 +1,5 @@ class String - # Enable more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>. + # Enables more predictable duck-typing on String-like classes. See <tt>Object#acts_like?</tt>. def acts_like_string? true end diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 2b1583d4ac..375ec1aef8 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -13,21 +13,34 @@ class String end # Performs a destructive squish. See String#squish. + # str = " foo bar \n \t boo" + # str.squish! # => "foo bar boo" + # str # => "foo bar boo" def squish! - gsub!(/\A[[:space:]]+/, '') - gsub!(/[[:space:]]+\z/, '') gsub!(/[[:space:]]+/, ' ') + strip! self end - # Returns a new string with all occurrences of the pattern removed. Short-hand for String#gsub(pattern, ''). - def remove(pattern) - gsub pattern, '' + # Returns a new string with all occurrences of the patterns removed. + # str = "foo bar test" + # str.remove(" test") # => "foo bar" + # str.remove(" test", /bar/) # => "foo " + # str # => "foo bar test" + def remove(*patterns) + dup.remove!(*patterns) end - # Alters the string by removing all occurrences of the pattern. Short-hand for String#gsub!(pattern, ''). - def remove!(pattern) - gsub! pattern, '' + # Alters the string by removing all occurrences of the patterns. + # str = "foo bar test" + # str.remove!(" test", /bar/) # => "foo " + # str # => "foo " + def remove!(*patterns) + patterns.each do |pattern| + gsub! pattern, "" + end + + self end # Truncates a given +text+ after a given <tt>length</tt> if +text+ is longer than <tt>length</tt>: @@ -80,7 +93,7 @@ class String def truncate_words(words_count, options = {}) sep = options[:separator] || /\s+/ sep = Regexp.escape(sep.to_s) unless Regexp === sep - if self =~ /\A((?:.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m + if self =~ /\A((?>.+?#{sep}){#{words_count - 1}}.+?)#{sep}.*/m $1 + (options[:omission] || '...') else dup diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb index 38d567c014..b2e713077c 100644 --- a/activesupport/lib/active_support/core_ext/string/inflections.rb +++ b/activesupport/lib/active_support/core_ext/string/inflections.rb @@ -164,7 +164,7 @@ class String # # <%= link_to(@person.name, person_path) %> # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> - def parameterize(sep = '-') + def parameterize(sep = '-'.freeze) ActiveSupport::Inflector.parameterize(self, sep) end @@ -172,17 +172,17 @@ class String # uses the +pluralize+ method on the last word in the string. # # 'RawScaledScorer'.tableize # => "raw_scaled_scorers" - # 'egg_and_ham'.tableize # => "egg_and_hams" + # 'ham_and_egg'.tableize # => "ham_and_eggs" # 'fancyCategory'.tableize # => "fancy_categories" def tableize ActiveSupport::Inflector.tableize(self) end - # Create a class name from a plural table name like Rails does for table names to models. + # Creates a class name from a plural table name like Rails does for table names to models. # Note that this returns a string and not a class. (To convert to an actual class # follow +classify+ with +constantize+.) # - # 'egg_and_hams'.classify # => "EggAndHam" + # 'ham_and_eggs'.classify # => "HamAndEgg" # 'posts'.classify # => "Post" def classify ActiveSupport::Inflector.classify(self) diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index a124202936..7055f7f699 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'active_support/multibyte' class String @@ -36,6 +35,13 @@ class String ActiveSupport::Multibyte.proxy_class.new(self) end + # Returns +true+ if string has utf_8 encoding. + # + # utf_8_str = "some string".encode "UTF-8" + # iso_str = "some string".encode "ISO-8859-1" + # + # utf_8_str.is_utf8? # => true + # iso_str.is_utf8? # => false def is_utf8? case encoding when Encoding::UTF_8 diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb index c761325108..c676b26b06 100644 --- a/activesupport/lib/active_support/core_ext/string/output_safety.rb +++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb @@ -1,6 +1,5 @@ require 'erb' require 'active_support/core_ext/kernel/singleton_class' -require 'active_support/deprecation' class ERB module Util @@ -14,7 +13,7 @@ class ERB # This method is also aliased as <tt>h</tt>. # # In your ERB templates, use this method to escape any unsafe content. For example: - # <%=h @person.name %> + # <%= h @person.name %> # # puts html_escape('is a > 0 & a < 10?') # # => is a > 0 & a < 10? @@ -86,6 +85,11 @@ class ERB # automatically flag the result as HTML safe, since the raw value is unsafe to # use inside HTML attributes. # + # If your JSON is being used downstream for insertion into the DOM, be aware of + # whether or not it is being inserted via +html()+. Most JQuery plugins do this. + # If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated + # content returned by your JSON. + # # If you need to output JSON elsewhere in your HTML, you can just do something # like this, as any unsafe characters (including quotation marks) will be # automatically escaped for you: @@ -150,7 +154,11 @@ module ActiveSupport #:nodoc: else if html_safe? new_safe_buffer = super - new_safe_buffer.instance_eval { @html_safe = true } + + if new_safe_buffer + new_safe_buffer.instance_variable_set :@html_safe, true + end + new_safe_buffer else to_str[*args] @@ -186,11 +194,6 @@ module ActiveSupport #:nodoc: super(html_escape_interpolated_argument(value)) end - def prepend!(value) - ActiveSupport::Deprecation.deprecation_warning "ActiveSupport::SafeBuffer#prepend!", :prepend - prepend value - end - def +(other) dup.concat(other) end @@ -219,7 +222,7 @@ module ActiveSupport #:nodoc: end def encode_with(coder) - coder.represent_scalar nil, to_str + coder.represent_object nil, to_str end UNSAFE_STRING_METHODS.each do |unsafe_method| @@ -247,6 +250,11 @@ module ActiveSupport #:nodoc: end class String + # Marks a string as trusted safe. It will be inserted into HTML with no + # additional escaping performed. It is your responsibilty to ensure that the + # string contains no malicious content. This method is equivalent to the + # `raw` helper in views. It is recommended that you use `sanitize` instead of + # this method. It should never be called on user input. def html_safe ActiveSupport::SafeBuffer.new(self) end diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb index 086c610976..9fdd9d8d2e 100644 --- a/activesupport/lib/active_support/core_ext/string/strip.rb +++ b/activesupport/lib/active_support/core_ext/string/strip.rb @@ -17,8 +17,8 @@ class String # # the user would see the usage message aligned against the left margin. # - # Technically, it looks for the least indented line in the whole string, and removes - # that amount of leading whitespace. + # Technically, it looks for the least indented non-empty line + # in the whole string, and removes that amount of leading whitespace. def strip_heredoc indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0 gsub(/^[ \t]{#{indent}}/, '') diff --git a/activesupport/lib/active_support/core_ext/struct.rb b/activesupport/lib/active_support/core_ext/struct.rb index c2c30044f2..1fde3db070 100644 --- a/activesupport/lib/active_support/core_ext/struct.rb +++ b/activesupport/lib/active_support/core_ext/struct.rb @@ -1,6 +1,3 @@ -# Backport of Struct#to_h from Ruby 2.0 -class Struct # :nodoc: - def to_h - Hash[members.zip(values)] - end -end unless Struct.instance_methods.include?(:to_h) +require 'active_support/deprecation' + +ActiveSupport::Deprecation.warn("This file is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/thread.rb b/activesupport/lib/active_support/core_ext/thread.rb deleted file mode 100644 index 4cd6634558..0000000000 --- a/activesupport/lib/active_support/core_ext/thread.rb +++ /dev/null @@ -1,86 +0,0 @@ -class Thread - LOCK = Mutex.new # :nodoc: - - # Returns the value of a thread local variable that has been set. Note that - # these are different than fiber local values. - # - # Thread local values are carried along with threads, and do not respect - # fibers. For example: - # - # Thread.new { - # Thread.current.thread_variable_set("foo", "bar") # set a thread local - # Thread.current["foo"] = "bar" # set a fiber local - # - # Fiber.new { - # Fiber.yield [ - # Thread.current.thread_variable_get("foo"), # get the thread local - # Thread.current["foo"], # get the fiber local - # ] - # }.resume - # }.join.value # => ['bar', nil] - # - # The value <tt>"bar"</tt> is returned for the thread local, where +nil+ is returned - # 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] - 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 - end - - # Returns an array of the names of the thread-local variables (as Symbols). - # - # thr = Thread.new do - # Thread.current.thread_variable_set(:cat, 'meow') - # Thread.current.thread_variable_set("dog", 'woof') - # end - # thr.join # => #<Thread:0x401b3f10 dead> - # thr.thread_variables # => [:dog, :cat] - # - # Note that these are not fiber local variables. Please see Thread#thread_variable_get - # for more details. - def thread_variables - _locals.keys - end - - # Returns <tt>true</tt> if the given string (or symbol) exists as a - # thread-local variable. - # - # me = Thread.current - # me.thread_variable_set(:oliver, "a") - # me.thread_variable?(:oliver) # => true - # me.thread_variable?(:stanley) # => false - # - # 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) - end - - # Freezes the thread so that thread local variables cannot be set via - # Thread#thread_variable_set, nor can fiber local variables be set. - # - # me = Thread.current - # me.freeze - # me.thread_variable_set(:oliver, "a") #=> RuntimeError: can't modify frozen thread locals - # me[:oliver] = "a" #=> RuntimeError: can't modify frozen thread locals - def freeze - _locals.freeze - super - end - - private - - def _locals - if defined?(@_locals) - @_locals - else - LOCK.synchronize { @_locals ||= {} } - end - end -end unless Thread.instance_methods.include?(:thread_variable_set) diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb index 32cffe237d..72c3234630 100644 --- a/activesupport/lib/active_support/core_ext/time.rb +++ b/activesupport/lib/active_support/core_ext/time.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/time/acts_like' require 'active_support/core_ext/time/calculations' require 'active_support/core_ext/time/conversions' -require 'active_support/core_ext/time/marshal' require 'active_support/core_ext/time/zones' diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb index ab8307429a..96156deebb 100644 --- a/activesupport/lib/active_support/core_ext/time/calculations.rb +++ b/activesupport/lib/active_support/core_ext/time/calculations.rb @@ -48,7 +48,11 @@ class Time alias_method :at, :at_with_coercion end - # Seconds since midnight: Time.now.seconds_since_midnight + # Returns the number of seconds since 00:00:00. + # + # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0 + # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296 + # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399 def seconds_since_midnight to_i - change(:hour => 0).to_i + (usec / 1.0e+6) end @@ -69,7 +73,7 @@ class Time # and minute is passed, then sec, usec and nsec is set to 0. The +options+ # parameter takes a hash with any of these keys: <tt>:year</tt>, <tt>:month</tt>, # <tt>:day</tt>, <tt>:hour</tt>, <tt>:min</tt>, <tt>:sec</tt>, <tt>:usec</tt> - # <tt>:nsec</tt>. Path either <tt>:usec</tt> or <tt>:nsec</tt>, not both. + # <tt>:nsec</tt>. Pass either <tt>:usec</tt> or <tt>:nsec</tt>, not both. # # Time.new(2012, 8, 29, 22, 35, 0).change(day: 1) # => Time.new(2012, 8, 1, 22, 35, 0) # Time.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => Time.new(1981, 8, 1, 22, 35, 0) @@ -94,7 +98,7 @@ class Time elsif zone ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec) else - raise ArgumentError, 'argument out of range' if new_usec > 999999 + raise ArgumentError, 'argument out of range' if new_usec >= 1000000 ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset) end end @@ -104,6 +108,12 @@ class Time # takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>, # <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>, # <tt>:seconds</tt>. + # + # Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700 + # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700 def advance(options) unless options[:weeks].nil? options[:weeks], partial_weeks = options[:weeks].divmod(1) @@ -162,7 +172,7 @@ class Time 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) + # Returns a new Time representing the end of the day, 23:59:59.999999 def end_of_day change( :hour => 23, @@ -179,7 +189,7 @@ class Time end alias :at_beginning_of_hour :beginning_of_hour - # Returns a new Time representing the end of the hour, x:59:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the hour, x:59:59.999999 def end_of_hour change( :min => 59, @@ -195,7 +205,7 @@ class Time end alias :at_beginning_of_minute :beginning_of_minute - # Returns a new Time representing the end of the minute, x:xx:59.999999 (.999999999 in ruby1.9) + # Returns a new Time representing the end of the minute, x:xx:59.999999 def end_of_minute change( :sec => 59, @@ -242,8 +252,10 @@ class Time # Layers additional behavior on Time#<=> so that DateTime and ActiveSupport::TimeWithZone instances # can be chronologically compared with a Time def compare_with_coercion(other) - # we're avoiding Time#to_datetime cause it's expensive - if other.is_a?(Time) + # we're avoiding Time#to_datetime and Time#to_time because they're expensive + if other.class == Time + compare_without_coercion(other) + elsif other.is_a?(Time) compare_without_coercion(other.to_time) else to_datetime <=> other diff --git a/activesupport/lib/active_support/core_ext/time/marshal.rb b/activesupport/lib/active_support/core_ext/time/marshal.rb index 497c4c3fb8..467bad1726 100644 --- a/activesupport/lib/active_support/core_ext/time/marshal.rb +++ b/activesupport/lib/active_support/core_ext/time/marshal.rb @@ -1,30 +1,3 @@ -# Ruby 1.9.2 adds utc_offset and zone to Time, but marshaling only -# preserves utc_offset. Preserve zone also, even though it may not -# work in some edge cases. -if Time.local(2010).zone != Marshal.load(Marshal.dump(Time.local(2010))).zone - class Time - class << self - alias_method :_load_without_zone, :_load - def _load(marshaled_time) - time = _load_without_zone(marshaled_time) - time.instance_eval do - if zone = defined?(@_zone) && remove_instance_variable('@_zone') - ary = to_a - ary[0] += subsec if ary[0] == sec - ary[-1] = zone - utc? ? Time.utc(*ary) : Time.local(*ary) - else - self - end - end - end - end +require 'active_support/deprecation' - alias_method :_dump_without_zone, :_dump - def _dump(*args) - obj = dup - obj.instance_variable_set('@_zone', zone) - obj.send :_dump_without_zone, *args - end - end -end +ActiveSupport::Deprecation.warn("This is deprecated and will be removed in Rails 5.1 with no replacement.") diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb index bbda04d60c..133d3938eb 100644 --- a/activesupport/lib/active_support/core_ext/time/zones.rb +++ b/activesupport/lib/active_support/core_ext/time/zones.rb @@ -1,4 +1,5 @@ require 'active_support/time_with_zone' +require 'active_support/core_ext/time/acts_like' require 'active_support/core_ext/date_and_time/zones' class Time @@ -25,7 +26,7 @@ class Time # <tt>current_user.time_zone</tt> just needs to return a string identifying the user's preferred time zone: # # class ApplicationController < ActionController::Base - # around_filter :set_time_zone + # around_action :set_time_zone # # def set_time_zone # if logged_in? @@ -50,12 +51,22 @@ class Time end end - # Returns a TimeZone instance or nil, or raises an ArgumentError for invalid timezones. + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by <tt>Time.zone=</tt>. + # Raises an ArgumentError for invalid time zones. + # + # Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...> + # Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...> + # Time.find_zone! -5.hours # => #<ActiveSupport::TimeZone @name="Bogota" ...> + # Time.find_zone! nil # => nil + # Time.find_zone! false # => false + # Time.find_zone! "NOT-A-TIMEZONE" # => ArgumentError: Invalid Timezone: NOT-A-TIMEZONE def find_zone!(time_zone) if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone) time_zone else - # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone) + # Look up the timezone based on the identifier (unless we've been + # passed a TZInfo::Timezone) unless time_zone.respond_to?(:period_for_local) time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone) end @@ -71,6 +82,12 @@ class Time raise ArgumentError, "Invalid Timezone: #{time_zone}" end + # Returns a TimeZone instance matching the time zone provided. + # Accepts the time zone in any format supported by <tt>Time.zone=</tt>. + # Returns +nil+ for invalid time zones. + # + # Time.find_zone "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...> + # Time.find_zone "NOT-A-TIMEZONE" # => nil def find_zone(time_zone) find_zone!(time_zone) rescue nil end diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb index bfe0832b37..0b2ff817c3 100644 --- a/activesupport/lib/active_support/core_ext/uri.rb +++ b/activesupport/lib/active_support/core_ext/uri.rb @@ -12,7 +12,7 @@ unless str == parser.unescape(parser.escape(str)) # YK: My initial experiments say yes, but let's be sure please enc = str.encoding enc = Encoding::UTF_8 if enc == Encoding::US_ASCII - str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc) + str.gsub(escaped) { |match| [match[1, 2].hex].pack('C') }.force_encoding(enc) end end end diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb index 93a11d4586..8215a3085e 100644 --- a/activesupport/lib/active_support/dependencies.rb +++ b/activesupport/lib/active_support/dependencies.rb @@ -12,12 +12,40 @@ require 'active_support/core_ext/kernel/reporting' require 'active_support/core_ext/load_error' require 'active_support/core_ext/name_error' require 'active_support/core_ext/string/starts_ends_with' +require "active_support/dependencies/interlock" require 'active_support/inflector' module ActiveSupport #:nodoc: module Dependencies #:nodoc: extend self + mattr_accessor :interlock + self.interlock = Interlock.new + + # :doc: + + # Execute the supplied block without interference from any + # concurrent loads + def self.run_interlock + Dependencies.interlock.running { yield } + end + + # Execute the supplied block while holding an exclusive lock, + # preventing any other thread from being inside a #run_interlock + # block at the same time + def self.load_interlock + Dependencies.interlock.loading { yield } + end + + # Execute the supplied block while holding an exclusive lock, + # preventing any other thread from being inside a #run_interlock + # block at the same time + def self.unload_interlock + Dependencies.interlock.unloading { yield } + end + + # :nodoc: + # Should we turn on Ruby warnings on the first load of dependent files? mattr_accessor :warnings_on_first_load self.warnings_on_first_load = false @@ -30,6 +58,10 @@ module ActiveSupport #:nodoc: mattr_accessor :loaded self.loaded = Set.new + # Stack of files being loaded. + mattr_accessor :loading + self.loading = [] + # Should we load files or require them? mattr_accessor :mechanism self.mechanism = ENV['NO_RELOAD'] ? :require : :load @@ -124,7 +156,7 @@ module ActiveSupport #:nodoc: # Normalize the list of new constants, and add them to the list we will return new_constants.each do |suffix| - constants << ([namespace, suffix] - ["Object"]).join("::") + constants << ([namespace, suffix] - ["Object"]).join("::".freeze) end end constants @@ -201,7 +233,10 @@ module ActiveSupport #:nodoc: # Object includes this module. module Loadable #:nodoc: def self.exclude_from(base) - base.class_eval { define_method(:load, Kernel.instance_method(:load)) } + base.class_eval do + define_method(:load, Kernel.instance_method(:load)) + private :load + end end def require_or_load(file_name) @@ -237,18 +272,6 @@ module ActiveSupport #:nodoc: raise end - def load(file, wrap = false) - result = false - load_dependency(file) { result = super } - result - end - - def require(file) - result = false - load_dependency(file) { result = super } - result - end - # Mark the given constant as unloadable. Unloadable constants are removed # each time dependencies are cleared. # @@ -265,6 +288,20 @@ module ActiveSupport #:nodoc: def unloadable(const_desc) Dependencies.mark_for_unload const_desc end + + private + + def load(file, wrap = false) + result = false + load_dependency(file) { result = super } + result + end + + def require(file) + result = false + load_dependency(file) { result = super } + result + end end # Exception file-blaming. @@ -316,8 +353,11 @@ module ActiveSupport #:nodoc: def clear log_call - loaded.clear - remove_unloadable_constants! + Dependencies.unload_interlock do + loaded.clear + loading.clear + remove_unloadable_constants! + end end def require_or_load(file_name, const_path = nil) @@ -326,41 +366,49 @@ module ActiveSupport #:nodoc: expanded = File.expand_path(file_name) return if loaded.include?(expanded) - # Record that we've seen this file *before* loading it to avoid an - # infinite loop with mutual dependencies. - loaded << expanded - - begin - if load? - log "loading #{file_name}" + Dependencies.load_interlock do + # Maybe it got loaded while we were waiting for our lock: + return if loaded.include?(expanded) - # Enable warnings if this file has not been loaded before and - # warnings_on_first_load is set. - load_args = ["#{file_name}.rb"] - load_args << const_path unless const_path.nil? + # Record that we've seen this file *before* loading it to avoid an + # infinite loop with mutual dependencies. + loaded << expanded + loading << expanded - if !warnings_on_first_load or history.include?(expanded) - result = load_file(*load_args) + begin + if load? + log "loading #{file_name}" + + # Enable warnings if this file has not been loaded before and + # warnings_on_first_load is set. + load_args = ["#{file_name}.rb"] + load_args << const_path unless const_path.nil? + + if !warnings_on_first_load or history.include?(expanded) + result = load_file(*load_args) + else + enable_warnings { result = load_file(*load_args) } + end else - enable_warnings { result = load_file(*load_args) } + log "requiring #{file_name}" + result = require file_name end - else - log "requiring #{file_name}" - result = require file_name + rescue Exception + loaded.delete expanded + raise + ensure + loading.pop end - rescue Exception - loaded.delete expanded - raise - end - # Record history *after* loading so first load gets warnings. - history << expanded - result + # Record history *after* loading so first load gets warnings. + history << expanded + result + end end # Is the provided constant path defined? def qualified_const_defined?(path) - Object.qualified_const_defined?(path.sub(/^::/, ''), false) + Object.const_defined?(path, false) end # Given +path+, a filesystem path to a ruby file, return an array of @@ -373,13 +421,13 @@ module ActiveSupport #:nodoc: bases.each do |root| expanded_root = File.expand_path(root) - next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path + next unless expanded_path.start_with?(expanded_root) - nesting = expanded_path[(expanded_root.size)..-1] - nesting = nesting[1..-1] if nesting && nesting[0] == ?/ - next if nesting.blank? + root_size = expanded_root.size + next if expanded_path[root_size] != ?/.freeze - paths << nesting.camelize + nesting = expanded_path[(root_size + 1)..-1] + paths << nesting.camelize unless nesting.blank? end paths.uniq! @@ -388,7 +436,7 @@ module ActiveSupport #:nodoc: # Search for a file in autoload_paths matching the provided suffix. def search_for_file(path_suffix) - path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb") + path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze) autoload_paths.each do |root| path = File.join(root, path_suffix) @@ -408,7 +456,7 @@ module ActiveSupport #:nodoc: end def load_once_path?(path) - # to_s works around a ruby1.9 issue where String#starts_with?(Pathname) + # to_s works around a ruby issue where String#starts_with?(Pathname) # will raise a TypeError: no implicit conversion of Pathname into String autoload_once_paths.any? { |base| path.starts_with? base.to_s } end @@ -473,9 +521,9 @@ module ActiveSupport #:nodoc: if file_path expanded = File.expand_path(file_path) - expanded.sub!(/\.rb\z/, '') + expanded.sub!(/\.rb\z/, ''.freeze) - if loaded.include?(expanded) + if loading.include?(expanded) raise "Circular dependency detected while autoloading constant #{qualified_name}" else require_or_load(expanded, qualified_name) @@ -594,7 +642,7 @@ module ActiveSupport #:nodoc: def autoloaded?(desc) return false if desc.is_a?(Module) && desc.anonymous? name = to_constant_name desc - return false unless qualified_const_defined? name + return false unless qualified_const_defined?(name) return autoloaded_constants.include?(name) end @@ -729,7 +777,7 @@ module ActiveSupport #:nodoc: protected def log_call(*args) if log_activity? - arg_str = args.collect { |arg| arg.inspect } * ', ' + arg_str = args.collect(&:inspect) * ', ' /in `([a-z_\?\!]+)'/ =~ caller(1).first selector = $1 || '<unknown>' log "called #{selector}(#{arg_str})" diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb index c0dba5f7fd..13036d521d 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -67,7 +67,7 @@ module ActiveSupport end def eager_load! - @_autoloads.values.each { |file| require file } + @_autoloads.each_value { |file| require file } end def autoloads diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb new file mode 100644 index 0000000000..fbeb904684 --- /dev/null +++ b/activesupport/lib/active_support/dependencies/interlock.rb @@ -0,0 +1,47 @@ +require 'active_support/concurrency/share_lock' + +module ActiveSupport #:nodoc: + module Dependencies #:nodoc: + class Interlock + def initialize # :nodoc: + @lock = ActiveSupport::Concurrency::ShareLock.new + end + + def loading + @lock.exclusive(purpose: :load, compatible: [:load]) do + yield + end + end + + def unloading + @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do + yield + end + end + + # Attempt to obtain an "unloading" (exclusive) lock. If possible, + # execute the supplied block while holding the lock. If there is + # concurrent activity, return immediately (without executing the + # block) instead of waiting. + def attempt_unloading + @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do + yield + end + end + + def start_running + @lock.start_sharing + end + + def done_running + @lock.stop_sharing + end + + def running + @lock.sharing do + yield + end + end + end + end +end diff --git a/activesupport/lib/active_support/deprecation/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb index 328b8c320a..28d2d78643 100644 --- a/activesupport/lib/active_support/deprecation/behaviors.rb +++ b/activesupport/lib/active_support/deprecation/behaviors.rb @@ -20,7 +20,7 @@ module ActiveSupport log: ->(message, callstack) { logger = - if defined?(Rails) && Rails.logger + if defined?(Rails.logger) && Rails.logger Rails.logger else require 'active_support/logger' @@ -38,6 +38,18 @@ module ActiveSupport silence: ->(message, callstack) {}, } + # Behavior module allows to determine how to display deprecation messages. + # You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+ + # constant. Available behaviors are: + # + # [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>. + # [+stderr+] Log all deprecation warnings to +$stderr+. + # [+log+] Log all deprecation warnings to +Rails.logger+. + # [+notify+] Use +ActiveSupport::Notifications+ to notify +deprecation.rails+. + # [+silence+] Do nothing. + # + # Setting behaviors only affects deprecations that happen after boot time. + # For more information you can read the documentation of the +behavior=+ method. module Behavior # Whether to print a backtrace along with the warning. attr_accessor :debug diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index cab8a1b14d..c74e9c40ac 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -31,12 +31,14 @@ module ActiveSupport method_names += options.keys method_names.each do |method_name| - target_module.alias_method_chain(method_name, :deprecation) do |target, punctuation| - target_module.send(:define_method, "#{target}_with_deprecation#{punctuation}") do |*args, &block| + mod = Module.new do + define_method(method_name) do |*args, &block| deprecator.deprecation_warning(method_name, options[method_name]) - send(:"#{target}_without_deprecation#{punctuation}", *args, &block) + super(*args, &block) end end + + target_module.prepend(mod) end end end diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb index a03a66b96b..6572ff78a6 100644 --- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb @@ -20,20 +20,22 @@ module ActiveSupport private def method_missing(called, *args, &block) - warn caller, called, args + warn caller_locations, called, args target.__send__(called, *args, &block) end end - # This DeprecatedObjectProxy transforms object to deprecated object. + # DeprecatedObjectProxy transforms an object into a deprecated one. It + # takes an object, a deprecation message and optionally a deprecator. The + # deprecator defaults to +ActiveSupport::Deprecator+ if none is specified. # - # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!") - # @old_object = DeprecatedObjectProxy.new(Object.new, "Don't use this object anymore!", deprecator_instance) + # deprecated_object = ActiveSupport::Deprecation::DeprecatedObjectProxy.new(Object.new, "This object is now deprecated") + # # => #<Object:0x007fb9b34c34b0> # - # When someone executes any method except +inspect+ on proxy object this will - # trigger +warn+ method on +deprecator_instance+. - # - # Default deprecator is <tt>ActiveSupport::Deprecation</tt> + # deprecated_object.to_s + # DEPRECATION WARNING: This object is now deprecated. + # (Backtrace) + # # => "#<Object:0x007fb9b34c34b0>" class DeprecatedObjectProxy < DeprecationProxy def initialize(object, message, deprecator = ActiveSupport::Deprecation.instance) @object = object @@ -51,13 +53,16 @@ module ActiveSupport end end - # This DeprecatedInstanceVariableProxy transforms instance variable to - # deprecated instance variable. + # DeprecatedInstanceVariableProxy transforms an instance variable into a + # deprecated one. It takes an instance of a class, a method on that class + # and an instance variable. It optionally takes a deprecator as the last + # argument. The deprecator defaults to +ActiveSupport::Deprecator+ if none + # is specified. # # class Example - # def initialize(deprecator) - # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request, deprecator) - # @_request = :a_request + # def initialize + # @request = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new(self, :request, :@request) + # @_request = :special_request # end # # def request @@ -69,12 +74,17 @@ module ActiveSupport # end # end # - # When someone execute any method on @request variable this will trigger - # +warn+ method on +deprecator_instance+ and will fetch <tt>@_request</tt> - # variable via +request+ method and execute the same method on non-proxy - # instance variable. + # example = Example.new + # # => #<Example:0x007fb9b31090b8 @_request=:special_request, @request=:special_request> + # + # example.old_request.to_s + # # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of + # @request.to_s + # (Bactrace information…) + # "special_request" # - # Default deprecator is <tt>ActiveSupport::Deprecation</tt>. + # example.request.to_s + # # => "special_request" class DeprecatedInstanceVariableProxy < DeprecationProxy def initialize(instance, method, var = "@#{method}", deprecator = ActiveSupport::Deprecation.instance) @instance = instance @@ -93,15 +103,23 @@ module ActiveSupport end end - # This DeprecatedConstantProxy transforms constant to deprecated constant. + # DeprecatedConstantProxy transforms a constant into a deprecated one. It + # takes the names of an old (deprecated) constant and of a new constant + # (both in string form) and optionally a deprecator. The deprecator defaults + # to +ActiveSupport::Deprecator+ if none is specified. The deprecated constant + # now returns the value of the new one. + # + # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto) # - # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST') - # OLD_CONST = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('OLD_CONST', 'NEW_CONST', deprecator_instance) + # (In a later update, the orignal implementation of `PLANETS` has been removed.) # - # When someone use old constant this will trigger +warn+ method on - # +deprecator_instance+. + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006') # - # Default deprecator is <tt>ActiveSupport::Deprecation</tt>. + # PLANETS.map { |planet| planet.capitalize } + # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead. + # (Bactrace information…) + # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"] class DeprecatedConstantProxy < DeprecationProxy def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance) @old_const = old_const @@ -109,6 +127,11 @@ module ActiveSupport @deprecator = deprecator end + # Returns the class of the new constant. + # + # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune) + # PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006') + # PLANETS.class # => Array def class target.class end diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb index a7d265d732..bbe25c9260 100644 --- a/activesupport/lib/active_support/deprecation/reporting.rb +++ b/activesupport/lib/active_support/deprecation/reporting.rb @@ -14,7 +14,7 @@ module ActiveSupport def warn(message = nil, callstack = nil) return if silenced - callstack ||= caller(2) + callstack ||= caller_locations(2) deprecation_message(callstack, message).tap do |m| behavior.each { |b| b.call(m, callstack) } end @@ -37,7 +37,7 @@ module ActiveSupport end def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil) - caller_backtrace ||= caller(2) + caller_backtrace ||= caller_locations(2) deprecated_method_warning(deprecated_method_name, message).tap do |msg| warn(msg, caller_backtrace) end @@ -79,6 +79,17 @@ module ActiveSupport end def extract_callstack(callstack) + return _extract_callstack(callstack) if callstack.first.is_a? String + + rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" + offending_line = callstack.find { |frame| + !frame.absolute_path.start_with?(rails_gem_root) + } || callstack.first + [offending_line.path, offending_line.lineno, offending_line.label] + end + + def _extract_callstack(callstack) + warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/" offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first if offending_line diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb index 584fc1e1c5..c63b61e97a 100644 --- a/activesupport/lib/active_support/duration.rb +++ b/activesupport/lib/active_support/duration.rb @@ -1,4 +1,3 @@ -require 'active_support/proxy_object' require 'active_support/core_ext/array/conversions' require 'active_support/core_ext/object/acts_like' @@ -53,10 +52,38 @@ module ActiveSupport end end + # Returns the amount of seconds a duration covers as a string. + # For more information check to_i method. + # + # 1.day.to_s # => "86400" def to_s @value.to_s end + # Returns the number of seconds that this Duration represents. + # + # 1.minute.to_i # => 60 + # 1.hour.to_i # => 3600 + # 1.day.to_i # => 86400 + # + # Note that this conversion makes some assumptions about the + # duration of some periods, e.g. months are always 30 days + # and years are 365.25 days: + # + # # equivalent to 30.days.to_i + # 1.month.to_i # => 2592000 + # + # # equivalent to 365.25.days.to_i + # 1.year.to_i # => 31557600 + # + # In such cases, Ruby's core + # Date[http://ruby-doc.org/stdlib/libdoc/date/rdoc/Date.html] and + # Time[http://ruby-doc.org/stdlib/libdoc/time/rdoc/Time.html] should be used for precision + # date and time arithmetic. + def to_i + @value.to_i + end + # Returns +true+ if +other+ is also a Duration instance, which has the # same parts as this one. def eql?(other) @@ -92,17 +119,19 @@ module ActiveSupport 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) + to_sentence(locale: ::I18n.default_locale) end def as_json(options = nil) #:nodoc: to_i end - def respond_to_missing?(method, include_private=false) #:nodoc + def respond_to_missing?(method, include_private=false) #:nodoc: @value.respond_to?(method, include_private) end + delegate :<=>, to: :value + protected def sum(sign, time = ::Time.current) #:nodoc: @@ -121,13 +150,6 @@ module ActiveSupport private - # We define it as a workaround to Ruby 2.0.0-p353 bug. - # For more information, check rails/rails#13055. - # Remove it when we drop support for 2.0.0-p353. - def ===(other) #:nodoc: - value === other - end - def method_missing(method, *args, &block) #:nodoc: value.send(method, *args, &block) end diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb index 6233c45787..7068f09d87 100644 --- a/activesupport/lib/active_support/gem_version.rb +++ b/activesupport/lib/active_support/gem_version.rb @@ -5,10 +5,10 @@ module ActiveSupport end module VERSION - MAJOR = 4 - MINOR = 2 + MAJOR = 5 + MINOR = 0 TINY = 0 - PRE = "beta2" + PRE = "alpha" STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".") end diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 1468c62151..0371f760b0 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -92,7 +92,7 @@ module ActiveSupport # hash = ActiveSupport::HashWithIndifferentAccess.new # hash[:key] = 'value' # - # This value can be later fetched using either +:key+ or +'key'+. + # This value can be later fetched using either +:key+ or <tt>'key'</tt>. def []=(key, value) regular_writer(convert_key(key), convert_value(value, for: :assignment)) end @@ -188,7 +188,7 @@ module ActiveSupport # dup[:a][:c] # => "c" def dup self.class.new(self).tap do |new_hash| - new_hash.default = default + set_defaults(new_hash) end end @@ -238,16 +238,20 @@ module ActiveSupport def to_options!; self end def select(*args, &block) + return to_enum(:select) unless block_given? dup.tap { |hash| hash.select!(*args, &block) } end def reject(*args, &block) + return to_enum(:reject) unless block_given? dup.tap { |hash| hash.reject!(*args, &block) } end # Convert to a regular hash with string keys. def to_hash - _new_hash = Hash.new(default) + _new_hash = Hash.new + set_defaults(_new_hash) + each do |key, value| _new_hash[key] = convert_value(value, for: :to_hash) end @@ -267,7 +271,7 @@ module ActiveSupport value.nested_under_indifferent_access end elsif value.is_a?(Array) - unless options[:for] == :assignment + if options[:for] != :assignment || value.frozen? value = value.dup end value.map! { |e| convert_value(e, options) } @@ -275,6 +279,14 @@ module ActiveSupport value end end + + def set_defaults(target) + if default_proc + target.default_proc = default_proc.dup + else + target.default = default + end + end end end diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb index affcfb7398..6775eec34b 100644 --- a/activesupport/lib/active_support/i18n_railtie.rb +++ b/activesupport/lib/active_support/i18n_railtie.rb @@ -37,10 +37,12 @@ module I18n enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil? I18n.enforce_available_locales = false + reloadable_paths = [] app.config.i18n.each do |setting, value| case setting when :railties_load_path - app.config.i18n.load_path.unshift(*value) + reloadable_paths = value + app.config.i18n.load_path.unshift(*value.map(&:existent).flatten) when :load_path I18n.load_path += value else @@ -53,16 +55,29 @@ module I18n # Restore available locales check so it will take place from now on. I18n.enforce_available_locales = enforce_available_locales - reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! } + directories = watched_dirs_with_extensions(reloadable_paths) + reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup, directories) do + I18n.load_path.keep_if { |p| File.exist?(p) } + I18n.load_path |= reloadable_paths.map(&:existent).flatten + + I18n.reload! + end + app.reloaders << reloader - ActionDispatch::Reloader.to_prepare { reloader.execute_if_updated } + ActionDispatch::Reloader.to_prepare do + reloader.execute_if_updated + # TODO: remove the following line as soon as the return value of + # callbacks is ignored, that is, returning `false` does not + # display a deprecation warning or halts the callback chain. + true + end reloader.execute @i18n_inited = true end def self.include_fallbacks_module - I18n.backend.class.send(:include, I18n::Backend::Fallbacks) + I18n.backend.class.include(I18n::Backend::Fallbacks) end def self.init_fallbacks(fallbacks) @@ -90,5 +105,11 @@ module I18n raise "Unexpected fallback type #{fallbacks.inspect}" end end + + def self.watched_dirs_with_extensions(paths) + paths.each_with_object({}) do |path, result| + result[path.absolute_current] = path.extensions + end + end end end diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 97401ccec7..42560f3515 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -27,6 +27,37 @@ module ActiveSupport class Inflections @__instance__ = ThreadSafe::Cache.new + class Uncountables < Array + def initialize + @regex_array = [] + super + end + + def delete(entry) + super entry + @regex_array.delete(to_regex(entry)) + end + + def <<(*word) + add(word) + end + + def add(words) + self.concat(words.flatten.map(&:downcase)) + @regex_array += self.map {|word| to_regex(word) } + self + end + + def uncountable?(str) + @regex_array.any? { |regex| regex === str } + end + + private + def to_regex(string) + /\b#{::Regexp.escape(string)}\Z/i + end + end + def self.instance(locale = :en) @__instance__[locale] ||= new end @@ -34,7 +65,7 @@ module ActiveSupport attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex def initialize - @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/ + @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/ end # Private, for the test suite. @@ -154,13 +185,13 @@ module ActiveSupport end end - # Add uncountable words that shouldn't be attempted inflected. + # Specifies words that are uncountable and should not be inflected. # # uncountable 'money' # uncountable 'money', 'information' # uncountable %w( money information rice ) def uncountable(*words) - @uncountables += words.flatten.map(&:downcase) + @uncountables.add(words) end # Specifies a humanized form of a string by a regular expression rule or @@ -185,7 +216,7 @@ module ActiveSupport def clear(scope = :all) case scope when :all - @plurals, @singulars, @uncountables, @humans = [], [], [], [] + @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, [] else instance_variable_set "@#{scope}", [] end diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb index 0e3c8517d1..ab5372ac43 100644 --- a/activesupport/lib/active_support/inflector/methods.rb +++ b/activesupport/lib/active_support/inflector/methods.rb @@ -22,58 +22,58 @@ module ActiveSupport # pluralized using rules defined for that language. By default, # this parameter is set to <tt>:en</tt>. # - # 'post'.pluralize # => "posts" - # 'octopus'.pluralize # => "octopi" - # 'sheep'.pluralize # => "sheep" - # 'words'.pluralize # => "words" - # 'CamelOctopus'.pluralize # => "CamelOctopi" - # 'ley'.pluralize(:es) # => "leyes" + # pluralize('post') # => "posts" + # pluralize('octopus') # => "octopi" + # pluralize('sheep') # => "sheep" + # pluralize('words') # => "words" + # pluralize('CamelOctopus') # => "CamelOctopi" + # pluralize('ley', :es) # => "leyes" def pluralize(word, locale = :en) apply_inflections(word, inflections(locale).plurals) end - # The reverse of +pluralize+, returns the singular form of a word in a + # The reverse of #pluralize, returns the singular form of a word in a # string. # # If passed an optional +locale+ parameter, the word will be # singularized using rules defined for that language. By default, # this parameter is set to <tt>:en</tt>. # - # 'posts'.singularize # => "post" - # 'octopi'.singularize # => "octopus" - # 'sheep'.singularize # => "sheep" - # 'word'.singularize # => "word" - # 'CamelOctopi'.singularize # => "CamelOctopus" - # 'leyes'.singularize(:es) # => "ley" + # singularize('posts') # => "post" + # singularize('octopi') # => "octopus" + # singularize('sheep') # => "sheep" + # singularize('word') # => "word" + # singularize('CamelOctopi') # => "CamelOctopus" + # singularize('leyes', :es) # => "ley" def singularize(word, locale = :en) apply_inflections(word, inflections(locale).singulars) end - # By default, +camelize+ converts strings to UpperCamelCase. If the argument - # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces + # Converts strings to UpperCamelCase. + # If the +uppercase_first_letter+ parameter is set to false, then produces # lowerCamelCase. # - # +camelize+ will also convert '/' to '::' which is useful for converting + # Also converts '/' to '::' which is useful for converting # paths to namespaces. # - # 'active_model'.camelize # => "ActiveModel" - # 'active_model'.camelize(:lower) # => "activeModel" - # 'active_model/errors'.camelize # => "ActiveModel::Errors" - # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors" + # camelize('active_model') # => "ActiveModel" + # camelize('active_model', false) # => "activeModel" + # camelize('active_model/errors') # => "ActiveModel::Errors" + # camelize('active_model/errors', false) # => "activeModel::Errors" # # As a rule of thumb you can think of +camelize+ as the inverse of - # +underscore+, though there are cases where that does not hold: + # #underscore, though there are cases where that does not hold: # - # 'SSLError'.underscore.camelize # => "SslError" + # camelize(underscore('SSLError')) # => "SslError" def camelize(term, uppercase_first_letter = true) string = term.to_s if uppercase_first_letter - string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize } + string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize } else - string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase } + string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase } end string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" } - string.gsub!('/', '::') + string.gsub!('/'.freeze, '::'.freeze) string end @@ -81,34 +81,34 @@ module ActiveSupport # # Changes '::' to '/' to convert namespaces to paths. # - # 'ActiveModel'.underscore # => "active_model" - # 'ActiveModel::Errors'.underscore # => "active_model/errors" + # underscore('ActiveModel') # => "active_model" + # underscore('ActiveModel::Errors') # => "active_model/errors" # # As a rule of thumb you can think of +underscore+ as the inverse of - # +camelize+, though there are cases where that does not hold: + # #camelize, though there are cases where that does not hold: # - # 'SSLError'.underscore.camelize # => "SslError" + # camelize(underscore('SSLError')) # => "SslError" def underscore(camel_cased_word) return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/ - word = camel_cased_word.to_s.gsub('::', '/') - word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" } - word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2') - word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') - word.tr!("-", "_") + word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze) + word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" } + word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze) + word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze) + word.tr!("-".freeze, "_".freeze) word.downcase! word end # Tweaks an attribute name for display to end users. # - # Specifically, +humanize+ performs these transformations: + # Specifically, performs these transformations: # - # * Applies human inflection rules to the argument. - # * Deletes leading underscores, if any. - # * Removes a "_id" suffix if present. - # * Replaces underscores with spaces, if any. - # * Downcases all words except acronyms. - # * Capitalizes the first word. + # * Applies human inflection rules to the argument. + # * Deletes leading underscores, if any. + # * Removes a "_id" suffix if present. + # * Replaces underscores with spaces, if any. + # * Downcases all words except acronyms. + # * Capitalizes the first word. # # The capitalization of the first word can be turned off by setting the # +:capitalize+ option to false (default is true). @@ -127,9 +127,9 @@ module ActiveSupport inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) } - result.sub!(/\A_+/, '') - result.sub!(/_id\z/, '') - result.tr!('_', ' ') + result.sub!(/\A_+/, ''.freeze) + result.sub!(/_id\z/, ''.freeze) + result.tr!('_'.freeze, ' '.freeze) result.gsub!(/([a-z\d]*)/i) do |match| "#{inflections.acronyms[match] || match.downcase}" @@ -148,54 +148,54 @@ module ActiveSupport # # +titleize+ is also aliased as +titlecase+. # - # 'man from the boondocks'.titleize # => "Man From The Boondocks" - # 'x-men: the last stand'.titleize # => "X Men: The Last Stand" - # 'TheManWithoutAPast'.titleize # => "The Man Without A Past" - # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark" + # titleize('man from the boondocks') # => "Man From The Boondocks" + # titleize('x-men: the last stand') # => "X Men: The Last Stand" + # titleize('TheManWithoutAPast') # => "The Man Without A Past" + # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark" def titleize(word) - humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize } + humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize } end - # Create the name of a table like Rails does for models to table names. This - # method uses the +pluralize+ method on the last word in the string. + # Creates the name of a table like Rails does for models to table names. + # This method uses the #pluralize method on the last word in the string. # - # 'RawScaledScorer'.tableize # => "raw_scaled_scorers" - # 'egg_and_ham'.tableize # => "egg_and_hams" - # 'fancyCategory'.tableize # => "fancy_categories" + # tableize('RawScaledScorer') # => "raw_scaled_scorers" + # tableize('ham_and_egg') # => "ham_and_eggs" + # tableize('fancyCategory') # => "fancy_categories" def tableize(class_name) pluralize(underscore(class_name)) end - # Create a class name from a plural table name like Rails does for table + # Creates a class name from a plural table name like Rails does for table # names to models. Note that this returns a string and not a Class (To - # convert to an actual class follow +classify+ with +constantize+). + # convert to an actual class follow +classify+ with #constantize). # - # 'egg_and_hams'.classify # => "EggAndHam" - # 'posts'.classify # => "Post" + # classify('ham_and_eggs') # => "HamAndEgg" + # classify('posts') # => "Post" # # Singular names are not handled correctly: # - # 'calculus'.classify # => "Calculu" + # classify('calculus') # => "Calculu" def classify(table_name) # strip out any leading schema name - camelize(singularize(table_name.to_s.sub(/.*\./, ''))) + camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze))) end # Replaces underscores with dashes in the string. # - # 'puni_puni'.dasherize # => "puni-puni" + # dasherize('puni_puni') # => "puni-puni" def dasherize(underscored_word) - underscored_word.tr('_', '-') + underscored_word.tr('_'.freeze, '-'.freeze) end # Removes the module part from the expression in the string. # - # 'ActiveRecord::CoreExtensions::String::Inflections'.demodulize # => "Inflections" - # 'Inflections'.demodulize # => "Inflections" - # '::Inflections'.demodulize # => "Inflections" - # ''.demodulize # => "" + # demodulize('ActiveRecord::CoreExtensions::String::Inflections') # => "Inflections" + # demodulize('Inflections') # => "Inflections" + # demodulize('::Inflections') # => "Inflections" + # demodulize('') # => "" # - # See also +deconstantize+. + # See also #deconstantize. def demodulize(path) path = path.to_s if i = path.rindex('::') @@ -207,13 +207,13 @@ module ActiveSupport # Removes the rightmost segment from the constant expression in the string. # - # 'Net::HTTP'.deconstantize # => "Net" - # '::Net::HTTP'.deconstantize # => "::Net" - # 'String'.deconstantize # => "" - # '::String'.deconstantize # => "" - # ''.deconstantize # => "" + # deconstantize('Net::HTTP') # => "Net" + # deconstantize('::Net::HTTP') # => "::Net" + # deconstantize('String') # => "" + # deconstantize('::String') # => "" + # deconstantize('') # => "" # - # See also +demodulize+. + # See also #demodulize. def deconstantize(path) path.to_s[0, path.rindex('::') || 0] # implementation based on the one in facets' Module#spacename end @@ -222,17 +222,17 @@ module ActiveSupport # +separate_class_name_and_id_with_underscore+ sets whether # the method should put '_' between the name and 'id'. # - # 'Message'.foreign_key # => "message_id" - # 'Message'.foreign_key(false) # => "messageid" - # 'Admin::Post'.foreign_key # => "post_id" + # foreign_key('Message') # => "message_id" + # foreign_key('Message', false) # => "messageid" + # foreign_key('Admin::Post') # => "post_id" def foreign_key(class_name, separate_class_name_and_id_with_underscore = true) underscore(demodulize(class_name)) + (separate_class_name_and_id_with_underscore ? "_id" : "id") end # Tries to find a constant with the name specified in the argument string. # - # 'Module'.constantize # => Module - # 'Test::Unit'.constantize # => Test::Unit + # 'Module'.constantize # => Module + # 'Foo::Bar'.constantize # => Foo::Bar # # The name is assumed to be the one of a top-level constant, no matter # whether it starts with "::" or not. No lexical context is taken into @@ -248,7 +248,7 @@ module ActiveSupport # NameError is raised when the name is not in CamelCase or the constant is # unknown. def constantize(camel_cased_word) - names = camel_cased_word.split('::') + names = camel_cased_word.split('::'.freeze) # Trigger a built-in NameError exception including the ill-formed constant in the message. Object.const_get(camel_cased_word) if names.empty? @@ -280,8 +280,8 @@ module ActiveSupport # Tries to find a constant with the name specified in the argument string. # - # 'Module'.safe_constantize # => Module - # 'Test::Unit'.safe_constantize # => Test::Unit + # safe_constantize('Module') # => Module + # safe_constantize('Foo::Bar') # => Foo::Bar # # The name is assumed to be the one of a top-level constant, no matter # whether it starts with "::" or not. No lexical context is taken into @@ -290,16 +290,16 @@ module ActiveSupport # C = 'outside' # module M # C = 'inside' - # C # => 'inside' - # 'C'.safe_constantize # => 'outside', same as ::C + # C # => 'inside' + # safe_constantize('C') # => 'outside', same as ::C # end # # +nil+ is returned when the name is not in CamelCase or the constant (or # part of it) is unknown. # - # 'blargle'.safe_constantize # => nil - # 'UnknownModule'.safe_constantize # => nil - # 'UnknownModule::Foo::Bar'.safe_constantize # => nil + # safe_constantize('blargle') # => nil + # safe_constantize('UnknownModule') # => nil + # safe_constantize('UnknownModule::Foo::Bar') # => nil def safe_constantize(camel_cased_word) constantize(camel_cased_word) rescue NameError => e @@ -354,7 +354,7 @@ module ActiveSupport # const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?" # const_regexp("::") # => "::" def const_regexp(camel_cased_word) #:nodoc: - parts = camel_cased_word.split("::") + parts = camel_cased_word.split("::".freeze) return Regexp.escape(camel_cased_word) if parts.blank? @@ -372,7 +372,7 @@ module ActiveSupport def apply_inflections(word, rules) result = word.to_s.dup - if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/]) + if word.empty? || inflections.uncountables.uncountable?(result) result else rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) } diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb index 1cde417fc5..7b28eeb6e2 100644 --- a/activesupport/lib/active_support/inflector/transliterate.rb +++ b/activesupport/lib/active_support/inflector/transliterate.rb @@ -58,7 +58,7 @@ module ActiveSupport # I18n.locale = :de # transliterate('Jürgen') # # => "Juergen" - def transliterate(string, replacement = "?") + def transliterate(string, replacement = "?".freeze) I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize( ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c), :replacement => replacement) @@ -67,31 +67,29 @@ module ActiveSupport # Replaces special characters in a string so that it may be used as part of # a 'pretty' URL. # - # class Person - # def to_param - # "#{id}-#{name.parameterize}" - # end - # end - # - # @person = Person.find(1) - # # => #<Person id: 1, name: "Donald E. Knuth"> - # - # <%= link_to(@person.name, person_path(@person)) %> - # # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a> + # parameterize("Donald E. Knuth") # => "donald-e-knuth" + # parameterize("^trés|Jolie-- ") # => "tres-jolie" def parameterize(string, sep = '-') # replace accented chars with their ascii equivalents parameterized_string = transliterate(string) # Turn unwanted chars into the separator parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep) unless sep.nil? || sep.empty? - re_sep = Regexp.escape(sep) + if sep == "-".freeze + re_duplicate_seperator = /-{2,}/ + re_leading_trailing_separator = /^-|-$/i + else + re_sep = Regexp.escape(sep) + re_duplicate_seperator = /#{re_sep}{2,}/ + re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i + end # No more than one of the separator in a row. - parameterized_string.gsub!(/#{re_sep}{2,}/, sep) + parameterized_string.gsub!(re_duplicate_seperator, sep) # Remove leading/trailing separator. - parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '') + parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze) end - parameterized_string.downcase + parameterized_string.downcase! + parameterized_string end - end end diff --git a/activesupport/lib/active_support/json/decoding.rb b/activesupport/lib/active_support/json/decoding.rb index 35548f3f56..2932954f03 100644 --- a/activesupport/lib/active_support/json/decoding.rb +++ b/activesupport/lib/active_support/json/decoding.rb @@ -9,20 +9,14 @@ module ActiveSupport module JSON # matches YAML-formatted dates DATE_REGEX = /^(?:\d{4}-\d{2}-\d{2}|\d{4}-\d{1,2}-\d{1,2}[T \t]+\d{1,2}:\d{2}:\d{2}(\.[0-9]*)?(([ \t]*)Z|[-+]\d{2}?(:\d{2})?))$/ - + class << self # Parses a JSON string (JavaScript Object Notation) into a hash. # See http://www.json.org for more info. # # ActiveSupport::JSON.decode("{\"team\":\"rails\",\"players\":\"36\"}") # => {"team" => "rails", "players" => "36"} - def decode(json, options = {}) - if options.present? - raise ArgumentError, "In Rails 4.1, ActiveSupport::JSON.decode no longer " \ - "accepts an options hash for MultiJSON. MultiJSON reached its end of life " \ - "and has been removed." - end - + def decode(json) data = ::JSON.parse(json, quirks_mode: true) if ActiveSupport.parse_json_times diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb index a14ed7ee94..031c5e9339 100644 --- a/activesupport/lib/active_support/json/encoding.rb +++ b/activesupport/lib/active_support/json/encoding.rb @@ -6,7 +6,6 @@ module ActiveSupport delegate :use_standard_json_time_format, :use_standard_json_time_format=, :time_precision, :time_precision=, :escape_html_entities_in_json, :escape_html_entities_in_json=, - :encode_big_decimal_as_string, :encode_big_decimal_as_string=, :json_encoder, :json_encoder=, :to => :'ActiveSupport::JSON::Encoding' end @@ -58,6 +57,10 @@ module ActiveSupport super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS end end + + def to_s + self + end end # Mark these as private so we don't leak encoding-specific constructs @@ -113,54 +116,6 @@ module ActiveSupport # Sets the encoder used by Rails to encode Ruby objects into JSON strings # in +Object#to_json+ and +ActiveSupport::JSON.encode+. attr_accessor :json_encoder - - def encode_big_decimal_as_string=(as_string) - message = \ - "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \ - "the new encoder will always encode them as strings.\n\n" \ - "You are seeing this error because you have 'active_support.encode_big_decimal_as_string' in " \ - "your configuration file. If you have been setting this to true, you can safely remove it from " \ - "your configuration. Otherwise, you should add the 'activesupport-json_encoder' gem to your " \ - "Gemfile in order to restore this functionality." - - raise NotImplementedError, message - end - - def encode_big_decimal_as_string - message = \ - "The JSON encoder in Rails 4.1 no longer supports encoding BigDecimals as JSON numbers. Instead, " \ - "the new encoder will always encode them as strings.\n\n" \ - "You are seeing this error because you are trying to check the value of the related configuration, " \ - "'active_support.encode_big_decimal_as_string'. If your application depends on this option, you should " \ - "add the 'activesupport-json_encoder' gem to your Gemfile. For now, this option will always be true. " \ - "In the future, it will be removed from Rails, so you should stop checking its value." - - ActiveSupport::Deprecation.warn message - - true - 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 diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb index 75f353f62c..cbc20c103d 100644 --- a/activesupport/lib/active_support/log_subscriber/test_helper.rb +++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb @@ -11,6 +11,7 @@ module ActiveSupport # include ActiveSupport::LogSubscriber::TestHelper # # def setup + # super # ActiveRecord::LogSubscriber.attach_to(:active_record) # end # @@ -33,7 +34,7 @@ module ActiveSupport # you can collect them doing @logger.logged(level), where level is the level # used in logging, like info, debug, warn and so on. module TestHelper - def setup + def setup # :nodoc: @logger = MockLogger.new @notifier = ActiveSupport::Notifications::Fanout.new @@ -44,7 +45,7 @@ module ActiveSupport ActiveSupport::Notifications.notifier = @notifier end - def teardown + def teardown # :nodoc: set_logger(nil) ActiveSupport::Notifications.notifier = @old_notifier end diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb index 92ab6fe648..c82a13511e 100644 --- a/activesupport/lib/active_support/message_encryptor.rb +++ b/activesupport/lib/active_support/message_encryptor.rb @@ -82,7 +82,7 @@ module ActiveSupport def _decrypt(encrypted_message) cipher = new_cipher - encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)} + encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)} cipher.decrypt cipher.key = @secret diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb index 6cb2884fb7..64c5232cf4 100644 --- a/activesupport/lib/active_support/message_verifier.rb +++ b/activesupport/lib/active_support/message_verifier.rb @@ -1,5 +1,6 @@ require 'base64' require 'active_support/core_ext/object/blank' +require 'active_support/security_utils' module ActiveSupport # +MessageVerifier+ makes it easy to generate and verify messages which are @@ -33,37 +34,90 @@ module ActiveSupport @serializer = options[:serializer] || Marshal end - def verify(signed_message) - raise InvalidSignature if signed_message.blank? + # Checks if a signed message could have been generated by signing an object + # with the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private message' + # verifier.valid_message?(signed_message) # => true + # + # tampered_message = signed_message.chop # editing the message invalidates the signature + # verifier.valid_message?(tampered_message) # => false + def valid_message?(signed_message) + return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank? + + data, digest = signed_message.split("--".freeze) + data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data)) + end - data, digest = signed_message.split("--") - if data.present? && digest.present? && secure_compare(digest, generate_digest(data)) + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # + # signed_message = verifier.generate 'a private message' + # verifier.verified(signed_message) # => 'a private message' + # + # Returns +nil+ if the message was not signed with the same secret. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # other_verifier.verified(signed_message) # => nil + # + # Returns +nil+ if the message is not Base64-encoded. + # + # invalid_message = "f--46a0120593880c733a53b6dad75b42ddc1c8996d" + # verifier.verified(invalid_message) # => nil + # + # Raises any error raised while decoding the signed message. + # + # incompatible_message = "test--dad7b06c94abba8d46a15fafaef56c327665d5ff" + # verifier.verified(incompatible_message) # => TypeError: incompatible marshal file format + def verified(signed_message) + if valid_message?(signed_message) begin - @serializer.load(::Base64.strict_decode64(data)) + data = signed_message.split("--".freeze)[0] + @serializer.load(decode(data)) rescue ArgumentError => argument_error - raise InvalidSignature if argument_error.message =~ %r{invalid base64} + return if argument_error.message =~ %r{invalid base64} raise end - else - raise InvalidSignature end end + # Decodes the signed message using the +MessageVerifier+'s secret. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # signed_message = verifier.generate 'a private message' + # + # verifier.verify(signed_message) # => 'a private message' + # + # Raises +InvalidSignature+ if the message was not signed with the same + # secret or was not Base64-encoded. + # + # other_verifier = ActiveSupport::MessageVerifier.new 'd1ff3r3nt-s3Krit' + # other_verifier.verify(signed_message) # => ActiveSupport::MessageVerifier::InvalidSignature + def verify(signed_message) + verified(signed_message) || raise(InvalidSignature) + end + + # Generates a signed message for the provided value. + # + # The message is signed with the +MessageVerifier+'s secret. Without knowing + # the secret, the original value cannot be extracted from the message. + # + # verifier = ActiveSupport::MessageVerifier.new 's3Krit' + # verifier.generate 'a private message' # => "BAhJIhRwcml2YXRlLW1lc3NhZ2UGOgZFVA==--e2d724331ebdee96a10fb99b089508d1c72bd772" def generate(value) - data = ::Base64.strict_encode64(@serializer.dump(value)) + data = encode(@serializer.dump(value)) "#{data}--#{generate_digest(data)}" end private - # constant-time comparison algorithm to prevent timing attacks - def secure_compare(a, b) - return false unless a.bytesize == b.bytesize - - l = a.unpack "C#{a.bytesize}" + def encode(data) + ::Base64.strict_encode64(data) + end - res = 0 - b.each_byte { |byte| res |= byte ^ l.shift } - res == 0 + def decode(data) + ::Base64.strict_decode64(data) end def generate_digest(data) diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb index 3c0cf9f137..45cf6fc1ef 100644 --- a/activesupport/lib/active_support/multibyte/chars.rb +++ b/activesupport/lib/active_support/multibyte/chars.rb @@ -86,8 +86,14 @@ module ActiveSupport #:nodoc: @wrapped_string.split(*args).map { |i| self.class.new(i) } end - # Works like like <tt>String#slice!</tt>, but returns an instance of + # Works like <tt>String#slice!</tt>, but returns an instance of # Chars, or nil if the string was not modified. + # + # string = 'Welcome' + # string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c"> + # string # => 'Welome' + # string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo"> + # string # => 'me' def slice!(*args) chars(@wrapped_string.slice!(*args)) end diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index 7d45961515..b6c991a287 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -11,7 +11,7 @@ module ActiveSupport NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation - UNICODE_VERSION = '6.3.0' + UNICODE_VERSION = '7.0.0' # The default normalization used for operations that require # normalization. It can be set to any of the normalizations @@ -58,7 +58,7 @@ module ActiveSupport # Returns a regular expression pattern that matches the passed Unicode # codepoints. def self.codepoints_to_pattern(array_of_codepoints) #:nodoc: - array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|') + array_of_codepoints.collect{ |e| [e].pack 'U*'.freeze }.join('|'.freeze) end TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u @@ -211,9 +211,8 @@ module ActiveSupport codepoints end - # Ruby >= 2.1 has String#scrub, which is faster than the workaround used for < 2.1. # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars. - if '<3'.respond_to?(:scrub) && !defined?(Rubinius) + if !defined?(Rubinius) # Replaces all ISO-8859-1 or CP1252 characters by their UTF-8 equivalent # resulting in a valid UTF-8 string. # @@ -274,7 +273,7 @@ module ActiveSupport compose(reorder_characters(decompose(:compatibility, codepoints))) else raise ArgumentError, "#{form} is not a valid normalization variant", caller - end.pack('U*') + end.pack('U*'.freeze) end def downcase(string) @@ -339,7 +338,7 @@ module ActiveSupport end # Redefine the === method so we can write shorter rules for grapheme cluster breaks - @boundary.each do |k,_| + @boundary.each_key do |k| @boundary[k].instance_eval do def ===(other) detect { |i| i === other } ? true : false diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 325a3d75dc..b9f8e1ab2c 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -16,7 +16,7 @@ module ActiveSupport # render text: 'Foo' # end # - # That executes the block first and notifies all subscribers once done. + # That first executes the block and then notifies all subscribers once done. # # In the example above +render+ is the name of the event, and the rest is called # the _payload_. The payload is a mechanism that allows instrumenters to pass diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb index 6bf8c7d5de..0131fe2572 100644 --- a/activesupport/lib/active_support/notifications/fanout.rb +++ b/activesupport/lib/active_support/notifications/fanout.rb @@ -111,7 +111,7 @@ module ActiveSupport end end - class Timed < Evented + class Timed < Evented # :nodoc: def publish(name, *args) @delegate.call name, *args end diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb index 3a244b34b5..075ddc2382 100644 --- a/activesupport/lib/active_support/notifications/instrumenter.rb +++ b/activesupport/lib/active_support/notifications/instrumenter.rb @@ -57,6 +57,18 @@ module ActiveSupport @duration = nil end + # Returns the difference in milliseconds between when the execution of the + # event started and when it ended. + # + # ActiveSupport::Notifications.subscribe('wait') do |*args| + # @event = ActiveSupport::Notifications::Event.new(*args) + # end + # + # ActiveSupport::Notifications.instrument('wait') do + # sleep 1 + # end + # + # @event.duration # => 1000.138 def duration @duration ||= 1000.0 * (self.end - time) end diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb index 5ecda9593a..9762d95145 100644 --- a/activesupport/lib/active_support/number_helper.rb +++ b/activesupport/lib/active_support/number_helper.rb @@ -94,9 +94,9 @@ module ActiveSupport # * <tt>:locale</tt> - Sets the locale to be used for formatting # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number - # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # (defaults to 3). Keeps the number's precision if nil. + # * <tt>:significant</tt> - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +false+). # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -116,6 +116,7 @@ module ActiveSupport # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000% # number_to_percentage(302.24398923423, precision: 5) # => 302.24399% # number_to_percentage(1000, locale: :fr) # => 1 000,000% + # number_to_percentage:(1000, precision: nil) # => 1000% # number_to_percentage('98a') # => 98a% # number_to_percentage(100, format: '%n %') # => 100 % def number_to_percentage(number, options = {}) @@ -134,6 +135,9 @@ module ActiveSupport # to ","). # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). + # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for + # deriving the placement of delimiter. Helpful when using currency formats + # like INR. # # ==== Examples # @@ -146,7 +150,10 @@ module ActiveSupport # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05 # number_to_delimited('112a') # => 112a # number_to_delimited(98765432.98, delimiter: ' ', separator: ',') - # # => 98 765 432,98 + # # => 98 765 432,98 + # number_to_delimited("123456.78", + # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/) + # # => 1,23,456.78 def number_to_delimited(number, options = {}) NumberToDelimitedConverter.convert(number, options) end @@ -161,9 +168,9 @@ module ActiveSupport # * <tt>:locale</tt> - Sets the locale to be used for formatting # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number - # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # (defaults to 3). Keeps the number's precision if nil. + # * <tt>:significant</tt> - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +false+). # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -182,6 +189,7 @@ module ActiveSupport # number_to_rounded(111.2345, significant: true) # => 111 # number_to_rounded(111.2345, precision: 1, significant: true) # => 100 # number_to_rounded(13, precision: 5, significant: true) # => 13.000 + # number_to_rounded(13, precision: nil) # => 13 # number_to_rounded(111.234, locale: :fr) # => 111,234 # # number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true) @@ -208,8 +216,8 @@ module ActiveSupport # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # * <tt>:significant</tt> - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +true+) # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -218,8 +226,6 @@ module ActiveSupport # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes # insignificant zeros after the decimal separator (defaults to # +true+) - # * <tt>:prefix</tt> - If +:si+ formats the number using the SI - # prefix (defaults to :binary) # # ==== Examples # @@ -258,8 +264,8 @@ module ActiveSupport # (defaults to current locale). # * <tt>:precision</tt> - Sets the precision of the number # (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # - # of significant_digits. If +false+, the # of fractional + # * <tt>:significant</tt> - If +true+, precision will be the number + # of significant_digits. If +false+, the number of fractional # digits (defaults to +true+) # * <tt>:separator</tt> - Sets the separator between the # fractional and integer digits (defaults to "."). @@ -272,12 +278,12 @@ module ActiveSupport # string containing an i18n scope where to find this hash. It # might have the following keys: # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, - # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, - # *<tt>:billion</tt>, <tt>:trillion</tt>, - # *<tt>:quadrillion</tt> + # <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, + # <tt>:billion</tt>, <tt>:trillion</tt>, + # <tt>:quadrillion</tt> # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, - # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, - # *<tt>:pico</tt>, <tt>:femto</tt> + # <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, + # <tt>:pico</tt>, <tt>:femto</tt> # * <tt>:format</tt> - Sets the format of the output string # (defaults to "%n %u"). The field types are: # * %u - The quantifier (ex.: 'thousand') diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb index 9ae27a896a..7986eb50f0 100644 --- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb @@ -13,7 +13,7 @@ module ActiveSupport end rounded_number = NumberToRoundedConverter.convert(number, options) - format.gsub('%n', rounded_number).gsub('%u', options[:unit]) + format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, options[:unit]) end private @@ -23,20 +23,20 @@ module ActiveSupport end def absolute_value(number) - number.respond_to?("abs") ? number.abs : number.sub(/\A-/, '') + number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, '') end def options @options ||= begin defaults = default_format_options.merge(i18n_opts) - # Override negative format if format options is given + # Override negative format if format options are given defaults[:negative_format] = "-#{opts[:format]}" if opts[:format] defaults.merge!(opts) end end def i18n_opts - # Set International negative format if not exists + # Set International negative format if it does not exist i18n = i18n_format_options i18n[:negative_format] ||= "-#{i18n[:format]}" if i18n[:format] i18n diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb index d85cc086d7..45ae8f1a93 100644 --- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb @@ -3,7 +3,7 @@ module ActiveSupport class NumberToDelimitedConverter < NumberConverter #:nodoc: self.validate_float = true - DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ + DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/ def convert parts.join(options[:separator]) @@ -13,11 +13,16 @@ module ActiveSupport def parts left, right = number.to_s.split('.') - left.gsub!(DELIMITED_REGEX) do |digit_to_delimit| + left.gsub!(delimiter_pattern) do |digit_to_delimit| "#{digit_to_delimit}#{options[:delimiter]}" end [left, right].compact end + + def delimiter_pattern + options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX) + end + end end end diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb index 9a3dc526ae..5c6fe2df83 100644 --- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb @@ -23,7 +23,7 @@ module ActiveSupport unit = determine_unit(units, exponent) rounded_number = NumberToRoundedConverter.convert(number, options) - format.gsub(/%n/, rounded_number).gsub(/%u/, unit).strip + format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, unit).strip end private @@ -59,7 +59,7 @@ module ActiveSupport translate_in_locale("human.decimal_units.units", raise: true) else raise ArgumentError, ":units must be a Hash or String translation scope." - end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by { |e| -e } + end.keys.map { |e_name| INVERTED_DECIMAL_UNITS[e_name] }.sort_by(&:-@) end end end diff --git a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb index 78d2c9ae6e..a4a8690bcd 100644 --- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb @@ -7,6 +7,10 @@ module ActiveSupport self.validate_float = true def convert + if opts.key?(:prefix) + ActiveSupport::Deprecation.warn('The :prefix option of `number_to_human_size` is deprecated and will be removed in Rails 5.1 with no replacement.') + end + @number = Float(number) # for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files @@ -20,7 +24,7 @@ module ActiveSupport human_size = number / (base ** exponent) number_to_format = NumberToRoundedConverter.convert(human_size, options) end - conversion_format.gsub(/%n/, number_to_format).gsub(/%u/, unit) + conversion_format.gsub('%n'.freeze, number_to_format).gsub('%u'.freeze, unit) end private diff --git a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb index eafe2844f7..4c04d40c19 100644 --- a/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_percentage_converter.rb @@ -5,7 +5,7 @@ module ActiveSupport def convert rounded_number = NumberToRoundedConverter.convert(number, options) - options[:format].gsub('%n', rounded_number) + options[:format].gsub('%n'.freeze, rounded_number) end end end diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb index dcf9a567e8..981c562551 100644 --- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb +++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb @@ -6,36 +6,39 @@ module ActiveSupport def convert precision = options.delete :precision - significant = options.delete :significant - case number - when Float, String - @number = BigDecimal(number.to_s) - when Rational - @number = BigDecimal(number, digit_count(number.to_i) + precision) - else - @number = number.to_d - end - - if significant && precision > 0 - digits, rounded_number = digits_and_rounded_number(precision) - precision -= digits - precision = 0 if precision < 0 # don't let it be negative - else - rounded_number = number.round(precision) - rounded_number = rounded_number.to_i if precision == 0 - rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros - end + if precision + case number + when Float, String + @number = BigDecimal(number.to_s) + when Rational + @number = BigDecimal(number, digit_count(number.to_i) + precision) + else + @number = number.to_d + end - formatted_string = - if BigDecimal === rounded_number && rounded_number.finite? - s = rounded_number.to_s('F') + '0'*precision - a, b = s.split('.', 2) - a + '.' + b[0, precision] + if options.delete(:significant) && precision > 0 + digits, rounded_number = digits_and_rounded_number(precision) + precision -= digits + precision = 0 if precision < 0 # don't let it be negative else - "%00.#{precision}f" % rounded_number + rounded_number = number.round(precision) + rounded_number = rounded_number.to_i if precision == 0 && rounded_number.finite? + rounded_number = rounded_number.abs if rounded_number.zero? # prevent showing negative zeros end + formatted_string = + if BigDecimal === rounded_number && rounded_number.finite? + s = rounded_number.to_s('F') + '0'*precision + a, b = s.split('.', 2) + a + '.' + b[0, precision] + else + "%00.#{precision}f" % rounded_number + end + else + formatted_string = number + end + delimited_number = NumberToDelimitedConverter.convert(formatted_string, options) format_number(delimited_number) end diff --git a/activesupport/lib/active_support/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb index a33e2c58a9..45864990ce 100644 --- a/activesupport/lib/active_support/ordered_options.rb +++ b/activesupport/lib/active_support/ordered_options.rb @@ -6,6 +6,7 @@ module ActiveSupport # h[:girl] = 'Mary' # h[:boy] # => 'John' # h[:girl] # => 'Mary' + # h[:dog] # => nil # # Using +OrderedOptions+, the above code could be reduced to: # @@ -14,6 +15,13 @@ module ActiveSupport # h.girl = 'Mary' # h.boy # => 'John' # h.girl # => 'Mary' + # h.dog # => nil + # + # To raise an exception when the value is blank, append a + # bang to the key name, like: + # + # h.dog! # => raises KeyError + # class OrderedOptions < Hash alias_method :_get, :[] # preserve the original #[] method protected :_get # make it protected @@ -31,7 +39,13 @@ module ActiveSupport if name_string.chomp!('=') self[name_string] = args.first else - self[name] + bangs = name_string.chomp!('!') + + if bangs + fetch(name_string.to_sym).presence || raise(KeyError.new("#{name_string} is blank.")) + else + self[name_string] + end end end diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb index b05c3ff126..c8e3a4bf53 100644 --- a/activesupport/lib/active_support/rails.rb +++ b/activesupport/lib/active_support/rails.rb @@ -1,8 +1,8 @@ # This is private interface. # # Rails components cherry pick from Active Support as needed, but there are a -# few features that are used for sure some way or another and it is not worth -# to put individual requires absolutely everywhere. Think blank? for example. +# few features that are used for sure in some way or another and it is not worth +# putting individual requires absolutely everywhere. Think blank? for example. # # This file is loaded by every Rails component except Active Support itself, # but it does not belong to the Rails public interface. It is internal to diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb index 133aa6a054..cd0fb51009 100644 --- a/activesupport/lib/active_support/railtie.rb +++ b/activesupport/lib/active_support/railtie.rb @@ -16,6 +16,11 @@ module ActiveSupport # Sets the default value for Time.zone # If assigned value cannot be matched to a TimeZone, an exception will be raised. initializer "active_support.initialize_time_zone" do |app| + begin + TZInfo::DataSource.get + rescue TZInfo::DataSourceNotFound => e + raise e.exception "tzinfo-data is not present. Please add gem 'tzinfo-data' to your Gemfile and run bundle install" + end require 'active_support/core_ext/time/zones' zone_default = Time.find_zone!(app.config.time_zone) diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb index a7eba91ac5..fcf5553061 100644 --- a/activesupport/lib/active_support/rescuable.rb +++ b/activesupport/lib/active_support/rescuable.rb @@ -60,7 +60,7 @@ module ActiveSupport end klasses.each do |klass| - key = if klass.is_a?(Class) && klass <= Exception + key = if klass.is_a?(Module) && klass.respond_to?(:===) klass.name elsif klass.is_a?(String) klass @@ -68,7 +68,7 @@ module ActiveSupport raise ArgumentError, "#{klass} is neither an Exception nor a String" end - # put the new handler at the end because the list is read in reverse + # Put the new handler at the end because the list is read in reverse. self.rescue_handlers += [[key, options[:with]]] end end @@ -100,8 +100,8 @@ module ActiveSupport # a string, otherwise a NameError will be raised by the interpreter # itself when rescue_from CONSTANT is executed. klass = self.class.const_get(klass_name) rescue nil - klass ||= klass_name.constantize rescue nil - exception.is_a?(klass) if klass + klass ||= (klass_name.constantize rescue nil) + klass === exception if klass end case rescuer diff --git a/activesupport/lib/active_support/security_utils.rb b/activesupport/lib/active_support/security_utils.rb new file mode 100644 index 0000000000..64c4801179 --- /dev/null +++ b/activesupport/lib/active_support/security_utils.rb @@ -0,0 +1,20 @@ +module ActiveSupport + module SecurityUtils + # Constant time string comparison. + # + # The values compared should be of fixed length, such as strings + # that have already been processed by HMAC. This should not be used + # on variable length plaintext strings because it could leak length info + # via timing attacks. + def secure_compare(a, b) + return false unless a.bytesize == b.bytesize + + l = a.unpack "C#{a.bytesize}" + + res = 0 + b.each_byte { |byte| res |= byte ^ l.shift } + res == 0 + end + module_function :secure_compare + end +end diff --git a/activesupport/lib/active_support/string_inquirer.rb b/activesupport/lib/active_support/string_inquirer.rb index 45271c9163..bc673150d0 100644 --- a/activesupport/lib/active_support/string_inquirer.rb +++ b/activesupport/lib/active_support/string_inquirer.rb @@ -1,7 +1,7 @@ module ActiveSupport # Wrapping a string in this class gives you a prettier way to test # for equality. The value returned by <tt>Rails.env</tt> is wrapped - # in a StringInquirer object so instead of calling this: + # in a StringInquirer object, so instead of calling this: # # Rails.env == 'production' # diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index 98be78b41b..1cd4b807ad 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -5,24 +5,19 @@ module ActiveSupport # ActiveSupport::Notifications. The subscriber dispatches notifications to # a registered object based on its given namespace. # - # An example would be Active Record subscriber responsible for collecting + # An example would be an Active Record subscriber responsible for collecting # statistics about queries: # # module ActiveRecord # class StatsSubscriber < ActiveSupport::Subscriber + # attach_to :active_record + # # def sql(event) # Statsd.timing("sql.#{event.payload[:name]}", event.duration) # end # end # end # - # And it's finally registered as: - # - # ActiveRecord::StatsSubscriber.attach_to :active_record - # - # Since we need to know all instance methods before attaching the log - # subscriber, the line above should be called after your subscriber definition. - # # After configured, whenever a "sql.active_record" notification is published, # it will properly dispatch the event (ActiveSupport::Notifications::Event) to # the +sql+ method. @@ -66,7 +61,7 @@ module ActiveSupport pattern = "#{event}.#{namespace}" - # don't add multiple subscribers (eg. if methods are redefined) + # Don't add multiple subscribers (eg. if methods are redefined). return if subscriber.patterns.include?(pattern) subscriber.patterns << pattern @@ -96,7 +91,7 @@ module ActiveSupport event.end = finished event.payload.merge!(payload) - method = name.split('.').first + method = name.split('.'.freeze).first send(method, event) end diff --git a/activesupport/lib/active_support/tagged_logging.rb b/activesupport/lib/active_support/tagged_logging.rb index d5c2222d2e..bcd7bf74c0 100644 --- a/activesupport/lib/active_support/tagged_logging.rb +++ b/activesupport/lib/active_support/tagged_logging.rb @@ -43,7 +43,9 @@ module ActiveSupport end def current_tags - Thread.current[:activesupport_tagged_logging_tags] ||= [] + # We use our object ID here to avoid conflicting with other instances + thread_key = @thread_key ||= "activesupport_tagged_logging_tags:#{object_id}".freeze + Thread.current[thread_key] ||= [] end private diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index 4c3e77b7fd..ae6f00b861 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -8,44 +8,46 @@ require 'active_support/testing/declarative' require 'active_support/testing/isolation' require 'active_support/testing/constant_lookup' require 'active_support/testing/time_helpers' +require 'active_support/testing/file_fixtures' +require 'active_support/testing/composite_filter' require 'active_support/core_ext/kernel/reporting' -require 'active_support/deprecation' module ActiveSupport class TestCase < ::Minitest::Test Assertion = Minitest::Assertion class << self + # Sets the order in which test cases are run. + # + # ActiveSupport::TestCase.test_order = :random # => :random + # + # Valid values are: + # * +:random+ (to run tests in random order) + # * +:parallel+ (to run tests in parallel) + # * +:sorted+ (to run tests alphabetically by method name) + # * +:alpha+ (equivalent to +:sorted+) def test_order=(new_order) ActiveSupport.test_order = new_order end + # Returns the order in which test cases are run. + # + # ActiveSupport::TestCase.test_order # => :random + # + # Possible values are +:random+, +:parallel+, +:alpha+, +:sorted+. + # Defaults to +:random+. def test_order - test_order = ActiveSupport.test_order - - if test_order.nil? - ActiveSupport::Deprecation.warn "You did not specify a value for the " \ - "configuration option 'active_support.test_order'. In Rails 5.0, " \ - "the default value of this option will change from `:sorted` to " \ - "`:random`.\n" \ - "To disable this warning and keep the current behavior, you can add " \ - "the following line to your `config/environments/test.rb`:\n" \ - "\n" \ - " Rails.application.configure do\n" \ - " config.active_support.test_order = :sorted\n" \ - " end\n" \ - "\n" \ - "Alternatively, you can opt into the future behavior by setting this " \ - "option to `:random`." + ActiveSupport.test_order ||= :random + end - test_order = :sorted - self.test_order = test_order + def run(reporter, options = {}) + if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ } + options[:filter] = \ + Testing::CompositeFilter.new(self, options[:filter], options[:patterns]) end - test_order + super end - - alias :my_tests_are_order_dependent! :i_suck_and_my_tests_are_order_dependent! end alias_method :method_name, :name @@ -55,6 +57,7 @@ module ActiveSupport include ActiveSupport::Testing::Assertions include ActiveSupport::Testing::Deprecation include ActiveSupport::Testing::TimeHelpers + include ActiveSupport::Testing::FileFixtures extend ActiveSupport::Testing::Declarative # test/unit backwards compatibility methods @@ -73,7 +76,7 @@ module ActiveSupport alias :assert_not_respond_to :refute_respond_to alias :assert_not_same :refute_same - # Fails if the block raises an exception. + # Reveals the intention that the block should not raise any exception. # # assert_nothing_raised do # ... diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb index 11cca82995..d87ce3474d 100644 --- a/activesupport/lib/active_support/testing/assertions.rb +++ b/activesupport/lib/active_support/testing/assertions.rb @@ -23,42 +23,42 @@ module ActiveSupport # result of what is evaluated in the yielded block. # # assert_difference 'Article.count' do - # post :create, article: {...} + # post :create, params: { article: {...} } # end # # An arbitrary expression is passed in and evaluated. # - # assert_difference 'assigns(:article).comments(:reload).size' do - # post :create, comment: {...} + # assert_difference 'Article.last.comments(:reload).size' do + # post :create, params: { comment: {...} } # end # # An arbitrary positive or negative difference can be specified. # The default is <tt>1</tt>. # # assert_difference 'Article.count', -1 do - # post :delete, id: ... + # post :delete, params: { id: ... } # end # # An array of expressions can also be passed in and evaluated. # # assert_difference [ 'Article.count', 'Post.count' ], 2 do - # post :create, article: {...} + # post :create, params: { article: {...} } # end # # A lambda or a list of lambdas can be passed in and evaluated: # # assert_difference ->{ Article.count }, 2 do - # post :create, article: {...} + # post :create, params: { article: {...} } # end # # assert_difference [->{ Article.count }, ->{ Post.count }], 2 do - # post :create, article: {...} + # post :create, params: { article: {...} } # end # # An error message can be specified. # # assert_difference 'Article.count', -1, 'An Article should be destroyed' do - # post :delete, id: ... + # post :delete, params: { id: ... } # end def assert_difference(expression, difference = 1, message = nil, &block) expressions = Array(expression) @@ -66,7 +66,7 @@ module ActiveSupport exps = expressions.map { |e| e.respond_to?(:call) ? e : lambda { eval(e, block.binding) } } - before = exps.map { |e| e.call } + before = exps.map(&:call) yield @@ -81,13 +81,13 @@ module ActiveSupport # changed before and after invoking the passed in block. # # assert_no_difference 'Article.count' do - # post :create, article: invalid_attributes + # post :create, params: { article: invalid_attributes } # end # # An error message can be specified. # # assert_no_difference 'Article.count', 'An Article should not be created' do - # post :create, article: invalid_attributes + # post :create, params: { article: invalid_attributes } # end def assert_no_difference(expression, message = nil, &block) assert_difference expression, 0, message, &block diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb index 5aa5f46310..84c6b89340 100644 --- a/activesupport/lib/active_support/testing/autorun.rb +++ b/activesupport/lib/active_support/testing/autorun.rb @@ -2,4 +2,11 @@ gem 'minitest' require 'minitest' -Minitest.autorun +if Minitest.respond_to?(:run_with_rails_extension) + unless Minitest.run_with_rails_extension + Minitest.run_with_autorun = true + Minitest.autorun + end +else + Minitest.autorun +end diff --git a/activesupport/lib/active_support/testing/composite_filter.rb b/activesupport/lib/active_support/testing/composite_filter.rb new file mode 100644 index 0000000000..bde723e30b --- /dev/null +++ b/activesupport/lib/active_support/testing/composite_filter.rb @@ -0,0 +1,54 @@ +require 'method_source' + +module ActiveSupport + module Testing + class CompositeFilter # :nodoc: + def initialize(runnable, filter, patterns) + @runnable = runnable + @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact + end + + def ===(method) + @filters.any? { |filter| filter === method } + end + + private + def derive_regexp(filter) + filter =~ %r%/(.*)/% ? Regexp.new($1) : filter + end + + def derive_line_filters(patterns) + patterns.map do |file_and_line| + file, line = file_and_line.split(':') + Filter.new(@runnable, file, line) if file + end + end + + class Filter # :nodoc: + def initialize(runnable, file, line) + @runnable, @file = runnable, File.expand_path(file) + @line = line.to_i if line + end + + def ===(method) + return unless @runnable.method_defined?(method) + + if @line + test_file, test_range = definition_for(@runnable.instance_method(method)) + test_file == @file && test_range.include?(@line) + else + @runnable.instance_method(method).source_location.first == @file + end + end + + private + def definition_for(method) + file, start_line = method.source_location + end_line = method.source.count("\n") + start_line - 1 + + return file, start_line..end_line + end + end + end + end +end diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb new file mode 100644 index 0000000000..4c6a0801b8 --- /dev/null +++ b/activesupport/lib/active_support/testing/file_fixtures.rb @@ -0,0 +1,34 @@ +module ActiveSupport + module Testing + # Adds simple access to sample files called file fixtures. + # File fixtures are normal files stored in + # <tt>ActiveSupport::TestCase.file_fixture_path</tt>. + # + # File fixtures are represented as +Pathname+ objects. + # This makes it easy to extract specific information: + # + # file_fixture("example.txt").read # get the file's content + # file_fixture("example.mp3").size # get the file size + module FileFixtures + extend ActiveSupport::Concern + + included do + class_attribute :file_fixture_path, instance_writer: false + end + + # Returns a +Pathname+ to the fixture file named +fixture_name+. + # + # Raises ArgumentError if +fixture_name+ can't be found. + def file_fixture(fixture_name) + path = Pathname.new(File.join(file_fixture_path, fixture_name)) + + if path.exist? + path + else + msg = "the directory '%s' does not contain a file named '%s'" + raise ArgumentError, msg % [file_fixture_path, fixture_name] + end + end + end + end +end diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb index 68bda35980..1de0a19998 100644 --- a/activesupport/lib/active_support/testing/isolation.rb +++ b/activesupport/lib/active_support/testing/isolation.rb @@ -1,5 +1,3 @@ -require 'rbconfig' - module ActiveSupport module Testing module Isolation @@ -12,7 +10,7 @@ module ActiveSupport end def self.forking_env? - !ENV["NO_FORK"] && ((RbConfig::CONFIG['host_os'] !~ /mswin|mingw/) && (RUBY_PLATFORM !~ /java/)) + !ENV["NO_FORK"] && Process.respond_to?(:fork) end @@class_setup_mutex = Mutex.new @@ -71,17 +69,17 @@ module ActiveSupport else Tempfile.open("isolation") do |tmpfile| env = { - ISOLATION_TEST: self.class.name, - ISOLATION_OUTPUT: tmpfile.path + 'ISOLATION_TEST' => self.class.name, + 'ISOLATION_OUTPUT' => tmpfile.path } load_paths = $-I.map {|p| "-I\"#{File.expand_path(p)}\"" }.join(" ") orig_args = ORIG_ARGV.join(" ") test_opts = "-n#{self.class.name}##{self.name}" - command = "#{Gem.ruby} #{load_paths} #{$0} #{orig_args} #{test_opts}" + command = "#{Gem.ruby} #{load_paths} #{$0} '#{orig_args}' #{test_opts}" # IO.popen lets us pass env in a cross-platform way - child = IO.popen([env, command]) + child = IO.popen(env, command) begin Process.wait(child.pid) diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb new file mode 100644 index 0000000000..fccaa54f40 --- /dev/null +++ b/activesupport/lib/active_support/testing/method_call_assertions.rb @@ -0,0 +1,41 @@ +require 'minitest/mock' + +module ActiveSupport + module Testing + module MethodCallAssertions # :nodoc: + private + def assert_called(object, method_name, message = nil, times: 1, returns: nil) + times_called = 0 + + object.stub(method_name, proc { times_called += 1; returns }) { yield } + + error = "Expected #{method_name} to be called #{times} times, " \ + "but was called #{times_called} times" + error = "#{message}.\n#{error}" if message + assert_equal times, times_called, error + end + + def assert_called_with(object, method_name, args = [], returns: nil) + mock = Minitest::Mock.new + + if args.all? { |arg| arg.is_a?(Array) } + args.each { |arg| mock.expect(:call, returns, arg) } + else + mock.expect(:call, returns, args) + end + + object.stub(method_name, mock) { yield } + + mock.verify + end + + def assert_not_called(object, method_name, message = nil, &block) + assert_called(object, method_name, message, times: 0, &block) + end + + def stub_any_instance(klass, instance: klass.new) + klass.stub(:new, instance) { yield instance } + end + end + end +end diff --git a/activesupport/lib/active_support/testing/stream.rb b/activesupport/lib/active_support/testing/stream.rb new file mode 100644 index 0000000000..895192ad05 --- /dev/null +++ b/activesupport/lib/active_support/testing/stream.rb @@ -0,0 +1,42 @@ +module ActiveSupport + module Testing + module Stream #:nodoc: + private + + def silence_stream(stream) + old_stream = stream.dup + stream.reopen(IO::NULL) + stream.sync = true + yield + ensure + stream.reopen(old_stream) + old_stream.close + end + + def quietly + silence_stream(STDOUT) do + silence_stream(STDERR) do + yield + end + end + end + + def capture(stream) + stream = stream.to_s + captured_stream = Tempfile.new(stream) + stream_io = eval("$#{stream}") + origin_stream = stream_io.dup + stream_io.reopen(captured_stream) + + yield + + stream_io.rewind + return captured_stream.read + ensure + captured_stream.close + captured_stream.unlink + stream_io.reopen(origin_stream) + end + end + end +end diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 8c63815660..fca0947c5b 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -39,15 +39,16 @@ module ActiveSupport end end - # Containing helpers that helps you test passage of time. + # Contains helpers that help you test passage of time. module TimeHelpers # Changes current time to the time in the future or in the past by a given time difference by - # stubbing +Time.now+ and +Date.today+. + # stubbing +Time.now+, +Date.today+, and +DateTime.now+. # - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel 1.day - # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00 - # Date.current # => Sun, 10 Nov 2013 + # Time.current # => Sun, 10 Nov 2013 15:34:49 EST -05:00 + # Date.current # => Sun, 10 Nov 2013 + # DateTime.current # => Sun, 10 Nov 2013 15:34:49 -0500 # # This method also accepts a block, which will return the current time back to its original # state at the end of the block: @@ -61,13 +62,14 @@ module ActiveSupport travel_to Time.now + duration, &block end - # Changes current time to the given time by stubbing +Time.now+ and - # +Date.today+ to return the time or date passed into this method. + # Changes current time to the given time by stubbing +Time.now+, + # +Date.today+, and +DateTime.now+ to return the time or date passed into this method. # - # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 + # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 # travel_to Time.new(2004, 11, 24, 01, 04, 44) - # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 - # Date.current # => Wed, 24 Nov 2004 + # Time.current # => Wed, 24 Nov 2004 01:04:44 EST -05:00 + # Date.current # => Wed, 24 Nov 2004 + # DateTime.current # => Wed, 24 Nov 2004 01:04:44 -0500 # # Dates are taken as their timestamp at the beginning of the day in the # application time zone. <tt>Time.current</tt> returns said timestamp, @@ -99,6 +101,7 @@ module ActiveSupport simple_stubs.stub_object(Time, :now, now) simple_stubs.stub_object(Date, :today, now.to_date) + simple_stubs.stub_object(DateTime, :now, now.to_datetime) if block_given? begin diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb index 4a0ed356b1..817522677f 100644 --- a/activesupport/lib/active_support/time_with_zone.rb +++ b/activesupport/lib/active_support/time_with_zone.rb @@ -1,3 +1,4 @@ +require 'active_support/duration' require 'active_support/values/time_zone' require 'active_support/core_ext/object/acts_like' @@ -75,8 +76,8 @@ module ActiveSupport # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your # system's <tt>ENV['TZ']</tt> zone. - def localtime - utc.respond_to?(:getlocal) ? utc.getlocal : utc.to_time.getlocal + def localtime(utc_offset = nil) + utc.respond_to?(:getlocal) ? utc.getlocal(utc_offset) : utc.to_time.getlocal(utc_offset) end alias_method :getlocal, :localtime @@ -121,16 +122,25 @@ module ActiveSupport utc? && alternate_utc_string || TimeZone.seconds_to_utc_offset(utc_offset, colon) end - # Time uses +zone+ to display the time zone abbreviation, so we're - # duck-typing it. + # Returns the time zone abbreviation. + # + # Time.zone = 'Eastern Time (US & Canada)' # => "Eastern Time (US & Canada)" + # Time.zone.now.zone # => "EST" def zone period.zone_identifier.to_s end + # Returns a string of the object's date, time, zone and offset from UTC. + # + # Time.zone.now.httpdate # => "Thu, 04 Dec 2014 11:00:25 EST -05:00" def inspect "#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}" end + # Returns a string of the object's date and time in the ISO 8601 standard + # format. + # + # Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00" def xmlschema(fraction_digits = 0) fraction = if fraction_digits.to_i > 0 (".%06i" % time.usec)[0, fraction_digits.to_i + 1] @@ -160,12 +170,13 @@ module ActiveSupport end end - def encode_with(coder) - if coder.respond_to?(:represent_object) - coder.represent_object(nil, utc) - else - coder.represent_scalar(nil, utc.strftime("%Y-%m-%d %H:%M:%S.%9NZ")) - end + def init_with(coder) #:nodoc: + initialize(coder['utc'], coder['zone'], coder['time']) + end + + def encode_with(coder) #:nodoc: + coder.tag = '!ruby/object:ActiveSupport::TimeWithZone' + coder.map = { 'utc' => utc, 'zone' => time_zone, 'time' => time } end # Returns a string of the object's date and time in the format used by @@ -187,7 +198,7 @@ module ActiveSupport # Returns a string of the object's date and time. # Accepts an optional <tt>format</tt>: - # * <tt>:default</tt> - default value, mimics Ruby 1.9 Time#to_s format. + # * <tt>:default</tt> - default value, mimics Ruby Time#to_s format. # * <tt>:db</tt> - format outputs time in UTC :db time. See Time#to_formatted_s(:db). # * Any key in <tt>Time::DATE_FORMATS</tt> can be used. See active_support/core_ext/time/conversions.rb. def to_s(format = :default) @@ -196,20 +207,16 @@ module ActiveSupport elsif formatter = ::Time::DATE_FORMATS[format] formatter.respond_to?(:call) ? formatter.call(self).to_s : strftime(formatter) else - "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby 1.9 Time#to_s format + "#{time.strftime("%Y-%m-%d %H:%M:%S")} #{formatted_offset(false, 'UTC')}" # mimicking Ruby Time#to_s format end end alias_method :to_formatted_s, :to_s - # Replaces <tt>%Z</tt> and <tt>%z</tt> directives with +zone+ and - # +formatted_offset+, respectively, before passing to Time#strftime, so - # that zone information is correct + # Replaces <tt>%Z</tt> directive with +zone before passing to Time#strftime, + # so that zone information is correct. def strftime(format) - format = format.gsub('%Z', zone) - .gsub('%z', formatted_offset(false)) - .gsub('%:z', formatted_offset(true)) - .gsub('%::z', formatted_offset(true) + ":00") - time.strftime(format) + format = format.gsub(/((?:\A|[^%])(?:%%)*)%Z/, "\\1#{zone}") + getlocal(utc_offset).strftime(format) end # Use the time in UTC for comparisons. @@ -239,17 +246,32 @@ module ActiveSupport utc.future? end + # Returns +true+ if +other+ is equal to current object. def eql?(other) - utc.eql?(other) + other.eql?(utc) end def hash utc.hash end + # Adds an interval of time to the current object's time and returns that + # value as a new TimeWithZone object. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00 + # now + 1000 # => Sun, 02 Nov 2014 01:43:08 EDT -04:00 + # + # If we're adding a Duration of variable length (i.e., years, months, days), + # move forward from #time, otherwise move forward from #utc, for accuracy + # when moving across DST boundaries. + # + # For instance, a time + 24.hours will advance exactly 24 hours, while a + # time + 1.day will advance 23-25 hours, depending on the day. + # + # now + 24.hours # => Mon, 03 Nov 2014 00:26:28 EST -05:00 + # now + 1.day # => Mon, 03 Nov 2014 01:26:28 EST -05:00 def +(other) - # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time, - # otherwise move forward from #utc, for accuracy when moving across DST boundaries if duration_of_variable_length?(other) method_missing(:+, other) else @@ -257,10 +279,25 @@ module ActiveSupport result.in_time_zone(time_zone) end end + alias_method :since, :+ + # Returns a new TimeWithZone object that represents the difference between + # the current object's time and the +other+ time. + # + # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)' + # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EST -05:00 + # now - 1000 # => Sun, 02 Nov 2014 01:09:48 EST -05:00 + # + # If subtracting a Duration of variable length (i.e., years, months, days), + # move backward from #time, otherwise move backward from #utc, for accuracy + # when moving across DST boundaries. + # + # For instance, a time - 24.hours will go subtract exactly 24 hours, while a + # time - 1.day will subtract 23-25 hours, depending on the day. + # + # now - 24.hours # => Sat, 01 Nov 2014 02:26:28 EDT -04:00 + # now - 1.day # => Sat, 01 Nov 2014 01:26:28 EDT -04:00 def -(other) - # If we're subtracting a Duration of variable length (i.e., years, months, days), move backwards from #time, - # otherwise move backwards #utc, for accuracy when moving across DST boundaries if other.acts_like?(:time) to_time - other.to_time elsif duration_of_variable_length?(other) @@ -271,16 +308,6 @@ module ActiveSupport end end - def since(other) - # If we're adding a Duration of variable length (i.e., years, months, days), move forward from #time, - # otherwise move forward from #utc, for accuracy when moving across DST boundaries - if duration_of_variable_length?(other) - method_missing(:since, other) - else - utc.since(other).in_time_zone(time_zone) - end - end - def ago(other) since(-other) end @@ -303,28 +330,49 @@ module ActiveSupport EOV end + # Returns Array of parts of Time in sequence of + # [seconds, minutes, hours, day, month, year, weekday, yearday, dst?, zone]. + # + # now = Time.zone.now # => Tue, 18 Aug 2015 02:29:27 UTC +00:00 + # now.to_a # => [27, 29, 2, 18, 8, 2015, 2, 230, false, "UTC"] def to_a [time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone] end + # Returns the object's date and time as a floating point number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_f # => 1417709320.285418 def to_f utc.to_f end + # Returns the object's date and time as an integer number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_i # => 1417709320 def to_i utc.to_i end alias_method :tv_sec, :to_i + # Returns the object's date and time as a rational number of seconds + # since the Epoch (January 1, 1970 00:00 UTC). + # + # Time.zone.now.to_r # => (708854548642709/500000) def to_r utc.to_r end - # Return an instance of Time in the system timezone. + # Returns an instance of Time in the system timezone. def to_time utc.to_time end + # Returns an instance of DateTime with the timezone's UTC offset + # + # Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000 + # Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000 def to_datetime utc.to_datetime.new_offset(Rational(utc_offset, 86_400)) end diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index 55533a5d40..2699a064d7 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -111,9 +111,11 @@ module ActiveSupport "Jerusalem" => "Asia/Jerusalem", "Harare" => "Africa/Harare", "Pretoria" => "Africa/Johannesburg", + "Kaliningrad" => "Europe/Kaliningrad", "Moscow" => "Europe/Moscow", "St. Petersburg" => "Europe/Moscow", - "Volgograd" => "Europe/Moscow", + "Volgograd" => "Europe/Volgograd", + "Samara" => "Europe/Samara", "Kuwait" => "Asia/Kuwait", "Riyadh" => "Asia/Riyadh", "Nairobi" => "Africa/Nairobi", @@ -170,6 +172,7 @@ module ActiveSupport "Guam" => "Pacific/Guam", "Port Moresby" => "Pacific/Port_Moresby", "Magadan" => "Asia/Magadan", + "Srednekolymsk" => "Asia/Srednekolymsk", "Solomon Is." => "Pacific/Guadalcanal", "New Caledonia" => "Pacific/Noumea", "Fiji" => "Pacific/Fiji", @@ -202,7 +205,7 @@ module ActiveSupport end def find_tzinfo(name) - TZInfo::TimezoneProxy.new(MAPPING[name] || name) + TZInfo::Timezone.new(MAPPING[name] || name) end alias_method :create, :new @@ -221,13 +224,6 @@ module ActiveSupport @zones ||= zones_map.values.sort end - def zones_map - @zones_map ||= begin - MAPPING.each_key {|place| self[place]} # load all the zones - @lazy_zones_map - end - end - # Locate a specific time zone object. If the argument is a string, it # is interpreted to mean the name of the timezone to locate. If it is a # numeric value it is either the hour offset, or the second offset, of the @@ -237,7 +233,7 @@ module ActiveSupport case arg when String begin - @lazy_zones_map[arg] ||= create(arg).tap { |tz| tz.utc_offset } + @lazy_zones_map[arg] ||= create(arg) rescue TZInfo::InvalidTimezoneIdentifier nil end @@ -254,6 +250,14 @@ module ActiveSupport def us_zones @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ } end + + private + def zones_map + @zones_map ||= begin + MAPPING.each_key {|place| self[place]} # load all the zones + @lazy_zones_map + end + end end include Comparable @@ -344,24 +348,31 @@ module ActiveSupport # # Time.zone.parse('Mar 2000') # => Wed, 01 Mar 2000 00:00:00 HST -10:00 def parse(str, now=now()) - parts = Date._parse(str, false) - return if parts.empty? - - time = Time.new( - parts.fetch(:year, now.year), - parts.fetch(:mon, now.month), - parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day), - parts.fetch(:hour, 0), - parts.fetch(:min, 0), - parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), - parts.fetch(:offset, 0) - ) - - if parts[:offset] - TimeWithZone.new(time.utc, self) - else - TimeWithZone.new(nil, self, time) - end + parts_to_time(Date._parse(str, false), now) + end + + # Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone. + # + # Assumes that +str+ is a time in the time zone +self+, + # unless +format+ includes an explicit time zone. + # (This is the same behavior as +parse+.) + # In either case, the returned TimeWithZone has the timezone of +self+. + # + # Time.zone = 'Hawaii' # => "Hawaii" + # Time.zone.strptime('1999-12-31 14:00:00', '%Y-%m-%d %H:%M:%S') # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # + # If upper components are missing from the string, they are supplied from + # TimeZone#now: + # + # Time.zone.now # => Fri, 31 Dec 1999 14:00:00 HST -10:00 + # Time.zone.strptime('22:30:00', '%H:%M:%S') # => Fri, 31 Dec 1999 22:30:00 HST -10:00 + # + # However, if the date component is not provided, but any other upper + # components are supplied, then the day of the month defaults to 1: + # + # Time.zone.strptime('Mar 2000', '%b %Y') # => Wed, 01 Mar 2000 00:00:00 HST -10:00 + def strptime(str, format, now=now()) + parts_to_time(DateTime._strptime(str, format), now) end # Returns an ActiveSupport::TimeWithZone instance representing the current @@ -417,7 +428,36 @@ module ActiveSupport tzinfo.periods_for_local(time) end + def init_with(coder) #:nodoc: + initialize(coder['name']) + end + + def encode_with(coder) #:nodoc: + coder.tag ="!ruby/object:#{self.class}" + coder.map = { 'name' => tzinfo.name } + end + private + def parts_to_time(parts, now) + return if parts.empty? + + time = Time.new( + parts.fetch(:year, now.year), + parts.fetch(:mon, now.month), + parts.fetch(:mday, parts[:year] || parts[:mon] ? 1 : now.day), + parts.fetch(:hour, 0), + parts.fetch(:min, 0), + parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0), + parts.fetch(:offset, 0) + ) + + if parts[:offset] + TimeWithZone.new(time.utc, self) + else + TimeWithZone.new(nil, self, time) + end + end + def time_now Time.now end diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat Binary files differindex 394ee95f4b..760be4c07a 100644 --- a/activesupport/lib/active_support/values/unicode_tables.dat +++ b/activesupport/lib/active_support/values/unicode_tables.dat diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index 009ee4db90..df7b081993 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -78,6 +78,9 @@ module ActiveSupport ) end + attr_accessor :depth + self.depth = 100 + delegate :parse, :to => :backend def backend diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb index 27c64c4dca..94751bbc04 100644 --- a/activesupport/lib/active_support/xml_mini/jdom.rb +++ b/activesupport/lib/active_support/xml_mini/jdom.rb @@ -46,7 +46,7 @@ module ActiveSupport xml_string_reader = StringReader.new(data) xml_input_source = InputSource.new(xml_string_reader) doc = @dbf.new_document_builder.parse(xml_input_source) - merge_element!({CONTENT_KEY => ''}, doc.document_element) + merge_element!({CONTENT_KEY => ''}, doc.document_element, XmlMini.depth) end end @@ -58,9 +58,10 @@ module ActiveSupport # Hash to merge the converted element into. # element:: # XML element to merge into hash - def merge_element!(hash, element) + def merge_element!(hash, element, depth) + raise 'Document too deep!' if depth == 0 delete_empty(hash) - merge!(hash, element.tag_name, collapse(element)) + merge!(hash, element.tag_name, collapse(element, depth)) end def delete_empty(hash) @@ -71,14 +72,14 @@ module ActiveSupport # # element:: # The document element to be collapsed. - def collapse(element) + def collapse(element, depth) hash = get_attributes(element) child_nodes = element.child_nodes if child_nodes.length > 0 (0...child_nodes.length).each do |i| child = child_nodes.item(i) - merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE + merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE end merge_texts!(hash, element) unless empty_content?(element) hash @@ -141,7 +142,7 @@ module ActiveSupport (0...attributes.length).each do |i| attribute_hash[CONTENT_KEY] ||= '' attribute_hash[attributes.item(i).name] = attributes.item(i).value - end + end attribute_hash end diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb index 47a2824186..bb0ea9c582 100644 --- a/activesupport/lib/active_support/xml_mini/libxml.rb +++ b/activesupport/lib/active_support/xml_mini/libxml.rb @@ -75,5 +75,5 @@ module LibXML #:nodoc: end end -LibXML::XML::Document.send(:include, LibXML::Conversions::Document) -LibXML::XML::Node.send(:include, LibXML::Conversions::Node) +LibXML::XML::Document.include(LibXML::Conversions::Document) +LibXML::XML::Node.include(LibXML::Conversions::Node) diff --git a/activesupport/lib/active_support/xml_mini/nokogiri.rb b/activesupport/lib/active_support/xml_mini/nokogiri.rb index 7398d4fa82..619cc7522d 100644 --- a/activesupport/lib/active_support/xml_mini/nokogiri.rb +++ b/activesupport/lib/active_support/xml_mini/nokogiri.rb @@ -77,7 +77,7 @@ module ActiveSupport end end - Nokogiri::XML::Document.send(:include, Conversions::Document) - Nokogiri::XML::Node.send(:include, Conversions::Node) + Nokogiri::XML::Document.include(Conversions::Document) + Nokogiri::XML::Node.include(Conversions::Node) end end diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb index 5c7c78bf70..924ed72345 100644 --- a/activesupport/lib/active_support/xml_mini/rexml.rb +++ b/activesupport/lib/active_support/xml_mini/rexml.rb @@ -29,7 +29,7 @@ module ActiveSupport doc = REXML::Document.new(data) if doc.root - merge_element!({}, doc.root) + merge_element!({}, doc.root, XmlMini.depth) else raise REXML::ParseException, "The document #{doc.to_s.inspect} does not have a valid root" @@ -44,19 +44,20 @@ module ActiveSupport # Hash to merge the converted element into. # element:: # XML element to merge into hash - def merge_element!(hash, element) - merge!(hash, element.name, collapse(element)) + def merge_element!(hash, element, depth) + raise REXML::ParseException, "The document is too deep" if depth == 0 + merge!(hash, element.name, collapse(element, depth)) end # Actually converts an XML document element into a data structure. # # element:: # The document element to be collapsed. - def collapse(element) + def collapse(element, depth) hash = get_attributes(element) if element.has_elements? - element.each_element {|child| merge_element!(hash, child) } + element.each_element {|child| merge_element!(hash, child, depth - 1) } merge_texts!(hash, element) unless empty_content?(element) hash else diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb index f65ec962f9..c0e23e89f7 100644 --- a/activesupport/test/abstract_unit.rb +++ b/activesupport/test/abstract_unit.rb @@ -15,6 +15,7 @@ silence_warnings do end require 'active_support/testing/autorun' +require 'active_support/testing/method_call_assertions' ENV['NO_RELOAD'] = '1' require 'active_support' @@ -37,9 +38,6 @@ def jruby_skip(message = '') skip message if defined?(JRUBY_VERSION) end -require 'mocha/setup' # FIXME: stop using mocha - -# FIXME: we have tests that depend on run order, we should fix that and -# remove this method call. -require 'active_support/test_case' -ActiveSupport::TestCase.test_order = :sorted +class ActiveSupport::TestCase + include ActiveSupport::Testing::MethodCallAssertions +end diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb new file mode 100644 index 0000000000..263ab3802b --- /dev/null +++ b/activesupport/test/array_inquirer_test.rb @@ -0,0 +1,41 @@ +require 'abstract_unit' +require 'active_support/core_ext/array' + +class ArrayInquirerTest < ActiveSupport::TestCase + def setup + @array_inquirer = ActiveSupport::ArrayInquirer.new([:mobile, :tablet, 'api']) + end + + def test_individual + assert @array_inquirer.mobile? + assert @array_inquirer.tablet? + assert_not @array_inquirer.desktop? + end + + def test_any + assert @array_inquirer.any?(:mobile, :desktop) + assert @array_inquirer.any?(:watch, :tablet) + assert_not @array_inquirer.any?(:desktop, :watch) + end + + def test_any_string_symbol_mismatch + assert @array_inquirer.any?('mobile') + assert @array_inquirer.any?(:api) + end + + def test_any_with_block + assert @array_inquirer.any? { |v| v == :mobile } + assert_not @array_inquirer.any? { |v| v == :desktop } + end + + def test_respond_to + assert_respond_to @array_inquirer, :development? + end + + def test_inquiry + result = [:mobile, :tablet, 'api'].inquiry + + assert_instance_of ActiveSupport::ArrayInquirer, result + assert_equal @array_inquirer, result + end +end diff --git a/activesupport/test/autoloading_fixtures/a/c/e/f.rb b/activesupport/test/autoloading_fixtures/a/c/e/f.rb deleted file mode 100644 index 57dba5a307..0000000000 --- a/activesupport/test/autoloading_fixtures/a/c/e/f.rb +++ /dev/null @@ -1,2 +0,0 @@ -class A::C::E::F -end
\ No newline at end of file diff --git a/activesupport/test/autoloading_fixtures/a/c/em/f.rb b/activesupport/test/autoloading_fixtures/a/c/em/f.rb new file mode 100644 index 0000000000..8b28e19148 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/a/c/em/f.rb @@ -0,0 +1,2 @@ +class A::C::EM::F +end
\ No newline at end of file diff --git a/activesupport/test/autoloading_fixtures/d.rb b/activesupport/test/autoloading_fixtures/d.rb new file mode 100644 index 0000000000..45c794d4ca --- /dev/null +++ b/activesupport/test/autoloading_fixtures/d.rb @@ -0,0 +1,2 @@ +class D +end
\ No newline at end of file diff --git a/activesupport/test/autoloading_fixtures/e.rb b/activesupport/test/autoloading_fixtures/e.rb deleted file mode 100644 index 2f59e4fb75..0000000000 --- a/activesupport/test/autoloading_fixtures/e.rb +++ /dev/null @@ -1,2 +0,0 @@ -class E -end
\ No newline at end of file diff --git a/activesupport/test/autoloading_fixtures/em.rb b/activesupport/test/autoloading_fixtures/em.rb new file mode 100644 index 0000000000..16a1838667 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/em.rb @@ -0,0 +1,2 @@ +class EM +end
\ No newline at end of file diff --git a/activesupport/test/autoloading_fixtures/typo.rb b/activesupport/test/autoloading_fixtures/typo.rb new file mode 100644 index 0000000000..8e047f5fd4 --- /dev/null +++ b/activesupport/test/autoloading_fixtures/typo.rb @@ -0,0 +1,2 @@ +TypO = 1 + diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index 5945605f7b..74ceff44f9 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -133,37 +133,42 @@ class CacheStoreSettingTest < ActiveSupport::TestCase end def test_mem_cache_fragment_cache_store - Dalli::Client.expects(:new).with(%w[localhost], {}) - store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost" - assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + assert_called_with(Dalli::Client, :new, [%w[localhost], {}]) do + store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost" + assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + end end def test_mem_cache_fragment_cache_store_with_given_mem_cache mem_cache = Dalli::Client.new - Dalli::Client.expects(:new).never - store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache - assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + assert_not_called(Dalli::Client, :new) do + store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache + assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + end end def test_mem_cache_fragment_cache_store_with_not_dalli_client - Dalli::Client.expects(:new).never - memcache = Object.new - assert_raises(ArgumentError) do - ActiveSupport::Cache.lookup_store :mem_cache_store, memcache + assert_not_called(Dalli::Client, :new) do + memcache = Object.new + assert_raises(ArgumentError) do + ActiveSupport::Cache.lookup_store :mem_cache_store, memcache + end end end def test_mem_cache_fragment_cache_store_with_multiple_servers - Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], {}) - store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1' - assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do + store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1' + assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + end end def test_mem_cache_fragment_cache_store_with_options - Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], { :timeout => 10 }) - store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10 - assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) - assert_equal 'foo', store.options[:namespace] + assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { :timeout => 10 }]) do + store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10 + assert_kind_of(ActiveSupport::Cache::MemCacheStore, store) + assert_equal 'foo', store.options[:namespace] + end end def test_object_assigned_fragment_cache_store @@ -224,13 +229,15 @@ module CacheStoreBehavior def test_fetch_without_cache_miss @cache.write('foo', 'bar') - @cache.expects(:write).never - assert_equal 'bar', @cache.fetch('foo') { 'baz' } + assert_not_called(@cache, :write) do + assert_equal 'bar', @cache.fetch('foo') { 'baz' } + end end def test_fetch_with_cache_miss - @cache.expects(:write).with('foo', 'baz', @cache.options) - assert_equal 'baz', @cache.fetch('foo') { 'baz' } + assert_called_with(@cache, :write, ['foo', 'baz', @cache.options]) do + assert_equal 'baz', @cache.fetch('foo') { 'baz' } + end end def test_fetch_with_cache_miss_passes_key_to_block @@ -245,15 +252,18 @@ module CacheStoreBehavior def test_fetch_with_forced_cache_miss @cache.write('foo', 'bar') - @cache.expects(:read).never - @cache.expects(:write).with('foo', 'bar', @cache.options.merge(:force => true)) - @cache.fetch('foo', :force => true) { 'bar' } + assert_not_called(@cache, :read) do + assert_called_with(@cache, :write, ['foo', 'bar', @cache.options.merge(:force => true)]) do + @cache.fetch('foo', :force => true) { 'bar' } + end + end end def test_fetch_with_cached_nil @cache.write('foo', nil) - @cache.expects(:write).never - assert_nil @cache.fetch('foo') { 'baz' } + assert_not_called(@cache, :write) do + assert_nil @cache.fetch('foo') { 'baz' } + end end def test_should_read_and_write_hash @@ -288,8 +298,9 @@ module CacheStoreBehavior @cache.write('foo', 'bar', :expires_in => 10) @cache.write('fu', 'baz') @cache.write('fud', 'biz') - Time.stubs(:now).returns(time + 11) - assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu')) + Time.stub(:now, time + 11) do + assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu')) + end end def test_fetch_multi @@ -303,8 +314,9 @@ module CacheStoreBehavior end def test_multi_with_objects - foo = stub(:title => 'FOO!', :cache_key => 'foo') - bar = stub(:cache_key => 'bar') + cache_struct = Struct.new(:cache_key, :title) + foo = cache_struct.new('foo', 'FOO!') + bar = cache_struct.new('bar') @cache.write('bar', 'BAM!') @@ -387,54 +399,74 @@ module CacheStoreBehavior def test_expires_in time = Time.local(2008, 4, 24) - Time.stubs(:now).returns(time) - @cache.write('foo', 'bar') - assert_equal 'bar', @cache.read('foo') + Time.stub(:now, time) do + @cache.write('foo', 'bar') + assert_equal 'bar', @cache.read('foo') + end - Time.stubs(:now).returns(time + 30) - assert_equal 'bar', @cache.read('foo') + Time.stub(:now, time + 30) do + assert_equal 'bar', @cache.read('foo') + end - Time.stubs(:now).returns(time + 61) - assert_nil @cache.read('foo') + Time.stub(:now, time + 61) do + assert_nil @cache.read('foo') + end end - def test_race_condition_protection - time = Time.now - @cache.write('foo', 'bar', :expires_in => 60) - Time.stubs(:now).returns(time + 61) - result = @cache.fetch('foo', :race_condition_ttl => 10) do - assert_equal 'bar', @cache.read('foo') - "baz" + def test_race_condition_protection_skipped_if_not_defined + @cache.write('foo', 'bar') + time = @cache.send(:read_entry, 'foo', {}).expires_at + + Time.stub(:now, Time.at(time)) do + result = @cache.fetch('foo') do + assert_equal nil, @cache.read('foo') + 'baz' + end + assert_equal 'baz', result end - assert_equal "baz", result end def test_race_condition_protection_is_limited time = Time.now @cache.write('foo', 'bar', :expires_in => 60) - Time.stubs(:now).returns(time + 71) - result = @cache.fetch('foo', :race_condition_ttl => 10) do - assert_equal nil, @cache.read('foo') - "baz" + Time.stub(:now, time + 71) do + result = @cache.fetch('foo', :race_condition_ttl => 10) do + assert_equal nil, @cache.read('foo') + "baz" + end + assert_equal "baz", result end - assert_equal "baz", result end def test_race_condition_protection_is_safe time = Time.now @cache.write('foo', 'bar', :expires_in => 60) - Time.stubs(:now).returns(time + 61) - begin - @cache.fetch('foo', :race_condition_ttl => 10) do + Time.stub(:now, time + 61) do + begin + @cache.fetch('foo', :race_condition_ttl => 10) do + assert_equal 'bar', @cache.read('foo') + raise ArgumentError.new + end + rescue ArgumentError + end + assert_equal "bar", @cache.read('foo') + end + Time.stub(:now, time + 91) do + assert_nil @cache.read('foo') + end + end + + def test_race_condition_protection + time = Time.now + @cache.write('foo', 'bar', :expires_in => 60) + Time.stub(:now, time + 61) do + result = @cache.fetch('foo', :race_condition_ttl => 10) do assert_equal 'bar', @cache.read('foo') - raise ArgumentError.new + "baz" end - rescue ArgumentError + assert_equal "baz", result end - assert_equal "bar", @cache.read('foo') - Time.stubs(:now).returns(time + 91) - assert_nil @cache.read('foo') end def test_crazy_key_characters @@ -624,37 +656,37 @@ module AutoloadingCacheBehavior include DependenciesTestHelpers def test_simple_autoloading with_autoloading_fixtures do - @cache.write('foo', E.new) + @cache.write('foo', EM.new) end - remove_constants(:E) + remove_constants(:EM) ActiveSupport::Dependencies.clear with_autoloading_fixtures do - assert_kind_of E, @cache.read('foo') + assert_kind_of EM, @cache.read('foo') end - remove_constants(:E) + remove_constants(:EM) ActiveSupport::Dependencies.clear end def test_two_classes_autoloading with_autoloading_fixtures do - @cache.write('foo', [E.new, ClassFolder.new]) + @cache.write('foo', [EM.new, ClassFolder.new]) end - remove_constants(:E, :ClassFolder) + remove_constants(:EM, :ClassFolder) ActiveSupport::Dependencies.clear with_autoloading_fixtures do loaded = @cache.read('foo') assert_kind_of Array, loaded assert_equal 2, loaded.size - assert_kind_of E, loaded[0] + assert_kind_of EM, loaded[0] assert_kind_of ClassFolder, loaded[1] end - remove_constants(:E, :ClassFolder) + remove_constants(:EM, :ClassFolder) ActiveSupport::Dependencies.clear end end @@ -672,6 +704,7 @@ class FileStoreTest < ActiveSupport::TestCase def teardown FileUtils.rm_r(cache_dir) + rescue Errno::ENOENT end def cache_dir @@ -691,6 +724,11 @@ class FileStoreTest < ActiveSupport::TestCase assert File.exist?(filepath) end + def test_clear_without_cache_dir + FileUtils.rm_r(cache_dir) + @cache.clear + end + def test_long_keys @cache.write("a"*10000, 1) assert_equal 1, @cache.read("a"*10000) @@ -747,9 +785,10 @@ class FileStoreTest < ActiveSupport::TestCase end def test_log_exception_when_cache_read_fails - File.expects(:exist?).raises(StandardError, "failed") - @cache.send(:read_entry, "winston", {}) - assert @buffer.string.present? + File.stub(:exist?, -> { raise StandardError.new("failed") }) do + @cache.send(:read_entry, "winston", {}) + assert @buffer.string.present? + end end def test_cleanup_removes_all_expired_entries @@ -757,11 +796,12 @@ class FileStoreTest < ActiveSupport::TestCase @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') + Time.stub(:now, time + 15) do + @cache.cleanup + assert_not @cache.exist?('foo') + assert @cache.exist?('baz') + assert @cache.exist?('quux') + end end def test_write_with_unless_exist @@ -998,10 +1038,6 @@ class NullStoreTest < ActiveSupport::TestCase end assert_nil @cache.read("name") end - - def test_setting_nil_cache_store - assert ActiveSupport::Cache.lookup_store.class.name, ActiveSupport::Cache::NullStore.name - end end class CacheStoreLoggerTest < ActiveSupport::TestCase @@ -1021,6 +1057,15 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase @cache.mute { @cache.fetch('foo') { 'bar' } } assert @buffer.string.blank? end + + def test_multi_read_loggin + @cache.write 'hello', 'goodbye' + @cache.write 'world', 'earth' + + @cache.read_multi('hello', 'world') + + assert_match "Caches multi read:\n- hello\n- world", @buffer.string + end end class CacheEntryTest < ActiveSupport::TestCase @@ -1029,9 +1074,9 @@ class CacheEntryTest < ActiveSupport::TestCase assert !entry.expired?, 'entry not expired' entry = ActiveSupport::Cache::Entry.new("value", :expires_in => 60) assert !entry.expired?, 'entry not expired' - time = Time.now + 61 - Time.stubs(:now).returns(time) - assert entry.expired?, 'entry is expired' + Time.stub(:now, Time.now + 61) do + assert entry.expired?, 'entry is expired' + end end def test_compress_values @@ -1047,30 +1092,4 @@ class CacheEntryTest < ActiveSupport::TestCase assert_equal value, entry.value assert_equal value.bytesize, entry.size end - - def test_restoring_version_4beta1_entries - version_4beta1_entry = ActiveSupport::Cache::Entry.allocate - version_4beta1_entry.instance_variable_set(:@v, "hello") - version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i + 60) - entry = Marshal.load(Marshal.dump(version_4beta1_entry)) - assert_equal "hello", entry.value - assert_equal false, entry.expired? - end - - def test_restoring_compressed_version_4beta1_entries - version_4beta1_entry = ActiveSupport::Cache::Entry.allocate - version_4beta1_entry.instance_variable_set(:@v, Zlib::Deflate.deflate(Marshal.dump("hello"))) - version_4beta1_entry.instance_variable_set(:@c, true) - entry = Marshal.load(Marshal.dump(version_4beta1_entry)) - assert_equal "hello", entry.value - end - - def test_restoring_expired_version_4beta1_entries - version_4beta1_entry = ActiveSupport::Cache::Entry.allocate - version_4beta1_entry.instance_variable_set(:@v, "hello") - version_4beta1_entry.instance_variable_set(:@x, Time.now.to_i - 1) - entry = Marshal.load(Marshal.dump(version_4beta1_entry)) - assert_equal "hello", entry.value - assert_equal true, entry.expired? - end end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 32c2dfdfc0..cda9732cae 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -49,7 +49,7 @@ module CallbacksTest def self.before(model) model.history << [:before_save, :class] end - + def self.after(model) model.history << [:after_save, :class] end @@ -73,8 +73,8 @@ module CallbacksTest class PersonSkipper < Person skip_callback :save, :before, :before_save_method, :if => :yes - skip_callback :save, :after, :before_save_method, :unless => :yes - skip_callback :save, :after, :before_save_method, :if => :no + skip_callback :save, :after, :after_save_method, :unless => :yes + skip_callback :save, :after, :after_save_method, :if => :no skip_callback :save, :before, :before_save_method, :unless => :no skip_callback :save, :before, CallbackClass , :if => :yes def yes; true; end @@ -501,21 +501,18 @@ module CallbacksTest end end - class CallbackTerminator + class AbstractCallbackTerminator include ActiveSupport::Callbacks - define_callbacks :save, :terminator => ->(_,result) { result == :halt } - - set_callback :save, :before, :first - set_callback :save, :before, :second - set_callback :save, :around, :around_it - set_callback :save, :before, :third - set_callback :save, :after, :first - set_callback :save, :around, :around_it - set_callback :save, :after, :second - set_callback :save, :around, :around_it - set_callback :save, :after, :third - + def self.set_save_callbacks + set_callback :save, :before, :first + set_callback :save, :before, :second + set_callback :save, :around, :around_it + set_callback :save, :before, :third + set_callback :save, :after, :first + set_callback :save, :around, :around_it + set_callback :save, :after, :third + end attr_reader :history, :saved, :halted def initialize @@ -552,6 +549,39 @@ module CallbacksTest end end + class CallbackTerminator < AbstractCallbackTerminator + define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt } + set_save_callbacks + end + + class CallbackTerminatorSkippingAfterCallbacks < AbstractCallbackTerminator + define_callbacks :save, terminator: ->(_, result_lambda) { result_lambda.call == :halt }, + skip_after_callbacks_if_terminated: true + set_save_callbacks + end + + class CallbackDefaultTerminator < AbstractCallbackTerminator + define_callbacks :save + + def second + @history << "second" + throw(:abort) + end + + set_save_callbacks + end + + class CallbackFalseTerminator < AbstractCallbackTerminator + define_callbacks :save + + def second + @history << "second" + false + end + + set_save_callbacks + end + class CallbackObject def before(caller) caller.record << "before" @@ -688,10 +718,10 @@ module CallbacksTest end class CallbackTerminatorTest < ActiveSupport::TestCase - def test_termination + def test_termination_skips_following_before_and_around_callbacks terminator = CallbackTerminator.new terminator.save - assert_equal ["first", "second", "third", "second", "first"], terminator.history + assert_equal ["first", "second", "third", "first"], terminator.history end def test_termination_invokes_hook @@ -707,6 +737,73 @@ module CallbacksTest end end + class CallbackTerminatorSkippingAfterCallbacksTest < ActiveSupport::TestCase + def test_termination_skips_after_callbacks + terminator = CallbackTerminatorSkippingAfterCallbacks.new + terminator.save + assert_equal ["first", "second"], terminator.history + end + end + + class CallbackDefaultTerminatorTest < ActiveSupport::TestCase + def test_default_termination + terminator = CallbackDefaultTerminator.new + terminator.save + assert_equal ["first", "second", "third", "first"], terminator.history + end + + def test_default_termination_invokes_hook + terminator = CallbackDefaultTerminator.new + terminator.save + assert_equal :second, terminator.halted + end + + def test_block_never_called_if_abort_is_thrown + obj = CallbackDefaultTerminator.new + obj.save + assert !obj.saved + end + end + + class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase + def test_returning_false_halts_callback_if_config_variable_is_not_set + obj = CallbackFalseTerminator.new + assert_deprecated do + obj.save + assert_equal :second, obj.halted + assert !obj.saved + end + end + end + + class CallbackFalseTerminatorWithConfigTrueTest < ActiveSupport::TestCase + def setup + ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = true + end + + def test_returning_false_halts_callback_if_config_variable_is_true + obj = CallbackFalseTerminator.new + assert_deprecated do + obj.save + assert_equal :second, obj.halted + assert !obj.saved + end + end + end + + class CallbackFalseTerminatorWithConfigFalseTest < ActiveSupport::TestCase + def setup + ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = false + end + + def test_returning_false_does_not_halt_callback_if_config_variable_is_false + obj = CallbackFalseTerminator.new + obj.save + assert_equal nil, obj.halted + assert obj.saved + end + end + class HyphenatedKeyTest < ActiveSupport::TestCase def test_save obj = HyphenatedCallbacks.new @@ -924,7 +1021,7 @@ module CallbacksTest define_callbacks :foo n.times { set_callback :foo, :before, callback } def run; run_callbacks :foo; end - def self.skip(thing); skip_callback :foo, :before, thing; end + def self.skip(*things); skip_callback :foo, :before, *things; end } end @@ -973,11 +1070,11 @@ module CallbacksTest } end - def test_skip_lambda # removes nothing + def test_skip_lambda # raises error calls = [] callback = ->(o) { calls << o } klass = build_class(callback) - 10.times { klass.skip callback } + assert_raises(ArgumentError) { klass.skip callback } klass.new.run assert_equal 10, calls.length end @@ -991,11 +1088,29 @@ module CallbacksTest assert_equal 0, calls.length end - def test_skip_eval # removes nothing + def test_skip_string # raises error calls = [] klass = build_class("bar") klass.class_eval { define_method(:bar) { calls << klass } } - klass.skip "bar" + assert_raises(ArgumentError) { klass.skip "bar" } + klass.new.run + assert_equal 1, calls.length + end + + def test_skip_undefined_callback # raises error + calls = [] + klass = build_class(:bar) + klass.class_eval { define_method(:bar) { calls << klass } } + assert_raises(ArgumentError) { klass.skip :qux } + klass.new.run + assert_equal 1, calls.length + end + + def test_skip_without_raise # removes nothing + calls = [] + klass = build_class(:bar) + klass.class_eval { define_method(:bar) { calls << klass } } + klass.skip :qux, raise: false klass.new.run assert_equal 1, calls.length end diff --git a/activesupport/test/concern_test.rb b/activesupport/test/concern_test.rb index 60bd8a06aa..8ea701cfb7 100644 --- a/activesupport/test/concern_test.rb +++ b/activesupport/test/concern_test.rb @@ -54,29 +54,54 @@ class ConcernTest < ActiveSupport::TestCase include Bar, Baz end + module Qux + module ClassMethods + end + end + def setup @klass = Class.new end def test_module_is_included_normally - @klass.send(:include, Baz) + @klass.include(Baz) assert_equal "baz", @klass.new.baz assert @klass.included_modules.include?(ConcernTest::Baz) end def test_class_methods_are_extended - @klass.send(:include, Baz) + @klass.include(Baz) assert_equal "baz", @klass.baz assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0] end + def test_class_methods_are_extended_only_on_expected_objects + ::Object.__send__(:include, Qux) + Object.extend(Qux::ClassMethods) + # module needs to be created after Qux is included in Object or bug won't + # be triggered + test_module = Module.new do + extend ActiveSupport::Concern + + class_methods do + def test + end + end + end + @klass.include test_module + assert_equal false, Object.respond_to?(:test) + Qux.class_eval do + remove_const :ClassMethods + end + end + def test_included_block_is_ran - @klass.send(:include, Baz) + @klass.include(Baz) assert_equal true, @klass.included_ran end def test_modules_dependencies_are_met - @klass.send(:include, Bar) + @klass.include(Bar) assert_equal "bar", @klass.new.bar assert_equal "bar+baz", @klass.new.baz assert_equal "bar's baz + baz", @klass.baz @@ -84,7 +109,7 @@ class ConcernTest < ActiveSupport::TestCase end def test_dependencies_with_multiple_modules - @klass.send(:include, Foo) + @klass.include(Foo) assert_equal [ConcernTest::Foo, ConcernTest::Bar, ConcernTest::Baz], @klass.included_modules[0..2] end diff --git a/activesupport/test/configurable_test.rb b/activesupport/test/configurable_test.rb index ef847fc557..5d22ded2de 100644 --- a/activesupport/test/configurable_test.rb +++ b/activesupport/test/configurable_test.rb @@ -111,6 +111,14 @@ class ConfigurableActiveSupport < ActiveSupport::TestCase end end + test 'the config_accessor method should not be publicly callable' do + assert_raises NoMethodError do + Class.new { + include ActiveSupport::Configurable + }.config_accessor :foo + end + end + def assert_method_defined(object, method) methods = object.public_methods.map(&:to_s) assert methods.include?(method.to_s), "Expected #{methods.inspect} to include #{method.to_s.inspect}" diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb index f14f64421d..3f1e0c4cb4 100644 --- a/activesupport/test/core_ext/array/access_test.rb +++ b/activesupport/test/core_ext/array/access_test.rb @@ -27,4 +27,8 @@ class AccessTest < ActiveSupport::TestCase assert_equal array[4], array.fifth assert_equal array[41], array.forty_two end + + def test_without + assert_equal [1, 2, 4], [1, 2, 3, 4, 5].without(3, 5) + end end diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb index 577b889410..507e13f968 100644 --- a/activesupport/test/core_ext/array/conversions_test.rb +++ b/activesupport/test/core_ext/array/conversions_test.rb @@ -60,6 +60,12 @@ class ToSentenceTest < ActiveSupport::TestCase assert_equal exception.message, "Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale" end + + def test_always_returns_string + assert_instance_of String, [ActiveSupport::SafeBuffer.new('one')].to_sentence + assert_instance_of String, [ActiveSupport::SafeBuffer.new('one'), 'two'].to_sentence + assert_instance_of String, [ActiveSupport::SafeBuffer.new('one'), 'two', 'three'].to_sentence + end end class ToSTest < ActiveSupport::TestCase diff --git a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb b/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb deleted file mode 100644 index e634679d20..0000000000 --- a/activesupport/test/core_ext/big_decimal/yaml_conversions_test.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'abstract_unit' - -class BigDecimalYamlConversionsTest < ActiveSupport::TestCase - def test_to_yaml - assert_deprecated { require 'active_support/core_ext/big_decimal/yaml_conversions' } - assert_match("--- 100000.30020320320000000000000000000000000000001\n", BigDecimal.new('100000.30020320320000000000000000000000000000001').to_yaml) - assert_match("--- .Inf\n", BigDecimal.new('Infinity').to_yaml) - assert_match("--- .NaN\n", BigDecimal.new('NaN').to_yaml) - assert_match("--- -.Inf\n", BigDecimal.new('-Infinity').to_yaml) - end -end diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb deleted file mode 100644 index 447b1d10ad..0000000000 --- a/activesupport/test/core_ext/class/delegating_attributes_test.rb +++ /dev/null @@ -1,122 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/class/delegating_attributes' - -module DelegatingFixtures - class Parent - end - - class Child < Parent - ActiveSupport::Deprecation.silence do - superclass_delegating_accessor :some_attribute - end - end - - class Mokopuna < Child - end - - class PercysMom - ActiveSupport::Deprecation.silence do - superclass_delegating_accessor :superpower - end - end - - class Percy < PercysMom - end -end - -class DelegatingAttributesTest < ActiveSupport::TestCase - include DelegatingFixtures - attr_reader :single_class - - def setup - @single_class = Class.new(Object) - end - - def test_simple_accessor_declaration - assert_deprecated do - single_class.superclass_delegating_accessor :both - end - - # Class should have accessor and mutator - # the instance should have an accessor only - assert_respond_to single_class, :both - assert_respond_to single_class, :both= - assert single_class.public_instance_methods.map(&:to_s).include?("both") - assert !single_class.public_instance_methods.map(&:to_s).include?("both=") - end - - def test_simple_accessor_declaration_with_instance_reader_false - _instance_methods = single_class.public_instance_methods - - assert_deprecated do - single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false - end - - assert_respond_to single_class, :no_instance_reader - assert_respond_to single_class, :no_instance_reader= - assert !_instance_methods.include?(:no_instance_reader) - assert !_instance_methods.include?(:no_instance_reader?) - assert !_instance_methods.include?(:_no_instance_reader) - end - - def test_working_with_simple_attributes - assert_deprecated do - single_class.superclass_delegating_accessor :both - end - - single_class.both = "HMMM" - - assert_equal "HMMM", single_class.both - assert_equal true, single_class.both? - - assert_equal "HMMM", single_class.new.both - assert_equal true, single_class.new.both? - - single_class.both = false - assert_equal false, single_class.both? - end - - def test_child_class_delegates_to_parent_but_can_be_overridden - parent = Class.new - - assert_deprecated do - parent.superclass_delegating_accessor :both - end - - child = Class.new(parent) - parent.both = "1" - assert_equal "1", child.both - - child.both = "2" - assert_equal "1", parent.both - assert_equal "2", child.both - - parent.both = "3" - assert_equal "3", parent.both - assert_equal "2", child.both - end - - def test_delegation_stops_at_the_right_level - assert_nil Percy.superpower - assert_nil PercysMom.superpower - - PercysMom.superpower = :heatvision - assert_equal :heatvision, Percy.superpower - end - - def test_delegation_stops_for_nil - Mokopuna.some_attribute = nil - Child.some_attribute="1" - - assert_equal "1", Child.some_attribute - assert_nil Mokopuna.some_attribute - ensure - Child.some_attribute=nil - end - - def test_deprecation_warning - assert_deprecated(/superclass_delegating_accessor is deprecated/) do - single_class.superclass_delegating_accessor :test_attribute - end - end -end diff --git a/activesupport/test/core_ext/date_and_time_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb index b4ef5a0597..784547bdf8 100644 --- a/activesupport/test/core_ext/date_and_time_behavior.rb +++ b/activesupport/test/core_ext/date_and_time_behavior.rb @@ -6,11 +6,21 @@ module DateAndTimeBehavior assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).yesterday.yesterday end + def test_prev_day + assert_equal date_time_init(2005,2,21,10,10,10), date_time_init(2005,2,22,10,10,10).prev_day + assert_equal date_time_init(2005,2,28,10,10,10), date_time_init(2005,3,2,10,10,10).prev_day.prev_day + end + def test_tomorrow assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).tomorrow assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).tomorrow.tomorrow end + def test_next_day + assert_equal date_time_init(2005,2,23,10,10,10), date_time_init(2005,2,22,10,10,10).next_day + assert_equal date_time_init(2005,3,2,10,10,10), date_time_init(2005,2,28,10,10,10).next_day.next_day + end + def test_days_ago assert_equal date_time_init(2005,6,4,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(1) assert_equal date_time_init(2005,5,31,10,10,10), date_time_init(2005,6,5,10,10,10).days_ago(5) @@ -115,6 +125,28 @@ module DateAndTimeBehavior end end + def test_next_week_at_same_time + assert_equal date_time_init(2005,2,28,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:monday, same_time: true) + assert_equal date_time_init(2005,3,4,15,15,10), date_time_init(2005,2,22,15,15,10).next_week(:friday, same_time: true) + assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:monday, same_time: true) + assert_equal date_time_init(2006,11,1,0,0,0), date_time_init(2006,10,23,0,0,0).next_week(:wednesday, same_time: true) + end + + def test_next_weekday_on_wednesday + assert_equal date_time_init(2015,1,8,0,0,0), date_time_init(2015,1,7,0,0,0).next_weekday + assert_equal date_time_init(2015,1,8,15,15,10), date_time_init(2015,1,7,15,15,10).next_weekday + end + + def test_next_weekday_on_friday + assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,2,0,0,0).next_weekday + assert_equal date_time_init(2015,1,5,15,15,10), date_time_init(2015,1,2,15,15,10).next_weekday + end + + def test_next_weekday_on_saturday + assert_equal date_time_init(2015,1,5,0,0,0), date_time_init(2015,1,3,0,0,0).next_weekday + assert_equal date_time_init(2015,1,5,15,15,10), date_time_init(2015,1,3,15,15,10).next_weekday + end + def test_next_month_on_31st assert_equal date_time_init(2005,9,30,15,15,10), date_time_init(2005,8,31,15,15,10).next_month end @@ -144,6 +176,29 @@ module DateAndTimeBehavior end end + def test_prev_week_at_same_time + assert_equal date_time_init(2005,2,21,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:monday, same_time: true) + assert_equal date_time_init(2005,2,22,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:tuesday, same_time: true) + assert_equal date_time_init(2005,2,25,15,15,10), date_time_init(2005,3,1,15,15,10).prev_week(:friday, same_time: true) + assert_equal date_time_init(2006,10,30,0,0,0), date_time_init(2006,11,6,0,0,0).prev_week(:monday, same_time: true) + assert_equal date_time_init(2006,11,15,0,0,0), date_time_init(2006,11,23,0,0,0).prev_week(:wednesday, same_time: true) + end + + def test_prev_weekday_on_wednesday + assert_equal date_time_init(2015,1,6,0,0,0), date_time_init(2015,1,7,0,0,0).prev_weekday + assert_equal date_time_init(2015,1,6,15,15,10), date_time_init(2015,1,7,15,15,10).prev_weekday + end + + def test_prev_weekday_on_monday + assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,5,0,0,0).prev_weekday + assert_equal date_time_init(2015,1,2,15,15,10), date_time_init(2015,1,5,15,15,10).prev_weekday + end + + def test_prev_weekday_on_sunday + assert_equal date_time_init(2015,1,2,0,0,0), date_time_init(2015,1,4,0,0,0).prev_weekday + assert_equal date_time_init(2015,1,2,15,15,10), date_time_init(2015,1,4,15,15,10).prev_weekday + end + def test_prev_month_on_31st assert_equal date_time_init(2004,2,29,10,10,10), date_time_init(2004,3,31,10,10,10).prev_month end @@ -231,6 +286,21 @@ module DateAndTimeBehavior end end + def test_on_weekend_on_saturday + assert date_time_init(2015,1,3,0,0,0).on_weekend? + assert date_time_init(2015,1,3,15,15,10).on_weekend? + end + + def test_on_weekend_on_sunday + assert date_time_init(2015,1,4,0,0,0).on_weekend? + assert date_time_init(2015,1,4,15,15,10).on_weekend? + end + + def test_on_weekend_on_monday + assert_not date_time_init(2015,1,5,0,0,0).on_weekend? + assert_not date_time_init(2015,1,5,15,15,10).on_weekend? + end + def with_bw_default(bw = :monday) old_bw = Date.beginning_of_week Date.beginning_of_week = bw diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb index e89be25b53..0fc3f765f5 100644 --- a/activesupport/test/core_ext/date_ext_test.rb +++ b/activesupport/test/core_ext/date_ext_test.rb @@ -191,8 +191,9 @@ class DateExtCalculationsTest < ActiveSupport::TestCase def test_yesterday_constructor_when_zone_is_set with_env_tz 'UTC' do with_tz_default ActiveSupport::TimeZone['Eastern Time (US & Canada)'] do # UTC -5 - Time.stubs(:now).returns Time.local(2000, 1, 1) - assert_equal Date.new(1999, 12, 30), Date.yesterday + Time.stub(:now, Time.local(2000, 1, 1)) do + assert_equal Date.new(1999, 12, 30), Date.yesterday + end end end end @@ -212,8 +213,9 @@ class DateExtCalculationsTest < ActiveSupport::TestCase def test_tomorrow_constructor_when_zone_is_set with_env_tz 'UTC' do with_tz_default ActiveSupport::TimeZone['Europe/Paris'] do # UTC +1 - Time.stubs(:now).returns Time.local(1999, 12, 31, 23) - assert_equal Date.new(2000, 1, 2), Date.tomorrow + Time.stub(:now, Time.local(1999, 12, 31, 23)) do + assert_equal Date.new(2000, 1, 2), Date.tomorrow + end end end end @@ -317,23 +319,26 @@ class DateExtCalculationsTest < ActiveSupport::TestCase end def test_past - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal true, Date.new(1999, 12, 31).past? - assert_equal false, Date.new(2000,1,1).past? - assert_equal false, Date.new(2000,1,2).past? + Date.stub(:current, Date.new(2000, 1, 1)) do + assert_equal true, Date.new(1999, 12, 31).past? + assert_equal false, Date.new(2000,1,1).past? + assert_equal false, Date.new(2000,1,2).past? + end end def test_future - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, Date.new(1999, 12, 31).future? - assert_equal false, Date.new(2000,1,1).future? - assert_equal true, Date.new(2000,1,2).future? + Date.stub(:current, Date.new(2000, 1, 1)) do + assert_equal false, Date.new(1999, 12, 31).future? + assert_equal false, Date.new(2000,1,1).future? + assert_equal true, Date.new(2000,1,2).future? + end end def test_current_returns_date_today_when_zone_not_set with_env_tz 'US/Central' do - Time.stubs(:now).returns Time.local(1999, 12, 31, 23) - assert_equal Date.today, Date.current + Time.stub(:now, Time.local(1999, 12, 31, 23)) do + assert_equal Date.today, Date.current + end end end diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb index 74319ecd09..6fe38c45ec 100644 --- a/activesupport/test/core_ext/date_time_ext_test.rb +++ b/activesupport/test/core_ext/date_time_ext_test.rb @@ -204,61 +204,69 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase end def test_today_with_offset - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, DateTime.civil(1999,12,31,23,59,59, Rational(-18000, 86400)).today? - assert_equal true, DateTime.civil(2000,1,1,0,0,0, Rational(-18000, 86400)).today? - assert_equal true, DateTime.civil(2000,1,1,23,59,59, Rational(-18000, 86400)).today? - assert_equal false, DateTime.civil(2000,1,2,0,0,0, Rational(-18000, 86400)).today? + Date.stub(:current, Date.new(2000, 1, 1)) do + assert_equal false, DateTime.civil(1999,12,31,23,59,59, Rational(-18000, 86400)).today? + assert_equal true, DateTime.civil(2000,1,1,0,0,0, Rational(-18000, 86400)).today? + assert_equal true, DateTime.civil(2000,1,1,23,59,59, Rational(-18000, 86400)).today? + assert_equal false, DateTime.civil(2000,1,2,0,0,0, Rational(-18000, 86400)).today? + end end def test_today_without_offset - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, DateTime.civil(1999,12,31,23,59,59).today? - assert_equal true, DateTime.civil(2000,1,1,0).today? - assert_equal true, DateTime.civil(2000,1,1,23,59,59).today? - assert_equal false, DateTime.civil(2000,1,2,0).today? + Date.stub(:current, Date.new(2000, 1, 1)) do + assert_equal false, DateTime.civil(1999,12,31,23,59,59).today? + assert_equal true, DateTime.civil(2000,1,1,0).today? + assert_equal true, DateTime.civil(2000,1,1,23,59,59).today? + assert_equal false, DateTime.civil(2000,1,2,0).today? + end end def test_past_with_offset - DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) - assert_equal true, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).past? - assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).past? - assert_equal false, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).past? + DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do + assert_equal true, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).past? + assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).past? + assert_equal false, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).past? + end end def test_past_without_offset - DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) - assert_equal true, DateTime.civil(2005,2,10,20,30,44).past? - assert_equal false, DateTime.civil(2005,2,10,20,30,45).past? - assert_equal false, DateTime.civil(2005,2,10,20,30,46).past? + DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do + assert_equal true, DateTime.civil(2005,2,10,20,30,44).past? + assert_equal false, DateTime.civil(2005,2,10,20,30,45).past? + assert_equal false, DateTime.civil(2005,2,10,20,30,46).past? + end end def test_future_with_offset - DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) - assert_equal false, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).future? - assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).future? - assert_equal true, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).future? + DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do + assert_equal false, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).future? + assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).future? + assert_equal true, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).future? + end end def test_future_without_offset - DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) - assert_equal false, DateTime.civil(2005,2,10,20,30,44).future? - assert_equal false, DateTime.civil(2005,2,10,20,30,45).future? - assert_equal true, DateTime.civil(2005,2,10,20,30,46).future? + DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do + assert_equal false, DateTime.civil(2005,2,10,20,30,44).future? + assert_equal false, DateTime.civil(2005,2,10,20,30,45).future? + assert_equal true, DateTime.civil(2005,2,10,20,30,46).future? + end end def test_current_returns_date_today_when_zone_is_not_set with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59) - assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current + Time.stub(:now, Time.local(1999, 12, 31, 23, 59, 59)) do + assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current + end end end def test_current_returns_time_zone_today_when_zone_is_set Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59) - assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current + Time.stub(:now, Time.local(1999, 12, 31, 23, 59, 59)) do + assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current + end end ensure Time.zone = nil @@ -335,6 +343,13 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) end + def test_compare_with_string + assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59).to_s + assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s + assert_equal( -1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1).to_s) + assert_equal nil, DateTime.civil(2000) <=> "Invalid as Time" + end + def test_to_f assert_equal 946684800.0, DateTime.civil(2000).to_f assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index 5a2af7f943..9e97acaffb 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -30,7 +30,6 @@ class DurationTest < ActiveSupport::TestCase assert ActiveSupport::Duration === 1.day assert !(ActiveSupport::Duration === 1.day.to_i) assert !(ActiveSupport::Duration === 'foo') - assert !(ActiveSupport::Duration === ActiveSupport::ProxyObject.new) end def test_equals @@ -71,6 +70,15 @@ class DurationTest < ActiveSupport::TestCase assert_equal '14 days', 1.fortnight.inspect end + def test_inspect_locale + current_locale = I18n.default_locale + I18n.default_locale = :de + I18n.backend.store_translations(:de, { support: { array: { last_word_connector: ' und ' } } }) + assert_equal '10 years, 1 month und 1 day', (10.years + 1.month + 1.day).inspect + ensure + I18n.default_locale = current_locale + end + def test_minus_with_duration_does_not_break_subtraction_of_date_from_date assert_nothing_raised { Date.today - Date.today } end @@ -132,28 +140,30 @@ class DurationTest < ActiveSupport::TestCase def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set Time.zone = nil with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - # since - assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.since - assert_equal Time.local(2000,1,1,0,0,5), 5.seconds.since - # ago - assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago - assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago + Time.stub(:now, Time.local(2000)) do + # since + assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.since + assert_equal Time.local(2000,1,1,0,0,5), 5.seconds.since + # ago + assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago + assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago + end end end def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - # since - assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.since - assert_equal Time.utc(2000,1,1,0,0,5), 5.seconds.since.time - assert_equal 'Eastern Time (US & Canada)', 5.seconds.since.time_zone.name - # ago - assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago - assert_equal Time.utc(1999,12,31,23,59,55), 5.seconds.ago.time - assert_equal 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name + Time.stub(:now, Time.local(2000)) do + # since + assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.since + assert_equal Time.utc(2000,1,1,0,0,5), 5.seconds.since.time + assert_equal 'Eastern Time (US & Canada)', 5.seconds.since.time_zone.name + # ago + assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago + assert_equal Time.utc(1999,12,31,23,59,55), 5.seconds.ago.time + assert_equal 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name + end end ensure Time.zone = nil @@ -200,4 +210,16 @@ class DurationTest < ActiveSupport::TestCase def test_hash assert_equal 1.minute.hash, 60.seconds.hash end + + def test_comparable + assert_equal(-1, (0.seconds <=> 1.second)) + assert_equal(-1, (1.second <=> 1.minute)) + assert_equal(-1, (1 <=> 1.minute)) + assert_equal(0, (0.seconds <=> 0.seconds)) + assert_equal(0, (0.seconds <=> 0.minutes)) + assert_equal(0, (1.second <=> 1.second)) + assert_equal(1, (1.second <=> 0.second)) + assert_equal(1, (1.minute <=> 1.second)) + assert_equal(1, (61 <=> 1.minute)) + end end diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb index 6fcf6e8743..f09b7d8850 100644 --- a/activesupport/test/core_ext/enumerable_test.rb +++ b/activesupport/test/core_ext/enumerable_test.rb @@ -3,6 +3,8 @@ require 'active_support/core_ext/array' require 'active_support/core_ext/enumerable' Payment = Struct.new(:price) +ExpandedPayment = Struct.new(:dollars, :cents) + class SummablePayment < Payment def +(p) self.class.new(price + p.price) end end @@ -71,14 +73,14 @@ class EnumerableTests < ActiveSupport::TestCase def test_index_by payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ]) assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, - payments.index_by { |p| p.price }) + payments.index_by(&:price)) assert_equal Enumerator, payments.index_by.class if Enumerator.method_defined? :size assert_equal nil, payments.index_by.size assert_equal 42, (1..42).index_by.size end assert_equal({ 5 => Payment.new(5), 15 => Payment.new(15), 10 => Payment.new(10) }, - payments.index_by.each { |p| p.price }) + payments.index_by.each(&:price)) end def test_many @@ -103,4 +105,23 @@ class EnumerableTests < ActiveSupport::TestCase assert_equal true, GenericEnumerable.new([ 1 ]).exclude?(2) assert_equal false, GenericEnumerable.new([ 1 ]).exclude?(1) end + + def test_without + assert_equal [1, 2, 4], GenericEnumerable.new((1..5).to_a).without(3, 5) + assert_equal [1, 2, 4], (1..5).to_a.without(3, 5) + assert_equal [1, 2, 4], (1..5).to_set.without(3, 5) + assert_equal({foo: 1, baz: 3}, {foo: 1, bar: 2, baz: 3}.without(:bar)) + end + + def test_pluck + payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ]) + assert_equal [5, 15, 10], payments.pluck(:price) + + payments = GenericEnumerable.new([ + ExpandedPayment.new(5, 99), + ExpandedPayment.new(15, 0), + ExpandedPayment.new(10, 50) + ]) + assert_equal [[5, 99], [15, 0], [10, 50]], payments.pluck(:dollars, :cents) + end end diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb index 2c04e9687c..cde0132b97 100644 --- a/activesupport/test/core_ext/file_test.rb +++ b/activesupport/test/core_ext/file_test.rb @@ -57,6 +57,16 @@ class AtomicWriteTest < ActiveSupport::TestCase File.unlink(file_name) rescue nil end + def test_atomic_write_returns_result_from_yielded_block + block_return_value = File.atomic_write(file_name, Dir.pwd) do |file| + "Hello world!" + end + + assert_equal "Hello world!", block_return_value + ensure + File.unlink(file_name) rescue nil + end + private def file_name "atomic.file" diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index 5e9fdfd872..4624338df1 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -365,7 +365,7 @@ class HashExtTest < ActiveSupport::TestCase :member? => true } hashes.each do |name, hash| - method_map.sort_by { |m| m.to_s }.each do |meth, expected| + method_map.sort_by(&:to_s).each do |meth, expected| assert_equal(expected, hash.__send__(meth, 'a'), "Calling #{name}.#{meth} 'a'") assert_equal(expected, hash.__send__(meth, :a), @@ -524,6 +524,10 @@ class HashExtTest < ActiveSupport::TestCase end def test_indifferent_reverse_merging + hash = HashWithIndifferentAccess.new key: :old_value + hash.reverse_merge! key: :new_value + assert_equal :old_value, hash[:key] + hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value') hash.reverse_merge!(:some => 'noclobber', :another => 'clobber') assert_equal 'value', hash[:some] @@ -547,6 +551,11 @@ class HashExtTest < ActiveSupport::TestCase assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash end + def test_indifferent_select_returns_enumerator + enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select + assert_instance_of Enumerator, enum + end + def test_indifferent_select_returns_a_hash_when_unchanged hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true} @@ -568,6 +577,11 @@ class HashExtTest < ActiveSupport::TestCase assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash end + def test_indifferent_reject_returns_enumerator + enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject + assert_instance_of Enumerator, enum + end + def test_indifferent_reject_bang indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) indifferent_strings.reject! {|k,v| v != 1} @@ -961,10 +975,11 @@ class HashExtTest < ActiveSupport::TestCase assert_raise(RuntimeError) { original.except!(:a) } end - def test_except_with_mocha_expectation_on_original + def test_except_does_not_delete_values_in_original original = { :a => 'x', :b => 'y' } - original.expects(:delete).never - original.except(:a) + assert_not_called(original, :delete) do + original.except(:a) + end end def test_compact @@ -998,6 +1013,37 @@ class HashExtTest < ActiveSupport::TestCase assert hash.key?('a') assert_equal 1, hash[:a] end + + def test_dup_with_default_proc + hash = HashWithIndifferentAccess.new + hash.default_proc = proc { |h, v| raise "walrus" } + assert_nothing_raised { hash.dup } + end + + def test_dup_with_default_proc_sets_proc + hash = HashWithIndifferentAccess.new + hash.default_proc = proc { |h, k| k + 1 } + new_hash = hash.dup + + assert_equal 3, new_hash[2] + + new_hash.default = 2 + assert_equal 2, new_hash[:non_existant] + end + + def test_to_hash_with_raising_default_proc + hash = HashWithIndifferentAccess.new + hash.default_proc = proc { |h, k| raise "walrus" } + + assert_nothing_raised { hash.to_hash } + end + + def test_new_from_hash_copying_default_should_not_raise_when_default_proc_does + hash = Hash.new + hash.default_proc = proc { |h, k| raise "walrus" } + + assert_nothing_raised { HashWithIndifferentAccess.new_from_hash_copying_default(hash) } + end end class IWriteMyOwnXML @@ -1549,6 +1595,14 @@ class HashToXmlTest < ActiveSupport::TestCase assert_not_same hash_wia, hash_wia.with_indifferent_access end + + def test_allows_setting_frozen_array_values_with_indifferent_access + value = [1, 2, 3].freeze + hash = HashWithIndifferentAccess.new + hash[:key] = value + assert_equal hash[:key], value + end + def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access hash = Hash.new(3) hash_wia = hash.with_indifferent_access diff --git a/activesupport/test/core_ext/kernel_test.rb b/activesupport/test/core_ext/kernel_test.rb index a87af0007c..503e6595cb 100644 --- a/activesupport/test/core_ext/kernel_test.rb +++ b/activesupport/test/core_ext/kernel_test.rb @@ -15,7 +15,6 @@ class KernelTest < ActiveSupport::TestCase assert_equal old_verbose, $VERBOSE end - def test_enable_warnings enable_warnings { assert_equal true, $VERBOSE } assert_equal 1234, enable_warnings { 1234 } @@ -29,57 +28,11 @@ class KernelTest < ActiveSupport::TestCase assert_equal old_verbose, $VERBOSE end - - def test_silence_stream - old_stream_position = STDOUT.tell - silence_stream(STDOUT) { STDOUT.puts 'hello world' } - assert_equal old_stream_position, STDOUT.tell - rescue Errno::ESPIPE - # Skip if we can't stream.tell - end - - def test_silence_stream_closes_file_descriptors - stream = StringIO.new - dup_stream = StringIO.new - stream.stubs(:dup).returns(dup_stream) - dup_stream.expects(:close) - silence_stream(stream) { stream.puts 'hello world' } - end - - def test_quietly - old_stdout_position, old_stderr_position = STDOUT.tell, STDERR.tell - assert_deprecated do - quietly do - puts 'see me, feel me' - STDERR.puts 'touch me, heal me' - end - end - assert_equal old_stdout_position, STDOUT.tell - assert_equal old_stderr_position, STDERR.tell - rescue Errno::ESPIPE - # Skip if we can't STDERR.tell - end - def test_class_eval o = Object.new class << o; @x = 1; end assert_equal 1, o.class_eval { @x } end - - def test_capture - assert_deprecated do - assert_equal 'STDERR', capture(:stderr) { $stderr.print 'STDERR' } - end - assert_deprecated do - assert_equal 'STDOUT', capture(:stdout) { print 'STDOUT' } - end - assert_deprecated do - assert_equal "STDERR\n", capture(:stderr) { system('echo STDERR 1>&2') } - end - assert_deprecated do - assert_equal "STDOUT\n", capture(:stdout) { system('echo STDOUT') } - end - end end class KernelSuppressTest < ActiveSupport::TestCase @@ -112,27 +65,3 @@ class MockStdErr puts(message) end end - -class KernelDebuggerTest < ActiveSupport::TestCase - def test_debugger_not_available_message_to_stderr - old_stderr = $stderr - $stderr = MockStdErr.new - debugger - assert_match(/Debugger requested/, $stderr.output.first) - ensure - $stderr = old_stderr - end - - def test_debugger_not_available_message_to_rails_logger - rails = Class.new do - def self.logger - @logger ||= MockStdErr.new - end - end - Object.const_set(:Rails, rails) - debugger - assert_match(/Debugger requested/, rails.logger.output.first) - ensure - Object.send(:remove_const, :Rails) - end -end if RUBY_VERSION < '2.0.0' diff --git a/activesupport/test/core_ext/load_error_test.rb b/activesupport/test/core_ext/load_error_test.rb index 5f804c749b..b2a75a2bcc 100644 --- a/activesupport/test/core_ext/load_error_test.rb +++ b/activesupport/test/core_ext/load_error_test.rb @@ -1,26 +1,11 @@ require 'abstract_unit' require 'active_support/core_ext/load_error' -class TestMissingSourceFile < ActiveSupport::TestCase - def test_with_require - assert_raise(MissingSourceFile) { require 'no_this_file_don\'t_exist' } - end - def test_with_load - assert_raise(MissingSourceFile) { load 'nor_does_this_one' } - end - def test_path - begin load 'nor/this/one.rb' - rescue MissingSourceFile => e - assert_equal 'nor/this/one.rb', e.path - end - end - def test_is_missing - begin load 'nor_does_this_one' - rescue MissingSourceFile => e - assert e.is_missing?('nor_does_this_one') - assert e.is_missing?('nor_does_this_one.rb') - assert_not e.is_missing?('some_other_file') +class TestMissingSourceFile < ActiveSupport::TestCase + def test_it_is_deprecated + assert_deprecated do + MissingSourceFile.new end end end diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb index 8f3f710dfd..825df439a5 100644 --- a/activesupport/test/core_ext/marshal_test.rb +++ b/activesupport/test/core_ext/marshal_test.rb @@ -8,28 +8,28 @@ class MarshalTest < ActiveSupport::TestCase def teardown ActiveSupport::Dependencies.clear - remove_constants(:E, :ClassFolder) + remove_constants(:EM, :ClassFolder) end test "that Marshal#load still works" do sanity_data = ["test", [1, 2, 3], {a: [1, 2, 3]}, ActiveSupport::TestCase] sanity_data.each do |obj| dumped = Marshal.dump(obj) - assert_equal Marshal.load_without_autoloading(dumped), Marshal.load(dumped) + assert_equal Marshal.method(:load).super_method.call(dumped), Marshal.load(dumped) end end test "that a missing class is autoloaded from string" do dumped = nil with_autoloading_fixtures do - dumped = Marshal.dump(E.new) + dumped = Marshal.dump(EM.new) end - remove_constants(:E) + remove_constants(:EM) ActiveSupport::Dependencies.clear with_autoloading_fixtures do - assert_kind_of E, Marshal.load(dumped) + assert_kind_of EM, Marshal.load(dumped) end end @@ -50,16 +50,16 @@ class MarshalTest < ActiveSupport::TestCase test "that more than one missing class is autoloaded" do dumped = nil with_autoloading_fixtures do - dumped = Marshal.dump([E.new, ClassFolder.new]) + dumped = Marshal.dump([EM.new, ClassFolder.new]) end - remove_constants(:E, :ClassFolder) + remove_constants(:EM, :ClassFolder) ActiveSupport::Dependencies.clear with_autoloading_fixtures do loaded = Marshal.load(dumped) assert_equal 2, loaded.size - assert_kind_of E, loaded[0] + assert_kind_of EM, loaded[0] assert_kind_of ClassFolder, loaded[1] end end @@ -67,10 +67,10 @@ class MarshalTest < ActiveSupport::TestCase test "that a real missing class is causing an exception" do dumped = nil with_autoloading_fixtures do - dumped = Marshal.dump(E.new) + dumped = Marshal.dump(EM.new) end - remove_constants(:E) + remove_constants(:EM) ActiveSupport::Dependencies.clear assert_raise(NameError) do @@ -84,10 +84,10 @@ class MarshalTest < ActiveSupport::TestCase end with_autoloading_fixtures do - dumped = Marshal.dump([E.new, SomeClass.new]) + dumped = Marshal.dump([EM.new, SomeClass.new]) end - remove_constants(:E) + remove_constants(:EM) self.class.send(:remove_const, :SomeClass) ActiveSupport::Dependencies.clear @@ -96,8 +96,8 @@ class MarshalTest < ActiveSupport::TestCase Marshal.load(dumped) end - assert_nothing_raised("E failed to load while we expect only SomeClass to fail loading") do - E.new + assert_nothing_raised("EM failed to load while we expect only SomeClass to fail loading") do + EM.new end assert_raise(NameError, "We expected SomeClass to not be loaded but it is!") do @@ -109,16 +109,16 @@ class MarshalTest < ActiveSupport::TestCase test "loading classes from files trigger autoloading" do Tempfile.open("object_serializer_test") do |f| with_autoloading_fixtures do - Marshal.dump(E.new, f) + Marshal.dump(EM.new, f) end f.rewind - remove_constants(:E) + remove_constants(:EM) ActiveSupport::Dependencies.clear with_autoloading_fixtures do - assert_kind_of E, Marshal.load(f) + assert_kind_of EM, Marshal.load(f) end end end -end
\ No newline at end of file +end diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb index 48f3cc579f..128c5e3d1a 100644 --- a/activesupport/test/core_ext/module/attribute_accessor_test.rb +++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb @@ -76,4 +76,10 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase assert_equal 'default_reader_value', @module.defr assert_equal 'default_writer_value', @module.class_variable_get('@@defw') end + + def test_should_not_invoke_default_value_block_multiple_times + count = 0 + @module.cattr_accessor(:defcount){ count += 1 } + assert_equal 1, count + end end diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb index 3c49c4d14f..bdfbadcf1d 100644 --- a/activesupport/test/core_ext/module_test.rb +++ b/activesupport/test/core_ext/module_test.rb @@ -78,7 +78,7 @@ Product = Struct.new(:name) do def type @type ||= begin - :thing_without_same_method_name_as_delegated.name + nil.type_name end end end @@ -358,148 +358,178 @@ class MethodAliasingTest < ActiveSupport::TestCase Object.instance_eval { remove_const :FooClassWithBarMethod } end - def test_alias_method_chain - assert @instance.respond_to?(:bar) - feature_aliases = [:bar_with_baz, :bar_without_baz] + def test_alias_method_chain_deprecated + assert_deprecated(/alias_method_chain/) do + Module.new do + def base + end + + def base_with_deprecated + end - feature_aliases.each do |method| - assert !@instance.respond_to?(method) + alias_method_chain :base, :deprecated + end end + end - assert_equal 'bar', @instance.bar + def test_alias_method_chain + assert_deprecated(/alias_method_chain/) do + assert @instance.respond_to?(:bar) + feature_aliases = [:bar_with_baz, :bar_without_baz] - FooClassWithBarMethod.class_eval { include BarMethodAliaser } + feature_aliases.each do |method| + assert !@instance.respond_to?(method) + end - feature_aliases.each do |method| - assert_respond_to @instance, method - end + assert_equal 'bar', @instance.bar + + FooClassWithBarMethod.class_eval { include BarMethodAliaser } + + feature_aliases.each do |method| + assert_respond_to @instance, method + end - assert_equal 'bar_with_baz', @instance.bar - assert_equal 'bar', @instance.bar_without_baz + assert_equal 'bar_with_baz', @instance.bar + assert_equal 'bar', @instance.bar_without_baz + end end def test_alias_method_chain_with_punctuation_method - FooClassWithBarMethod.class_eval do - def quux!; 'quux' end - end + assert_deprecated(/alias_method_chain/) do + FooClassWithBarMethod.class_eval do + def quux!; 'quux' end + end - assert !@instance.respond_to?(:quux_with_baz!) - FooClassWithBarMethod.class_eval do - include BarMethodAliaser - alias_method_chain :quux!, :baz - end - assert_respond_to @instance, :quux_with_baz! + assert !@instance.respond_to?(:quux_with_baz!) + FooClassWithBarMethod.class_eval do + include BarMethodAliaser + alias_method_chain :quux!, :baz + end + assert_respond_to @instance, :quux_with_baz! - assert_equal 'quux_with_baz', @instance.quux! - assert_equal 'quux', @instance.quux_without_baz! + assert_equal 'quux_with_baz', @instance.quux! + assert_equal 'quux', @instance.quux_without_baz! + end end def test_alias_method_chain_with_same_names_between_predicates_and_bang_methods - FooClassWithBarMethod.class_eval do - def quux!; 'quux!' end - def quux?; true end - def quux=(v); 'quux=' end - end + assert_deprecated(/alias_method_chain/) do + FooClassWithBarMethod.class_eval do + def quux!; 'quux!' end + def quux?; true end + def quux=(v); 'quux=' end + end - assert !@instance.respond_to?(:quux_with_baz!) - assert !@instance.respond_to?(:quux_with_baz?) - assert !@instance.respond_to?(:quux_with_baz=) + assert !@instance.respond_to?(:quux_with_baz!) + assert !@instance.respond_to?(:quux_with_baz?) + assert !@instance.respond_to?(:quux_with_baz=) - FooClassWithBarMethod.class_eval { include BarMethodAliaser } - assert_respond_to @instance, :quux_with_baz! - assert_respond_to @instance, :quux_with_baz? - assert_respond_to @instance, :quux_with_baz= + FooClassWithBarMethod.class_eval { include BarMethodAliaser } + assert_respond_to @instance, :quux_with_baz! + assert_respond_to @instance, :quux_with_baz? + assert_respond_to @instance, :quux_with_baz= - FooClassWithBarMethod.alias_method_chain :quux!, :baz - assert_equal 'quux!_with_baz', @instance.quux! - assert_equal 'quux!', @instance.quux_without_baz! + FooClassWithBarMethod.alias_method_chain :quux!, :baz + assert_equal 'quux!_with_baz', @instance.quux! + assert_equal 'quux!', @instance.quux_without_baz! - FooClassWithBarMethod.alias_method_chain :quux?, :baz - assert_equal false, @instance.quux? - assert_equal true, @instance.quux_without_baz? + FooClassWithBarMethod.alias_method_chain :quux?, :baz + assert_equal false, @instance.quux? + assert_equal true, @instance.quux_without_baz? - FooClassWithBarMethod.alias_method_chain :quux=, :baz - assert_equal 'quux=_with_baz', @instance.send(:quux=, 1234) - assert_equal 'quux=', @instance.send(:quux_without_baz=, 1234) + FooClassWithBarMethod.alias_method_chain :quux=, :baz + assert_equal 'quux=_with_baz', @instance.send(:quux=, 1234) + assert_equal 'quux=', @instance.send(:quux_without_baz=, 1234) + end end def test_alias_method_chain_with_feature_punctuation - FooClassWithBarMethod.class_eval do - def quux; 'quux' end - def quux?; 'quux?' end - include BarMethodAliaser - alias_method_chain :quux, :baz! - end + assert_deprecated(/alias_method_chain/) do + FooClassWithBarMethod.class_eval do + def quux; 'quux' end + def quux?; 'quux?' end + include BarMethodAliaser + alias_method_chain :quux, :baz! + end - assert_nothing_raised do - assert_equal 'quux_with_baz', @instance.quux_with_baz! - end + assert_nothing_raised do + assert_equal 'quux_with_baz', @instance.quux_with_baz! + end - assert_raise(NameError) do - FooClassWithBarMethod.alias_method_chain :quux?, :baz! + assert_raise(NameError) do + FooClassWithBarMethod.alias_method_chain :quux?, :baz! + end end end def test_alias_method_chain_yields_target_and_punctuation - args = nil + assert_deprecated(/alias_method_chain/) do + args = nil - FooClassWithBarMethod.class_eval do - def quux?; end - include BarMethods + FooClassWithBarMethod.class_eval do + def quux?; end + include BarMethods - FooClassWithBarMethod.alias_method_chain :quux?, :baz do |target, punctuation| - args = [target, punctuation] + FooClassWithBarMethod.alias_method_chain :quux?, :baz do |target, punctuation| + args = [target, punctuation] + end end - end - assert_not_nil args - assert_equal 'quux', args[0] - assert_equal '?', args[1] + assert_not_nil args + assert_equal 'quux', args[0] + assert_equal '?', args[1] + end end def test_alias_method_chain_preserves_private_method_status - FooClassWithBarMethod.class_eval do - def duck; 'duck' end - include BarMethodAliaser - private :duck - alias_method_chain :duck, :orange - end + assert_deprecated(/alias_method_chain/) do + FooClassWithBarMethod.class_eval do + def duck; 'duck' end + include BarMethodAliaser + private :duck + alias_method_chain :duck, :orange + end - assert_raise NoMethodError do - @instance.duck - end + assert_raise NoMethodError do + @instance.duck + end - assert_equal 'duck_with_orange', @instance.instance_eval { duck } - assert FooClassWithBarMethod.private_method_defined?(:duck) + assert_equal 'duck_with_orange', @instance.instance_eval { duck } + assert FooClassWithBarMethod.private_method_defined?(:duck) + end end def test_alias_method_chain_preserves_protected_method_status - FooClassWithBarMethod.class_eval do - def duck; 'duck' end - include BarMethodAliaser - protected :duck - alias_method_chain :duck, :orange - end + assert_deprecated(/alias_method_chain/) do + FooClassWithBarMethod.class_eval do + def duck; 'duck' end + include BarMethodAliaser + protected :duck + alias_method_chain :duck, :orange + end - assert_raise NoMethodError do - @instance.duck - end + assert_raise NoMethodError do + @instance.duck + end - assert_equal 'duck_with_orange', @instance.instance_eval { duck } - assert FooClassWithBarMethod.protected_method_defined?(:duck) + assert_equal 'duck_with_orange', @instance.instance_eval { duck } + assert FooClassWithBarMethod.protected_method_defined?(:duck) + end end def test_alias_method_chain_preserves_public_method_status - FooClassWithBarMethod.class_eval do - def duck; 'duck' end - include BarMethodAliaser - public :duck - alias_method_chain :duck, :orange - end + assert_deprecated(/alias_method_chain/) do + FooClassWithBarMethod.class_eval do + def duck; 'duck' end + include BarMethodAliaser + public :duck + alias_method_chain :duck, :orange + end - assert_equal 'duck_with_orange', @instance.duck - assert FooClassWithBarMethod.public_method_defined?(:duck) + assert_equal 'duck_with_orange', @instance.duck + assert FooClassWithBarMethod.public_method_defined?(:duck) + end end def test_delegate_with_case diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb index b82448458d..0ff8f0f89b 100644 --- a/activesupport/test/core_ext/numeric_ext_test.rb +++ b/activesupport/test/core_ext/numeric_ext_test.rb @@ -280,14 +280,16 @@ class NumericExtFormattingTest < ActiveSupport::TestCase end def test_to_s__human_size_with_si_prefix - assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si) - assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si) - assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si) - assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si) - assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si) - assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si) - assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si) - assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si) + assert_deprecated do + assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si) + assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si) + assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si) + assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si) + assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si) + assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si) + assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si) + assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si) + end end def test_to_s__human_size_with_options_hash @@ -389,4 +391,88 @@ class NumericExtFormattingTest < ActiveSupport::TestCase def test_in_milliseconds assert_equal 10_000, 10.seconds.in_milliseconds end + + # TODO: Remove positive and negative tests when we drop support to ruby < 2.3 + b = 2**64 + b *= b until Bignum === b + + T_ZERO = b.coerce(0).first + T_ONE = b.coerce(1).first + T_MONE = b.coerce(-1).first + + def test_positive + assert_predicate(1, :positive?) + assert_not_predicate(0, :positive?) + assert_not_predicate(-1, :positive?) + assert_predicate(+1.0, :positive?) + assert_not_predicate(+0.0, :positive?) + assert_not_predicate(-0.0, :positive?) + assert_not_predicate(-1.0, :positive?) + assert_predicate(+(0.0.next_float), :positive?) + assert_not_predicate(-(0.0.next_float), :positive?) + assert_predicate(Float::INFINITY, :positive?) + assert_not_predicate(-Float::INFINITY, :positive?) + assert_not_predicate(Float::NAN, :positive?) + + a = Class.new(Numeric) do + def >(x); true; end + end.new + assert_predicate(a, :positive?) + + a = Class.new(Numeric) do + def >(x); false; end + end.new + assert_not_predicate(a, :positive?) + + assert_predicate(1/2r, :positive?) + assert_not_predicate(-1/2r, :positive?) + + assert_predicate(T_ONE, :positive?) + assert_not_predicate(T_MONE, :positive?) + assert_not_predicate(T_ZERO, :positive?) + + e = assert_raises(NoMethodError) do + Complex(1).positive? + end + + assert_match(/positive\?/, e.message) + end + + def test_negative + assert_predicate(-1, :negative?) + assert_not_predicate(0, :negative?) + assert_not_predicate(1, :negative?) + assert_predicate(-1.0, :negative?) + assert_not_predicate(-0.0, :negative?) + assert_not_predicate(+0.0, :negative?) + assert_not_predicate(+1.0, :negative?) + assert_predicate(-(0.0.next_float), :negative?) + assert_not_predicate(+(0.0.next_float), :negative?) + assert_predicate(-Float::INFINITY, :negative?) + assert_not_predicate(Float::INFINITY, :negative?) + assert_not_predicate(Float::NAN, :negative?) + + a = Class.new(Numeric) do + def <(x); true; end + end.new + assert_predicate(a, :negative?) + + a = Class.new(Numeric) do + def <(x); false; end + end.new + assert_not_predicate(a, :negative?) + + assert_predicate(-1/2r, :negative?) + assert_not_predicate(1/2r, :negative?) + + assert_not_predicate(T_ONE, :negative?) + assert_predicate(T_MONE, :negative?) + assert_not_predicate(T_ZERO, :negative?) + + e = assert_raises(NoMethodError) do + Complex(1).negative? + end + + assert_match(/negative\?/, e.message) + end end diff --git a/activesupport/test/core_ext/object/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb index 246bc7fa61..8a5e385dd7 100644 --- a/activesupport/test/core_ext/object/blank_test.rb +++ b/activesupport/test/core_ext/object/blank_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'abstract_unit' require 'active_support/core_ext/object/blank' diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb index 91d558dbb5..791b5e7172 100644 --- a/activesupport/test/core_ext/object/deep_dup_test.rb +++ b/activesupport/test/core_ext/object/deep_dup_test.rb @@ -50,4 +50,10 @@ class DeepDupTest < ActiveSupport::TestCase assert dup.instance_variable_defined?(:@a) end + def test_deep_dup_with_hash_class_key + hash = { Fixnum => 1 } + dup = hash.deep_dup + assert_equal 1, dup.keys.length + end + end diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index 8cc39ae7b9..042f5cfb34 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -6,18 +6,12 @@ require 'active_support/core_ext/numeric/time' class DuplicableTest < ActiveSupport::TestCase RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts)] ALLOW_DUP = ['1', Object.new, /foo/, [], {}, Time.now, Class.new, Module.new] - - # Needed to support Ruby 1.9.x, as it doesn't allow dup on BigDecimal, instead - # raises TypeError exception. Checking here on the runtime whether BigDecimal - # will allow dup or not. - begin - bd = BigDecimal.new('4.56') - ALLOW_DUP << bd.dup - rescue TypeError - RAISE_DUP << bd - end + ALLOW_DUP << BigDecimal.new('4.56') def test_duplicable + rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \ + "* https://github.com/rubinius/rubinius/issues/3089" + RAISE_DUP.each do |v| assert !v.duplicable? assert_raises(TypeError, v.class.name) { v.dup } diff --git a/activesupport/test/core_ext/object/itself_test.rb b/activesupport/test/core_ext/object/itself_test.rb deleted file mode 100644 index 65db0ddf40..0000000000 --- a/activesupport/test/core_ext/object/itself_test.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/object' - -class Object::ItselfTest < ActiveSupport::TestCase - test 'itself returns self' do - object = 'fun' - assert_equal object, object.itself - end -end diff --git a/activesupport/test/core_ext/object/json_gem_encoding_test.rb b/activesupport/test/core_ext/object/json_gem_encoding_test.rb new file mode 100644 index 0000000000..02ab17fb64 --- /dev/null +++ b/activesupport/test/core_ext/object/json_gem_encoding_test.rb @@ -0,0 +1,66 @@ +require 'abstract_unit' +require 'json' +require 'json/encoding_test_cases' + +# These test cases were added to test that we do not interfere with json gem's +# output when the AS encoder is loaded, primarily for problems reported in +# #20775. They need to be executed in isolation to reproduce the scenario +# correctly, because other test cases might have already loaded additional +# dependencies. + +# The AS::JSON encoder requires the BigDecimal core_ext, which, unfortunately, +# changes the BigDecimal#to_s output, and consequently the JSON gem output. So +# we need to require this unfront to ensure we don't get a false failure, but +# ideally we should just fix the BigDecimal core_ext to not change to_s without +# arguments. +require 'active_support/core_ext/big_decimal' + +class JsonGemEncodingTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Isolation + + JSONTest::EncodingTestCases.constants.each_with_index do |name| + JSONTest::EncodingTestCases.const_get(name).each_with_index do |(subject, _), i| + test("#{name[0..-6].underscore} #{i}") do + assert_same_with_or_without_active_support(subject) + end + end + end + + class CustomToJson + def to_json(*) + '"custom"' + end + end + + test "custom to_json" do + assert_same_with_or_without_active_support(CustomToJson.new) + end + + private + def require_or_skip(file) + require(file) || skip("'#{file}' was already loaded") + end + + def assert_same_with_or_without_active_support(subject) + begin + expected = JSON.generate(subject, quirks_mode: true) + rescue JSON::GeneratorError => e + exception = e + end + + require_or_skip 'active_support/core_ext/object/json' + + if exception + assert_raises_with_message JSON::GeneratorError, e.message do + JSON.generate(subject, quirks_mode: true) + end + else + assert_equal expected, JSON.generate(subject, quirks_mode: true) + end + end + + def assert_raises_with_message(exception_class, message, &block) + err = assert_raises(exception_class) { block.call } + assert_match message, err.message + end +end diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb index 225c20fa36..5ea0f0eca6 100644 --- a/activesupport/test/core_ext/object/try_test.rb +++ b/activesupport/test/core_ext/object/try_test.rb @@ -30,10 +30,6 @@ class ObjectTryTest < ActiveSupport::TestCase assert_raise(NoMethodError) { @string.try!(method, 'llo', 'y') } end - def test_try_only_block_bang - assert_equal @string.reverse, @string.try! { |s| s.reverse } - end - def test_valid_method assert_equal 5, @string.try(:size) end @@ -56,7 +52,11 @@ class ObjectTryTest < ActiveSupport::TestCase end def test_try_only_block - assert_equal @string.reverse, @string.try { |s| s.reverse } + assert_equal @string.reverse, @string.try(&:reverse) + end + + def test_try_only_block_bang + assert_equal @string.reverse, @string.try!(&:reverse) end def test_try_only_block_nil @@ -69,13 +69,17 @@ class ObjectTryTest < ActiveSupport::TestCase assert_equal @string.reverse, @string.try { reverse } end + def test_try_with_instance_eval_block_bang + assert_equal @string.reverse, @string.try! { reverse } + end + def test_try_with_private_method_bang klass = Class.new do private - def private_method - 'private method' - end + def private_method + 'private method' + end end assert_raise(NoMethodError) { klass.new.try!(:private_method) } @@ -85,11 +89,75 @@ class ObjectTryTest < ActiveSupport::TestCase klass = Class.new do private - def private_method - 'private method' - end + def private_method + 'private method' + end end assert_nil klass.new.try(:private_method) end + + class Decorator < SimpleDelegator + def delegator_method + 'delegator method' + end + + def reverse + 'overridden reverse' + end + + private + + def private_delegator_method + 'private delegator method' + end + end + + def test_try_with_method_on_delegator + assert_equal 'delegator method', Decorator.new(@string).try(:delegator_method) + end + + def test_try_with_method_on_delegator_target + assert_equal 5, Decorator.new(@string).size + end + + def test_try_with_overriden_method_on_delegator + assert_equal 'overridden reverse', Decorator.new(@string).reverse + end + + def test_try_with_private_method_on_delegator + assert_nil Decorator.new(@string).try(:private_delegator_method) + end + + def test_try_with_private_method_on_delegator_bang + assert_raise(NoMethodError) do + Decorator.new(@string).try!(:private_delegator_method) + end + end + + def test_try_with_private_method_on_delegator_target + klass = Class.new do + private + + def private_method + 'private method' + end + end + + assert_nil Decorator.new(klass.new).try(:private_method) + end + + def test_try_with_private_method_on_delegator_target_bang + klass = Class.new do + private + + def private_method + 'private method' + end + end + + assert_raise(NoMethodError) do + Decorator.new(klass.new).try!(:private_method) + end + end end diff --git a/activesupport/test/core_ext/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb index 98c4ec6b5e..f096328cee 100644 --- a/activesupport/test/core_ext/range_ext_test.rb +++ b/activesupport/test/core_ext/range_ext_test.rb @@ -115,11 +115,11 @@ class RangeTest < ActiveSupport::TestCase def test_date_time_with_each datetime = DateTime.now - assert ((datetime - 1.hour)..datetime).each {} + assert(((datetime - 1.hour)..datetime).each {}) end def test_date_time_with_step datetime = DateTime.now - assert ((datetime - 1.hour)..datetime).step(1) {} + assert(((datetime - 1.hour)..datetime).step(1) {}) end end diff --git a/activesupport/test/core_ext/secure_random_test.rb b/activesupport/test/core_ext/secure_random_test.rb new file mode 100644 index 0000000000..dfacb7fe9f --- /dev/null +++ b/activesupport/test/core_ext/secure_random_test.rb @@ -0,0 +1,20 @@ +require 'abstract_unit' +require 'active_support/core_ext/securerandom' + +class SecureRandomTest < ActiveSupport::TestCase + def test_base58 + s1 = SecureRandom.base58 + s2 = SecureRandom.base58 + + assert_not_equal s1, s2 + assert_equal 16, s1.length + end + + def test_base58_with_length + s1 = SecureRandom.base58(24) + s2 = SecureRandom.base58(24) + + assert_not_equal s1, s2 + assert_equal 24, s1.length + end +end diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 2f4691817f..3a5d6df06d 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'date' require 'abstract_unit' require 'inflector_test_cases' @@ -250,6 +249,15 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal "Hello<br>Big<br>World!", "Hello<br>Big<br>World!".truncate_words(3, :omission => "[...]", :separator => '<br>') end + def test_truncate_words_with_complex_string + Timeout.timeout(10) do + complex_string = "aa aa aaa aa aaa aaa aaa aa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaa aaaa aaaaa aaaaa aaaaaa aa aa aa aaa aa aaa aa aa aa aa a aaa aaa \n a aaa <<s" + assert_equal complex_string.truncate_words(80), complex_string + end + rescue Timeout::Error + assert false + end + def test_truncate_multibyte assert_equal "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 ...".force_encoding(Encoding::UTF_8), "\354\225\204\353\246\254\353\236\221 \354\225\204\353\246\254 \354\225\204\353\235\274\353\246\254\354\230\244".force_encoding(Encoding::UTF_8).truncate(10) @@ -260,20 +268,32 @@ class StringInflectionsTest < ActiveSupport::TestCase end def test_remove - assert_equal "Summer", "Fast Summer".remove(/Fast /) - assert_equal "Summer", "Fast Summer".remove!(/Fast /) + original = "This is a good day to die" + assert_equal "This is a good day", original.remove(" to die") + assert_equal "This is a good day", original.remove(" to ", /die/) + assert_equal "This is a good day to die", original + end + + def test_remove_for_multiple_occurrences + original = "This is a good day to die to die" + assert_equal "This is a good day", original.remove(" to die") + assert_equal "This is a good day to die to die", original + end + + def test_remove! + original = "This is a very good day to die" + assert_equal "This is a good day to die", original.remove!(" very") + assert_equal "This is a good day to die", original + assert_equal "This is a good day", original.remove!(" to ", /die/) + assert_equal "This is a good day", original end def test_constantize - run_constantize_tests_on do |string| - string.constantize - end + run_constantize_tests_on(&:constantize) end def test_safe_constantize - run_safe_constantize_tests_on do |string| - string.safe_constantize - end + run_safe_constantize_tests_on(&:safe_constantize) end end @@ -404,128 +424,134 @@ class StringConversionsTest < ActiveSupport::TestCase end def test_partial_string_to_time - with_env_tz "Europe/Moscow" do + with_env_tz "Europe/Moscow" do # use timezone which does not observe DST. now = Time.now assert_equal Time.local(now.year, now.month, now.day, 23, 50), "23:50".to_time assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "23:50".to_time(:utc) - assert_equal Time.local(now.year, now.month, now.day, 18, 50), "13:50 -0100".to_time + assert_equal Time.local(now.year, now.month, now.day, 17, 50), "13:50 -0100".to_time assert_equal Time.utc(now.year, now.month, now.day, 23, 50), "22:50 -0100".to_time(:utc) end end def test_standard_time_string_to_time_when_current_time_is_standard_time with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 1, 1)) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc) + Time.stub(:now, Time.local(2012, 1, 1)) do + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc) + end end end def test_standard_time_string_to_time_when_current_time_is_daylight_savings with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 7, 1)) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc) + Time.stub(:now, Time.local(2012, 7, 1)) do + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc) + end end end def test_daylight_savings_string_to_time_when_current_time_is_standard_time with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 1, 1)) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc) + Time.stub(:now, Time.local(2012, 1, 1)) do + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc) + end end end def test_daylight_savings_string_to_time_when_current_time_is_daylight_savings with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 7, 1)) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc) + Time.stub(:now, Time.local(2012, 7, 1)) do + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc) + end end end def test_partial_string_to_time_when_current_time_is_standard_time with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 1, 1)) - assert_equal Time.local(2012, 1, 1, 10, 0), "10:00".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 6, 0), "10:00 -0100".to_time - assert_equal Time.utc(2012, 1, 1, 11, 0), "10:00 -0100".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 -0500".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 -0500".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 5, 0), "10:00 UTC".to_time - assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 13, 0), "10:00 PST".to_time - assert_equal Time.utc(2012, 1, 1, 18, 0), "10:00 PST".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 12, 0), "10:00 PDT".to_time - assert_equal Time.utc(2012, 1, 1, 17, 0), "10:00 PDT".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 EST".to_time - assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 EST".to_time(:utc) - assert_equal Time.local(2012, 1, 1, 9, 0), "10:00 EDT".to_time - assert_equal Time.utc(2012, 1, 1, 14, 0), "10:00 EDT".to_time(:utc) + Time.stub(:now, Time.local(2012, 1, 1)) do + assert_equal Time.local(2012, 1, 1, 10, 0), "10:00".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 6, 0), "10:00 -0100".to_time + assert_equal Time.utc(2012, 1, 1, 11, 0), "10:00 -0100".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 -0500".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 -0500".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 5, 0), "10:00 UTC".to_time + assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 13, 0), "10:00 PST".to_time + assert_equal Time.utc(2012, 1, 1, 18, 0), "10:00 PST".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 12, 0), "10:00 PDT".to_time + assert_equal Time.utc(2012, 1, 1, 17, 0), "10:00 PDT".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 EST".to_time + assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 EST".to_time(:utc) + assert_equal Time.local(2012, 1, 1, 9, 0), "10:00 EDT".to_time + assert_equal Time.utc(2012, 1, 1, 14, 0), "10:00 EDT".to_time(:utc) + end end end def test_partial_string_to_time_when_current_time_is_daylight_savings with_env_tz "US/Eastern" do - Time.stubs(:now).returns(Time.local(2012, 7, 1)) - assert_equal Time.local(2012, 7, 1, 10, 0), "10:00".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 7, 0), "10:00 -0100".to_time - assert_equal Time.utc(2012, 7, 1, 11, 0), "10:00 -0100".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 -0500".to_time - assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 -0500".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 6, 0), "10:00 UTC".to_time - assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00 UTC".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 14, 0), "10:00 PST".to_time - assert_equal Time.utc(2012, 7, 1, 18, 0), "10:00 PST".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 13, 0), "10:00 PDT".to_time - assert_equal Time.utc(2012, 7, 1, 17, 0), "10:00 PDT".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 EST".to_time - assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 EST".to_time(:utc) - assert_equal Time.local(2012, 7, 1, 10, 0), "10:00 EDT".to_time - assert_equal Time.utc(2012, 7, 1, 14, 0), "10:00 EDT".to_time(:utc) + Time.stub(:now, Time.local(2012, 7, 1)) do + assert_equal Time.local(2012, 7, 1, 10, 0), "10:00".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 7, 0), "10:00 -0100".to_time + assert_equal Time.utc(2012, 7, 1, 11, 0), "10:00 -0100".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 -0500".to_time + assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 -0500".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 6, 0), "10:00 UTC".to_time + assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00 UTC".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 14, 0), "10:00 PST".to_time + assert_equal Time.utc(2012, 7, 1, 18, 0), "10:00 PST".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 13, 0), "10:00 PDT".to_time + assert_equal Time.utc(2012, 7, 1, 17, 0), "10:00 PDT".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 EST".to_time + assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 EST".to_time(:utc) + assert_equal Time.local(2012, 7, 1, 10, 0), "10:00 EDT".to_time + assert_equal Time.utc(2012, 7, 1, 14, 0), "10:00 EDT".to_time(:utc) + end end end @@ -655,16 +681,6 @@ class OutputSafetyTest < ActiveSupport::TestCase assert_equal other, "<foo>other" end - test "Deprecated #prepend! method is still present" do - other = "other".html_safe - - assert_deprecated do - other.prepend! "<foo>" - end - - assert_equal other, "<foo>other" - end - test "Concatting safe onto unsafe yields unsafe" do @other_string = "other" diff --git a/activesupport/test/core_ext/struct_test.rb b/activesupport/test/core_ext/struct_test.rb deleted file mode 100644 index 0dff7b32d2..0000000000 --- a/activesupport/test/core_ext/struct_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/struct' - -class StructExt < ActiveSupport::TestCase - def test_to_h - x = Struct.new(:foo, :bar) - z = x.new(1, 2) - assert_equal({ foo: 1, bar: 2 }, z.to_h) - end -end diff --git a/activesupport/test/core_ext/thread_test.rb b/activesupport/test/core_ext/thread_test.rb deleted file mode 100644 index 6a7c6e0604..0000000000 --- a/activesupport/test/core_ext/thread_test.rb +++ /dev/null @@ -1,75 +0,0 @@ -require 'abstract_unit' -require 'active_support/core_ext/thread' - -class ThreadExt < ActiveSupport::TestCase - def test_main_thread_variable_in_enumerator - assert_equal Thread.main, Thread.current - - Thread.current.thread_variable_set :foo, "bar" - - thread, value = Fiber.new { - Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] - }.resume - - assert_equal Thread.current, thread - assert_equal Thread.current.thread_variable_get(:foo), value - end - - def test_thread_variable_in_enumerator - Thread.new { - Thread.current.thread_variable_set :foo, "bar" - - thread, value = Fiber.new { - Fiber.yield [Thread.current, Thread.current.thread_variable_get(:foo)] - }.resume - - assert_equal Thread.current, thread - assert_equal Thread.current.thread_variable_get(:foo), value - }.join - end - - def test_thread_variables - assert_equal [], Thread.new { Thread.current.thread_variables }.join.value - - t = Thread.new { - Thread.current.thread_variable_set(:foo, "bar") - Thread.current.thread_variables - } - assert_equal [:foo], t.join.value - end - - def test_thread_variable? - assert_not Thread.new { Thread.current.thread_variable?("foo") }.join.value - t = Thread.new { - Thread.current.thread_variable_set("foo", "bar") - }.join - - assert t.thread_variable?("foo") - assert t.thread_variable?(:foo) - assert_not t.thread_variable?(:bar) - end - - def test_thread_variable_strings_and_symbols_are_the_same_key - t = Thread.new {}.join - t.thread_variable_set("foo", "bar") - assert_equal "bar", t.thread_variable_get(:foo) - end - - def test_thread_variable_frozen - t = Thread.new { }.join - t.freeze - assert_raises(RuntimeError) do - t.thread_variable_set(:foo, "bar") - end - end - - 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 d59775001b..b14c04fba6 100644 --- a/activesupport/test/core_ext/time_ext_test.rb +++ b/activesupport/test/core_ext/time_ext_test.rb @@ -149,6 +149,9 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST' assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST' end + with_env_tz 'Asia/Yekaterinburg' do + assert_equal Time.local(2015, 2, 8, 23, 59, 59, Rational(999999999, 1000)), Time.new(2015, 2, 8, 8, 0, 0, '+05:00').end_of_day + end end def test_end_of_hour @@ -389,6 +392,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(:usec => 8) assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,2).change(:nsec => 8000) assert_raise(ArgumentError) { Time.local(2005,1,2,11,22,33, 8).change(:usec => 1, :nsec => 1) } + assert_nothing_raised(ArgumentError) { Time.new(2015, 5, 9, 10, 00, 00, '+03:00').change(nsec: 999999999) } end def test_utc_change @@ -601,13 +605,15 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_days_in_month_feb_in_common_year_without_year_arg - Time.stubs(:now).returns(Time.utc(2007)) - assert_equal 28, Time.days_in_month(2) + Time.stub(:now, Time.utc(2007)) do + assert_equal 28, Time.days_in_month(2) + end end def test_days_in_month_feb_in_leap_year_without_year_arg - Time.stubs(:now).returns(Time.utc(2008)) - assert_equal 29, Time.days_in_month(2) + Time.stub(:now, Time.utc(2008)) do + assert_equal 29, Time.days_in_month(2) + end end def test_last_month_on_31st @@ -619,68 +625,74 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase end def test_today_with_time_local - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, Time.local(1999,12,31,23,59,59).today? - assert_equal true, Time.local(2000,1,1,0).today? - assert_equal true, Time.local(2000,1,1,23,59,59).today? - assert_equal false, Time.local(2000,1,2,0).today? + Date.stub(:current, Date.new(2000, 1, 1)) do + assert_equal false, Time.local(1999,12,31,23,59,59).today? + assert_equal true, Time.local(2000,1,1,0).today? + assert_equal true, Time.local(2000,1,1,23,59,59).today? + assert_equal false, Time.local(2000,1,2,0).today? + end end def test_today_with_time_utc - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, Time.utc(1999,12,31,23,59,59).today? - assert_equal true, Time.utc(2000,1,1,0).today? - assert_equal true, Time.utc(2000,1,1,23,59,59).today? - assert_equal false, Time.utc(2000,1,2,0).today? + Date.stub(:current, Date.new(2000, 1, 1)) do + assert_equal false, Time.utc(1999,12,31,23,59,59).today? + assert_equal true, Time.utc(2000,1,1,0).today? + assert_equal true, Time.utc(2000,1,1,23,59,59).today? + assert_equal false, Time.utc(2000,1,2,0).today? + end end def test_past_with_time_current_as_time_local with_env_tz 'US/Eastern' do - Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45)) - assert_equal true, Time.local(2005,2,10,15,30,44).past? - assert_equal false, Time.local(2005,2,10,15,30,45).past? - assert_equal false, Time.local(2005,2,10,15,30,46).past? - assert_equal true, Time.utc(2005,2,10,20,30,44).past? - assert_equal false, Time.utc(2005,2,10,20,30,45).past? - assert_equal false, Time.utc(2005,2,10,20,30,46).past? + Time.stub(:current, Time.local(2005,2,10,15,30,45)) do + assert_equal true, Time.local(2005,2,10,15,30,44).past? + assert_equal false, Time.local(2005,2,10,15,30,45).past? + assert_equal false, Time.local(2005,2,10,15,30,46).past? + assert_equal true, Time.utc(2005,2,10,20,30,44).past? + assert_equal false, Time.utc(2005,2,10,20,30,45).past? + assert_equal false, Time.utc(2005,2,10,20,30,46).past? + end end end def test_past_with_time_current_as_time_with_zone with_env_tz 'US/Eastern' do twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)') - Time.stubs(:current).returns(twz) - assert_equal true, Time.local(2005,2,10,10,30,44).past? - assert_equal false, Time.local(2005,2,10,10,30,45).past? - assert_equal false, Time.local(2005,2,10,10,30,46).past? - assert_equal true, Time.utc(2005,2,10,15,30,44).past? - assert_equal false, Time.utc(2005,2,10,15,30,45).past? - assert_equal false, Time.utc(2005,2,10,15,30,46).past? + Time.stub(:current, twz) do + assert_equal true, Time.local(2005,2,10,10,30,44).past? + assert_equal false, Time.local(2005,2,10,10,30,45).past? + assert_equal false, Time.local(2005,2,10,10,30,46).past? + assert_equal true, Time.utc(2005,2,10,15,30,44).past? + assert_equal false, Time.utc(2005,2,10,15,30,45).past? + assert_equal false, Time.utc(2005,2,10,15,30,46).past? + end end end def test_future_with_time_current_as_time_local with_env_tz 'US/Eastern' do - Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45)) - assert_equal false, Time.local(2005,2,10,15,30,44).future? - assert_equal false, Time.local(2005,2,10,15,30,45).future? - assert_equal true, Time.local(2005,2,10,15,30,46).future? - assert_equal false, Time.utc(2005,2,10,20,30,44).future? - assert_equal false, Time.utc(2005,2,10,20,30,45).future? - assert_equal true, Time.utc(2005,2,10,20,30,46).future? + Time.stub(:current, Time.local(2005,2,10,15,30,45)) do + assert_equal false, Time.local(2005,2,10,15,30,44).future? + assert_equal false, Time.local(2005,2,10,15,30,45).future? + assert_equal true, Time.local(2005,2,10,15,30,46).future? + assert_equal false, Time.utc(2005,2,10,20,30,44).future? + assert_equal false, Time.utc(2005,2,10,20,30,45).future? + assert_equal true, Time.utc(2005,2,10,20,30,46).future? + end end end def test_future_with_time_current_as_time_with_zone with_env_tz 'US/Eastern' do twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)') - Time.stubs(:current).returns(twz) - assert_equal false, Time.local(2005,2,10,10,30,44).future? - assert_equal false, Time.local(2005,2,10,10,30,45).future? - assert_equal true, Time.local(2005,2,10,10,30,46).future? - assert_equal false, Time.utc(2005,2,10,15,30,44).future? - assert_equal false, Time.utc(2005,2,10,15,30,45).future? - assert_equal true, Time.utc(2005,2,10,15,30,46).future? + Time.stub(:current, twz) do + assert_equal false, Time.local(2005,2,10,10,30,44).future? + assert_equal false, Time.local(2005,2,10,10,30,45).future? + assert_equal true, Time.local(2005,2,10,10,30,46).future? + assert_equal false, Time.utc(2005,2,10,15,30,44).future? + assert_equal false, Time.utc(2005,2,10,15,30,45).future? + assert_equal true, Time.utc(2005,2,10,15,30,46).future? + end end end @@ -721,6 +733,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] )) end + def test_compare_with_string + assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999).to_s + assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s + assert_equal( -1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1, 0).to_s) + assert_equal nil, Time.utc(2000) <=> 'Invalid as Time' + end + def test_at_with_datetime assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0)) diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index 3000da8da4..ccb7f02331 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -1,6 +1,8 @@ require 'abstract_unit' require 'active_support/time' require 'time_zone_test_helpers' +require 'active_support/core_ext/string/strip' +require 'yaml' class TimeWithZoneTest < ActiveSupport::TestCase include TimeZoneTestHelpers @@ -79,6 +81,11 @@ class TimeWithZoneTest < ActiveSupport::TestCase assert_equal '1999-12-31 19:00:00 EST -0500', @twz.strftime('%Y-%m-%d %H:%M:%S %Z %z') end + def test_strftime_with_escaping + assert_equal '%Z %z', @twz.strftime('%%Z %%z') + assert_equal '%EST %-0500', @twz.strftime('%%%Z %%%z') + end + def test_inspect assert_equal 'Fri, 31 Dec 1999 19:00:00 EST -05:00', @twz.inspect end @@ -118,11 +125,53 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_to_yaml - assert_match(/^--- 2000-01-01 00:00:00(\.0+)?\s*Z\n/, @twz.to_yaml) + yaml = <<-EOF.strip_heredoc + --- !ruby/object:ActiveSupport::TimeWithZone + utc: 2000-01-01 00:00:00.000000000 Z + zone: !ruby/object:ActiveSupport::TimeZone + name: America/New_York + time: 1999-12-31 19:00:00.000000000 Z + EOF + + assert_equal(yaml, @twz.to_yaml) end def test_ruby_to_yaml - assert_match(/---\s*\n:twz: 2000-01-01 00:00:00(\.0+)?\s*Z\n/, {:twz => @twz}.to_yaml) + yaml = <<-EOF.strip_heredoc + --- + twz: !ruby/object:ActiveSupport::TimeWithZone + utc: 2000-01-01 00:00:00.000000000 Z + zone: !ruby/object:ActiveSupport::TimeZone + name: America/New_York + time: 1999-12-31 19:00:00.000000000 Z + EOF + + assert_equal(yaml, { 'twz' => @twz }.to_yaml) + end + + def test_yaml_load + yaml = <<-EOF.strip_heredoc + --- !ruby/object:ActiveSupport::TimeWithZone + utc: 2000-01-01 00:00:00.000000000 Z + zone: !ruby/object:ActiveSupport::TimeZone + name: America/New_York + time: 1999-12-31 19:00:00.000000000 Z + EOF + + assert_equal(@twz, YAML.load(yaml)) + end + + def test_ruby_yaml_load + yaml = <<-EOF.strip_heredoc + --- + twz: !ruby/object:ActiveSupport::TimeWithZone + utc: 2000-01-01 00:00:00.000000000 Z + zone: !ruby/object:ActiveSupport::TimeZone + name: America/New_York + time: 1999-12-31 19:00:00.000000000 Z + EOF + + assert_equal({ 'twz' => @twz }, YAML.load(yaml)) end def test_httpdate @@ -157,52 +206,61 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_today - Date.stubs(:current).returns(Date.new(2000, 1, 1)) - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(1999,12,31,23,59,59) ).today? - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,0) ).today? - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,23,59,59) ).today? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,2,0) ).today? + Date.stub(:current, Date.new(2000, 1, 1)) do + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(1999,12,31,23,59,59) ).today? + assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,0) ).today? + assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,23,59,59) ).today? + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,2,0) ).today? + end end def test_past_with_time_current_as_time_local with_env_tz 'US/Eastern' do - Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45)) - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past? + Time.stub(:current, Time.local(2005,2,10,15,30,45)) do + assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past? + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past? + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past? + end end end def test_past_with_time_current_as_time_with_zone twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) ) - Time.stubs(:current).returns(twz) - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past? + Time.stub(:current, twz) do + assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past? + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past? + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past? + end end def test_future_with_time_current_as_time_local with_env_tz 'US/Eastern' do - Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45)) - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future? - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future? + Time.stub(:current, Time.local(2005,2,10,15,30,45)) do + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future? + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future? + assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future? + end end end def test_future_with_time_current_as_time_with_zone twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) ) - Time.stubs(:current).returns(twz) - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future? - assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future? - assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future? + Time.stub(:current, twz) do + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future? + assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future? + assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future? + end end def test_eql? + assert_equal true, @twz.eql?(@twz.dup) assert_equal true, @twz.eql?(Time.utc(2000)) assert_equal true, @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) ) assert_equal false, @twz.eql?( Time.utc(2000, 1, 1, 0, 0, 1) ) assert_equal false, @twz.eql?( DateTime.civil(1999, 12, 31, 23, 59, 59) ) + + other_twz = ActiveSupport::TimeWithZone.new(DateTime.now.utc, @time_zone) + assert_equal true, other_twz.eql?(other_twz.dup) end def test_hash @@ -429,22 +487,24 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_method_missing_with_non_time_return_value - @twz.time.expects(:foo).returns('bar') + time = @twz.time + def time.foo; 'bar'; end assert_equal 'bar', @twz.foo end def test_date_part_value_methods twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone) - twz.expects(:method_missing).never - assert_equal 1999, twz.year - assert_equal 12, twz.month - assert_equal 31, twz.day - assert_equal 14, twz.hour - assert_equal 18, twz.min - assert_equal 17, twz.sec - assert_equal 500, twz.usec - assert_equal 5, twz.wday - assert_equal 365, twz.yday + assert_not_called(twz, :method_missing) do + assert_equal 1999, twz.year + assert_equal 12, twz.month + assert_equal 31, twz.day + assert_equal 14, twz.hour + assert_equal 18, twz.min + assert_equal 17, twz.sec + assert_equal 500, twz.usec + assert_equal 5, twz.wday + assert_equal 365, twz.yday + end end def test_usec_returns_0_when_datetime_is_wrapped @@ -807,6 +867,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_no_method_error_has_proper_context + rubinius_skip "Error message inconsistency" + e = assert_raises(NoMethodError) { @twz.this_method_does_not_exist } @@ -983,19 +1045,21 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase def test_current_returns_time_now_when_zone_not_set with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - assert_equal false, Time.current.is_a?(ActiveSupport::TimeWithZone) - assert_equal Time.local(2000), Time.current + Time.stub(:now, Time.local(2000)) do + assert_equal false, Time.current.is_a?(ActiveSupport::TimeWithZone) + assert_equal Time.local(2000), Time.current + end end end def test_current_returns_time_zone_now_when_zone_set Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] with_env_tz 'US/Eastern' do - Time.stubs(:now).returns Time.local(2000) - assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone) - assert_equal 'Eastern Time (US & Canada)', Time.current.time_zone.name - assert_equal Time.utc(2000), Time.current.time + Time.stub(:now, Time.local(2000)) do + assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone) + assert_equal 'Eastern Time (US & Canada)', Time.current.time_zone.name + assert_equal Time.utc(2000), Time.current.time + end end end diff --git a/activesupport/test/core_ext/uri_ext_test.rb b/activesupport/test/core_ext/uri_ext_test.rb index 43a5997ddd..1694fe7e72 100644 --- a/activesupport/test/core_ext/uri_ext_test.rb +++ b/activesupport/test/core_ext/uri_ext_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'abstract_unit' require 'uri' require 'active_support/core_ext/uri' diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb index 899bb75eae..757e600646 100644 --- a/activesupport/test/dependencies_test.rb +++ b/activesupport/test/dependencies_test.rb @@ -16,15 +16,18 @@ module ModuleWithConstant end class DependenciesTest < ActiveSupport::TestCase - def teardown - ActiveSupport::Dependencies.clear + include DependenciesTestHelpers + + setup do + @loaded_features_copy = $LOADED_FEATURES.dup end - include DependenciesTestHelpers + teardown do + ActiveSupport::Dependencies.clear + $LOADED_FEATURES.replace(@loaded_features_copy) + end def test_depend_on_path - skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0' - expected = assert_raises(LoadError) do Kernel.require 'omgwtfbbq' end @@ -53,8 +56,7 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal 2, ActiveSupport::Dependencies.loaded.size end ensure - Object.send(:remove_const, :ServiceOne) if Object.const_defined?(:ServiceOne) - Object.send(:remove_const, :ServiceTwo) if Object.const_defined?(:ServiceTwo) + remove_constants(:ServiceOne, :ServiceTwo) end def test_tracking_identical_loaded_files @@ -64,11 +66,11 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal 1, ActiveSupport::Dependencies.loaded.size end ensure - Object.send(:remove_const, :ServiceOne) if Object.const_defined?(:ServiceOne) + remove_constants(:ServiceOne) end def test_missing_dependency_raises_missing_source_file - assert_raise(MissingSourceFile) { require_dependency("missing_service") } + assert_raise(LoadError) { require_dependency("missing_service") } end def test_dependency_which_raises_exception_isnt_added_to_loaded_set @@ -84,8 +86,8 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal 'Loading me failed, so do not add to loaded or history.', e.message assert_equal count + 1, $raises_exception_load_count - assert !ActiveSupport::Dependencies.loaded.include?(filename) - assert !ActiveSupport::Dependencies.history.include?(filename) + assert_not ActiveSupport::Dependencies.loaded.include?(filename) + assert_not ActiveSupport::Dependencies.history.include?(filename) end end end @@ -93,7 +95,6 @@ class DependenciesTest < ActiveSupport::TestCase def test_dependency_which_raises_doesnt_blindly_call_blame_file! with_loading do filename = 'dependencies/raises_exception_without_blame_file' - assert_raises(Exception) { require_dependency filename } end end @@ -101,13 +102,12 @@ class DependenciesTest < ActiveSupport::TestCase def test_warnings_should_be_enabled_on_first_load with_loading 'dependencies' do old_warnings, ActiveSupport::Dependencies.warnings_on_first_load = ActiveSupport::Dependencies.warnings_on_first_load, true - filename = "check_warnings" expanded = File.expand_path("#{File.dirname(__FILE__)}/dependencies/#{filename}") $check_warnings_load_count = 0 - assert !ActiveSupport::Dependencies.loaded.include?(expanded) - assert !ActiveSupport::Dependencies.history.include?(expanded) + assert_not ActiveSupport::Dependencies.loaded.include?(expanded) + assert_not ActiveSupport::Dependencies.history.include?(expanded) silence_warnings { require_dependency filename } assert_equal 1, $check_warnings_load_count @@ -115,7 +115,7 @@ class DependenciesTest < ActiveSupport::TestCase assert ActiveSupport::Dependencies.loaded.include?(expanded) ActiveSupport::Dependencies.clear - assert !ActiveSupport::Dependencies.loaded.include?(expanded) + assert_not ActiveSupport::Dependencies.loaded.include?(expanded) assert ActiveSupport::Dependencies.history.include?(expanded) silence_warnings { require_dependency filename } @@ -124,7 +124,7 @@ class DependenciesTest < ActiveSupport::TestCase assert ActiveSupport::Dependencies.loaded.include?(expanded) ActiveSupport::Dependencies.clear - assert !ActiveSupport::Dependencies.loaded.include?(expanded) + assert_not ActiveSupport::Dependencies.loaded.include?(expanded) assert ActiveSupport::Dependencies.history.include?(expanded) enable_warnings { require_dependency filename } @@ -157,12 +157,37 @@ class DependenciesTest < ActiveSupport::TestCase end end + def test_ensures_the_expected_constant_is_defined + with_autoloading_fixtures do + e = assert_raise(LoadError) { Typo } + assert_match %r{Unable to autoload constant Typo, expected .*/test/autoloading_fixtures/typo.rb to define it}, e.message + end + end + + def test_require_dependency_does_not_assume_any_particular_constant_is_defined + with_autoloading_fixtures do + require_dependency 'typo' + assert_equal 1, TypO + end + end + + # Regression, see https://github.com/rails/rails/issues/16468. + def test_require_dependency_interaction_with_autoloading + with_autoloading_fixtures do + require_dependency 'typo' + assert_equal 1, TypO + + e = assert_raise(LoadError) { Typo } + assert_match %r{Unable to autoload constant Typo, expected .*/test/autoloading_fixtures/typo.rb to define it}, e.message + end + end + def test_module_loading with_autoloading_fixtures do assert_kind_of Module, A assert_kind_of Class, A::B assert_kind_of Class, A::C::D - assert_kind_of Class, A::C::E::F + assert_kind_of Class, A::C::EM::F end end @@ -178,43 +203,49 @@ class DependenciesTest < ActiveSupport::TestCase def test_directories_manifest_as_modules_unless_const_defined with_autoloading_fixtures do assert_kind_of Module, ModuleFolder - Object.__send__ :remove_const, :ModuleFolder end + ensure + remove_constants(:ModuleFolder) end def test_module_with_nested_class with_autoloading_fixtures do assert_kind_of Class, ModuleFolder::NestedClass - Object.__send__ :remove_const, :ModuleFolder end + ensure + remove_constants(:ModuleFolder) end def test_module_with_nested_inline_class with_autoloading_fixtures do assert_kind_of Class, ModuleFolder::InlineClass - Object.__send__ :remove_const, :ModuleFolder end + ensure + remove_constants(:ModuleFolder) end def test_directories_may_manifest_as_nested_classes with_autoloading_fixtures do assert_kind_of Class, ClassFolder - Object.__send__ :remove_const, :ClassFolder end + ensure + remove_constants(:ClassFolder) end def test_class_with_nested_class with_autoloading_fixtures do assert_kind_of Class, ClassFolder::NestedClass - Object.__send__ :remove_const, :ClassFolder end + ensure + remove_constants(:ClassFolder) end def test_class_with_nested_inline_class with_autoloading_fixtures do assert_kind_of Class, ClassFolder::InlineClass - Object.__send__ :remove_const, :ClassFolder end + ensure + remove_constants(:ClassFolder) end def test_class_with_nested_inline_subclass_of_parent @@ -222,8 +253,9 @@ class DependenciesTest < ActiveSupport::TestCase assert_kind_of Class, ClassFolder::ClassFolderSubclass assert_kind_of Class, ClassFolder assert_equal 'indeed', ClassFolder::ClassFolderSubclass::ConstantInClassFolder - Object.__send__ :remove_const, :ClassFolder end + ensure + remove_constants(:ClassFolder) end def test_nested_class_can_access_sibling @@ -231,16 +263,15 @@ class DependenciesTest < ActiveSupport::TestCase sibling = ModuleFolder::NestedClass.class_eval "NestedSibling" assert defined?(ModuleFolder::NestedSibling) assert_equal ModuleFolder::NestedSibling, sibling - Object.__send__ :remove_const, :ModuleFolder end + ensure + remove_constants(:ModuleFolder) end def test_doesnt_break_normal_require path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) original_path = $:.dup - original_features = $".dup $:.push(path) - with_autoloading_fixtures do # The _ = assignments are to prevent warnings _ = RequiresConstant @@ -252,15 +283,13 @@ class DependenciesTest < ActiveSupport::TestCase assert defined?(LoadedConstant) end ensure - remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant) - $".replace(original_features) + remove_constants(:RequiresConstant, :LoadedConstant) $:.replace(original_path) end def test_doesnt_break_normal_require_nested path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) original_path = $:.dup - original_features = $".dup $:.push(path) with_autoloading_fixtures do @@ -275,14 +304,12 @@ class DependenciesTest < ActiveSupport::TestCase end ensure remove_constants(:RequiresConstant, :LoadedConstant, :LoadsConstant) - $".replace(original_features) $:.replace(original_path) end def test_require_returns_true_when_file_not_yet_required path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) original_path = $:.dup - original_features = $".dup $:.push(path) with_loading do @@ -290,14 +317,12 @@ class DependenciesTest < ActiveSupport::TestCase end ensure remove_constants(:LoadedConstant) - $".replace(original_features) $:.replace(original_path) end def test_require_returns_true_when_file_not_yet_required_even_when_no_new_constants_added path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) original_path = $:.dup - original_features = $".dup $:.push(path) with_loading do @@ -306,14 +331,12 @@ class DependenciesTest < ActiveSupport::TestCase end ensure remove_constants(:LoadedConstant) - $".replace(original_features) $:.replace(original_path) end def test_require_returns_false_when_file_already_required path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) original_path = $:.dup - original_features = $".dup $:.push(path) with_loading do @@ -322,7 +345,6 @@ class DependenciesTest < ActiveSupport::TestCase end ensure remove_constants(:LoadedConstant) - $".replace(original_features) $:.replace(original_path) end @@ -330,14 +352,11 @@ class DependenciesTest < ActiveSupport::TestCase with_loading do assert_raise(LoadError) { require 'this_file_dont_exist_dude' } end - ensure - remove_constants(:LoadedConstant) end def test_load_returns_true_when_file_found path = File.expand_path("../autoloading_fixtures/load_path", __FILE__) original_path = $:.dup - original_features = $".dup $:.push(path) with_loading do @@ -346,7 +365,6 @@ class DependenciesTest < ActiveSupport::TestCase end ensure remove_constants(:LoadedConstant) - $".replace(original_features) $:.replace(original_path) end @@ -354,17 +372,16 @@ class DependenciesTest < ActiveSupport::TestCase with_loading do assert_raise(LoadError) { load 'this_file_dont_exist_dude.rb' } end - ensure - remove_constants(:LoadedConstant) end def failing_test_access_thru_and_upwards_fails with_autoloading_fixtures do - assert ! defined?(ModuleFolder) + assert_not defined?(ModuleFolder) assert_raise(NameError) { ModuleFolder::Object } assert_raise(NameError) { ModuleFolder::NestedClass::Object } - Object.__send__ :remove_const, :ModuleFolder end + ensure + remove_constants(:ModuleFolder) end def test_non_existing_const_raises_name_error_with_fully_qualified_name @@ -377,6 +394,8 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal "uninitialized constant A::B::DoesNotExist", e.message assert_equal :DoesNotExist, e.name end + ensure + remove_constants(:A) end def test_smart_name_error_strings @@ -466,9 +485,9 @@ class DependenciesTest < ActiveSupport::TestCase nil_name = Module.new def nil_name.name() nil end assert !ActiveSupport::Dependencies.autoloaded?(nil_name) - - Object.class_eval { remove_const :ModuleFolder } end + ensure + remove_constants(:ModuleFolder) end def test_qualified_name_for @@ -526,37 +545,45 @@ class DependenciesTest < ActiveSupport::TestCase assert_kind_of Module, ::ModuleWithCustomConstMissing::A assert_kind_of String, ::ModuleWithCustomConstMissing::A::B end + ensure + remove_constants(:ModuleWithCustomConstMissing) end def test_const_missing_in_anonymous_modules_loads_top_level_constants with_autoloading_fixtures do # class_eval STRING pushes the class to the nesting of the eval'ed code. - klass = Class.new.class_eval "E" - assert_equal E, klass + klass = Class.new.class_eval "EM" + assert_equal EM, klass end + ensure + remove_constants(:EM) end def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object with_autoloading_fixtures do - require_dependency 'e' + require_dependency 'em' mod = Module.new - e = assert_raise(NameError) { mod::E } - assert_equal 'E cannot be autoloaded from an anonymous class or module', e.message - assert_equal :E, e.name + e = assert_raise(NameError) { mod::EM } + assert_equal 'EM cannot be autoloaded from an anonymous class or module', e.message + assert_equal :EM, e.name end + ensure + remove_constants(:EM) end def test_removal_from_tree_should_be_detected with_loading 'dependencies' do c = ServiceOne ActiveSupport::Dependencies.clear - assert ! defined?(ServiceOne) + assert_not defined?(ServiceOne) e = assert_raise ArgumentError do ActiveSupport::Dependencies.load_missing_constant(c, :FakeMissing) end assert_match %r{ServiceOne has been removed from the module tree}i, e.message end + ensure + remove_constants(:ServiceOne) end def test_references_should_work @@ -565,42 +592,46 @@ class DependenciesTest < ActiveSupport::TestCase service_one_first = ServiceOne assert_equal service_one_first, c.get("ServiceOne") ActiveSupport::Dependencies.clear - assert ! defined?(ServiceOne) - + assert_not defined?(ServiceOne) service_one_second = ServiceOne assert_not_equal service_one_first, c.get("ServiceOne") assert_equal service_one_second, c.get("ServiceOne") end + ensure + remove_constants(:ServiceOne) end def test_constantize_shortcut_for_cached_constant_lookups with_loading 'dependencies' do assert_equal ServiceOne, ActiveSupport::Dependencies.constantize("ServiceOne") end + ensure + remove_constants(:ServiceOne) end def test_nested_load_error_isnt_rescued with_loading 'dependencies' do - assert_raise(MissingSourceFile) do + assert_raise(LoadError) do RequiresNonexistent1 end end end def test_autoload_once_paths_do_not_add_to_autoloaded_constants + old_path = ActiveSupport::Dependencies.autoload_once_paths with_autoloading_fixtures do ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths.dup - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder") - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder) + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") + assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder) 1 if ModuleFolder::NestedClass # 1 if to avoid warning - assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) + assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) end ensure - Object.class_eval { remove_const :ModuleFolder } - ActiveSupport::Dependencies.autoload_once_paths = [] + remove_constants(:ModuleFolder) + ActiveSupport::Dependencies.autoload_once_paths = old_path end def test_autoload_once_pathnames_do_not_add_to_autoloaded_constants @@ -609,15 +640,15 @@ class DependenciesTest < ActiveSupport::TestCase ActiveSupport::Dependencies.autoload_paths = pathnames ActiveSupport::Dependencies.autoload_once_paths = pathnames - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder") - assert ! ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") - assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder) + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder") + assert_not ActiveSupport::Dependencies.autoloaded?("ModuleFolder::NestedClass") + assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder) 1 if ModuleFolder::NestedClass # 1 if to avoid warning - assert ! ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) + assert_not ActiveSupport::Dependencies.autoloaded?(ModuleFolder::NestedClass) end ensure - Object.class_eval { remove_const :ModuleFolder } + remove_constants(:ModuleFolder) ActiveSupport::Dependencies.autoload_once_paths = [] end @@ -627,24 +658,25 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal 10, ApplicationController assert ActiveSupport::Dependencies.autoloaded?(:ApplicationController) end + ensure + remove_constants(:ApplicationController) end def test_preexisting_constants_are_not_marked_as_autoloaded with_autoloading_fixtures do - require_dependency 'e' - assert ActiveSupport::Dependencies.autoloaded?(:E) + require_dependency 'em' + assert ActiveSupport::Dependencies.autoloaded?(:EM) ActiveSupport::Dependencies.clear end - Object.const_set :E, Class.new + Object.const_set :EM, Class.new with_autoloading_fixtures do - require_dependency 'e' - assert ! ActiveSupport::Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!" + require_dependency 'em' + assert ! ActiveSupport::Dependencies.autoloaded?(:EM), "EM shouldn't be marked autoloaded!" ActiveSupport::Dependencies.clear end - ensure - Object.class_eval { remove_const :E } + remove_constants(:EM) end def test_constants_in_capitalized_nesting_marked_as_autoloaded @@ -653,6 +685,8 @@ class DependenciesTest < ActiveSupport::TestCase assert ActiveSupport::Dependencies.autoloaded?("HTML::SomeClass") end + ensure + remove_constants(:HTML) end def test_unloadable @@ -683,18 +717,19 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal false, M.unloadable end ensure - Object.class_eval { remove_const :M } + remove_constants(:M) end def test_unloadable_constants_should_receive_callback - Object.const_set :C, Class.new + Object.const_set :C, Class.new { def self.before_remove_const; end } C.unloadable - C.expects(:before_remove_const).once - assert C.respond_to?(:before_remove_const) - ActiveSupport::Dependencies.clear - assert !defined?(C) + assert_called(C, :before_remove_const, times: 1) do + assert C.respond_to?(:before_remove_const) + ActiveSupport::Dependencies.clear + assert !defined?(C) + end ensure - Object.class_eval { remove_const :C } if defined?(C) + remove_constants(:C) end def test_new_contants_in_without_constants @@ -708,7 +743,7 @@ class DependenciesTest < ActiveSupport::TestCase }.map(&:to_s) assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } ensure - Object.class_eval { remove_const :Hello } + remove_constants(:Hello) end def test_new_constants_in_with_nesting @@ -725,9 +760,7 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal ["OuterAfter", "OuterBefore"], outer.sort.map(&:to_s) assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } ensure - %w(OuterBefore Inner OuterAfter).each do |name| - Object.class_eval { remove_const name if const_defined?(name) } - end + remove_constants(:OuterBefore, :Inner, :OuterAfter) end def test_new_constants_in_module @@ -746,7 +779,7 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } ensure - Object.class_eval { remove_const :M } + remove_constants(:M) end def test_new_constants_in_module_using_name @@ -764,7 +797,7 @@ class DependenciesTest < ActiveSupport::TestCase assert_equal ["M::OuterAfter", "M::OuterBefore"], outer.sort assert ActiveSupport::Dependencies.constant_watch_stack.all? {|k,v| v.empty? } ensure - Object.class_eval { remove_const :M } + remove_constants(:M) end def test_new_constants_in_with_inherited_constants @@ -782,26 +815,27 @@ class DependenciesTest < ActiveSupport::TestCase def test_file_with_multiple_constants_and_require_dependency with_autoloading_fixtures do - assert ! defined?(MultipleConstantFile) - assert ! defined?(SiblingConstant) + assert_not defined?(MultipleConstantFile) + assert_not defined?(SiblingConstant) require_dependency 'multiple_constant_file' assert defined?(MultipleConstantFile) assert defined?(SiblingConstant) assert ActiveSupport::Dependencies.autoloaded?(:MultipleConstantFile) assert ActiveSupport::Dependencies.autoloaded?(:SiblingConstant) - ActiveSupport::Dependencies.clear - assert ! defined?(MultipleConstantFile) - assert ! defined?(SiblingConstant) + assert_not defined?(MultipleConstantFile) + assert_not defined?(SiblingConstant) end + ensure + remove_constants(:MultipleConstantFile, :SiblingConstant) end def test_file_with_multiple_constants_and_auto_loading with_autoloading_fixtures do - assert ! defined?(MultipleConstantFile) - assert ! defined?(SiblingConstant) + assert_not defined?(MultipleConstantFile) + assert_not defined?(SiblingConstant) assert_equal 10, MultipleConstantFile @@ -812,15 +846,17 @@ class DependenciesTest < ActiveSupport::TestCase ActiveSupport::Dependencies.clear - assert ! defined?(MultipleConstantFile) - assert ! defined?(SiblingConstant) + assert_not defined?(MultipleConstantFile) + assert_not defined?(SiblingConstant) end + ensure + remove_constants(:MultipleConstantFile, :SiblingConstant) end def test_nested_file_with_multiple_constants_and_require_dependency with_autoloading_fixtures do - assert ! defined?(ClassFolder::NestedClass) - assert ! defined?(ClassFolder::SiblingClass) + assert_not defined?(ClassFolder::NestedClass) + assert_not defined?(ClassFolder::SiblingClass) require_dependency 'class_folder/nested_class' @@ -831,15 +867,17 @@ class DependenciesTest < ActiveSupport::TestCase ActiveSupport::Dependencies.clear - assert ! defined?(ClassFolder::NestedClass) - assert ! defined?(ClassFolder::SiblingClass) + assert_not defined?(ClassFolder::NestedClass) + assert_not defined?(ClassFolder::SiblingClass) end + ensure + remove_constants(:ClassFolder) end def test_nested_file_with_multiple_constants_and_auto_loading with_autoloading_fixtures do - assert ! defined?(ClassFolder::NestedClass) - assert ! defined?(ClassFolder::SiblingClass) + assert_not defined?(ClassFolder::NestedClass) + assert_not defined?(ClassFolder::SiblingClass) assert_kind_of Class, ClassFolder::NestedClass @@ -850,9 +888,11 @@ class DependenciesTest < ActiveSupport::TestCase ActiveSupport::Dependencies.clear - assert ! defined?(ClassFolder::NestedClass) - assert ! defined?(ClassFolder::SiblingClass) + assert_not defined?(ClassFolder::NestedClass) + assert_not defined?(ClassFolder::SiblingClass) end + ensure + remove_constants(:ClassFolder) end def test_autoload_doesnt_shadow_no_method_error_with_relative_constant @@ -863,9 +903,8 @@ class DependenciesTest < ActiveSupport::TestCase assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!" end end - ensure - Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) } + remove_constants(:RaisesNoMethodError) end def test_autoload_doesnt_shadow_no_method_error_with_absolute_constant @@ -876,9 +915,8 @@ class DependenciesTest < ActiveSupport::TestCase assert !defined?(::RaisesNoMethodError), "::RaisesNoMethodError is defined but it should have failed!" end end - ensure - Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) } + remove_constants(:RaisesNoMethodError) end def test_autoload_doesnt_shadow_error_when_mechanism_not_set_to_load @@ -894,7 +932,6 @@ class DependenciesTest < ActiveSupport::TestCase def test_autoload_doesnt_shadow_name_error with_autoloading_fixtures do - Object.send(:remove_const, :RaisesNameError) if defined?(::RaisesNameError) 2.times do e = assert_raise NameError do ::RaisesNameError::FooBarBaz.object_id @@ -909,19 +946,20 @@ class DependenciesTest < ActiveSupport::TestCase assert !defined?(::RaisesNameError), "::RaisesNameError is defined but it should have failed!" end end - ensure - Object.class_eval { remove_const :RaisesNoMethodError if const_defined?(:RaisesNoMethodError) } + remove_constants(:RaisesNameError) end def test_remove_constant_handles_double_colon_at_start Object.const_set 'DeleteMe', Module.new DeleteMe.const_set 'OrMe', Module.new ActiveSupport::Dependencies.remove_constant "::DeleteMe::OrMe" - assert ! defined?(DeleteMe::OrMe) + assert_not defined?(DeleteMe::OrMe) assert defined?(DeleteMe) ActiveSupport::Dependencies.remove_constant "::DeleteMe" - assert ! defined?(DeleteMe) + assert_not defined?(DeleteMe) + ensure + remove_constants(:DeleteMe) end def test_remove_constant_does_not_trigger_loading_autoloads @@ -931,7 +969,9 @@ class DependenciesTest < ActiveSupport::TestCase end assert_nil ActiveSupport::Dependencies.remove_constant(constant), "Kernel#autoload has been triggered by remove_constant" - assert !defined?(ShouldNotBeAutoloaded) + assert_not defined?(ShouldNotBeAutoloaded) + ensure + remove_constants(constant) end def test_remove_constant_does_not_autoload_already_removed_parents_as_a_side_effect @@ -940,11 +980,14 @@ class DependenciesTest < ActiveSupport::TestCase _ = ::A::B # assignment to silence parse-time warning "possibly useless use of :: in void context" ActiveSupport::Dependencies.remove_constant('A') ActiveSupport::Dependencies.remove_constant('A::B') - assert !defined?(A) + assert_not defined?(A) end + ensure + remove_constants(:A) end def test_load_once_constants_should_not_be_unloaded + old_path = ActiveSupport::Dependencies.autoload_once_paths with_autoloading_fixtures do ActiveSupport::Dependencies.autoload_once_paths = ActiveSupport::Dependencies.autoload_paths _ = ::A # assignment to silence parse-time warning "possibly useless use of :: in void context" @@ -953,8 +996,8 @@ class DependenciesTest < ActiveSupport::TestCase assert defined?(A) end ensure - ActiveSupport::Dependencies.autoload_once_paths = [] - Object.class_eval { remove_const :A if const_defined?(:A) } + ActiveSupport::Dependencies.autoload_once_paths = old_path + remove_constants(:A) end def test_access_unloaded_constants_for_reload @@ -966,29 +1009,45 @@ class DependenciesTest < ActiveSupport::TestCase A::B # Make sure no circular dependency error end + ensure + remove_constants(:A) end def test_autoload_once_paths_should_behave_when_recursively_loading + old_path = ActiveSupport::Dependencies.autoload_once_paths with_loading 'dependencies', 'autoloading_fixtures' do ActiveSupport::Dependencies.autoload_once_paths = [ActiveSupport::Dependencies.autoload_paths.last] - assert !defined?(CrossSiteDependency) + assert_not defined?(CrossSiteDependency) assert_nothing_raised { CrossSiteDepender.nil? } assert defined?(CrossSiteDependency) - assert !ActiveSupport::Dependencies.autoloaded?(CrossSiteDependency), + assert_not ActiveSupport::Dependencies.autoloaded?(CrossSiteDependency), "CrossSiteDependency shouldn't be marked as autoloaded!" ActiveSupport::Dependencies.clear assert defined?(CrossSiteDependency), "CrossSiteDependency shouldn't have been unloaded!" end ensure - ActiveSupport::Dependencies.autoload_once_paths = [] + ActiveSupport::Dependencies.autoload_once_paths = old_path + remove_constants(:CrossSiteDependency) end def test_hook_called_multiple_times assert_nothing_raised { ActiveSupport::Dependencies.hook! } end + def test_load_and_require_stay_private + assert Object.private_methods.include?(:load) + assert Object.private_methods.include?(:require) + + ActiveSupport::Dependencies.unhook! + + assert Object.private_methods.include?(:load) + assert Object.private_methods.include?(:require) + ensure + ActiveSupport::Dependencies.hook! + end + def test_unhook ActiveSupport::Dependencies.unhook! assert !Module.new.respond_to?(:const_missing_without_dependencies) diff --git a/activesupport/test/dependencies_test_helpers.rb b/activesupport/test/dependencies_test_helpers.rb index 9268512a97..e4d5197112 100644 --- a/activesupport/test/dependencies_test_helpers.rb +++ b/activesupport/test/dependencies_test_helpers.rb @@ -13,6 +13,7 @@ module DependenciesTestHelpers ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths ActiveSupport::Dependencies.mechanism = old_mechanism ActiveSupport::Dependencies.explicitly_unloadable_constants = [] + ActiveSupport::Dependencies.clear end def with_autoloading_fixtures(&block) diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb index 7aff56cbad..7e8844b301 100644 --- a/activesupport/test/deprecation_test.rb +++ b/activesupport/test/deprecation_test.rb @@ -1,4 +1,5 @@ require 'abstract_unit' +require 'active_support/testing/stream' class Deprecatee def initialize @@ -36,6 +37,8 @@ end class DeprecationTest < ActiveSupport::TestCase + include ActiveSupport::Testing::Stream + def setup # Track the last warning. @old_behavior = ActiveSupport::Deprecation.behavior @@ -253,17 +256,17 @@ class DeprecationTest < ActiveSupport::TestCase end def test_deprecate_with_custom_deprecator - custom_deprecator = mock('Deprecator') do - expects(:deprecation_warning) - end + custom_deprecator = Struct.new(:deprecation_warning).new - klass = Class.new do - def method + assert_called_with(custom_deprecator, :deprecation_warning, [:method, nil]) do + klass = Class.new do + def method + end + deprecate :method, deprecator: custom_deprecator end - deprecate :method, deprecator: custom_deprecator - end - klass.new.method + klass.new.method + end end def test_deprecated_constant_with_deprecator_given @@ -356,20 +359,4 @@ class DeprecationTest < ActiveSupport::TestCase deprecator end - def capture(stream) - stream = stream.to_s - captured_stream = Tempfile.new(stream) - stream_io = eval("$#{stream}") - origin_stream = stream_io.dup - stream_io.reopen(captured_stream) - - yield - - stream_io.rewind - return captured_stream.read - ensure - captured_stream.close - captured_stream.unlink - stream_io.reopen(origin_stream) - end end diff --git a/activesupport/test/file_fixtures/sample.txt b/activesupport/test/file_fixtures/sample.txt new file mode 100644 index 0000000000..0fa80e7383 --- /dev/null +++ b/activesupport/test/file_fixtures/sample.txt @@ -0,0 +1 @@ +sample file fixture diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb deleted file mode 100644 index 843994147b..0000000000 --- a/activesupport/test/hash_with_indifferent_access_test.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'abstract_unit' -require 'active_support/hash_with_indifferent_access' - -class HashWithIndifferentAccessTest < ActiveSupport::TestCase - def test_reverse_merge - hash = HashWithIndifferentAccess.new key: :old_value - hash.reverse_merge! key: :new_value - assert_equal :old_value, hash[:key] - end -end
\ No newline at end of file diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb index be68bb2e2e..a0764f6d6b 100644 --- a/activesupport/test/inflector_test.rb +++ b/activesupport/test/inflector_test.rb @@ -8,6 +8,20 @@ class InflectorTest < ActiveSupport::TestCase include InflectorTestCases include ConstantizeTestCases + def setup + # Dups the singleton before each test, restoring the original inflections later. + # + # This helper is implemented by setting @__instance__ because in some tests + # there are module functions that access ActiveSupport::Inflector.inflections, + # so we need to replace the singleton itself. + @original_inflections = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en] + ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: @original_inflections.dup) + end + + def teardown + ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: @original_inflections) + end + def test_pluralize_plurals assert_equal "plurals", ActiveSupport::Inflector.pluralize("plurals") assert_equal "Plurals", ActiveSupport::Inflector.pluralize("Plurals") @@ -26,20 +40,18 @@ class InflectorTest < ActiveSupport::TestCase end def test_uncountable_word_is_not_greedy - with_dup do - uncountable_word = "ors" - countable_word = "sponsor" + uncountable_word = "ors" + countable_word = "sponsor" - ActiveSupport::Inflector.inflections.uncountable << uncountable_word + ActiveSupport::Inflector.inflections.uncountable << uncountable_word - assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word) - assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word) - assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word) + assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word) + assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word) + assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word) - assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word) - assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word) - assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word)) - end + assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word) + assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word) + assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word)) end SingularToPlural.each do |singular, plural| @@ -70,11 +82,9 @@ class InflectorTest < ActiveSupport::TestCase def test_overwrite_previous_inflectors - with_dup do - assert_equal("series", ActiveSupport::Inflector.singularize("series")) - ActiveSupport::Inflector.inflections.singular "series", "serie" - assert_equal("serie", ActiveSupport::Inflector.singularize("series")) - end + assert_equal("series", ActiveSupport::Inflector.singularize("series")) + ActiveSupport::Inflector.inflections.singular "series", "serie" + assert_equal("serie", ActiveSupport::Inflector.singularize("series")) end MixtureToTitleCase.each_with_index do |(before, titleized), index| @@ -367,10 +377,8 @@ class InflectorTest < ActiveSupport::TestCase %w{plurals singulars uncountables humans}.each do |inflection_type| class_eval <<-RUBY, __FILE__, __LINE__ + 1 def test_clear_#{inflection_type} - with_dup do - ActiveSupport::Inflector.inflections.clear :#{inflection_type} - assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\" - end + ActiveSupport::Inflector.inflections.clear :#{inflection_type} + assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\" end RUBY end @@ -405,73 +413,63 @@ class InflectorTest < ActiveSupport::TestCase end def test_clear_all - with_dup do - ActiveSupport::Inflector.inflections do |inflect| - # ensure any data is present - inflect.plural(/(quiz)$/i, '\1zes') - inflect.singular(/(database)s$/i, '\1') - inflect.uncountable('series') - inflect.human("col_rpted_bugs", "Reported bugs") - - inflect.clear :all - - assert inflect.plurals.empty? - assert inflect.singulars.empty? - assert inflect.uncountables.empty? - assert inflect.humans.empty? - end + ActiveSupport::Inflector.inflections do |inflect| + # ensure any data is present + inflect.plural(/(quiz)$/i, '\1zes') + inflect.singular(/(database)s$/i, '\1') + inflect.uncountable('series') + inflect.human("col_rpted_bugs", "Reported bugs") + + inflect.clear :all + + assert inflect.plurals.empty? + assert inflect.singulars.empty? + assert inflect.uncountables.empty? + assert inflect.humans.empty? end end def test_clear_with_default - with_dup do - ActiveSupport::Inflector.inflections do |inflect| - # ensure any data is present - inflect.plural(/(quiz)$/i, '\1zes') - inflect.singular(/(database)s$/i, '\1') - inflect.uncountable('series') - inflect.human("col_rpted_bugs", "Reported bugs") - - inflect.clear - - assert inflect.plurals.empty? - assert inflect.singulars.empty? - assert inflect.uncountables.empty? - assert inflect.humans.empty? - end + ActiveSupport::Inflector.inflections do |inflect| + # ensure any data is present + inflect.plural(/(quiz)$/i, '\1zes') + inflect.singular(/(database)s$/i, '\1') + inflect.uncountable('series') + inflect.human("col_rpted_bugs", "Reported bugs") + + inflect.clear + + assert inflect.plurals.empty? + assert inflect.singulars.empty? + assert inflect.uncountables.empty? + assert inflect.humans.empty? end end 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 + 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 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 + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular(singular, plural) + assert_equal plural, ActiveSupport::Inflector.pluralize(plural) end end end 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 + ActiveSupport::Inflector.inflections do |inflect| + inflect.irregular(singular, plural) + assert_equal singular, ActiveSupport::Inflector.singularize(singular) end end end @@ -503,12 +501,10 @@ class InflectorTest < ActiveSupport::TestCase %w(plurals singulars uncountables humans acronyms).each do |scope| define_method("test_clear_inflections_with_#{scope}") do - with_dup do - # clear the inflections - ActiveSupport::Inflector.inflections do |inflect| - inflect.clear(scope) - assert_equal [], inflect.send(scope) - end + # clear the inflections + ActiveSupport::Inflector.inflections do |inflect| + inflect.clear(scope) + assert_equal [], inflect.send(scope) end end end @@ -520,18 +516,4 @@ class InflectorTest < ActiveSupport::TestCase assert_equal "HTTP", ActiveSupport::Inflector.pluralize("HTTP") end - - # Dups the singleton and yields, restoring the original inflections later. - # Use this in tests what modify the state of the singleton. - # - # This helper is implemented by setting @__instance__ because in some tests - # there are module functions that access ActiveSupport::Inflector.inflections, - # so we need to replace the singleton itself. - def with_dup - original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en] - ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup) - yield - ensure - ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original) - end end diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb index 3770f00fe3..18a8b92eb9 100644 --- a/activesupport/test/inflector_test_cases.rb +++ b/activesupport/test/inflector_test_cases.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 module InflectorTestCases SingularToPlural = { diff --git a/activesupport/test/json/decoding_test.rb b/activesupport/test/json/decoding_test.rb index 80bf255080..f2fc456f4b 100644 --- a/activesupport/test/json/decoding_test.rb +++ b/activesupport/test/json/decoding_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'abstract_unit' require 'active_support/json' require 'active_support/time' diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb index ad358ad21d..9f4b62fd8b 100644 --- a/activesupport/test/json/encoding_test.rb +++ b/activesupport/test/json/encoding_test.rb @@ -1,126 +1,27 @@ -# encoding: utf-8 require 'securerandom' require 'abstract_unit' require 'active_support/core_ext/string/inflections' require 'active_support/json' require 'active_support/time' require 'time_zone_test_helpers' +require 'json/encoding_test_cases' class TestJSONEncoding < ActiveSupport::TestCase include TimeZoneTestHelpers - class Foo - def initialize(a, b) - @a, @b = a, b - end - end - - class Hashlike - def to_hash - { :foo => "hello", :bar => "world" } - end - end - - class Custom - def initialize(serialized) - @serialized = serialized - end - - def as_json(options = nil) - @serialized - end - end - - class CustomWithOptions - attr_accessor :foo, :bar - - def as_json(options={}) - options[:only] = %w(foo bar) - super(options) - end - end - - class OptionsTest - def as_json(options = :default) - options - end - end - - class HashWithAsJson < Hash - attr_accessor :as_json_called - - def initialize(*) - super - end - - def as_json(options={}) - @as_json_called = true - super - end - end - - TrueTests = [[ true, %(true) ]] - FalseTests = [[ false, %(false) ]] - NilTests = [[ nil, %(null) ]] - NumericTests = [[ 1, %(1) ], - [ 2.5, %(2.5) ], - [ 0.0/0.0, %(null) ], - [ 1.0/0.0, %(null) ], - [ -1.0/0.0, %(null) ], - [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ], - [ BigDecimal('2.5'), %("#{BigDecimal('2.5').to_s}") ]] - - StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")], - [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ], - [ 'http://test.host/posts/1', %("http://test.host/posts/1")], - [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029", - %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]] - - ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ], - [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]] - - RangeTests = [[ 1..2, %("1..2")], - [ 1...2, %("1...2")], - [ 1.5..2.5, %("1.5..2.5")]] - - SymbolTests = [[ :a, %("a") ], - [ :this, %("this") ], - [ :"a b", %("a b") ]] - - ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]] - HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]] - CustomTests = [[ Custom.new("custom"), '"custom"' ], - [ Custom.new(nil), 'null' ], - [ Custom.new(:a), '"a"' ], - [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ], - [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ], - [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ], - [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]] - - RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']] - - DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]] - TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]] - 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: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) return json unless json =~ /^\{.*\}$/ '{' + json[1..-2].split(',').sort.join(',') + '}' end - constants.grep(/Tests$/).each do |class_tests| + JSONTest::EncodingTestCases.constants.each do |class_tests| define_method("test_#{class_tests[0..-6].underscore}") do begin prev = ActiveSupport.use_standard_json_time_format ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/ ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/ - self.class.const_get(class_tests).each do |pair| + JSONTest::EncodingTestCases.const_get(class_tests).each do |pair| assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first)) end ensure @@ -131,6 +32,8 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_process_status + rubinius_skip "https://github.com/rubinius/rubinius/issues/3334" + # 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}") @@ -146,6 +49,13 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d)) end + def test_hash_keys_encoding + ActiveSupport.escape_html_entities_in_json = true + assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>") + ensure + ActiveSupport.escape_html_entities_in_json = false + end + def test_utf8_string_encoded_properly result = ActiveSupport::JSON.encode('€2.99') assert_equal '"€2.99"', result @@ -176,46 +86,6 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal "𐒑", decoded_hash['string'] end - def test_reading_encode_big_decimal_as_string_option - assert_deprecated do - assert ActiveSupport.encode_big_decimal_as_string - end - end - - def test_setting_deprecated_encode_big_decimal_as_string_option - assert_raise(NotImplementedError) do - ActiveSupport.encode_big_decimal_as_string = true - end - - assert_raise(NotImplementedError) do - ActiveSupport.encode_big_decimal_as_string = false - end - end - - def test_exception_raised_when_encoding_circular_reference_in_array - a = [1] - a << 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_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_deprecated do - assert_raise(ActiveSupport::JSON::Encoding::CircularReferenceError) { ActiveSupport::JSON.encode(a) } - end - end - def test_hash_key_identifiers_are_always_quoted values = {0 => 0, 1 => 1, :_ => :_, "$" => "$", "a" => "a", :A => :A, :A0 => :A0, "A0B" => "A0B"} assert_equal %w( "$" "A" "A0" "A0B" "_" "a" "0" "1" ).sort, object_keys(ActiveSupport::JSON.encode(values)) @@ -256,7 +126,7 @@ class TestJSONEncoding < ActiveSupport::TestCase end def test_hash_like_with_options - h = Hashlike.new + h = JSONTest::Hashlike.new json = h.to_json :only => [:foo] assert_equal({"foo"=>"hello"}, JSON.parse(json)) @@ -377,6 +247,15 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json) end + class CustomWithOptions + attr_accessor :foo, :bar + + def as_json(options={}) + options[:only] = %w(foo bar) + super(options) + end + end + def test_hash_to_json_should_not_keep_options_around f = CustomWithOptions.new f.foo = "hello" @@ -397,6 +276,12 @@ class TestJSONEncoding < ActiveSupport::TestCase {"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json)) end + class OptionsTest + def as_json(options = :default) + options + end + end + def test_hash_as_json_without_options json = { foo: OptionsTest.new }.as_json assert_equal({"foo" => :default}, json) @@ -444,6 +329,19 @@ class TestJSONEncoding < ActiveSupport::TestCase assert_equal false, false.as_json end + class HashWithAsJson < Hash + attr_accessor :as_json_called + + def initialize(*) + super + end + + def as_json(options={}) + @as_json_called = true + super + end + end + def test_json_gem_dump_by_passing_active_support_encoder h = HashWithAsJson.new h[:foo] = "hello" diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb new file mode 100644 index 0000000000..0159ba8606 --- /dev/null +++ b/activesupport/test/json/encoding_test_cases.rb @@ -0,0 +1,88 @@ +require 'bigdecimal' + +module JSONTest + class Foo + def initialize(a, b) + @a, @b = a, b + end + end + + class Hashlike + def to_hash + { :foo => "hello", :bar => "world" } + end + end + + class Custom + def initialize(serialized) + @serialized = serialized + end + + def as_json(options = nil) + @serialized + end + end + + class MyStruct < Struct.new(:name, :value) + def initialize(*) + @unused = "unused instance variable" + super + end + end + + module EncodingTestCases + TrueTests = [[ true, %(true) ]] + FalseTests = [[ false, %(false) ]] + NilTests = [[ nil, %(null) ]] + NumericTests = [[ 1, %(1) ], + [ 2.5, %(2.5) ], + [ 0.0/0.0, %(null) ], + [ 1.0/0.0, %(null) ], + [ -1.0/0.0, %(null) ], + [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ], + [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]] + + StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")], + [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ], + [ 'http://test.host/posts/1', %("http://test.host/posts/1")], + [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029", + %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]] + + ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ], + [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]] + + HashTests = [[ {foo: "bar"}, %({\"foo\":\"bar\"}) ], + [ {1 => 1, 2 => 'a', 3 => :b, 4 => nil, 5 => false}, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]] + + RangeTests = [[ 1..2, %("1..2")], + [ 1...2, %("1...2")], + [ 1.5..2.5, %("1.5..2.5")]] + + SymbolTests = [[ :a, %("a") ], + [ :this, %("this") ], + [ :"a b", %("a b") ]] + + ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]] + HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]] + StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ], + [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]] + CustomTests = [[ Custom.new("custom"), '"custom"' ], + [ Custom.new(nil), 'null' ], + [ Custom.new(:a), '"a"' ], + [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ], + [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ], + [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ], + [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]] + + RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']] + + DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]] + TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]] + 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: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>")]] + end +end diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb index 2a0e8d20ed..998a6887c5 100644 --- a/activesupport/test/log_subscriber_test.rb +++ b/activesupport/test/log_subscriber_test.rb @@ -82,10 +82,11 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase def test_does_not_send_the_event_if_logger_is_nil ActiveSupport::LogSubscriber.logger = nil - @log_subscriber.expects(:some_event).never - ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber - instrument "some_event.my_log_subscriber" - wait + assert_not_called(@log_subscriber, :some_event) do + ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber + instrument "some_event.my_log_subscriber" + wait + end end def test_does_not_fail_with_non_namespaced_events diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb index b6c0a08b05..eb71369397 100644 --- a/activesupport/test/message_encryptor_test.rb +++ b/activesupport/test/message_encryptor_test.rb @@ -1,12 +1,5 @@ require 'abstract_unit' - -begin - require 'openssl' - OpenSSL::Digest::SHA1 -rescue LoadError, NameError - $stderr.puts "Skipping MessageEncryptor test: broken OpenSSL install" -else - +require 'openssl' require 'active_support/time' require 'active_support/json' @@ -97,5 +90,3 @@ class MessageEncryptorTest < ActiveSupport::TestCase ::Base64.strict_encode64(bits) end end - -end diff --git a/activesupport/test/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb index 28035bc428..668d78492e 100644 --- a/activesupport/test/message_verifier_test.rb +++ b/activesupport/test/message_verifier_test.rb @@ -1,12 +1,5 @@ require 'abstract_unit' - -begin - require 'openssl' - OpenSSL::Digest::SHA1 -rescue LoadError, NameError - $stderr.puts "Skipping MessageVerifier test: broken OpenSSL install" -else - +require 'openssl' require 'active_support/time' require 'active_support/json' @@ -27,21 +20,30 @@ class MessageVerifierTest < ActiveSupport::TestCase @data = { :some => "data", :now => Time.local(2010) } end + def test_valid_message + data, hash = @verifier.generate(@data).split("--") + assert !@verifier.valid_message?(nil) + assert !@verifier.valid_message?("") + assert !@verifier.valid_message?("\xff") # invalid encoding + assert !@verifier.valid_message?("#{data.reverse}--#{hash}") + assert !@verifier.valid_message?("#{data}--#{hash.reverse}") + assert !@verifier.valid_message?("purejunk") + end + def test_simple_round_tripping message = @verifier.generate(@data) + assert_equal @data, @verifier.verified(message) assert_equal @data, @verifier.verify(message) end - def test_missing_signature_raises - assert_not_verified(nil) - assert_not_verified("") + def test_verified_returns_false_on_invalid_message + assert !@verifier.verified("purejunk") end - def test_tampered_data_raises - data, hash = @verifier.generate(@data).split("--") - assert_not_verified("#{data.reverse}--#{hash}") - assert_not_verified("#{data}--#{hash.reverse}") - assert_not_verified("purejunk") + def test_verify_exception_on_invalid_message + assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do + @verifier.verify("purejunk") + end end def test_alternative_serialization_method @@ -50,6 +52,7 @@ class MessageVerifierTest < ActiveSupport::TestCase 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:00.000Z" } + assert_equal exp, verifier.verified(message) assert_equal exp, verifier.verify(message) ensure ActiveSupport.use_standard_json_time_format = prev @@ -63,6 +66,11 @@ class MessageVerifierTest < ActiveSupport::TestCase # valid_message = "BAh7BjoIZm9vbzonTWVzc2FnZVZlcmlmaWVyVGVzdDo6QXV0b2xvYWRDbGFzcwY6CUBmb29JIghmb28GOgZFVA==--f3ef39a5241c365083770566dc7a9eb5d6ace914" exception = assert_raise(ArgumentError, NameError) do + @verifier.verified(valid_message) + end + assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass", + "undefined class/module MessageVerifierTest::AutoloadClass"], exception.message + exception = assert_raise(ArgumentError, NameError) do @verifier.verify(valid_message) end assert_includes ["uninitialized constant MessageVerifierTest::AutoloadClass", @@ -75,12 +83,4 @@ class MessageVerifierTest < ActiveSupport::TestCase end assert_equal exception.message, 'Secret should not be nil.' end - - def assert_not_verified(message) - assert_raise(ActiveSupport::MessageVerifier::InvalidSignature) do - @verifier.verify(message) - end - end -end - end diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb index feca013675..e1c4b705f8 100644 --- a/activesupport/test/multibyte_chars_test.rb +++ b/activesupport/test/multibyte_chars_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'abstract_unit' require 'multibyte_test_helpers' require 'active_support/core_ext/string/multibyte' @@ -182,7 +181,7 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase end def test_sortability - words = %w(builder armor zebra).sort_by { |s| s.mb_chars } + words = %w(builder armor zebra).sort_by(&:mb_chars) assert_equal %w(armor builder zebra), words end diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb index aba81b8248..d8704716e7 100644 --- a/activesupport/test/multibyte_conformance_test.rb +++ b/activesupport/test/multibyte_conformance_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'abstract_unit' require 'multibyte_test_helpers' @@ -115,7 +114,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase next if (line.empty? || line =~ /^\#/) cols, comment = line.split("#") - cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? } + cols = cols.split(";").map(&:strip).reject(&:empty?) next unless cols.length == 5 # codepoints are in hex in the test suite, pack wants them as integers diff --git a/activesupport/test/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb index d8ffd7ca9c..11f5374017 100644 --- a/activesupport/test/multibyte_proxy_test.rb +++ b/activesupport/test/multibyte_proxy_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'abstract_unit' diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb index 90af2dadd4..2e4b5cc873 100644 --- a/activesupport/test/multibyte_test_helpers.rb +++ b/activesupport/test/multibyte_test_helpers.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 module MultibyteTestHelpers UNICODE_STRING = 'こにちわ'.freeze diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb index bec65daf50..dd33641ec2 100644 --- a/activesupport/test/multibyte_unicode_database_test.rb +++ b/activesupport/test/multibyte_unicode_database_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'abstract_unit' @@ -12,8 +11,9 @@ class MultibyteUnicodeDatabaseTest < ActiveSupport::TestCase UnicodeDatabase::ATTRIBUTES.each do |attribute| define_method "test_lazy_loading_on_attribute_access_of_#{attribute}" do - @ucd.expects(:load) - @ucd.send(attribute) + assert_called(@ucd, :load) do + @ucd.send(attribute) + end end end diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb index 50d84a9470..944bce1b41 100644 --- a/activesupport/test/number_helper_test.rb +++ b/activesupport/test/number_helper_test.rb @@ -83,6 +83,14 @@ module ActiveSupport assert_equal("98a%", number_helper.number_to_percentage("98a")) assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN)) assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY)) + assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN, precision: 0)) + assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY, precision: 0)) + assert_equal("NaN%", number_helper.number_to_percentage(Float::NAN, precision: 1)) + assert_equal("Inf%", number_helper.number_to_percentage(Float::INFINITY, precision: 1)) + assert_equal("1000%", number_helper.number_to_percentage(1000, precision: nil)) + assert_equal("1000%", number_helper.number_to_percentage(1000, precision: nil)) + assert_equal("1000.1%", number_helper.number_to_percentage(1000.1, precision: nil)) + assert_equal("-0.13 %", number_helper.number_to_percentage("-0.13", precision: nil, format: "%n %")) end end @@ -98,6 +106,7 @@ module ActiveSupport assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901)) assert_equal("0.78901", number_helper.number_to_delimited(0.78901)) assert_equal("123,456.78", number_helper.number_to_delimited("123456.78")) + assert_equal("1,23,456.78", number_helper.number_to_delimited("123456.78", delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/)) assert_equal("123,456.78", number_helper.number_to_delimited("123456.78".html_safe)) end end @@ -226,15 +235,17 @@ module ActiveSupport end def test_number_to_human_size_with_si_prefix - [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| - assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si) - assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si) - assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si) - assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si) - assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si) - assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si) - assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si) - assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si) + assert_deprecated do + [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper| + assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si) + assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si) + assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si) + assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si) + assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si) + assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si) + assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si) + assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si) + end end end diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb index fdc745b23b..18767a3536 100644 --- a/activesupport/test/ordered_options_test.rb +++ b/activesupport/test/ordered_options_test.rb @@ -85,4 +85,19 @@ class OrderedOptionsTest < ActiveSupport::TestCase assert_equal 42, a.method(:blah=).call(42) assert_equal 42, a.method(:blah).call end + + def test_raises_with_bang + a = ActiveSupport::OrderedOptions.new + a[:foo] = :bar + assert a.respond_to?(:foo!) + + assert_nothing_raised { a.foo! } + assert_equal a.foo, a.foo! + + assert_raises(KeyError) do + a.foo = nil + a.foo! + end + assert_raises(KeyError) { a.non_existing_key! } + end end diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb index ec9d231125..bd43ad0797 100644 --- a/activesupport/test/rescuable_test.rb +++ b/activesupport/test/rescuable_test.rb @@ -12,6 +12,12 @@ end class CoolError < StandardError end +module WeirdError + def self.===(other) + Exception === other && other.respond_to?(:weird?) + end +end + class Stargate attr_accessor :result @@ -29,6 +35,10 @@ class Stargate @result = e.message end + rescue_from WeirdError do + @result = 'weird' + end + def dispatch(method) send(method) rescue Exception => e @@ -47,6 +57,16 @@ class Stargate raise MadRonon.new("dex") end + def weird + StandardError.new.tap do |exc| + def exc.weird? + true + end + + raise exc + end + end + def sos @result = 'killed' end @@ -91,15 +111,20 @@ class RescuableTest < ActiveSupport::TestCase assert_equal 'dex', @stargate.result end + def test_rescue_from_error_dispatchers_with_case_operator + @stargate.dispatch :weird + assert_equal 'weird', @stargate.result + end + def test_rescues_defined_later_are_added_at_end_of_the_rescue_handlers_array - expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon"] - result = @stargate.send(:rescue_handlers).collect {|e| e.first} + expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "WeirdError"] + result = @stargate.send(:rescue_handlers).collect(&:first) assert_equal expected, result end def test_children_should_inherit_rescue_definitions_from_parents_and_child_rescue_should_be_appended - expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "CoolError"] - result = @cool_stargate.send(:rescue_handlers).collect {|e| e.first} + expected = ["WraithAttack", "WraithAttack", "NuclearExplosion", "MadRonon", "WeirdError", "CoolError"] + result = @cool_stargate.send(:rescue_handlers).collect(&:first) assert_equal expected, result end end diff --git a/activesupport/test/safe_buffer_test.rb b/activesupport/test/safe_buffer_test.rb index efa9d5e61f..18fb6d2fbf 100644 --- a/activesupport/test/safe_buffer_test.rb +++ b/activesupport/test/safe_buffer_test.rb @@ -61,6 +61,13 @@ class SafeBufferTest < ActiveSupport::TestCase assert_equal({'str' => str}, YAML.load(yaml)) end + test "Should work with primitive-like-strings in to_yaml conversion" do + assert_equal 'true', YAML.load(ActiveSupport::SafeBuffer.new('true').to_yaml) + assert_equal 'false', YAML.load(ActiveSupport::SafeBuffer.new('false').to_yaml) + assert_equal '1', YAML.load(ActiveSupport::SafeBuffer.new('1').to_yaml) + assert_equal '1.1', YAML.load(ActiveSupport::SafeBuffer.new('1.1').to_yaml) + end + test "Should work with underscore" do str = "MyTest".html_safe.underscore assert_equal "my_test", str @@ -165,4 +172,9 @@ class SafeBufferTest < ActiveSupport::TestCase x = 'foo %{x} bar'.html_safe % { x: 'qux' } assert x.html_safe?, 'should be safe' end + + test 'Should not affect frozen objects when accessing characters' do + x = 'Hello'.html_safe + assert_equal x[/a/, 1], nil + end end diff --git a/activesupport/test/security_utils_test.rb b/activesupport/test/security_utils_test.rb new file mode 100644 index 0000000000..08d2e3baa6 --- /dev/null +++ b/activesupport/test/security_utils_test.rb @@ -0,0 +1,9 @@ +require 'abstract_unit' +require 'active_support/security_utils' + +class SecurityUtilsTest < ActiveSupport::TestCase + def test_secure_compare_should_perform_string_comparison + assert ActiveSupport::SecurityUtils.secure_compare('a', 'a') + assert !ActiveSupport::SecurityUtils.secure_compare('a', 'b') + end +end diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb new file mode 100644 index 0000000000..ad41db608b --- /dev/null +++ b/activesupport/test/share_lock_test.rb @@ -0,0 +1,333 @@ +require 'abstract_unit' +require 'concurrent/atomics' +require 'active_support/concurrency/share_lock' + +class ShareLockTest < ActiveSupport::TestCase + def setup + @lock = ActiveSupport::Concurrency::ShareLock.new + end + + def test_reentrancy + thread = Thread.new do + @lock.sharing { @lock.sharing {} } + @lock.exclusive { @lock.exclusive {} } + end + assert_threads_not_stuck thread + end + + def test_sharing_doesnt_block + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_latch| + assert_threads_not_stuck(Thread.new {@lock.sharing {} }) + end + end + + def test_sharing_blocks_exclusive + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + @lock.exclusive(no_wait: true) { flunk } # polling should fail + exclusive_thread = Thread.new { @lock.exclusive {} } + assert_threads_stuck_but_releasable_by_latch exclusive_thread, sharing_thread_release_latch + end + end + + def test_exclusive_blocks_sharing + with_thread_waiting_in_lock_section(:exclusive) do |exclusive_thread_release_latch| + sharing_thread = Thread.new { @lock.sharing {} } + assert_threads_stuck_but_releasable_by_latch sharing_thread, exclusive_thread_release_latch + end + end + + def test_multiple_exlusives_are_able_to_progress + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + exclusive_threads = (1..2).map do + Thread.new do + @lock.exclusive {} + end + end + + assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch + end + end + + def test_sharing_is_upgradeable_to_exclusive + upgrading_thread = Thread.new do + @lock.sharing do + @lock.exclusive {} + end + end + assert_threads_not_stuck upgrading_thread + end + + def test_exclusive_upgrade_waits_for_other_sharers_to_leave + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + in_sharing = Concurrent::CountDownLatch.new + + upgrading_thread = Thread.new do + @lock.sharing do + in_sharing.count_down + @lock.exclusive {} + end + end + + in_sharing.wait + assert_threads_stuck_but_releasable_by_latch upgrading_thread, sharing_thread_release_latch + end + end + + def test_exclusive_matching_purpose + [true, false].each do |use_upgrading| + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + exclusive_threads = (1..2).map do + Thread.new do + @lock.send(use_upgrading ? :sharing : :tap) do + @lock.exclusive(purpose: :load, compatible: [:load, :unload]) {} + end + end + end + + assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch + end + end + end + + def test_killed_thread_loses_lock + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + thread = Thread.new do + @lock.sharing do + @lock.exclusive {} + end + end + + assert_threads_stuck thread + thread.kill + + sharing_thread_release_latch.count_down + + thread = Thread.new do + @lock.exclusive {} + end + + assert_threads_not_stuck thread + end + end + + def test_exclusive_conflicting_purpose + [true, false].each do |use_upgrading| + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + begin + conflicting_exclusive_threads = [ + Thread.new do + @lock.send(use_upgrading ? :sharing : :tap) do + @lock.exclusive(purpose: :red, compatible: [:green, :purple]) {} + end + end, + Thread.new do + @lock.send(use_upgrading ? :sharing : :tap) do + @lock.exclusive(purpose: :blue, compatible: [:green]) {} + end + end + ] + + assert_threads_stuck conflicting_exclusive_threads # wait for threads to get into their respective `exclusive {}` blocks + + # This thread will be stuck as long as any other thread is in + # a sharing block. While it's blocked, it holds no lock, so it + # doesn't interfere with any other attempts. + no_purpose_thread = Thread.new do + @lock.exclusive {} + end + assert_threads_stuck no_purpose_thread + + # This thread is compatible with both of the "primary" + # attempts above. It's initially stuck on the outer share + # lock, but as soon as that's released, it can run -- + # regardless of whether those threads hold share locks. + compatible_thread = Thread.new do + @lock.exclusive(purpose: :green, compatible: []) {} + end + assert_threads_stuck compatible_thread + + assert_threads_stuck conflicting_exclusive_threads + + sharing_thread_release_latch.count_down + + assert_threads_not_stuck compatible_thread # compatible thread is now able to squeak through + + if use_upgrading + # The "primary" threads both each hold a share lock, and are + # mutually incompatible; they're still stuck. + assert_threads_stuck conflicting_exclusive_threads + + # The thread without a specified purpose is also stuck; it's + # not compatible with anything. + assert_threads_stuck no_purpose_thread + else + # As the primaries didn't hold a share lock, as soon as the + # outer one was released, all the exclusive locks are free + # to be acquired in turn. + + assert_threads_not_stuck conflicting_exclusive_threads + assert_threads_not_stuck no_purpose_thread + end + ensure + conflicting_exclusive_threads.each(&:kill) + no_purpose_thread.kill + end + end + end + end + + def test_exclusive_ordering + scratch_pad = [] + scratch_pad_mutex = Mutex.new + + load_params = [:load, [:load]] + unload_params = [:unload, [:unload, :load]] + + [load_params, load_params, unload_params, unload_params].permutation do |thread_params| + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + threads = thread_params.map do |purpose, compatible| + Thread.new do + @lock.sharing do + @lock.exclusive(purpose: purpose, compatible: compatible) do + scratch_pad_mutex.synchronize { scratch_pad << purpose } + end + end + end + end + + sleep(0.01) + scratch_pad_mutex.synchronize { assert_empty scratch_pad } + + sharing_thread_release_latch.count_down + + assert_threads_not_stuck threads + scratch_pad_mutex.synchronize do + assert_equal [:load, :load, :unload, :unload], scratch_pad + scratch_pad.clear + end + end + end + end + + def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads + scratch_pad = [] + scratch_pad_mutex = Mutex.new + + upgrading_load_params = [:load, [:load], true] + non_upgrading_unload_params = [:unload, [:load, :unload], false] + + [upgrading_load_params, non_upgrading_unload_params].permutation do |thread_params| + with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch| + threads = thread_params.map do |purpose, compatible, use_upgrading| + Thread.new do + @lock.send(use_upgrading ? :sharing : :tap) do + @lock.exclusive(purpose: purpose, compatible: compatible) do + scratch_pad_mutex.synchronize { scratch_pad << purpose } + end + end + end + end + + assert_threads_stuck threads + scratch_pad_mutex.synchronize { assert_empty scratch_pad } + + sharing_thread_release_latch.count_down + + assert_threads_not_stuck threads + scratch_pad_mutex.synchronize do + assert_equal [:load, :unload], scratch_pad + scratch_pad.clear + end + end + end + end + + private + + module CustomAssertions + SUFFICIENT_TIMEOUT = 0.2 + + private + + def assert_threads_stuck_but_releasable_by_latch(threads, latch) + assert_threads_stuck threads + latch.count_down + assert_threads_not_stuck threads + end + + def assert_threads_stuck(threads) + sleep(SUFFICIENT_TIMEOUT) # give threads time to do their business + assert(Array(threads).all? { |t| t.join(0.001).nil? }) + end + + def assert_threads_not_stuck(threads) + assert(Array(threads).all? { |t| t.join(SUFFICIENT_TIMEOUT) }) + end + end + + class CustomAssertionsTest < ActiveSupport::TestCase + include CustomAssertions + + def setup + @latch = Concurrent::CountDownLatch.new + @thread = Thread.new { @latch.wait } + end + + def teardown + @latch.count_down + @thread.join + end + + def test_happy_path + assert_threads_stuck_but_releasable_by_latch @thread, @latch + end + + def test_detects_stuck_thread + assert_raises(Minitest::Assertion) do + assert_threads_not_stuck @thread + end + end + + def test_detects_free_thread + @latch.count_down + assert_raises(Minitest::Assertion) do + assert_threads_stuck @thread + end + end + + def test_detects_already_released + @latch.count_down + assert_raises(Minitest::Assertion) do + assert_threads_stuck_but_releasable_by_latch @thread, @latch + end + end + + def test_detects_remains_latched + another_latch = Concurrent::CountDownLatch.new + assert_raises(Minitest::Assertion) do + assert_threads_stuck_but_releasable_by_latch @thread, another_latch + end + end + end + + include CustomAssertions + + def with_thread_waiting_in_lock_section(lock_section) + in_section = Concurrent::CountDownLatch.new + section_release = Concurrent::CountDownLatch.new + + stuck_thread = Thread.new do + @lock.send(lock_section) do + in_section.count_down + section_release.wait + end + end + + in_section.wait + + yield section_release + ensure + section_release.count_down + stuck_thread.join # clean up + end +end diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb index 27f629474e..03a63e94e8 100644 --- a/activesupport/test/tagged_logging_test.rb +++ b/activesupport/test/tagged_logging_test.rb @@ -79,6 +79,19 @@ class TaggedLoggingTest < ActiveSupport::TestCase assert_equal "[OMG] Cool story bro\n[BCX] Funky time\n", @output.string end + test "keeps each tag in their own instance" do + @other_output = StringIO.new + @other_logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@other_output)) + @logger.tagged("OMG") do + @other_logger.tagged("BCX") do + @logger.info "Cool story bro" + @other_logger.info "Funky time" + end + end + assert_equal "[OMG] Cool story bro\n", @output.string + assert_equal "[BCX] Funky time\n", @other_output.string + end + test "cleans up the taggings on flush" do @logger.tagged("BCX") do Thread.new do diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb index 5e852c8050..9e6d1a91d0 100644 --- a/activesupport/test/test_case_test.rb +++ b/activesupport/test/test_case_test.rb @@ -182,40 +182,27 @@ class TestOrderTest < ActiveSupport::TestCase ActiveSupport::TestCase.test_order = @original_test_order end - def test_defaults_to_sorted_with_warning + def test_defaults_to_random ActiveSupport::TestCase.test_order = nil - assert_equal :sorted, assert_deprecated { ActiveSupport::TestCase.test_order } + assert_equal :random, ActiveSupport::TestCase.test_order - # It should only produce a deprecation warning the first time this is accessed - assert_equal :sorted, assert_not_deprecated { ActiveSupport::TestCase.test_order } - assert_equal :sorted, assert_not_deprecated { ActiveSupport.test_order } + assert_equal :random, ActiveSupport.test_order end def test_test_order_is_global - ActiveSupport::TestCase.test_order = :random - - assert_equal :random, ActiveSupport.test_order - assert_equal :random, ActiveSupport::TestCase.test_order - assert_equal :random, self.class.test_order - assert_equal :random, Class.new(ActiveSupport::TestCase).test_order - - ActiveSupport.test_order = :sorted + ActiveSupport::TestCase.test_order = :sorted assert_equal :sorted, ActiveSupport.test_order assert_equal :sorted, ActiveSupport::TestCase.test_order assert_equal :sorted, self.class.test_order assert_equal :sorted, Class.new(ActiveSupport::TestCase).test_order - end - def test_i_suck_and_my_tests_are_order_dependent! - ActiveSupport::TestCase.test_order = :random - - klass = Class.new(ActiveSupport::TestCase) do - i_suck_and_my_tests_are_order_dependent! - end + ActiveSupport.test_order = :random - assert_equal :alpha, klass.test_order + assert_equal :random, ActiveSupport.test_order assert_equal :random, ActiveSupport::TestCase.test_order + assert_equal :random, self.class.test_order + assert_equal :random, Class.new(ActiveSupport::TestCase).test_order end end diff --git a/activesupport/test/testing/file_fixtures_test.rb b/activesupport/test/testing/file_fixtures_test.rb new file mode 100644 index 0000000000..91b8a9071c --- /dev/null +++ b/activesupport/test/testing/file_fixtures_test.rb @@ -0,0 +1,28 @@ +require 'abstract_unit' + +class FileFixturesTest < ActiveSupport::TestCase + self.file_fixture_path = File.expand_path("../../file_fixtures", __FILE__) + + test "#file_fixture returns Pathname to file fixture" do + path = file_fixture("sample.txt") + assert_kind_of Pathname, path + assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s + end + + test "raises an exception when the fixture file does not exist" do + e = assert_raises(ArgumentError) do + file_fixture("nope") + end + assert_match(/^the directory '[^']+test\/file_fixtures' does not contain a file named 'nope'$/, e.message) + end +end + +class FileFixturesPathnameDirectoryTest < ActiveSupport::TestCase + self.file_fixture_path = Pathname.new(File.expand_path("../../file_fixtures", __FILE__)) + + test "#file_fixture_path returns Pathname to file fixture" do + path = file_fixture("sample.txt") + assert_kind_of Pathname, path + assert_match %r{activesupport/test/file_fixtures/sample.txt$}, path.to_s + end +end diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb new file mode 100644 index 0000000000..3e5ba7c079 --- /dev/null +++ b/activesupport/test/testing/method_call_assertions_test.rb @@ -0,0 +1,123 @@ +require 'abstract_unit' +require 'active_support/testing/method_call_assertions' + +class MethodCallAssertionsTest < ActiveSupport::TestCase + include ActiveSupport::Testing::MethodCallAssertions + + class Level + def increment; 1; end + def decrement; end + def <<(arg); end + end + + setup do + @object = Level.new + end + + def test_assert_called_with_defaults_to_expect_once + assert_called @object, :increment do + @object.increment + end + end + + def test_assert_called_more_than_once + assert_called(@object, :increment, times: 2) do + @object.increment + @object.increment + end + end + + def test_assert_called_method_with_arguments + assert_called(@object, :<<) do + @object << 2 + end + end + + def test_assert_called_returns + assert_called(@object, :increment, returns: 10) do + assert_equal 10, @object.increment + end + end + + def test_assert_called_failure + error = assert_raises(Minitest::Assertion) do + assert_called(@object, :increment) do + # Call nothing... + end + end + + assert_equal "Expected increment to be called 1 times, but was called 0 times.\nExpected: 1\n Actual: 0", error.message + end + + def test_assert_called_with_message + error = assert_raises(Minitest::Assertion) do + assert_called(@object, :increment, 'dang it') do + # Call nothing... + end + end + + assert_match(/dang it.\nExpected increment/, error.message) + end + + def test_assert_called_with + assert_called_with(@object, :increment) do + @object.increment + end + end + + def test_assert_called_with_arguments + assert_called_with(@object, :<<, [ 2 ]) do + @object << 2 + end + end + + def test_assert_called_with_failure + assert_raises(MockExpectationError) do + assert_called_with(@object, :<<, [ 4567 ]) do + @object << 2 + end + end + end + + def test_assert_called_with_returns + assert_called_with(@object, :increment, returns: 1) do + @object.increment + end + end + + def test_assert_called_with_multiple_expected_arguments + assert_called_with(@object, :<<, [ [ 1 ], [ 2 ] ]) do + @object << 1 + @object << 2 + end + end + + def test_assert_not_called + assert_not_called(@object, :decrement) do + @object.increment + end + end + + def test_assert_not_called_failure + error = assert_raises(Minitest::Assertion) do + assert_not_called(@object, :increment) do + @object.increment + end + end + + assert_equal "Expected increment to be called 0 times, but was called 1 times.\nExpected: 0\n Actual: 1", error.message + end + + def test_stub_any_instance + stub_any_instance(Level) do |instance| + assert_equal instance, Level.new + end + end + + def test_stub_any_instance_with_instance + stub_any_instance(Level, instance: @object) do |instance| + assert_equal @object, instance + assert_equal instance, Level.new + end + end +end diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb index 065539671d..59c3e52c2f 100644 --- a/activesupport/test/time_travel_test.rb +++ b/activesupport/test/time_travel_test.rb @@ -1,72 +1,90 @@ require 'abstract_unit' -require 'active_support/core_ext/date' +require 'active_support/core_ext/date_time' require 'active_support/core_ext/numeric/time' class TimeTravelTest < ActiveSupport::TestCase - setup do - Time.stubs now: Time.now - end - teardown do travel_back end def test_time_helper_travel - expected_time = Time.now + 1.day - travel 1.day - - assert_equal expected_time.to_s(:db), Time.now.to_s(:db) - assert_equal expected_time.to_date, Date.today - end - - def test_time_helper_travel_with_block - expected_time = Time.now + 1.day + Time.stub(:now, Time.now) do + expected_time = Time.now + 1.day + travel 1.day - travel 1.day do assert_equal expected_time.to_s(:db), Time.now.to_s(:db) assert_equal expected_time.to_date, Date.today + assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db) end - - assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db) - assert_not_equal expected_time.to_date, Date.today end - def test_time_helper_travel_to - expected_time = Time.new(2004, 11, 24, 01, 04, 44) - travel_to expected_time - - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today + def test_time_helper_travel_with_block + Time.stub(:now, Time.now) do + expected_time = Time.now + 1.day + + travel 1.day do + assert_equal expected_time.to_s(:db), Time.now.to_s(:db) + assert_equal expected_time.to_date, Date.today + assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db) + end + + assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db) + assert_not_equal expected_time.to_date, Date.today + assert_not_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db) + end end - def test_time_helper_travel_to_with_block - expected_time = Time.new(2004, 11, 24, 01, 04, 44) + def test_time_helper_travel_to + Time.stub(:now, Time.now) do + expected_time = Time.new(2004, 11, 24, 01, 04, 44) + travel_to expected_time - travel_to expected_time do assert_equal expected_time, Time.now assert_equal Date.new(2004, 11, 24), Date.today + assert_equal expected_time.to_datetime, DateTime.now end + end - assert_not_equal expected_time, Time.now - assert_not_equal Date.new(2004, 11, 24), Date.today + def test_time_helper_travel_to_with_block + Time.stub(:now, Time.now) do + expected_time = Time.new(2004, 11, 24, 01, 04, 44) + + travel_to expected_time do + assert_equal expected_time, Time.now + assert_equal Date.new(2004, 11, 24), Date.today + assert_equal expected_time.to_datetime, DateTime.now + end + + assert_not_equal expected_time, Time.now + assert_not_equal Date.new(2004, 11, 24), Date.today + assert_not_equal expected_time.to_datetime, DateTime.now + end end def test_time_helper_travel_back - expected_time = Time.new(2004, 11, 24, 01, 04, 44) + Time.stub(:now, Time.now) do + expected_time = Time.new(2004, 11, 24, 01, 04, 44) - travel_to expected_time - assert_equal expected_time, Time.now - assert_equal Date.new(2004, 11, 24), Date.today - travel_back + travel_to expected_time + assert_equal expected_time, Time.now + assert_equal Date.new(2004, 11, 24), Date.today + assert_equal expected_time.to_datetime, DateTime.now + travel_back - assert_not_equal expected_time, Time.now - assert_not_equal Date.new(2004, 11, 24), Date.today + assert_not_equal expected_time, Time.now + assert_not_equal Date.new(2004, 11, 24), Date.today + assert_not_equal expected_time.to_datetime, DateTime.now + end end def test_travel_to_will_reset_the_usec_to_avoid_mysql_rouding - travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do - assert_equal 50, Time.now.sec - assert_equal 0, Time.now.usec + Time.stub(:now, Time.now) do + travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do + assert_equal 50, Time.now.sec + assert_equal 0, Time.now.usec + assert_equal 50, DateTime.now.sec + assert_equal 0, DateTime.now.usec + end end end end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 3e6d9652bb..00d40c4497 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -1,6 +1,7 @@ require 'abstract_unit' require 'active_support/time' require 'time_zone_test_helpers' +require 'yaml' class TimeZoneTest < ActiveSupport::TestCase include TimeZoneTestHelpers @@ -252,9 +253,10 @@ class TimeZoneTest < ActiveSupport::TestCase def test_parse_with_incomplete_date zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - zone.stubs(:now).returns zone.local(1999,12,31) - twz = zone.parse('19:00:00') - assert_equal Time.utc(1999,12,31,19), twz.time + zone.stub(:now, zone.local(1999,12,31)) do + twz = zone.parse('19:00:00') + assert_equal Time.utc(1999,12,31,19), twz.time + end end def test_parse_with_day_omitted @@ -284,9 +286,10 @@ class TimeZoneTest < ActiveSupport::TestCase def test_parse_with_missing_time_components zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] - zone.stubs(:now).returns zone.local(1999, 12, 31, 12, 59, 59) - twz = zone.parse('2012-12-01') - assert_equal Time.utc(2012, 12, 1), twz.time + zone.stub(:now, zone.local(1999, 12, 31, 12, 59, 59)) do + twz = zone.parse('2012-12-01') + assert_equal Time.utc(2012, 12, 1), twz.time + end end def test_parse_with_javascript_date @@ -311,6 +314,80 @@ class TimeZoneTest < ActiveSupport::TestCase end end + def test_strptime + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + twz = zone.strptime('1999-12-31 12:00:00', '%Y-%m-%d %H:%M:%S') + assert_equal Time.utc(1999,12,31,17), twz + assert_equal Time.utc(1999,12,31,12), twz.time + assert_equal Time.utc(1999,12,31,17), twz.utc + assert_equal zone, twz.time_zone + end + + def test_strptime_with_nondefault_time_zone + with_tz_default ActiveSupport::TimeZone['Pacific Time (US & Canada)'] do + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + twz = zone.strptime('1999-12-31 12:00:00', '%Y-%m-%d %H:%M:%S') + assert_equal Time.utc(1999,12,31,17), twz + assert_equal Time.utc(1999,12,31,12), twz.time + assert_equal Time.utc(1999,12,31,17), twz.utc + assert_equal zone, twz.time_zone + end + end + + def test_strptime_with_explicit_time_zone_as_abbrev + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + twz = zone.strptime('1999-12-31 12:00:00 PST', '%Y-%m-%d %H:%M:%S %Z') + assert_equal Time.utc(1999,12,31,20), twz + assert_equal Time.utc(1999,12,31,15), twz.time + assert_equal Time.utc(1999,12,31,20), twz.utc + assert_equal zone, twz.time_zone + end + + def test_strptime_with_explicit_time_zone_as_h_offset + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + twz = zone.strptime('1999-12-31 12:00:00 -08', '%Y-%m-%d %H:%M:%S %:::z') + assert_equal Time.utc(1999,12,31,20), twz + assert_equal Time.utc(1999,12,31,15), twz.time + assert_equal Time.utc(1999,12,31,20), twz.utc + assert_equal zone, twz.time_zone + end + + def test_strptime_with_explicit_time_zone_as_hm_offset + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + twz = zone.strptime('1999-12-31 12:00:00 -08:00', '%Y-%m-%d %H:%M:%S %:z') + assert_equal Time.utc(1999,12,31,20), twz + assert_equal Time.utc(1999,12,31,15), twz.time + assert_equal Time.utc(1999,12,31,20), twz.utc + assert_equal zone, twz.time_zone + end + + def test_strptime_with_explicit_time_zone_as_hms_offset + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + twz = zone.strptime('1999-12-31 12:00:00 -08:00:00', '%Y-%m-%d %H:%M:%S %::z') + assert_equal Time.utc(1999,12,31,20), twz + assert_equal Time.utc(1999,12,31,15), twz.time + assert_equal Time.utc(1999,12,31,20), twz.utc + assert_equal zone, twz.time_zone + end + + def test_strptime_with_almost_explicit_time_zone + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + twz = zone.strptime('1999-12-31 12:00:00 %Z', '%Y-%m-%d %H:%M:%S %%Z') + assert_equal Time.utc(1999,12,31,17), twz + assert_equal Time.utc(1999,12,31,12), twz.time + assert_equal Time.utc(1999,12,31,17), twz.utc + assert_equal zone, twz.time_zone + end + + def test_strptime_with_day_omitted + with_env_tz 'US/Eastern' do + zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)'] + assert_equal Time.local(2000, 2, 1), zone.strptime('Feb', '%b', Time.local(2000, 1, 1)) + assert_equal Time.local(2005, 2, 1), zone.strptime('Feb 2005', '%b %Y', Time.local(2000, 1, 1)) + assert_equal Time.local(2005, 2, 2), zone.strptime('2 Feb 2005', '%e %b %Y', Time.local(2000, 1, 1)) + end + end + def test_utc_offset_lazy_loaded_from_tzinfo_when_not_passed_in_to_initialize tzinfo = TZInfo::Timezone.get('America/New_York') zone = ActiveSupport::TimeZone.create(tzinfo.name, nil, tzinfo) @@ -395,20 +472,14 @@ class TimeZoneTest < ActiveSupport::TestCase assert_raise(ArgumentError) { ActiveSupport::TimeZone[false] } end - def test_unknown_zone_should_have_tzinfo_but_exception_on_utc_offset - zone = ActiveSupport::TimeZone.create("bogus") - assert_instance_of TZInfo::TimezoneProxy, zone.tzinfo - assert_raise(TZInfo::InvalidTimezoneIdentifier) { zone.utc_offset } - end - - def test_unknown_zone_with_utc_offset - zone = ActiveSupport::TimeZone.create("bogus", -21_600) - assert_equal(-21_600, zone.utc_offset) + def test_unknown_zone_raises_exception + assert_raise TZInfo::InvalidTimezoneIdentifier do + ActiveSupport::TimeZone.create("bogus") + end end def test_unknown_zones_dont_store_mapping_keys - ActiveSupport::TimeZone["bogus"] - assert !ActiveSupport::TimeZone.zones_map.key?("bogus") + assert_nil ActiveSupport::TimeZone["bogus"] end def test_new @@ -419,4 +490,13 @@ class TimeZoneTest < ActiveSupport::TestCase assert ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Hawaii"]) assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"]) end + + def test_to_yaml + assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml) + assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml) + end + + def test_yaml_load + assert_equal(ActiveSupport::TimeZone["Pacific/Honolulu"], YAML.load("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n")) + end end diff --git a/activesupport/test/transliterate_test.rb b/activesupport/test/transliterate_test.rb index 6833ae68a7..378421fedd 100644 --- a/activesupport/test/transliterate_test.rb +++ b/activesupport/test/transliterate_test.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 require 'abstract_unit' require 'active_support/inflector/transliterate' diff --git a/activesupport/test/xml_mini/nokogiri_engine_test.rb b/activesupport/test/xml_mini/nokogiri_engine_test.rb index 2e962576b5..1314c9065a 100644 --- a/activesupport/test/xml_mini/nokogiri_engine_test.rb +++ b/activesupport/test/xml_mini/nokogiri_engine_test.rb @@ -8,15 +8,13 @@ require 'active_support/xml_mini' require 'active_support/core_ext/hash/conversions' class NokogiriEngineTest < ActiveSupport::TestCase - include ActiveSupport - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'Nokogiri' + @default_backend = ActiveSupport::XmlMini.backend + ActiveSupport::XmlMini.backend = 'Nokogiri' end def teardown - XmlMini.backend = @default_backend + ActiveSupport::XmlMini.backend = @default_backend end def test_file_from_xml @@ -56,13 +54,13 @@ class NokogiriEngineTest < ActiveSupport::TestCase end def test_setting_nokogiri_as_backend - XmlMini.backend = 'Nokogiri' - assert_equal XmlMini_Nokogiri, XmlMini.backend + ActiveSupport::XmlMini.backend = 'Nokogiri' + assert_equal ActiveSupport::XmlMini_Nokogiri, ActiveSupport::XmlMini.backend end def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) + assert_equal({}, ActiveSupport::XmlMini.parse(nil)) + assert_equal({}, ActiveSupport::XmlMini.parse('')) end def test_array_type_makes_an_array @@ -207,9 +205,9 @@ class NokogiriEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) + parsed_xml = ActiveSupport::XmlMini.parse(xml) xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini/nokogirisax_engine_test.rb b/activesupport/test/xml_mini/nokogirisax_engine_test.rb index 4f078f31e0..7978a50921 100644 --- a/activesupport/test/xml_mini/nokogirisax_engine_test.rb +++ b/activesupport/test/xml_mini/nokogirisax_engine_test.rb @@ -8,15 +8,13 @@ require 'active_support/xml_mini' require 'active_support/core_ext/hash/conversions' class NokogiriSAXEngineTest < ActiveSupport::TestCase - include ActiveSupport - def setup - @default_backend = XmlMini.backend - XmlMini.backend = 'NokogiriSAX' + @default_backend = ActiveSupport::XmlMini.backend + ActiveSupport::XmlMini.backend = 'NokogiriSAX' end def teardown - XmlMini.backend = @default_backend + ActiveSupport::XmlMini.backend = @default_backend end def test_file_from_xml @@ -57,13 +55,13 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase end def test_setting_nokogirisax_as_backend - XmlMini.backend = 'NokogiriSAX' - assert_equal XmlMini_NokogiriSAX, XmlMini.backend + ActiveSupport::XmlMini.backend = 'NokogiriSAX' + assert_equal ActiveSupport::XmlMini_NokogiriSAX, ActiveSupport::XmlMini.backend end def test_blank_returns_empty_hash - assert_equal({}, XmlMini.parse(nil)) - assert_equal({}, XmlMini.parse('')) + assert_equal({}, ActiveSupport::XmlMini.parse(nil)) + assert_equal({}, ActiveSupport::XmlMini.parse('')) end def test_array_type_makes_an_array @@ -208,9 +206,9 @@ class NokogiriSAXEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) + parsed_xml = ActiveSupport::XmlMini.parse(xml) xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb index 0c1f11803c..f0067ca656 100644 --- a/activesupport/test/xml_mini/rexml_engine_test.rb +++ b/activesupport/test/xml_mini/rexml_engine_test.rb @@ -2,19 +2,17 @@ require 'abstract_unit' require 'active_support/xml_mini' class REXMLEngineTest < ActiveSupport::TestCase - include ActiveSupport - def test_default_is_rexml - assert_equal XmlMini_REXML, XmlMini.backend + assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend end def test_set_rexml_as_backend - XmlMini.backend = 'REXML' - assert_equal XmlMini_REXML, XmlMini.backend + ActiveSupport::XmlMini.backend = 'REXML' + assert_equal ActiveSupport::XmlMini_REXML, ActiveSupport::XmlMini.backend end def test_parse_from_io - XmlMini.backend = 'REXML' + ActiveSupport::XmlMini.backend = 'REXML' io = StringIO.new(<<-eoxml) <root> good @@ -29,9 +27,9 @@ class REXMLEngineTest < ActiveSupport::TestCase private def assert_equal_rexml(xml) - parsed_xml = XmlMini.parse(xml) + parsed_xml = ActiveSupport::XmlMini.parse(xml) xml.rewind if xml.respond_to?(:rewind) - hash = XmlMini.with_backend('REXML') { XmlMini.parse(xml) } + hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) } assert_equal(hash, parsed_xml) end end diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb index f49431cbbf..55e8181b54 100644 --- a/activesupport/test/xml_mini_test.rb +++ b/activesupport/test/xml_mini_test.rb @@ -1,9 +1,9 @@ require 'abstract_unit' require 'active_support/xml_mini' require 'active_support/builder' -require 'active_support/core_ext/array' require 'active_support/core_ext/hash' require 'active_support/core_ext/big_decimal' +require 'yaml' module XmlMiniTest class RenameKeyTest < ActiveSupport::TestCase @@ -11,7 +11,7 @@ module XmlMiniTest assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key") end - def test_rename_key_does_nothing_with_dasherize_true + def test_rename_key_dasherizes_with_dasherize_true assert_equal "my-key", ActiveSupport::XmlMini.rename_key("my_key", :dasherize => true) end |