aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md594
-rw-r--r--activesupport/activesupport.gemspec2
-rw-r--r--activesupport/lib/active_support.rb9
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb6
-rw-r--r--activesupport/lib/active_support/cache.rb55
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb2
-rw-r--r--activesupport/lib/active_support/callbacks.rb8
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/array/grouping.rb27
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/zones.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/zones.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb36
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/time.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/json.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb5
-rw-r--r--activesupport/lib/active_support/dependencies.rb57
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb1
-rw-r--r--activesupport/lib/active_support/duration.rb26
-rw-r--r--activesupport/lib/active_support/duration/iso8601_parser.rb122
-rw-r--r--activesupport/lib/active_support/duration/iso8601_serializer.rb51
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb22
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb45
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb21
-rw-r--r--activesupport/lib/active_support/gem_version.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb9
-rw-r--r--activesupport/lib/active_support/number_helper.rb9
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb2
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_phone_converter.rb13
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb8
-rw-r--r--activesupport/lib/active_support/reloader.rb8
-rw-r--r--activesupport/lib/active_support/rescuable.rb142
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb23
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb18
-rw-r--r--activesupport/lib/active_support/xml_mini.rb33
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb4
-rw-r--r--activesupport/test/abstract_unit.rb3
-rw-r--r--activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb4
-rw-r--r--activesupport/test/autoloading_fixtures/throws.rb4
-rw-r--r--activesupport/test/caching_test.rb14
-rw-r--r--activesupport/test/core_ext/array/conversions_test.rb6
-rw-r--r--activesupport/test/core_ext/array/grouping_test.rb13
-rw-r--r--activesupport/test/core_ext/bigdecimal_test.rb2
-rw-r--r--activesupport/test/core_ext/date_and_time_compatibility_test.rb127
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb21
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb54
-rw-r--r--activesupport/test/core_ext/duration_test.rb101
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb102
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb16
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb34
-rw-r--r--activesupport/test/core_ext/marshal_test.rb21
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb7
-rw-r--r--activesupport/test/core_ext/object/deep_dup_test.rb2
-rw-r--r--activesupport/test/core_ext/object/json_gem_encoding_test.rb2
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb51
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb14
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb5
-rw-r--r--activesupport/test/dependencies_test.rb22
-rw-r--r--activesupport/test/deprecation_test.rb2
-rw-r--r--activesupport/test/executor_test.rb107
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb32
-rw-r--r--activesupport/test/inflector_test_cases.rb3
-rw-r--r--activesupport/test/json/encoding_test.rb5
-rw-r--r--activesupport/test/multibyte_conformance_test.rb2
-rw-r--r--activesupport/test/multibyte_grapheme_break_conformance_test.rb2
-rw-r--r--activesupport/test/multibyte_normalization_conformance_test.rb2
-rw-r--r--activesupport/test/number_helper_test.rb2
-rw-r--r--activesupport/test/rescuable_test.rb21
-rw-r--r--activesupport/test/time_zone_test.rb16
-rw-r--r--activesupport/test/time_zone_test_helpers.rb8
-rw-r--r--activesupport/test/xml_mini/rexml_engine_test.rb26
94 files changed, 1447 insertions, 953 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 15dc9946c5..25b8af7d34 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,592 +1,6 @@
-* Prevent `Marshal.load` from looping infinitely when trying to autoload a constant
- which resolves to a different name.
+* Rescuable: If a handler doesn't match the exception, check for handlers
+ matching the exception's cause.
- *Olek Janiszewski*
+ *Jeremy Daer*
-* Deprecate `Module.local_constants`. Please use `Module.constants(false)` instead.
-
- *Yuichiro Kaneko*
-
-* Publish ActiveSupport::Executor and ActiveSupport::Reloader APIs to allow
- components and libraries to manage, and participate in, the execution of
- application code, and the application reloading process.
-
- *Matthew Draper*
-
-
-## Rails 5.0.0.beta3 (February 24, 2016) ##
-
-* Deprecate arguments on `assert_nothing_raised`.
-
- `assert_nothing_raised` does not assert the arguments that have been passed
- in (usually a specific exception class) since the method only yields the
- block. So as not to confuse the users that the arguments have meaning, they
- are being deprecated.
-
- *Tara Scherner de la Fuente*
-
-* Make `benchmark('something', silence: true)` actually work
-
- *DHH*
-
-* Add `#on_weekday?` method to `Date`, `Time`, and `DateTime`.
-
- `#on_weekday?` returns `true` if the receiving date/time does not fall on a Saturday
- or Sunday.
-
- *Vipul A M*
-
-* Add `Array#second_to_last` and `Array#third_to_last` methods.
-
- *Brian Christian*
-
-* Fix regression in `Hash#dig` for HashWithIndifferentAccess.
-
- *Jon Moss*
-
-## Rails 5.0.0.beta2 (February 01, 2016) ##
-
-* Change number_to_currency behavior for checking negativity.
-
- Used `to_f.negative` instead of using `to_f.phase` for checking negativity
- of a number in number_to_currency helper.
- This change works same for all cases except when number is "-0.0".
-
- -0.0.to_f.negative? => false
- -0.0.to_f.phase? => 3.14
-
- This change reverts changes from https://github.com/rails/rails/pull/6512.
- But it should be acceptable as we could not find any currency which
- supports negative zeros.
-
- *Prathamesh Sonpatki*, *Rafael Mendonça França*
-
-* Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`.
-
- *David Cornu*
-
-* Adds `:exception_object` key to `ActiveSupport::Notifications::Instrumenter`
- payload when an exception is raised.
-
- Adds new key/value pair to payload when an exception is raised:
- e.g. `:exception_object => #<RuntimeError: FAIL>`.
-
- *Ryan T. Hosford*
-
-* Support extended grapheme clusters and UAX 29.
-
- *Adam Roben*
-
-* Add petabyte and exabyte numeric conversion.
-
- *Akshay Vishnoi*
-
-## Rails 5.0.0.beta1 (December 18, 2015) ##
-
-* Add thread_m/cattr_accessor/reader/writer suite of methods for declaring class and module variables that live per-thread.
- This makes it easy to declare per-thread globals that are encapsulated. Note: This is a sharp edge. A wild proliferation
- of globals is A Bad Thing. But like other sharp tools, when it's right, it's right.
-
- Here's an example of a simple event tracking system where the object being tracked needs not pass a creator that it
- doesn't need itself along:
-
- module Current
- thread_mattr_accessor :account
- thread_mattr_accessor :user
-
- def self.reset() self.account = self.user = nil end
- end
-
- class ApplicationController < ActionController::Base
- before_action :set_current
- after_action { Current.reset }
-
- private
- def set_current
- Current.account = Account.find(params[:account_id])
- Current.user = Current.account.users.find(params[:user_id])
- end
- end
-
- class MessagesController < ApplicationController
- def create
- @message = Message.create!(message_params)
- end
- end
-
- class Message < ApplicationRecord
- has_many :events
- after_create :track_created
-
- private
- def track_created
- events.create! origin: self, action: :create
- end
- end
-
- class Event < ApplicationRecord
- belongs_to :creator, class_name: 'User'
- before_validation { self.creator ||= Current.user }
- end
-
- *DHH*
-
-
-* Deprecated `Module#qualified_const_` in favour of the builtin Module#const_
- methods.
-
- *Genadi Samokovarov*
-
-* Deprecate passing string to define callback.
-
- *Yuichiro Kaneko*
-
-* `ActiveSupport::Cache::Store#namespaced_key`,
- `ActiveSupport::Cache::MemCachedStore#escape_key`, and
- `ActiveSupport::Cache::FileStore#key_file_path`
- are deprecated and replaced with `normalize_key` that now calls `super`.
-
- `ActiveSupport::Cache::LocaleCache#set_cache_value` is deprecated and replaced with `write_cache_value`.
-
- *Michael Grosser*
-
-* Implements an evented file watcher to asynchronously detect changes in the
- application source code, routes, locales, etc.
-
- This watcher is disabled by default, applications my enable it in the configuration:
-
- # config/environments/development.rb
- config.file_watcher = ActiveSupport::EventedFileUpdateChecker
-
- This feature depends on the [listen](https://github.com/guard/listen) gem:
-
- group :development do
- gem 'listen', '~> 3.0.5'
- end
-
- *Puneet Agarwal* and *Xavier Noria*
-
-* Added `Time.days_in_year` to return the number of days in the given year, or the
- current year if no argument is provided.
-
- *Jon Pascoe*
-
-* Updated `parameterize` to preserve the case of a string, optionally.
-
- Example:
-
- parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
- parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
-
- *Swaathi Kakarla*
-
-* `HashWithIndifferentAccess.new` respects the default value or proc on objects
- that respond to `#to_hash`. `.new_from_hash_copying_default` simply invokes `.new`.
- All calls to `.new_from_hash_copying_default` are replaced with `.new`.
-
- *Gordon Chan*
-
-* Change Integer#year to return a Fixnum instead of a Float to improve
- consistency.
-
- Integer#years returned a Float while the rest of the accompanying methods
- (days, weeks, months, etc.) return a Fixnum.
-
- Before:
-
- 1.year # => 31557600.0
-
- After:
-
- 1.year # => 31557600
-
- *Konstantinos Rousis*
-
-* Handle invalid UTF-8 strings when HTML escaping.
-
- Use `ActiveSupport::Multibyte::Unicode.tidy_bytes` to handle invalid UTF-8
- strings in `ERB::Util.unwrapped_html_escape` and `ERB::Util.html_escape_once`.
- Prevents user-entered input passed from a querystring into a form field from
- causing invalid byte sequence errors.
-
- *Grey Baker*
-
-* Update `ActiveSupport::Multibyte::Chars#slice!` to return `nil` if the
- arguments are out of bounds, to mirror the behavior of `String#slice!`
-
- *Gourav Tiwari*
-
-* Fix `number_to_human` so that 999999999 rounds to "1 Billion" instead of
- "1000 Million".
-
- *Max Jacobson*
-
-* Fix `ActiveSupport::Deprecation#deprecate_methods` to report using the
- current deprecator instance, where applicable.
-
- *Brandon Dunne*
-
-* `Cache#fetch` instrumentation marks whether it was a `:hit`.
-
- *Robin Clowers*
-
-* `assert_difference` and `assert_no_difference` now returns the result of the
- yielded block.
-
- Example:
-
- post = assert_difference -> { Post.count }, 1 do
- Post.create
- end
-
- *Lucas Mazza*
-
-* Short-circuit `blank?` on date and time values since they are never blank.
-
- Fixes #21657.
-
- *Andrew White*
-
-* Replaced deprecated `ThreadSafe::Cache` with its successor `Concurrent::Map` now that
- the thread_safe gem has been merged into concurrent-ruby.
-
- *Jerry D'Antonio*
-
-* Updated Unicode version to 8.0.0
-
- *Anshul Sharma*
-
-* `number_to_currency` and `number_with_delimiter` now accept custom `delimiter_pattern` option
- to handle placement of delimiter, to support currency formats like INR
-
- Example:
-
- number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n")
- # => '₹ 12,30,000.00'
-
- *Vipul A M*
-
-* Deprecate `:prefix` option of `number_to_human_size` with no replacement.
-
- *Jean Boussier*
-
-* Fix `TimeWithZone#eql?` to properly handle `TimeWithZone` created from `DateTime`:
- twz = DateTime.now.in_time_zone
- twz.eql?(twz.dup) => true
-
- Fixes #14178.
-
- *Roque Pinel*
-
-* ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return
- enumerator if called without block.
-
- Fixes #20095.
-
- *Bernard Potocki*
-
-* Removed `ActiveSupport::Concurrency::Latch`, superseded by `Concurrent::CountDownLatch`
- from the concurrent-ruby gem.
-
- *Jerry D'Antonio*
-
-* Fix not calling `#default` on `HashWithIndifferentAccess#to_hash` when only
- `default_proc` is set, which could raise.
-
- *Simon Eskildsen*
-
-* Fix setting `default_proc` on `HashWithIndifferentAccess#dup`.
-
- *Simon Eskildsen*
-
-* Fix a range of values for parameters of the Time#change.
-
- *Nikolay Kondratyev*
-
-* Add `Enumerable#pluck` to get the same values from arrays as from ActiveRecord
- associations.
-
- Fixes #20339.
-
- *Kevin Deisz*
-
-* Add a bang version to `ActiveSupport::OrderedOptions` get methods which will raise
- an `KeyError` if the value is `.blank?`.
-
- Before:
-
- if (slack_url = Rails.application.secrets.slack_url).present?
- # Do something worthwhile
- else
- # Raise as important secret password is not specified
- end
-
- After:
-
- slack_url = Rails.application.secrets.slack_url!
-
- *Aditya Sanghi*, *Gaurish Sharma*
-
-* Remove deprecated `Class#superclass_delegating_accessor`.
- Use `Class#class_attribute` instead.
-
- *Akshay Vishnoi*
-
-* Patch `Delegator` to work with `#try`.
-
- Fixes #5790.
-
- *Nate Smith*
-
-* Add `Integer#positive?` and `Integer#negative?` query methods
- in the vein of `Fixnum#zero?`.
-
- This makes it nicer to do things like `bunch_of_numbers.select(&:positive?)`.
-
- *DHH*
-
-* Encoding `ActiveSupport::TimeWithZone` to YAML now preserves the timezone information.
-
- Fixes #9183.
-
- *Andrew White*
-
-* Added `ActiveSupport::TimeZone#strptime` to allow parsing times as if
- from a given timezone.
-
- *Paul A Jungwirth*
-
-* `ActiveSupport::Callbacks#skip_callback` now raises an `ArgumentError` if
- an unrecognized callback is removed.
-
- *Iain Beeston*
-
-* Added `ActiveSupport::ArrayInquirer` and `Array#inquiry`.
-
- Wrapping an array in an `ArrayInquirer` gives a friendlier way to check its
- contents:
-
- variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
-
- variants.phone? # => true
- variants.tablet? # => true
- variants.desktop? # => false
-
- variants.any?(:phone, :tablet) # => true
- variants.any?(:phone, :desktop) # => true
- variants.any?(:desktop, :watch) # => false
-
- `Array#inquiry` is a shortcut for wrapping the receiving array in an
- `ArrayInquirer`.
-
- *George Claghorn*
-
-* Deprecate `alias_method_chain` in favour of `Module#prepend` introduced in
- Ruby 2.0.
-
- *Kir Shatrov*
-
-* Added `#without` on `Enumerable` and `Array` to return a copy of an
- enumerable without the specified elements.
-
- *Todd Bealmear*
-
-* Fixed a problem where `String#truncate_words` would get stuck with a complex
- string.
-
- *Henrik Nygren*
-
-* Fixed a roundtrip problem with `AS::SafeBuffer` where primitive-like strings
- will be dumped as primitives:
-
- Before:
-
- 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:
-
- 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*
-
-* Enable `number_to_percentage` to keep the number's precision by allowing
- `:precision` to be `nil`.
-
- *Jack Xu*
-
-* `config_accessor` became a private method, as with Ruby's `attr_accessor`.
-
- *Akira Matsuda*
-
-* `AS::Testing::TimeHelpers#travel_to` now changes `DateTime.now` as well as
- `Time.now` and `Date.today`.
-
- *Yuki Nishijima*
-
-* Add `file_fixture` to `ActiveSupport::TestCase`.
- It provides a simple mechanism to access sample files in your test cases.
-
- By default file fixtures are stored in `test/fixtures/files`. This can be
- configured per test-case using the `file_fixture_path` class attribute.
-
- *Yves Senn*
-
-* Return value of yielded block in `File.atomic_write`.
-
- *Ian Ker-Seymer*
-
-* 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`.
-
- Fixes #18550.
-
- *Aditya Kapoor*
-
-* Add missing time zone definitions for Russian Federation and sync them
- with `zone.tab` file from tzdata version 2014j (latest).
-
- *Andrey Novikov*
-
-* Add `SecureRandom.base58` for generation of random base58 strings.
-
- *Matthew Draper*, *Guillermo Iguaran*
-
-* Add `#prev_day` and `#next_day` counterparts to `#yesterday` and
- `#tomorrow` for `Date`, `Time`, and `DateTime`.
-
- *George Claghorn*
-
-* Add `same_time` option to `#next_week` and `#prev_week` for `Date`, `Time`,
- and `DateTime`.
-
- *George Claghorn*
-
-* Add `#on_weekend?`, `#next_weekday`, `#prev_weekday` methods to `Date`,
- `Time`, and `DateTime`.
-
- `#on_weekend?` returns `true` if the receiving date/time falls on a Saturday
- or Sunday.
-
- `#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.
-
- *George Claghorn*
-
-* Change the default test order from `:sorted` to `:random`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActiveSupport::JSON::Encoding::CircularReferenceError`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated methods `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string=`
- and `ActiveSupport::JSON::Encoding.encode_big_decimal_as_string`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `ActiveSupport::SafeBuffer#prepend`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated methods at `Kernel`.
-
- `silence_stderr`, `silence_stream`, `capture` and `quietly`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated `active_support/core_ext/big_decimal/yaml_conversions`
- file.
-
- *Rafael Mendonça França*
-
-* Remove deprecated methods `ActiveSupport::Cache::Store.instrument` and
- `ActiveSupport::Cache::Store.instrument=`.
-
- *Rafael Mendonça França*
-
-* Change the way in which callback chains can be halted.
-
- The preferred method to halt a callback chain from now on is to explicitly
- `throw(:abort)`.
- In the past, callbacks could only be halted by explicitly providing a
- terminator and by having a callback match the conditions of the terminator.
-
-* Add `ActiveSupport.halt_callback_chains_on_return_false`
-
- Setting `ActiveSupport.halt_callback_chains_on_return_false`
- to `true` will let an app support the deprecated way of halting Active Record,
- and Active Model callback chains by returning `false`.
-
- Setting the value to `false` will tell the app to ignore any `false` value
- returned by those callbacks, and only halt the chain upon `throw(:abort)`.
-
- 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.
-
- *claudiob*, *Roque Pinel*
-
-* Changes arguments and default value of CallbackChain's `:terminator` option
-
- Chains of callbacks defined without an explicit `:terminator` option will
- now be halted as soon as a `before_` callback throws `:abort`.
-
- 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.
-
- *claudiob*
-
-* Deprecate `MissingSourceFile` in favor of `LoadError`.
-
- `MissingSourceFile` was just an alias to `LoadError` and was not being
- raised inside the framework.
-
- *Rafael Mendonça França*
-
-* Add support for error dispatcher classes in `ActiveSupport::Rescuable`.
- Now it acts closer to Ruby's rescue.
-
- Example:
-
- class BaseController < ApplicationController
- module ErrorDispatcher
- def self.===(other)
- Exception === other && other.respond_to?(:status)
- end
- end
-
- rescue_from ErrorDispatcher do |error|
- render status: error.status, json: { error: error.to_s }
- end
- 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.
-
- *Logan Leger*
-
-Please check [4-2-stable](https://github.com/rails/rails/blob/4-2-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 71fe4d7253..c6ca969844 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -23,5 +23,5 @@ Gem::Specification.new do |s|
s.add_dependency 'i18n', '~> 0.7'
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
- s.add_dependency 'concurrent-ruby', '~> 1.0'
+ s.add_dependency 'concurrent-ruby', '~> 1.0', '>= 1.0.2'
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 72777baecd..11569add37 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -26,6 +26,7 @@ require "active_support/dependencies/autoload"
require "active_support/version"
require "active_support/logger"
require "active_support/lazy_load_hooks"
+require "active_support/core_ext/date_and_time/compatibility"
module ActiveSupport
extend ActiveSupport::Autoload
@@ -85,6 +86,14 @@ module ActiveSupport
def self.halt_callback_chains_on_return_false=(value)
Callbacks.halt_and_display_warning_on_return_false = value
end
+
+ def self.to_time_preserves_timezone
+ DateAndTime::Compatibility.preserve_timezone
+ end
+
+ def self.to_time_preserves_timezone=(value)
+ DateAndTime::Compatibility.preserve_timezone = value
+ end
end
autoload :I18n, "active_support/i18n"
diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb
index f59ddf5403..ea328f603e 100644
--- a/activesupport/lib/active_support/array_inquirer.rb
+++ b/activesupport/lib/active_support/array_inquirer.rb
@@ -9,8 +9,10 @@ module ActiveSupport
# variants.desktop? # => false
class ArrayInquirer < Array
# Passes each element of +candidates+ collection to ArrayInquirer collection.
- # The method returns true if at least one element is the same. If +candidates+
- # collection is not given, method returns true.
+ # The method returns true if any element from the ArrayInquirer collection
+ # is equal to the stringified or symbolized form of any element in the +candidates+ collection.
+ #
+ # If +candidates+ collection is not given, method returns true.
#
# variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
#
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 1c63e8a93f..bc114e0785 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -158,20 +158,20 @@ module ActiveSupport
attr_reader :silence, :options
alias :silence? :silence
- # Create a new cache. The options will be passed to any write method calls
+ # Creates a new cache. The options will be passed to any write method calls
# except for <tt>:namespace</tt> which can be used to set the global
# namespace for the cache.
def initialize(options = nil)
@options = options ? options.dup : {}
end
- # Silence the logger.
+ # Silences the logger.
def silence!
@silence = true
self
end
- # Silence the logger within a block.
+ # Silences the logger within a block.
def mute
previous_silence, @silence = defined?(@silence) && @silence, true
yield
@@ -198,10 +198,17 @@ module ActiveSupport
# cache.fetch('city') # => "Duckburgh"
#
# You may also specify additional options via the +options+ argument.
- # Setting <tt>force: true</tt> will force a cache miss:
+ # Setting <tt>force: true</tt> forces a cache "miss," meaning we treat
+ # the cache value as missing even if it's present. Passing a block is
+ # required when `force` is true so this always results in a cache write.
#
# cache.write('today', 'Monday')
- # cache.fetch('today', force: true) # => nil
+ # cache.fetch('today', force: true) { 'Tuesday' } # => 'Tuesday'
+ # cache.fetch('today', force: true) # => ArgumentError
+ #
+ # The `:force` option is useful when you're calling some other method to
+ # ask whether you should force a cache write. Otherwise, it's clearer to
+ # just call `Cache#write`.
#
# Setting <tt>:compress</tt> will store a large cache entry set by the call
# in a compressed format.
@@ -292,6 +299,8 @@ module ActiveSupport
else
save_block_result_to_cache(name, options) { |_name| yield _name }
end
+ elsif options && options[:force]
+ raise ArgumentError, 'Missing block: Calling `Cache#fetch` with `force: true` requires a block.'
else
read(name, options)
end
@@ -323,7 +332,7 @@ module ActiveSupport
end
end
- # Read multiple values at once from the cache. Options can be passed
+ # Reads multiple values at once from the cache. Options can be passed
# in the last argument.
#
# Some cache implementation may optimize this method.
@@ -413,7 +422,7 @@ module ActiveSupport
end
end
- # Delete all entries with keys matching the pattern.
+ # Deletes all entries with keys matching the pattern.
#
# Options are passed to the underlying cache implementation.
#
@@ -422,7 +431,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support delete_matched")
end
- # Increment an integer value in the cache.
+ # Increments an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
@@ -431,7 +440,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support increment")
end
- # Decrement an integer value in the cache.
+ # Decrements an integer value in the cache.
#
# Options are passed to the underlying cache implementation.
#
@@ -440,7 +449,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support decrement")
end
- # Cleanup the cache by removing expired entries.
+ # Cleanups the cache by removing expired entries.
#
# Options are passed to the underlying cache implementation.
#
@@ -449,7 +458,7 @@ module ActiveSupport
raise NotImplementedError.new("#{self.class.name} does not support cleanup")
end
- # Clear the entire cache. Be careful with this method since it could
+ # Clears the entire cache. Be careful with this method since it could
# affect other processes if shared cache is being used.
#
# The options hash is passed to the underlying cache implementation.
@@ -460,7 +469,7 @@ module ActiveSupport
end
protected
- # Add the namespace defined in the options to a pattern designed to
+ # Adds the namespace defined in the options to a pattern designed to
# match keys. Implementations that support delete_matched should call
# this method to translate a pattern that matches names into one that
# matches namespaced keys.
@@ -479,26 +488,26 @@ module ActiveSupport
end
end
- # Read an entry from the cache implementation. Subclasses must implement
+ # Reads an entry from the cache implementation. Subclasses must implement
# this method.
def read_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
- # Write an entry to the cache implementation. Subclasses must implement
+ # Writes an entry to the cache implementation. Subclasses must implement
# this method.
def write_entry(key, entry, options) # :nodoc:
raise NotImplementedError.new
end
- # Delete an entry from the cache implementation. Subclasses must
+ # Deletes an entry from the cache implementation. Subclasses must
# implement this method.
def delete_entry(key, options) # :nodoc:
raise NotImplementedError.new
end
private
- # Merge the default options with ones specific to a method call.
+ # Merges the default options with ones specific to a method call.
def merged_options(call_options) # :nodoc:
if call_options
options.merge(call_options)
@@ -507,7 +516,7 @@ module ActiveSupport
end
end
- # Expand key to be a consistent string value. Invoke +cache_key+ if
+ # Expands key to be a consistent string value. Invokes +cache_key+ if
# object responds to +cache_key+. Otherwise, +to_param+ method will be
# called. If the key is a Hash, then keys will be sorted alphabetically.
def expanded_key(key) # :nodoc:
@@ -527,7 +536,7 @@ module ActiveSupport
key.to_param
end
- # Prefix a key with the namespace. Namespace and key will be delimited
+ # Prefixes a key with the namespace. Namespace and key will be delimited
# with a colon.
def normalize_key(key, options)
key = expanded_key(key)
@@ -575,12 +584,12 @@ module ActiveSupport
end
def get_entry_value(entry, name, options)
- instrument(:fetch_hit, name, options) { |payload| }
+ instrument(:fetch_hit, name, options) { }
entry.value
end
def save_block_result_to_cache(name, options)
- result = instrument(:generate, name, options) do |payload|
+ result = instrument(:generate, name, options) do
yield(name)
end
@@ -598,7 +607,7 @@ module ActiveSupport
class Entry # :nodoc:
DEFAULT_COMPRESS_LIMIT = 16.kilobytes
- # Create a new cache entry for the specified value. Options supported are
+ # Creates a new cache entry for the specified value. Options supported are
# +:compress+, +:compress_threshold+, and +:expires_in+.
def initialize(value, options = {})
if should_compress?(value, options)
@@ -617,7 +626,7 @@ module ActiveSupport
compressed? ? uncompress(@value) : @value
end
- # Check if the entry is expired. The +expires_in+ parameter can override
+ # Checks if the entry is expired. The +expires_in+ parameter can override
# the value set when the entry was created.
def expired?
@expires_in && @created_at + @expires_in <= Time.now.to_f
@@ -652,7 +661,7 @@ module ActiveSupport
end
end
- # Duplicate the value in a class. This is used by cache implementations that don't natively
+ # Duplicates the value in a class. This is used by cache implementations that don't natively
# serialize entries to protect against accidental cache modifications.
def dup_value!
if @value && !compressed? && !(@value.is_a?(Numeric) || @value == true || @value == false)
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index df38dbcf11..1c678dc2af 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -126,7 +126,7 @@ module ActiveSupport
def set_cache_value(value, name, amount, options) # :nodoc:
ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
`set_cache_value` is deprecated and will be removed from Rails 5.1.
- Please use `write_cache_value`
+ Please use `write_cache_value` instead.
MESSAGE
write_cache_value name, value, options
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index e6baddf5db..904d3f0eb0 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -571,15 +571,15 @@ module ActiveSupport
# Install a callback for the given event.
#
- # set_callback :save, :before, :before_meth
- # set_callback :save, :after, :after_meth, if: :condition
+ # set_callback :save, :before, :before_method
+ # set_callback :save, :after, :after_method, if: :condition
# set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
#
# The second argument indicates whether the callback is to be run +:before+,
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
# means the first example above can also be written as:
#
- # set_callback :save, :before_meth
+ # set_callback :save, :before_method
#
# The callback can be specified as a symbol naming an instance method; as a
# proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
@@ -782,7 +782,7 @@ module ActiveSupport
def display_deprecation_warning_for_false_terminator
ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in the next release of Rails.
+ Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1.
To explicitly halt the callback chain, please use `throw :abort` instead.
MSG
end
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
index 54244317e4..89e63aefd4 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -144,9 +144,9 @@ module ActiveSupport
end
compatible |= [false] unless block_share
@waiting[Thread.current] = [purpose, compatible]
-
- @cv.broadcast
end
+
+ @cv.broadcast
end
begin
diff --git a/activesupport/lib/active_support/core_ext/array/grouping.rb b/activesupport/lib/active_support/core_ext/array/grouping.rb
index 87ae052eb0..ea9d85f6e3 100644
--- a/activesupport/lib/active_support/core_ext/array/grouping.rb
+++ b/activesupport/lib/active_support/core_ext/array/grouping.rb
@@ -89,28 +89,19 @@ class Array
# [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
# (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
def split(value = nil)
+ arr = self.dup
+ result = []
if block_given?
- inject([[]]) do |results, element|
- if yield(element)
- results << []
- else
- results.last << element
- end
-
- results
+ while (idx = arr.index { |i| yield i })
+ result << arr.shift(idx)
+ arr.shift
end
else
- results, arr = [[]], self.dup
- until arr.empty?
- if (idx = arr.index(value))
- results.last.concat(arr.shift(idx))
- arr.shift
- results << []
- else
- results.last.concat(arr.shift(arr.size))
- end
+ while (idx = arr.index(value))
+ result << arr.shift(idx)
+ arr.shift
end
- results
end
+ result << arr
end
end
diff --git a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
index 22fc7ecf92..074e2eabf8 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -3,10 +3,8 @@ require 'bigdecimal/util'
module ActiveSupport
module BigDecimalWithDefaultFormat #:nodoc:
- DEFAULT_STRING_FORMAT = 'F'
-
- def to_s(format = nil)
- super(format || DEFAULT_STRING_FORMAT)
+ def to_s(format = 'F')
+ super(format)
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index b0f9a8be34..1d8c33b43e 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -26,8 +26,6 @@ class Class
# Returns an array with the direct children of +self+.
#
- # Integer.subclasses # => [Fixnum, Bignum]
- #
# class Foo; end
# class Bar < Foo; end
# class Baz < Bar; end
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index ed8bca77ac..9a6d7bb415 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -80,6 +80,7 @@ class Date
#
# date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
def to_time(form = :local)
+ raise ArgumentError, "Expected :local or :utc, got #{form.inspect}." unless [:local, :utc].include?(form)
::Time.send(form, year, month, day)
end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
index 4da7fdd159..6206546672 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/calculations.rb
@@ -294,6 +294,11 @@ module DateAndTime
end
alias :at_end_of_year :end_of_year
+ # Returns a Range representing the whole day of the current date/time.
+ def all_day
+ beginning_of_day..end_of_day
+ end
+
# Returns a Range representing the whole week of the current date/time.
# Week starts on start_day, default is <tt>Date.week_start</tt> or <tt>config.week_start</tt> when set.
def all_week(start_day = Date.beginning_of_week)
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
new file mode 100644
index 0000000000..19e596a144
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -0,0 +1,18 @@
+require 'active_support/core_ext/module/attribute_accessors'
+
+module DateAndTime
+ module Compatibility
+ # If true, +to_time+ preserves the timezone offset of receiver.
+ #
+ # NOTE: With Ruby 2.4+ the default for +to_time+ changed from
+ # converting to the local system time, to preserving the offset
+ # of the receiver. For backwards compatibility we're overriding
+ # this behavior, but new apps will have an initializer that sets
+ # this to true, because the new behavior is preferred.
+ mattr_accessor(:preserve_timezone, instance_writer: false) { false }
+
+ def to_time
+ preserve_timezone ? getlocal(utc_offset) : getlocal
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
index d29a8db5cf..e2432c8f8a 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/zones.rb
@@ -5,7 +5,7 @@ module DateAndTime
#
# Time.zone = 'Hawaii' # => 'Hawaii'
# Time.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
- # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
+ # Date.new(2000).in_time_zone # => Sat, 01 Jan 2000 00:00:00 HST -10:00
#
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
# instead of the operating system's time zone.
@@ -14,7 +14,7 @@ module DateAndTime
# and the conversion will be based on that zone instead of <tt>Time.zone</tt>.
#
# Time.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
- # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
+ # Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
time_zone = ::Time.find_zone! zone
time = acts_like?(:time) ? self : nil
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index bcb228b09a..86177488c0 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/date_time/acts_like'
require 'active_support/core_ext/date_time/blank'
require 'active_support/core_ext/date_time/calculations'
+require 'active_support/core_ext/date_time/compatibility'
require 'active_support/core_ext/date_time/conversions'
-require 'active_support/core_ext/date_time/zones'
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 95617fb8c2..9e89a33491 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -28,6 +28,13 @@ class DateTime
end_of_day.to_i - to_i
end
+ # Returns the fraction of a second as a +Rational+
+ #
+ # DateTime.new(2012, 8, 29, 0, 0, 0.5).subsec # => (1/2)
+ def subsec
+ sec_fraction
+ end
+
# Returns a new DateTime where one or more of the elements have been changed
# according to the +options+ parameter. The time options (<tt>:hour</tt>,
# <tt>:min</tt>, <tt>:sec</tt>) reset cascadingly, so if only the hour is
@@ -143,14 +150,32 @@ class DateTime
end
alias :at_end_of_minute :end_of_minute
- # Adjusts DateTime to UTC by adding its offset value; offset is set to 0.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
+ def localtime(utc_offset = nil)
+ utc = new_offset(0)
+
+ Time.utc(
+ utc.year, utc.month, utc.day,
+ utc.hour, utc.min, utc.sec + utc.sec_fraction
+ ).getlocal(utc_offset)
+ end
+ alias_method :getlocal, :localtime
+
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
#
# DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)) # => Mon, 21 Feb 2005 10:11:12 -0600
- # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 +0000
+ # DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc # => Mon, 21 Feb 2005 16:11:12 UTC
def utc
- new_offset(0)
+ utc = new_offset(0)
+
+ Time.utc(
+ utc.year, utc.month, utc.day,
+ utc.hour, utc.min, utc.sec + utc.sec_fraction
+ )
end
+ alias_method :getgm, :utc
alias_method :getutc, :utc
+ alias_method :gmtime, :utc
# Returns +true+ if <tt>offset == 0</tt>.
def utc?
@@ -165,13 +190,10 @@ class DateTime
# Layers additional behavior on DateTime#<=> so that Time and
# ActiveSupport::TimeWithZone instances can be compared with a DateTime.
def <=>(other)
- if other.kind_of?(Infinity)
- super
- elsif other.respond_to? :to_datetime
+ if other.respond_to? :to_datetime
super other.to_datetime rescue nil
else
- nil
+ super
end
end
-
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
new file mode 100644
index 0000000000..03e4a2adfa
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/date_and_time/compatibility'
+
+class DateTime
+ prepend DateAndTime::Compatibility
+end
diff --git a/activesupport/lib/active_support/core_ext/date_time/zones.rb b/activesupport/lib/active_support/core_ext/date_time/zones.rb
deleted file mode 100644
index c39f358395..0000000000
--- a/activesupport/lib/active_support/core_ext/date_time/zones.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-require 'date'
-require 'active_support/core_ext/date_and_time/zones'
-
-class DateTime
- include DateAndTime::Zones
-end
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 8a74ad4d66..8ebe758078 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -17,11 +17,12 @@ module Enumerable
# The default sum of an empty list is zero. You can override this default:
#
# [].sum(Payment.new(0)) { |i| i.amount } # => Payment.new(0)
- def sum(identity = 0, &block)
+ def sum(identity = nil, &block)
if block_given?
map(&block).sum(identity)
else
- inject(:+) || identity
+ sum = identity ? inject(identity, :+) : inject(:+)
+ sum || identity || 0
end
end
@@ -33,7 +34,9 @@ module Enumerable
# => { "Chade- Fowlersburg-e" => <Person ...>, "David Heinemeier Hansson" => <Person ...>, ...}
def index_by
if block_given?
- Hash[map { |elem| [yield(elem), elem] }]
+ result = {}
+ each { |elem| result[yield(elem)] = elem }
+ result
else
to_enum(:index_by) { size if respond_to?(:size) }
end
@@ -91,15 +94,36 @@ end
class Range #:nodoc:
# Optimize range sum to use arithmetic progression if a block is not given and
# we have a range of numeric values.
- def sum(identity = 0)
+ def sum(identity = nil)
if block_given? || !(first.is_a?(Integer) && last.is_a?(Integer))
super
else
actual_last = exclude_end? ? (last - 1) : last
if actual_last >= first
- (actual_last - first + 1) * (actual_last + first) / 2
+ sum = identity || 0
+ sum + (actual_last - first + 1) * (actual_last + first) / 2
else
- identity
+ identity || 0
+ end
+ end
+ end
+end
+
+# Array#sum was added in Ruby 2.4 but it only works with Numeric elements.
+#
+# We tried shimming it to attempt the fast native method, rescue TypeError,
+# and fall back to the compatible implementation, but that's much slower than
+# just calling the compat method in the first place.
+if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
+ class Array
+ alias :orig_sum :sum
+
+ def sum(init = nil, &block) #:nodoc:
+ if init.is_a?(Numeric) || first.is_a?(Numeric)
+ init ||= 0
+ orig_sum(init, &block)
+ else
+ super
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 6741e732f0..2fc514cfce 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -31,7 +31,7 @@ class Hash
# with +key+ as <tt>:root</tt>, and +key+ singularized as second argument. The
# callable can add nodes by using <tt>options[:builder]</tt>.
#
- # 'foo'.to_xml(lambda { |options, key| options[:builder].b(key) })
+ # {foo: lambda { |options, key| options[:builder].b(key) }}.to_xml
# # => "<b>foo</b>"
#
# * If +value+ responds to +to_xml+ the method is invoked with +key+ as <tt>:root</tt>.
@@ -55,8 +55,7 @@ class Hash
#
# XML_TYPE_NAMES = {
# "Symbol" => "symbol",
- # "Fixnum" => "integer",
- # "Bignum" => "integer",
+ # "Integer" => "integer",
# "BigDecimal" => "decimal",
# "Float" => "float",
# "TrueClass" => "boolean",
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index 8b2366c4b3..1bfa18aeee 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -11,7 +11,7 @@ class Hash
# hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
def transform_keys
return enum_for(:transform_keys) { size } unless block_given?
- result = self.class.new
+ result = {}
each_key do |key|
result[yield(key)] = self[key]
end
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index ca278cb2fa..edfc8296fe 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -3,7 +3,7 @@ module ActiveSupport
def load(source)
super(source)
rescue ArgumentError, NameError => exc
- if exc.message.match(%r|undefined class/module (.+)|)
+ if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|)
# try loading the class/module
loaded = $1.constantize
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 76825862d7..567ac825e9 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -27,7 +27,7 @@ class Module
# <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
#
# module HairColors
- # mattr_writer :hair_colors, instance_reader: false
+ # mattr_reader :hair_colors, instance_reader: false
# end
#
# class Person
@@ -40,7 +40,7 @@ class Module
# Also, you can pass a block to set up the attribute with a default value.
#
# module HairColors
- # cattr_reader :hair_colors do
+ # mattr_reader :hair_colors do
# [:brown, :black, :blonde, :red]
# end
# end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 0d46248582..24450cd221 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -149,14 +149,11 @@ class Module
#
# The target method must be public, otherwise it will raise +NoMethodError+.
#
- def delegate(*methods)
- options = methods.pop
- unless options.is_a?(Hash) && to = options[:to]
+ def delegate(*methods, to: nil, prefix: nil, allow_nil: nil)
+ unless to
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
end
- prefix, allow_nil = options.values_at(:prefix, :allow_nil)
-
if prefix == true && to =~ /^[^a-z_]/
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index b25925b9d4..6586a351f8 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -134,6 +134,12 @@ module ActiveSupport::NumericWithFormat
deprecate to_formatted_s: :to_s
end
-[Fixnum, Bignum, Float, BigDecimal].each do |klass|
- klass.prepend(ActiveSupport::NumericWithFormat)
+# Ruby 2.4+ unifies Fixnum & Bignum into Integer.
+if Integer == Fixnum
+ Integer.prepend ActiveSupport::NumericWithFormat
+else
+ Fixnum.prepend ActiveSupport::NumericWithFormat
+ Bignum.prepend ActiveSupport::NumericWithFormat
end
+Float.prepend ActiveSupport::NumericWithFormat
+BigDecimal.prepend ActiveSupport::NumericWithFormat
diff --git a/activesupport/lib/active_support/core_ext/numeric/time.rb b/activesupport/lib/active_support/core_ext/numeric/time.rb
index 6c4a975495..c6ece22f8d 100644
--- a/activesupport/lib/active_support/core_ext/numeric/time.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/time.rb
@@ -25,17 +25,17 @@ class Numeric
# Returns a Duration instance matching the number of minutes provided.
#
- # 2.minutes # => 120 seconds
+ # 2.minutes # => 2 minutes
def minutes
- ActiveSupport::Duration.new(self * 60, [[:seconds, self * 60]])
+ ActiveSupport::Duration.new(self * 60, [[:minutes, self]])
end
alias :minute :minutes
# Returns a Duration instance matching the number of hours provided.
#
- # 2.hours # => 7_200 seconds
+ # 2.hours # => 2 hours
def hours
- ActiveSupport::Duration.new(self * 3600, [[:seconds, self * 3600]])
+ ActiveSupport::Duration.new(self * 3600, [[:hours, self]])
end
alias :hour :hours
@@ -49,17 +49,17 @@ class Numeric
# Returns a Duration instance matching the number of weeks provided.
#
- # 2.weeks # => 14 days
+ # 2.weeks # => 2 weeks
def weeks
- ActiveSupport::Duration.new(self * 7.days, [[:days, self * 7]])
+ ActiveSupport::Duration.new(self * 7.days, [[:weeks, self]])
end
alias :week :weeks
# Returns a Duration instance matching the number of fortnights provided.
#
- # 2.fortnights # => 28 days
+ # 2.fortnights # => 4 weeks
def fortnights
- ActiveSupport::Duration.new(self * 2.weeks, [[:days, self * 14]])
+ ActiveSupport::Duration.new(self * 2.weeks, [[:weeks, self * 2]])
end
alias :fortnight :fortnights
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 039c50a4a2..cb74bad73e 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -112,7 +112,10 @@ class String
#
# @return [true, false]
def blank?
- BLANK_RE === self
+ # The regexp that matches blank strings is expensive. For the case of empty
+ # strings we can speed up this method (~3.5x) with an empty? call. The
+ # penalty for the rest of strings is marginal.
+ empty? || BLANK_RE === self
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index befa5aee21..9bc5ee65ba 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -70,7 +70,7 @@ class Numeric
# Numbers are not duplicable:
#
# 3.duplicable? # => false
- # 3.dup # => TypeError: can't dup Fixnum
+ # 3.dup # => TypeError: can't dup Integer
def duplicable?
false
end
diff --git a/activesupport/lib/active_support/core_ext/object/json.rb b/activesupport/lib/active_support/core_ext/object/json.rb
index 0db787010c..d49b2fbe54 100644
--- a/activesupport/lib/active_support/core_ext/object/json.rb
+++ b/activesupport/lib/active_support/core_ext/object/json.rb
@@ -197,3 +197,9 @@ class Process::Status #:nodoc:
{ :exitstatus => exitstatus, :pid => pid }
end
end
+
+class Exception
+ def as_json(options = nil)
+ to_s
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 8c16d95b62..3b6d9da216 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -99,7 +99,7 @@ class Object
#
# "a".try!(:upcase) # => "A"
# nil.try!(:upcase) # => nil
- # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Fixnum
+ # 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
end
class Delegator
diff --git a/activesupport/lib/active_support/core_ext/string/access.rb b/activesupport/lib/active_support/core_ext/string/access.rb
index ebd0dd3fc7..213a91aa7a 100644
--- a/activesupport/lib/active_support/core_ext/string/access.rb
+++ b/activesupport/lib/active_support/core_ext/string/access.rb
@@ -1,5 +1,5 @@
class String
- # If you pass a single Fixnum, returns a substring of one character at that
+ # If you pass a single integer, returns a substring of one character at that
# position. The first character of the string is at position 0, the next at
# position 1, and so on. If a range is supplied, a substring containing
# characters at offsets given by the range is returned. In both cases, if an
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index fd79a40e31..946976c5e9 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -18,7 +18,8 @@ class String
# "12/13/2012".to_time # => ArgumentError: argument out of range
def to_time(form = :local)
parts = Date._parse(self, false)
- return if parts.empty?
+ used_keys = %i(year mon mday hour min sec sec_fraction offset)
+ return if (parts.keys & used_keys).empty?
now = Time.now
time = Time.new(
@@ -31,7 +32,7 @@ class String
parts.fetch(:offset, form == :utc ? 0 : nil)
)
- form == :utc ? time.utc : time.getlocal
+ form == :utc ? time.utc : time.to_time
end
# Converts a string to a Date value.
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index cc71b8155d..7277f51076 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -222,6 +222,15 @@ class String
ActiveSupport::Inflector.humanize(self, options)
end
+ # Converts just the first character to uppercase.
+ #
+ # 'what a Lovely Day'.upcase_first # => "What a Lovely Day"
+ # 'w'.upcase_first # => "W"
+ # ''.upcase_first # => ""
+ def upcase_first
+ ActiveSupport::Inflector.upcase_first(self)
+ end
+
# Creates a foreign key name from a class name.
# +separate_class_name_and_id_with_underscore+ sets whether
# the method should put '_' between the name and 'id'.
diff --git a/activesupport/lib/active_support/core_ext/string/output_safety.rb b/activesupport/lib/active_support/core_ext/string/output_safety.rb
index 43b9fd4bf7..005ad93b08 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -250,7 +250,7 @@ end
class String
# Marks a string as trusted safe. It will be inserted into HTML with no
- # additional escaping performed. It is your responsibilty to ensure that the
+ # additional escaping performed. It is your responsibility to ensure that the
# string contains no malicious content. This method is equivalent to the
# `raw` helper in views. It is recommended that you use `sanitize` instead of
# this method. It should never be called on user input.
diff --git a/activesupport/lib/active_support/core_ext/time.rb b/activesupport/lib/active_support/core_ext/time.rb
index 72c3234630..0bce632222 100644
--- a/activesupport/lib/active_support/core_ext/time.rb
+++ b/activesupport/lib/active_support/core_ext/time.rb
@@ -1,4 +1,5 @@
require 'active_support/core_ext/time/acts_like'
require 'active_support/core_ext/time/calculations'
+require 'active_support/core_ext/time/compatibility'
require 'active_support/core_ext/time/conversions'
require 'active_support/core_ext/time/zones'
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 768c9a1b2c..e81b48ab26 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -73,6 +73,13 @@ class Time
end_of_day.to_i - to_i
end
+ # Returns the fraction of a second as a +Rational+
+ #
+ # Time.new(2012, 8, 29, 0, 0, 0.5).sec_fraction # => (1/2)
+ def sec_fraction
+ subsec
+ end
+
# Returns a new Time where one or more of the elements have been changed according
# to the +options+ parameter. The time options (<tt>:hour</tt>, <tt>:min</tt>,
# <tt>:sec</tt>, <tt>:usec</tt>, <tt>:nsec</tt>) reset cascadingly, so if only
@@ -220,11 +227,6 @@ class Time
end
alias :at_end_of_minute :end_of_minute
- # Returns a Range representing the whole day of the current time.
- def all_day
- beginning_of_day..end_of_day
- end
-
def plus_with_duration(other) #:nodoc:
if ActiveSupport::Duration === other
other.since(self)
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
new file mode 100644
index 0000000000..945319461b
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -0,0 +1,5 @@
+require 'active_support/core_ext/date_and_time/compatibility'
+
+class Time
+ prepend DateAndTime::Compatibility
+end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 0da01b0fe8..57f6286de3 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -88,15 +88,6 @@ module ActiveSupport #:nodoc:
mattr_accessor :explicitly_unloadable_constants
self.explicitly_unloadable_constants = []
- # The logger is used for generating information on the action run-time
- # (including benchmarking) if available. Can be set to nil for no logging.
- # Compatible with both Ruby's own Logger and Log4r loggers.
- mattr_accessor :logger
-
- # Set to +true+ to enable logging of const_missing and file loads.
- mattr_accessor :log_activity
- self.log_activity = false
-
# The WatchStack keeps a stack of the modules being watched as files are
# loaded. If a file in the process of being loaded (parent.rb) triggers the
# load of another file (child.rb) the stack will ensure that child.rb
@@ -352,7 +343,6 @@ module ActiveSupport #:nodoc:
end
def clear
- log_call
Dependencies.unload_interlock do
loaded.clear
loading.clear
@@ -361,7 +351,6 @@ module ActiveSupport #:nodoc:
end
def require_or_load(file_name, const_path = nil)
- log_call file_name, const_path
file_name = $` if file_name =~ /\.rb\z/
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
@@ -377,8 +366,6 @@ module ActiveSupport #:nodoc:
begin
if load?
- log "loading #{file_name}"
-
# Enable warnings if this file has not been loaded before and
# warnings_on_first_load is set.
load_args = ["#{file_name}.rb"]
@@ -390,7 +377,6 @@ module ActiveSupport #:nodoc:
enable_warnings { result = load_file(*load_args) }
end
else
- log "requiring #{file_name}"
result = require file_name
end
rescue Exception
@@ -483,7 +469,6 @@ module ActiveSupport #:nodoc:
# set of names that the file at +path+ may define. See
# +loadable_constants_for_path+ for more details.
def load_file(path, const_paths = loadable_constants_for_path(path))
- log_call path, const_paths
const_paths = [const_paths].compact unless const_paths.is_a? Array
parent_paths = const_paths.collect { |const_path| const_path[/.*(?=::)/] || ::Object }
@@ -494,7 +479,6 @@ module ActiveSupport #:nodoc:
autoloaded_constants.concat newly_defined_paths unless load_once_path?(path)
autoloaded_constants.uniq!
- log "loading #{path} defined #{newly_defined_paths * ', '}" unless newly_defined_paths.empty?
result
end
@@ -508,8 +492,6 @@ module ActiveSupport #:nodoc:
# it is not possible to load the constant into from_mod, try its parent
# module using +const_missing+.
def load_missing_constant(from_mod, const_name)
- log_call from_mod, const_name
-
unless qualified_const_defined?(from_mod.name) && Inflector.constantize(from_mod.name).equal?(from_mod)
raise ArgumentError, "A copy of #{from_mod} has been removed from the module tree but is still active!"
end
@@ -673,25 +655,20 @@ module ActiveSupport #:nodoc:
# exception, any new constants are regarded as being only partially defined
# and will be removed immediately.
def new_constants_in(*descs)
- log_call(*descs)
-
constant_watch_stack.watch_namespaces(descs)
- aborting = true
+ success = false
begin
yield # Now yield to the code that is to define new constants.
- aborting = false
+ success = true
ensure
new_constants = constant_watch_stack.new_constants
- log "New constants: #{new_constants * ', '}"
- return new_constants unless aborting
+ return new_constants if success
- log "Error during loading, removing partially loaded constants "
- new_constants.each { |c| remove_constant(c) }.clear
+ # Remove partially loaded constants.
+ new_constants.each { |c| remove_constant(c) }
end
-
- []
end
# Convert the provided const desc to a qualified constant name (as a string).
@@ -738,8 +715,6 @@ module ActiveSupport #:nodoc:
parent = constantize(parent_name)
end
- log "removing constant #{const}"
-
# In an autoloaded user.rb like this
#
# autoload :Foo, 'foo'
@@ -760,7 +735,7 @@ module ActiveSupport #:nodoc:
begin
constantized = parent.const_get(to_remove, false)
rescue NameError
- log "the constant #{const} is not reachable anymore, skipping"
+ # The constant is no longer reachable, just skip it.
return
else
constantized.before_remove_const if constantized.respond_to?(:before_remove_const)
@@ -770,27 +745,9 @@ module ActiveSupport #:nodoc:
begin
parent.instance_eval { remove_const to_remove }
rescue NameError
- log "the constant #{const} is not reachable anymore, skipping"
+ # The constant is no longer reachable, just skip it.
end
end
-
- protected
- def log_call(*args)
- if log_activity?
- arg_str = args.collect(&:inspect) * ', '
- /in `([a-z_\?\!]+)'/ =~ caller(1).first
- selector = $1 || '<unknown>'
- log "called #{selector}(#{arg_str})"
- end
- end
-
- def log(msg)
- logger.debug "Dependencies: #{msg}" if log_activity?
- end
-
- def log_activity?
- logger && log_activity
- end
end
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 24545d766c..b581710067 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -32,7 +32,7 @@ module ActiveSupport
# and the second is a library name
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = '5.1', gem_name = 'Rails')
+ def initialize(deprecation_horizon = '5.2', gem_name = 'Rails')
self.gem_name = gem_name
self.deprecation_horizon = deprecation_horizon
# By default, warnings are not silenced and debugging is off.
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index 35f084dd7a..de5b233679 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -65,7 +65,6 @@ module ActiveSupport
def deprecation_message(callstack, message = nil)
message ||= "You are using deprecated behavior which will be removed from the next major or minor release."
- message += '.' unless message =~ /\.$/
"DEPRECATION WARNING: #{message} #{deprecation_caller_message(callstack)}"
end
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index c63b61e97a..47d09f4f5a 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -9,6 +9,9 @@ module ActiveSupport
class Duration
attr_accessor :value, :parts
+ autoload :ISO8601Parser, 'active_support/duration/iso8601_parser'
+ autoload :ISO8601Serializer, 'active_support/duration/iso8601_serializer'
+
def initialize(value, parts) #:nodoc:
@value, @parts = value, parts
end
@@ -117,7 +120,7 @@ module ActiveSupport
def inspect #:nodoc:
parts.
reduce(::Hash.new(0)) { |h,(l,r)| h[l] += r; h }.
- sort_by {|unit, _ | [:years, :months, :days, :minutes, :seconds].index(unit)}.
+ sort_by {|unit, _ | [:years, :months, :weeks, :days, :hours, :minutes, :seconds].index(unit)}.
map {|unit, val| "#{val} #{val == 1 ? unit.to_s.chop : unit.to_s}"}.
to_sentence(locale: ::I18n.default_locale)
end
@@ -130,6 +133,23 @@ module ActiveSupport
@value.respond_to?(method, include_private)
end
+ # Creates a new Duration from string formatted according to ISO 8601 Duration.
+ #
+ # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
+ # This method allows negative parts to be present in pattern.
+ # If invalid string is provided, it will raise +ActiveSupport::Duration::ISO8601Parser::ParsingError+.
+ def self.parse(iso8601duration)
+ parts = ISO8601Parser.new(iso8601duration).parse!
+ time = ::Time.current
+ new(time.advance(parts) - time, parts)
+ end
+
+ # Build ISO 8601 Duration string for this duration.
+ # The +precision+ parameter can be used to limit seconds' precision of duration.
+ def iso8601(precision: nil)
+ ISO8601Serializer.new(self, precision: precision).serialize
+ end
+
delegate :<=>, to: :value
protected
@@ -139,6 +159,10 @@ module ActiveSupport
if t.acts_like?(:time) || t.acts_like?(:date)
if type == :seconds
t.since(sign * number)
+ elsif type == :minutes
+ t.since(sign * number * 60)
+ elsif type == :hours
+ t.since(sign * number * 3600)
else
t.advance(type => sign * number)
end
diff --git a/activesupport/lib/active_support/duration/iso8601_parser.rb b/activesupport/lib/active_support/duration/iso8601_parser.rb
new file mode 100644
index 0000000000..07af58ad99
--- /dev/null
+++ b/activesupport/lib/active_support/duration/iso8601_parser.rb
@@ -0,0 +1,122 @@
+require 'strscan'
+
+module ActiveSupport
+ class Duration
+ # Parses a string formatted according to ISO 8601 Duration into the hash.
+ #
+ # See {ISO 8601}[http://en.wikipedia.org/wiki/ISO_8601#Durations] for more information.
+ #
+ # This parser allows negative parts to be present in pattern.
+ class ISO8601Parser # :nodoc:
+ class ParsingError < ::ArgumentError; end
+
+ PERIOD_OR_COMMA = /\.|,/
+ PERIOD = '.'.freeze
+ COMMA = ','.freeze
+
+ SIGN_MARKER = /\A\-|\+|/
+ DATE_MARKER = /P/
+ TIME_MARKER = /T/
+ DATE_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(Y|M|D|W)/
+ TIME_COMPONENT = /(\-?\d+(?:[.,]\d+)?)(H|M|S)/
+
+ DATE_TO_PART = { 'Y' => :years, 'M' => :months, 'W' => :weeks, 'D' => :days }
+ TIME_TO_PART = { 'H' => :hours, 'M' => :minutes, 'S' => :seconds }
+
+ DATE_COMPONENTS = [:years, :months, :days]
+ TIME_COMPONENTS = [:hours, :minutes, :seconds]
+
+ attr_reader :parts, :scanner
+ attr_accessor :mode, :sign
+
+ def initialize(string)
+ @scanner = StringScanner.new(string)
+ @parts = {}
+ @mode = :start
+ @sign = 1
+ end
+
+ def parse!
+ while !finished?
+ case mode
+ when :start
+ if scan(SIGN_MARKER)
+ self.sign = (scanner.matched == '-') ? -1 : 1
+ self.mode = :sign
+ else
+ raise_parsing_error
+ end
+
+ when :sign
+ if scan(DATE_MARKER)
+ self.mode = :date
+ else
+ raise_parsing_error
+ end
+
+ when :date
+ if scan(TIME_MARKER)
+ self.mode = :time
+ elsif scan(DATE_COMPONENT)
+ parts[DATE_TO_PART[scanner[2]]] = number * sign
+ else
+ raise_parsing_error
+ end
+
+ when :time
+ if scan(TIME_COMPONENT)
+ parts[TIME_TO_PART[scanner[2]]] = number * sign
+ else
+ raise_parsing_error
+ end
+
+ end
+ end
+
+ validate!
+ parts
+ end
+
+ private
+
+ def finished?
+ scanner.eos?
+ end
+
+ # Parses number which can be a float with either comma or period.
+ def number
+ scanner[1] =~ PERIOD_OR_COMMA ? scanner[1].tr(COMMA, PERIOD).to_f : scanner[1].to_i
+ end
+
+ def scan(pattern)
+ scanner.scan(pattern)
+ end
+
+ def raise_parsing_error(reason = nil)
+ raise ParsingError, "Invalid ISO 8601 duration: #{scanner.string.inspect} #{reason}".strip
+ end
+
+ # Checks for various semantic errors as stated in ISO 8601 standard.
+ def validate!
+ raise_parsing_error('is empty duration') if parts.empty?
+
+ # Mixing any of Y, M, D with W is invalid.
+ if parts.key?(:weeks) && (parts.keys & DATE_COMPONENTS).any?
+ raise_parsing_error('mixing weeks with other date parts not allowed')
+ end
+
+ # Specifying an empty T part is invalid.
+ if mode == :time && (parts.keys & TIME_COMPONENTS).empty?
+ raise_parsing_error('time part marker is present but time part is empty')
+ end
+
+ fractions = parts.values.reject(&:zero?).select { |a| (a % 1) != 0 }
+ unless fractions.empty? || (fractions.size == 1 && fractions.last == @parts.values.reject(&:zero?).last)
+ raise_parsing_error '(only last part can be fractional)'
+ end
+
+ return true
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb
new file mode 100644
index 0000000000..05c6a083a9
--- /dev/null
+++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb
@@ -0,0 +1,51 @@
+require 'active_support/core_ext/object/blank'
+require 'active_support/core_ext/hash/transform_values'
+
+module ActiveSupport
+ class Duration
+ # Serializes duration to string according to ISO 8601 Duration format.
+ class ISO8601Serializer
+ def initialize(duration, precision: nil)
+ @duration = duration
+ @precision = precision
+ end
+
+ # Builds and returns output string.
+ def serialize
+ output = 'P'
+ parts, sign = normalize
+ output << "#{parts[:years]}Y" if parts.key?(:years)
+ output << "#{parts[:months]}M" if parts.key?(:months)
+ output << "#{parts[:weeks]}W" if parts.key?(:weeks)
+ output << "#{parts[:days]}D" if parts.key?(:days)
+ time = ''
+ time << "#{parts[:hours]}H" if parts.key?(:hours)
+ time << "#{parts[:minutes]}M" if parts.key?(:minutes)
+ if parts.key?(:seconds)
+ time << "#{sprintf(@precision ? "%0.0#{@precision}f" : '%g', parts[:seconds])}S"
+ end
+ output << "T#{time}" if time.present?
+ "#{sign}#{output}"
+ end
+
+ private
+
+ # Return pair of duration's parts and whole duration sign.
+ # Parts are summarized (as they can become repetitive due to addition, etc).
+ # Zero parts are removed as not significant.
+ # If all parts are negative it will negate all of them and return minus as a sign.
+ def normalize
+ parts = @duration.parts.each_with_object(Hash.new(0)) do |(k,v),p|
+ p[k] += v unless v.zero?
+ end
+ # If all parts are negative - let's make a negative duration
+ sign = ''
+ if parts.values.all? { |v| v < 0 }
+ sign = '-'
+ parts.transform_values!(&:-@)
+ end
+ [parts, sign]
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index 6a02a838b7..21fdf7bb80 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -86,16 +86,6 @@ module ActiveSupport
end
class PathHelper
- using Module.new {
- refine Pathname do
- def ascendant_of?(other)
- self != other && other.ascend do |ascendant|
- break true if self == ascendant
- end
- end
- end
- }
-
def xpath(path)
Pathname.new(path).expand_path
end
@@ -112,7 +102,7 @@ module ActiveSupport
lcsp = Pathname.new(paths[0])
paths[1..-1].each do |path|
- until lcsp.ascendant_of?(path)
+ until ascendant_of?(lcsp, path)
if lcsp.root?
# If we get here a root directory is not an ascendant of path.
# This may happen if there are paths in different drives on
@@ -145,13 +135,21 @@ module ActiveSupport
dir = dirs_sorted_by_nparts.shift
dirs_sorted_by_nparts.reject! do |possible_descendant|
- dir.ascendant_of?(possible_descendant) && descendants << possible_descendant
+ ascendant_of?(dir, possible_descendant) && descendants << possible_descendant
end
end
# Array#- preserves order.
dirs - descendants
end
+
+ private
+
+ def ascendant_of?(base, other)
+ base != other && other.ascend do |ascendant|
+ break true if base == ascendant
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/execution_wrapper.rb b/activesupport/lib/active_support/execution_wrapper.rb
index 2bd1c01d35..00c5745a25 100644
--- a/activesupport/lib/active_support/execution_wrapper.rb
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -19,6 +19,32 @@ module ActiveSupport
set_callback(:complete, *args, &block)
end
+ # Register an object to be invoked during both the +run+ and
+ # +complete+ steps.
+ #
+ # +hook.complete+ will be passed the value returned from +hook.run+,
+ # and will only be invoked if +run+ has previously been called.
+ # (Mostly, this means it won't be invoked if an exception occurs in
+ # a preceding +to_run+ block; all ordinary +to_complete+ blocks are
+ # invoked in that situation.)
+ def self.register_hook(hook, outer: false)
+ if outer
+ run_args = [prepend: true]
+ complete_args = [:after]
+ else
+ run_args = complete_args = []
+ end
+
+ to_run(*run_args) do
+ hook_state[hook] = hook.run
+ end
+ to_complete(*complete_args) do
+ if hook_state.key?(hook)
+ hook.complete hook_state[hook]
+ end
+ end
+ end
+
# Run this execution.
#
# Returns an instance, whose +complete!+ method *must* be invoked
@@ -29,7 +55,15 @@ module ActiveSupport
if active?
Null
else
- new.tap(&:run!)
+ new.tap do |instance|
+ success = nil
+ begin
+ instance.run!
+ success = true
+ ensure
+ instance.complete! unless success
+ end
+ end
end
end
@@ -37,11 +71,11 @@ module ActiveSupport
def self.wrap
return yield if active?
- state = run!
+ instance = run!
begin
yield
ensure
- state.complete!
+ instance.complete!
end
end
@@ -74,5 +108,10 @@ module ActiveSupport
ensure
self.class.active.delete Thread.current
end
+
+ private
+ def hook_state
+ @_hook_state ||= {}
+ end
end
end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 8708a502e6..b5667b6ac8 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/time/calculations'
+
module ActiveSupport
# FileUpdateChecker specifies the API used by Rails to watch files
# and control reloading. The API depends on four methods:
@@ -112,7 +114,24 @@ module ActiveSupport
# reloading is not triggered.
def max_mtime(paths)
time_now = Time.now
- paths.map {|path| File.mtime(path)}.reject {|mtime| time_now < mtime}.max
+ max_mtime = nil
+
+ # Time comparisons are performed with #compare_without_coercion because
+ # AS redefines these operators in a way that is much slower and does not
+ # bring any benefit in this particular code.
+ #
+ # Read t1.compare_without_coercion(t2) < 0 as t1 < t2.
+ paths.each do |path|
+ mtime = File.mtime(path)
+
+ next if time_now.compare_without_coercion(mtime) < 0
+
+ if max_mtime.nil? || max_mtime.compare_without_coercion(mtime) < 0
+ max_mtime = mtime
+ end
+ end
+
+ max_mtime
end
def compile_glob(hash)
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 4166ffc2fb..74f2d8dd4b 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveSupport
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
- PRE = "beta3"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index f741c0bfb8..f94e12e14f 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -140,6 +140,15 @@ module ActiveSupport
result
end
+ # Converts just the first character to uppercase.
+ #
+ # upcase_first('what a Lovely Day') # => "What a Lovely Day"
+ # upcase_first('w') # => "W"
+ # upcase_first('') # => ""
+ def upcase_first(string)
+ string.length > 0 ? string[0].upcase.concat(string[1..-1]) : ''
+ end
+
# Capitalizes all the words and replaces some characters in the string to
# create a nicer looking title. +titleize+ is meant for creating pretty
# output. It is not used in the Rails internals.
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 55628f0313..7a49bbb960 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -15,7 +15,7 @@ module ActiveSupport
extend self
- # Formats a +number+ into a US phone number (e.g., (555)
+ # Formats a +number+ into a phone number (US by default e.g., (555)
# 123-9876). You can customize the format in the +options+ hash.
#
# ==== Options
@@ -27,6 +27,8 @@ module ActiveSupport
# end of the generated number.
# * <tt>:country_code</tt> - Sets the country code for the phone
# number.
+ # * <tt>:pattern</tt> - Specifies how the number is divided into three
+ # groups with the custom regexp to override the default format.
# ==== Examples
#
# number_to_phone(5551234) # => "555-1234"
@@ -40,6 +42,11 @@ module ActiveSupport
#
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
# # => "+1.123.555.1234 x 1343"
+ #
+ # number_to_phone(75561234567, pattern: /(\d{1,4})(\d{4})(\d{4})$/, area_code: true)
+ # # => "(755) 6123-4567"
+ # number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})$/))
+ # # => "133-1234-5678"
def number_to_phone(number, options = {})
NumberToPhoneConverter.convert(number, options)
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
index 45ae8f1a93..43c5540b6f 100644
--- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -12,7 +12,7 @@ module ActiveSupport
private
def parts
- left, right = number.to_s.split('.')
+ left, right = number.to_s.split('.'.freeze)
left.gsub!(delimiter_pattern) do |digit_to_delimit|
"#{digit_to_delimit}#{options[:delimiter]}"
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
index af2ee56d91..dee74fa7a6 100644
--- a/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_phone_converter.rb
@@ -18,12 +18,16 @@ module ActiveSupport
end
def convert_with_area_code(number)
- number.gsub!(/(\d{1,3})(\d{3})(\d{4}$)/,"(\\1) \\2#{delimiter}\\3")
+ default_pattern = /(\d{1,3})(\d{3})(\d{4}$)/
+ number.gsub!(regexp_pattern(default_pattern),
+ "(\\1) \\2#{delimiter}\\3")
number
end
def convert_without_area_code(number)
- number.gsub!(/(\d{0,3})(\d{3})(\d{4})$/,"\\1#{delimiter}\\2#{delimiter}\\3")
+ default_pattern = /(\d{0,3})(\d{3})(\d{4})$/
+ number.gsub!(regexp_pattern(default_pattern),
+ "\\1#{delimiter}\\2#{delimiter}\\3")
number.slice!(0, 1) if start_with_delimiter?(number)
number
end
@@ -43,6 +47,11 @@ module ActiveSupport
def phone_ext(ext)
ext.blank? ? "" : " x #{ext}"
end
+
+ def regexp_pattern(default_pattern)
+ opts.fetch :pattern, default_pattern
+ end
+
end
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
index 981c562551..9fb7dfb779 100644
--- a/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_rounded_converter.rb
@@ -29,9 +29,11 @@ module ActiveSupport
formatted_string =
if BigDecimal === rounded_number && rounded_number.finite?
- s = rounded_number.to_s('F') + '0'*precision
- a, b = s.split('.', 2)
- a + '.' + b[0, precision]
+ s = rounded_number.to_s('F')
+ s << '0'.freeze * precision
+ a, b = s.split('.'.freeze, 2)
+ a << '.'.freeze
+ a << b[0, precision]
else
"%00.#{precision}f" % rounded_number
end
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
index 5d1f0e1e66..5623bdd349 100644
--- a/activesupport/lib/active_support/reloader.rb
+++ b/activesupport/lib/active_support/reloader.rb
@@ -43,7 +43,13 @@ module ActiveSupport
# Initiate a manual reload
def self.reload!
executor.wrap do
- new.tap(&:run!).complete!
+ new.tap do |instance|
+ begin
+ instance.run!
+ ensure
+ instance.complete!
+ end
+ end
end
prepare!
end
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index 73bc52b56f..2c05deee41 100644
--- a/activesupport/lib/active_support/rescuable.rb
+++ b/activesupport/lib/active_support/rescuable.rb
@@ -1,7 +1,6 @@
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/string/inflections'
-require 'active_support/core_ext/array/extract_options'
module ActiveSupport
# Rescuable module adds support for easier exception handling.
@@ -48,14 +47,12 @@ module ActiveSupport
# end
#
# Exceptions raised inside exception handlers are not propagated up.
- def rescue_from(*klasses, &block)
- options = klasses.extract_options!
-
- unless options.has_key?(:with)
+ def rescue_from(*klasses, with: nil, &block)
+ unless with
if block_given?
- options[:with] = block
+ with = block
else
- raise ArgumentError, "Need a handler. Supply an options hash that has a :with key as the last argument."
+ raise ArgumentError, 'Need a handler. Pass the with: keyword argument or provide a block.'
end
end
@@ -65,65 +62,104 @@ module ActiveSupport
elsif klass.is_a?(String)
klass
else
- raise ArgumentError, "#{klass} is neither an Exception nor a String"
+ raise ArgumentError, "#{klass.inspect} must be an Exception class or a String referencing an Exception class"
end
# Put the new handler at the end because the list is read in reverse.
- self.rescue_handlers += [[key, options[:with]]]
+ self.rescue_handlers += [[key, with]]
end
end
- end
- # Tries to rescue the exception by looking up and calling a registered handler.
- def rescue_with_handler(exception)
- if handler = handler_for_rescue(exception)
- handler.arity != 0 ? handler.call(exception) : handler.call
- true # don't rely on the return value of the handler
+ # Matches an exception to a handler based on the exception class.
+ #
+ # If no handler matches the exception, check for a handler matching the
+ # (optional) exception.cause. If no handler matches the exception or its
+ # cause, this returns nil so you can deal with unhandled exceptions.
+ # Be sure to re-raise unhandled exceptions if this is what you expect.
+ #
+ # begin
+ # …
+ # rescue => exception
+ # rescue_with_handler(exception) || raise
+ # end
+ #
+ # Returns the exception if it was handled and nil if it was not.
+ def rescue_with_handler(exception, object: self)
+ if handler = handler_for_rescue(exception, object: object)
+ handler.call exception
+ exception
+ end
end
- end
- def handler_for_rescue(exception)
- # We go from right to left because pairs are pushed onto rescue_handlers
- # as rescue_from declarations are found.
- _, rescuer = self.class.rescue_handlers.reverse.detect do |klass_name, handler|
- # The purpose of allowing strings in rescue_from is to support the
- # declaration of handler associations for exception classes whose
- # definition is yet unknown.
- #
- # Since this loop needs the constants it would be inconsistent to
- # assume they should exist at this point. An early raised exception
- # could trigger some other handler and the array could include
- # precisely a string whose corresponding constant has not yet been
- # seen. This is why we are tolerant to unknown constants.
- #
- # Note that this tolerance only matters if the exception was given as
- # a string, otherwise a NameError will be raised by the interpreter
- # itself when rescue_from CONSTANT is executed.
- klass = self.class.const_get(klass_name) rescue nil
- klass ||= (klass_name.constantize rescue nil)
- klass === exception if klass
+ def handler_for_rescue(exception, object: self) #:nodoc:
+ case rescuer = find_rescue_handler(exception)
+ when Symbol
+ method = object.method(rescuer)
+ if method.arity == 0
+ -> e { method.call }
+ else
+ method
+ end
+ when Proc
+ if rescuer.arity == 0
+ -> e { object.instance_exec(&rescuer) }
+ else
+ -> e { object.instance_exec(e, &rescuer) }
+ end
+ end
end
- case rescuer
- when Symbol
- method(rescuer)
- when Proc
- if rescuer.arity == 0
- Proc.new { instance_exec(&rescuer) }
- else
- Proc.new { |_exception| instance_exec(_exception, &rescuer) }
+ private
+ def find_rescue_handler(exception)
+ if exception
+ # Handlers are in order of declaration but the most recently declared
+ # is the highest priority match, so we search for matching handlers
+ # in reverse.
+ _, handler = rescue_handlers.reverse_each.detect do |class_or_name, _|
+ if klass = constantize_rescue_handler_class(class_or_name)
+ klass === exception
+ end
+ end
+
+ handler || find_rescue_handler(exception.cause)
+ end
+ end
+
+ def constantize_rescue_handler_class(class_or_name)
+ case class_or_name
+ when String, Symbol
+ begin
+ # Try a lexical lookup first since we support
+ #
+ # class Super
+ # rescue_from 'Error', with: …
+ # end
+ #
+ # class Sub
+ # class Error < StandardError; end
+ # end
+ #
+ # so an Error raised in Sub will hit the 'Error' handler.
+ const_get class_or_name
+ rescue NameError
+ class_or_name.safe_constantize
+ end
+ else
+ class_or_name
+ end
end
- end
end
- def index_of_handler_for_rescue(exception)
- handlers = self.class.rescue_handlers.reverse_each.with_index
- _, index = handlers.detect do |(klass_name, _), _|
- klass = self.class.const_get(klass_name) rescue nil
- klass ||= (klass_name.constantize rescue nil)
- klass === exception if klass
- end
- index
+ # Delegates to the class method, but uses the instance as the subject for
+ # rescue_from handlers (method calls, instance_exec blocks).
+ def rescue_with_handler(exception)
+ self.class.rescue_with_handler exception, object: self
+ end
+
+ # Internal handler lookup. Delegates to class method. Some libraries call
+ # this directly, so keeping it around for compatibility.
+ def handler_for_rescue(exception) #:nodoc:
+ self.class.handler_for_rescue exception, object: self
end
end
end
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 79cc748cf5..b1cec43124 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,6 +1,7 @@
require 'active_support/duration'
require 'active_support/values/time_zone'
require 'active_support/core_ext/object/acts_like'
+require 'active_support/core_ext/date_and_time/compatibility'
module ActiveSupport
# A Time-like class that can represent a time in any time zone. Necessary
@@ -44,20 +45,21 @@ module ActiveSupport
PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze }
PRECISIONS[0] = '%FT%T'.freeze
- include Comparable
+ include Comparable, DateAndTime::Compatibility
attr_reader :time_zone
def initialize(utc_time, time_zone, local_time = nil, period = nil)
- @utc, @time_zone, @time = utc_time, time_zone, local_time
+ @utc = utc_time ? transfer_time_values_to_utc_constructor(utc_time) : nil
+ @time_zone, @time = time_zone, local_time
@period = @utc ? period : get_period_and_ensure_valid_local_time(period)
end
- # Returns a Time or DateTime instance that represents the time in +time_zone+.
+ # Returns a <tt>Time</tt> instance that represents the time in +time_zone+.
def time
@time ||= period.to_local(@utc)
end
- # Returns a Time or DateTime instance that represents the time in UTC.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the UTC timezone.
def utc
@utc ||= period.to_utc(@time)
end
@@ -77,10 +79,9 @@ module ActiveSupport
utc.in_time_zone(new_zone)
end
- # Returns a <tt>Time.local()</tt> instance of the simultaneous time in your
- # system's <tt>ENV['TZ']</tt> zone.
+ # Returns a <tt>Time</tt> instance of the simultaneous time in the system timezone.
def localtime(utc_offset = nil)
- utc.respond_to?(:getlocal) ? utc.getlocal(utc_offset) : utc.to_time.getlocal(utc_offset)
+ utc.getlocal(utc_offset)
end
alias_method :getlocal, :localtime
@@ -401,11 +402,6 @@ module ActiveSupport
utc.to_r
end
- # Returns an instance of Time in the system timezone.
- def to_time
- utc.to_time
- end
-
# Returns an instance of DateTime with the timezone's UTC offset
#
# Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000
@@ -454,7 +450,6 @@ module ActiveSupport
# Ensure proxy class responds to all methods that underlying time instance
# responds to.
def respond_to_missing?(sym, include_priv)
- # consistently respond false to acts_like?(:date), regardless of whether #time is a Time or DateTime
return false if sym.to_sym == :acts_like_date?
time.respond_to?(sym, include_priv)
end
@@ -482,7 +477,7 @@ module ActiveSupport
end
def transfer_time_values_to_utc_constructor(time)
- ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec, Rational(time.nsec, 1000))
+ ::Time.utc(time.year, time.month, time.day, time.hour, time.min, time.sec + time.subsec)
end
def duration_of_variable_length?(obj)
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 118bf8eab0..19420cee5e 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -184,6 +184,7 @@ module ActiveSupport
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '')
@lazy_zones_map = Concurrent::Map.new
+ @country_zones = Concurrent::Map.new
class << self
# Assumes self represents an offset from UTC in seconds (as returned from
@@ -242,7 +243,18 @@ module ActiveSupport
# A convenience method for returning a collection of TimeZone objects
# for time zones in the USA.
def us_zones
- @us_zones ||= all.find_all { |z| z.name =~ /US|Arizona|Indiana|Hawaii|Alaska/ }
+ country_zones(:us)
+ end
+
+ # A convenience method for returning a collection of TimeZone objects
+ # for time zones in the country specified by its ISO 3166-1 Alpha2 code.
+ def country_zones(country_code)
+ code = country_code.to_s.upcase
+ @country_zones[code] ||=
+ TZInfo::Country.get(code).zone_identifiers.map do |tz_id|
+ name = MAPPING.key(tz_id)
+ name && self[name]
+ end.compact.sort!
end
private
@@ -266,7 +278,6 @@ module ActiveSupport
@name = name
@utc_offset = utc_offset
@tzinfo = tzinfo || TimeZone.find_tzinfo(name)
- @current_period = nil
end
# Returns the offset of this time zone from UTC in seconds.
@@ -274,8 +285,7 @@ module ActiveSupport
if @utc_offset
@utc_offset
else
- @current_period ||= tzinfo.current_period if tzinfo
- @current_period.utc_offset if @current_period
+ tzinfo.current_period.utc_offset if tzinfo && tzinfo.current_period
end
end
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index df7b081993..99fc26549e 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -32,20 +32,25 @@ module ActiveSupport
"binary" => "base64"
} unless defined?(DEFAULT_ENCODINGS)
- TYPE_NAMES = {
- "Symbol" => "symbol",
- "Fixnum" => "integer",
- "Bignum" => "integer",
- "BigDecimal" => "decimal",
- "Float" => "float",
- "TrueClass" => "boolean",
- "FalseClass" => "boolean",
- "Date" => "date",
- "DateTime" => "dateTime",
- "Time" => "dateTime",
- "Array" => "array",
- "Hash" => "hash"
- } unless defined?(TYPE_NAMES)
+ unless defined?(TYPE_NAMES)
+ TYPE_NAMES = {
+ "Symbol" => "symbol",
+ "Integer" => "integer",
+ "BigDecimal" => "decimal",
+ "Float" => "float",
+ "TrueClass" => "boolean",
+ "FalseClass" => "boolean",
+ "Date" => "date",
+ "DateTime" => "dateTime",
+ "Time" => "dateTime",
+ "Array" => "array",
+ "Hash" => "hash"
+ }
+
+ # No need to map these on Ruby 2.4+
+ TYPE_NAMES["Fixnum"] = "integer" unless Fixnum == Integer
+ TYPE_NAMES["Bignum"] = "integer" unless Bignum == Integer
+ end
FORMATTING = {
"symbol" => Proc.new { |symbol| symbol.to_s },
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 924ed72345..95af5af2c0 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -20,11 +20,9 @@ module ActiveSupport
data = StringIO.new(data || '')
end
- char = data.getc
- if char.nil?
+ if data.eof?
{}
else
- data.ungetc(char)
silence_warnings { require 'rexml/document' } unless defined?(REXML::Document)
doc = REXML::Document.new(data)
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 7f0fcd6996..bab7440397 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -18,6 +18,9 @@ Thread.abort_on_exception = true
# Show backtraces for deprecated behavior for quicker cleanup.
ActiveSupport::Deprecation.debug = true
+# Default to old to_time behavior but allow running tests with new behavior
+ActiveSupport.to_time_preserves_timezone = ENV['PRESERVE_TIMEZONES'] == '1'
+
# Disable available locale checks to avoid warnings running the test suite.
I18n.enforce_available_locales = false
diff --git a/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
new file mode 100644
index 0000000000..3ca4213c71
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/raises_arbitrary_exception.rb
@@ -0,0 +1,4 @@
+RaisesArbitraryException = 1
+_ = A::B # Autoloading recursion, also expected to be watched and discarded.
+
+raise Exception, 'arbitray exception message'
diff --git a/activesupport/test/autoloading_fixtures/throws.rb b/activesupport/test/autoloading_fixtures/throws.rb
new file mode 100644
index 0000000000..e1d96cc512
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/throws.rb
@@ -0,0 +1,4 @@
+Throws = 1
+_ = A::B # Autoloading recursion, expected to be discarded.
+
+throw :t
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 9e744afb2b..ec7d028d7e 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -266,6 +266,20 @@ module CacheStoreBehavior
end
end
+ def test_fetch_with_forced_cache_miss_with_block
+ @cache.write('foo', 'bar')
+ assert_equal 'foo_bar', @cache.fetch('foo', force: true) { 'foo_bar' }
+ end
+
+ def test_fetch_with_forced_cache_miss_without_block
+ @cache.write('foo', 'bar')
+ assert_raises(ArgumentError) do
+ @cache.fetch('foo', force: true)
+ end
+
+ assert_equal 'bar', @cache.read('foo')
+ end
+
def test_should_read_and_write_hash
assert @cache.write('foo', {:a => "b"})
assert_equal({:a => "b"}, @cache.read('foo'))
diff --git a/activesupport/test/core_ext/array/conversions_test.rb b/activesupport/test/core_ext/array/conversions_test.rb
index 507e13f968..de36e2026d 100644
--- a/activesupport/test/core_ext/array/conversions_test.rb
+++ b/activesupport/test/core_ext/array/conversions_test.rb
@@ -101,10 +101,10 @@ class ToXmlTest < ActiveSupport::TestCase
end
def test_to_xml_with_non_hash_elements
- xml = [1, 2, 3].to_xml(skip_instruct: true, indent: 0)
+ xml = %w[1 2 3].to_xml(skip_instruct: true, indent: 0)
- assert_equal '<fixnums type="array"><fixnum', xml.first(29)
- assert xml.include?(%(<fixnum type="integer">2</fixnum>)), xml
+ assert_equal '<strings type="array"><string', xml.first(29)
+ assert xml.include?(%(<string>2</string>)), xml
end
def test_to_xml_with_non_hash_different_type_elements
diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb
index 2eb0f05141..0682241f0b 100644
--- a/activesupport/test/core_ext/array/grouping_test.rb
+++ b/activesupport/test/core_ext/array/grouping_test.rb
@@ -3,11 +3,12 @@ require 'active_support/core_ext/array'
class GroupingTest < ActiveSupport::TestCase
def setup
- Fixnum.send :private, :/ # test we avoid Integer#/ (redefined by mathn)
+ # In Ruby < 2.4, test we avoid Integer#/ (redefined by mathn)
+ Fixnum.send :private, :/ unless Fixnum == Integer
end
def teardown
- Fixnum.send :public, :/
+ Fixnum.send :public, :/ unless Fixnum == Integer
end
def test_in_groups_of_with_perfect_fit
@@ -123,4 +124,12 @@ class SplitTest < ActiveSupport::TestCase
assert_equal [[], [2, 3, 4], []], a.split { |i| i == 1 || i == 5 }
assert_equal [1, 2, 3, 4, 5], a
end
+
+ def test_split_with_repeated_values
+ a = [1, 2, 3, 5, 5, 3, 4, 6, 2, 1, 3]
+ assert_equal [[1, 2], [5, 5], [4, 6, 2, 1], []], a.split(3)
+ assert_equal [[1, 2, 3], [], [3, 4, 6, 2, 1, 3]], a.split(5)
+ assert_equal [[1, 2], [], [], [], [4, 6, 2, 1], []], a.split { |i| i == 3 || i == 5 }
+ assert_equal [1, 2, 3, 5, 5, 3, 4, 6, 2, 1, 3], a
+ end
end
diff --git a/activesupport/test/core_ext/bigdecimal_test.rb b/activesupport/test/core_ext/bigdecimal_test.rb
index 423a3f2e9d..6e82e3892b 100644
--- a/activesupport/test/core_ext/bigdecimal_test.rb
+++ b/activesupport/test/core_ext/bigdecimal_test.rb
@@ -5,5 +5,7 @@ class BigDecimalTest < ActiveSupport::TestCase
def test_to_s
bd = BigDecimal.new '0.01'
assert_equal '0.01', bd.to_s
+ assert_equal '+0.01', bd.to_s('+F')
+ assert_equal '+0.0 1', bd.to_s('+1F')
end
end
diff --git a/activesupport/test/core_ext/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
new file mode 100644
index 0000000000..11cb1469da
--- /dev/null
+++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
@@ -0,0 +1,127 @@
+require 'abstract_unit'
+require 'active_support/time'
+require 'time_zone_test_helpers'
+
+class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
+ include TimeZoneTestHelpers
+
+ def setup
+ @utc_time = Time.utc(2016, 4, 23, 14, 11, 12)
+ @date_time = DateTime.new(2016, 4, 23, 14, 11, 12, 0)
+ @utc_offset = 3600
+ @system_offset = -14400
+ @zone = ActiveSupport::TimeZone['London']
+ end
+
+ def test_time_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_time_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_datetime_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_datetime_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1,24)).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_twz_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+
+ time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_twz_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+
+ time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_string_to_time_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz 'US/Eastern' do
+ time = "2016-04-23T15:11:12+01:00".to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_string_to_time_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz 'US/Eastern' do
+ time = "2016-04-23T15:11:12+01:00".to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index 0fc3f765f5..8052d38c33 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -49,6 +49,10 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
end
+
+ assert_raise(ArgumentError) do
+ Date.new(2005, 2, 21).to_time(:tokyo)
+ end
end
def test_compare_to_time
@@ -280,6 +284,23 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_all_day
+ beginning_of_day = Time.local(2011,6,7,0,0,0)
+ end_of_day = Time.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day
+ end
+
+ def test_all_day_when_zone_is_set
+ zone = ActiveSupport::TimeZone["Hawaii"]
+ with_env_tz "UTC" do
+ with_tz_default zone do
+ beginning_of_day = zone.local(2011,6,7,0,0,0)
+ end_of_day = zone.local(2011,6,7,23,59,59,Rational(999999999, 1000))
+ assert_equal beginning_of_day..end_of_day, Date.new(2011,6,7).all_day
+ end
+ end
+ end
+
def test_all_week
assert_equal Date.new(2011,6,6)..Date.new(2011,6,12), Date.new(2011,6,7).all_week
assert_equal Date.new(2011,6,5)..Date.new(2011,6,11), Date.new(2011,6,7).all_week(:sunday)
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index b183a20e0d..306316efcd 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -40,6 +40,24 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
Time::DATE_FORMATS.delete(:custom)
end
+ def test_localtime
+ with_env_tz 'US/Eastern' do
+ assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime
+ assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).localtime
+ assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).localtime
+ assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).localtime
+ end
+ end
+
+ def test_getlocal
+ with_env_tz 'US/Eastern' do
+ assert_instance_of Time, DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal
+ assert_equal Time.local(2016, 3, 11, 10, 11, 12), DateTime.new(2016, 3, 11, 15, 11, 12, 0).getlocal
+ assert_equal Time.local(2016, 3, 21, 11, 11, 12), DateTime.new(2016, 3, 21, 15, 11, 12, 0).getlocal
+ assert_equal Time.local(2016, 4, 1, 11, 11, 12), DateTime.new(2016, 4, 1, 16, 11, 12, Rational(1,24)).getlocal
+ end
+ end
+
def test_to_date
assert_equal Date.new(2005, 2, 21), DateTime.new(2005, 2, 21, 14, 30, 0).to_date
end
@@ -50,9 +68,15 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
def test_to_time
with_env_tz 'US/Eastern' do
- assert_equal Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.class
- assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
- assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset
+ assert_instance_of Time, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
+
+ if ActiveSupport.to_time_preserves_timezone
+ assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
+ assert_equal Time.local(2005, 2, 21, 5, 11, 12).getlocal(0).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset
+ else
+ assert_equal Time.local(2005, 2, 21, 5, 11, 12), DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time
+ assert_equal Time.local(2005, 2, 21, 5, 11, 12).utc_offset, DateTime.new(2005, 2, 21, 10, 11, 12, 0).to_time.utc_offset
+ end
end
end
@@ -310,6 +334,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_utc
+ assert_instance_of Time, DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc
assert_equal DateTime.civil(2005, 2, 21, 16, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-6, 24)).utc
assert_equal DateTime.civil(2005, 2, 21, 15, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, Rational(-5, 24)).utc
assert_equal DateTime.civil(2005, 2, 21, 10, 11, 12, 0), DateTime.civil(2005, 2, 21, 10, 11, 12, 0).utc
@@ -354,6 +379,24 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal nil, DateTime.civil(2000) <=> "Invalid as Time"
end
+ def test_compare_with_integer
+ assert_equal 1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440587
+ assert_equal 0, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440588
+ assert_equal(-1, DateTime.civil(1970, 1, 1, 12, 0, 0) <=> 2440589)
+ end
+
+ def test_compare_with_float
+ assert_equal 1, DateTime.civil(1970) <=> 2440586.5
+ assert_equal 0, DateTime.civil(1970) <=> 2440587.5
+ assert_equal(-1, DateTime.civil(1970) <=> 2440588.5)
+ end
+
+ def test_compare_with_rational
+ assert_equal 1, DateTime.civil(1970) <=> Rational(4881173, 2)
+ assert_equal 0, DateTime.civil(1970) <=> Rational(4881175, 2)
+ assert_equal(-1, DateTime.civil(1970) <=> Rational(4881177, 2))
+ 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
@@ -374,4 +417,9 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal 0, DateTime.civil(2000).nsec
assert_equal 500000000, DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).nsec
end
+
+ def test_subsec
+ assert_equal 0, DateTime.civil(2000).subsec
+ assert_equal Rational(1,2), DateTime.civil(2000, 1, 1, 0, 0, Rational(1,2)).subsec
+ end
end
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 9e97acaffb..502e2811fa 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -12,7 +12,7 @@ class DurationTest < ActiveSupport::TestCase
assert d.is_a?(ActiveSupport::Duration)
assert_kind_of ActiveSupport::Duration, d
assert_kind_of Numeric, d
- assert_kind_of Fixnum, d
+ assert_kind_of Integer, d
assert !d.is_a?(Hash)
k = Class.new
@@ -66,8 +66,9 @@ class DurationTest < ActiveSupport::TestCase
assert_equal '10 years, 2 months, and 1 day', (10.years + 2.months + 1.day).inspect
assert_equal '10 years, 2 months, and 1 day', (10.years + 1.month + 1.day + 1.month).inspect
assert_equal '10 years, 2 months, and 1 day', (1.day + 10.years + 2.months).inspect
- assert_equal '7 days', 1.week.inspect
- assert_equal '14 days', 1.fortnight.inspect
+ assert_equal '7 days', 7.days.inspect
+ assert_equal '1 week', 1.week.inspect
+ assert_equal '2 weeks', 1.fortnight.inspect
end
def test_inspect_locale
@@ -87,6 +88,15 @@ class DurationTest < ActiveSupport::TestCase
assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration"
end
+ def test_time_plus_duration_returns_same_time_datatype
+ twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone['Moscow'] , Time.utc(2016,4,28,00,45))
+ now = Time.now.utc
+ %w( second minute hour day week month year ).each do |unit|
+ assert_equal((now + 1.send(unit)).class, Time, "Time + 1.#{unit} must be Time")
+ assert_equal((twz + 1.send(unit)).class, ActiveSupport::TimeWithZone, "TimeWithZone + 1.#{unit} must be TimeWithZone")
+ end
+ end
+
def test_argument_error
e = assert_raise ArgumentError do
1.second.ago('')
@@ -222,4 +232,89 @@ class DurationTest < ActiveSupport::TestCase
assert_equal(1, (1.minute <=> 1.second))
assert_equal(1, (61 <=> 1.minute))
end
+
+ # ISO8601 string examples are taken from ISO8601 gem at https://github.com/arnau/ISO8601/blob/b93d466840/spec/iso8601/duration_spec.rb
+ # published under the conditions of MIT license at https://github.com/arnau/ISO8601/blob/b93d466840/LICENSE
+ #
+ # Copyright (c) 2012-2014 Arnau Siches
+ #
+ # MIT License
+ #
+ # Permission is hereby granted, free of charge, to any person obtaining
+ # a copy of this software and associated documentation files (the
+ # "Software"), to deal in the Software without restriction, including
+ # without limitation the rights to use, copy, modify, merge, publish,
+ # distribute, sublicense, and/or sell copies of the Software, and to
+ # permit persons to whom the Software is furnished to do so, subject to
+ # the following conditions:
+ #
+ # The above copyright notice and this permission notice shall be
+ # included in all copies or substantial portions of the Software.
+ #
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ def test_iso8601_parsing_wrong_patterns_with_raise
+ invalid_patterns = ['', 'P', 'PT', 'P1YT', 'T', 'PW', 'P1Y1W', '~P1Y', '.P1Y', 'P1.5Y0.5M', 'P1.5Y1M', 'P1.5MT10.5S']
+ invalid_patterns.each do |pattern|
+ assert_raise ActiveSupport::Duration::ISO8601Parser::ParsingError, pattern.inspect do
+ ActiveSupport::Duration.parse(pattern)
+ end
+ end
+ end
+
+ def test_iso8601_output
+ expectations = [
+ ['P1Y', 1.year ],
+ ['P1W', 1.week ],
+ ['P1Y1M', 1.year + 1.month ],
+ ['P1Y1M1D', 1.year + 1.month + 1.day ],
+ ['-P1Y1D', -1.year - 1.day ],
+ ['P1Y-1DT-1S', 1.year - 1.day - 1.second ], # Parts with different signs are exists in PostgreSQL interval datatype.
+ ['PT1S', 1.second ],
+ ['PT1.4S', (1.4).seconds ],
+ ['P1Y1M1DT1H', 1.year + 1.month + 1.day + 1.hour],
+ ]
+ expectations.each do |expected_output, duration|
+ assert_equal expected_output, duration.iso8601, expected_output.inspect
+ end
+ end
+
+ def test_iso8601_output_precision
+ expectations = [
+ [nil, 'P1Y1MT5.55S', 1.year + 1.month + (5.55).seconds ],
+ [0, 'P1Y1MT6S', 1.year + 1.month + (5.55).seconds ],
+ [1, 'P1Y1MT5.5S', 1.year + 1.month + (5.55).seconds ],
+ [2, 'P1Y1MT5.55S', 1.year + 1.month + (5.55).seconds ],
+ [3, 'P1Y1MT5.550S', 1.year + 1.month + (5.55).seconds ],
+ [nil, 'PT1S', 1.second ],
+ [2, 'PT1.00S', 1.second ],
+ [nil, 'PT1.4S', (1.4).seconds ],
+ [0, 'PT1S', (1.4).seconds ],
+ [1, 'PT1.4S', (1.4).seconds ],
+ [5, 'PT1.40000S', (1.4).seconds ],
+ ]
+ expectations.each do |precision, expected_output, duration|
+ assert_equal expected_output, duration.iso8601(precision: precision), expected_output.inspect
+ end
+ end
+
+ def test_iso8601_output_and_reparsing
+ patterns = %w[
+ P1Y P0.5Y P0,5Y P1Y1M P1Y0.5M P1Y0,5M P1Y1M1D P1Y1M0.5D P1Y1M0,5D P1Y1M1DT1H P1Y1M1DT0.5H P1Y1M1DT0,5H P1W +P1Y -P1Y
+ P1Y1M1DT1H1M P1Y1M1DT1H0.5M P1Y1M1DT1H0,5M P1Y1M1DT1H1M1S P1Y1M1DT1H1M1.0S P1Y1M1DT1H1M1,0S P-1Y-2M3DT-4H-5M-6S
+ ]
+ # That could be weird, but if we parse P1Y1M0.5D and output it to ISO 8601, we'll get P1Y1MT12.0H.
+ # So we check that initially parsed and reparsed duration added to time will result in the same time.
+ time = Time.current
+ patterns.each do |pattern|
+ duration = ActiveSupport::Duration.parse(pattern)
+ assert_equal time+duration, time+ActiveSupport::Duration.parse(duration.iso8601), pattern.inspect
+ end
+ end
end
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index f09b7d8850..99c3236c35 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -10,22 +10,27 @@ class SummablePayment < Payment
end
class EnumerableTests < ActiveSupport::TestCase
-
class GenericEnumerable
include Enumerable
+
def initialize(values = [1, 2, 3])
@values = values
end
def each
- @values.each{|v| yield v}
+ @values.each { |v| yield v }
end
end
+ def assert_typed_equal(e, v, cls, msg=nil)
+ assert_kind_of(cls, v, msg)
+ assert_equal(e, v, msg)
+ end
+
def test_sums
enum = GenericEnumerable.new([5, 15, 10])
assert_equal 30, enum.sum
- assert_equal 60, enum.sum { |i| i * 2}
+ assert_equal 60, enum.sum { |i| i * 2 }
enum = GenericEnumerable.new(%w(a b c))
assert_equal 'abc', enum.sum
@@ -38,6 +43,40 @@ class EnumerableTests < ActiveSupport::TestCase
payments = GenericEnumerable.new([ SummablePayment.new(5), SummablePayment.new(15) ])
assert_equal SummablePayment.new(20), payments.sum
assert_equal SummablePayment.new(20), payments.sum { |p| p }
+
+ sum = GenericEnumerable.new([3, 5.quo(1)]).sum
+ assert_typed_equal(8, sum, Rational)
+
+ sum = GenericEnumerable.new([3, 5.quo(1)]).sum(0.0)
+ assert_typed_equal(8.0, sum, Float)
+
+ sum = GenericEnumerable.new([3, 5.quo(1), 7.0]).sum
+ assert_typed_equal(15.0, sum, Float)
+
+ sum = GenericEnumerable.new([3, 5.quo(1), Complex(7)]).sum
+ assert_typed_equal(Complex(15), sum, Complex)
+ assert_typed_equal(15, sum.real, Rational)
+ assert_typed_equal(0, sum.imag, Integer)
+
+ sum = GenericEnumerable.new([3.5, 5]).sum
+ assert_typed_equal(8.5, sum, Float)
+
+ sum = GenericEnumerable.new([2, 8.5]).sum
+ assert_typed_equal(10.5, sum, Float)
+
+ sum = GenericEnumerable.new([1.quo(2), 1]).sum
+ assert_typed_equal(3.quo(2), sum, Rational)
+
+ sum = GenericEnumerable.new([1.quo(2), 1.quo(3)]).sum
+ assert_typed_equal(5.quo(6), sum, Rational)
+
+ sum = GenericEnumerable.new([2.0, 3.0*Complex::I]).sum
+ assert_typed_equal(Complex(2.0, 3.0), sum, Complex)
+ assert_typed_equal(2.0, sum.real, Float)
+ assert_typed_equal(3.0, sum.imag, Float)
+
+ sum = GenericEnumerable.new([1, 2]).sum(10) {|v| v * 2 }
+ assert_typed_equal(16, sum, Integer)
end
def test_nil_sums
@@ -55,6 +94,7 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal 0, GenericEnumerable.new([]).sum
assert_equal 0, GenericEnumerable.new([]).sum { |i| i + 10 }
assert_equal Payment.new(0), GenericEnumerable.new([]).sum(Payment.new(0))
+ assert_typed_equal 0.0, GenericEnumerable.new([]).sum(0.0), Float
end
def test_range_sums
@@ -68,6 +108,62 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal 5, (10..0).sum(5)
assert_equal 10, (10..10).sum
assert_equal 42, (10...10).sum(42)
+ assert_typed_equal 20.0, (1..4).sum(0.0) { |i| i * 2 }, Float
+ assert_typed_equal 10.0, (1..4).sum(0.0), Float
+ assert_typed_equal 20.0, (1..4).sum(10.0), Float
+ assert_typed_equal 5.0, (10..0).sum(5.0), Float
+ end
+
+ def test_array_sums
+ enum = [5, 15, 10]
+ assert_equal 30, enum.sum
+ assert_equal 60, enum.sum { |i| i * 2 }
+
+ enum = %w(a b c)
+ assert_equal 'abc', enum.sum
+ assert_equal 'aabbcc', enum.sum { |i| i * 2 }
+
+ payments = [ Payment.new(5), Payment.new(15), Payment.new(10) ]
+ assert_equal 30, payments.sum(&:price)
+ assert_equal 60, payments.sum { |p| p.price * 2 }
+
+ payments = [ SummablePayment.new(5), SummablePayment.new(15) ]
+ assert_equal SummablePayment.new(20), payments.sum
+ assert_equal SummablePayment.new(20), payments.sum { |p| p }
+
+ sum = [3, 5.quo(1)].sum
+ assert_typed_equal(8, sum, Rational)
+
+ sum = [3, 5.quo(1)].sum(0.0)
+ assert_typed_equal(8.0, sum, Float)
+
+ sum = [3, 5.quo(1), 7.0].sum
+ assert_typed_equal(15.0, sum, Float)
+
+ sum = [3, 5.quo(1), Complex(7)].sum
+ assert_typed_equal(Complex(15), sum, Complex)
+ assert_typed_equal(15, sum.real, Rational)
+ assert_typed_equal(0, sum.imag, Integer)
+
+ sum = [3.5, 5].sum
+ assert_typed_equal(8.5, sum, Float)
+
+ sum = [2, 8.5].sum
+ assert_typed_equal(10.5, sum, Float)
+
+ sum = [1.quo(2), 1].sum
+ assert_typed_equal(3.quo(2), sum, Rational)
+
+ sum = [1.quo(2), 1.quo(3)].sum
+ assert_typed_equal(5.quo(6), sum, Rational)
+
+ sum = [2.0, 3.0*Complex::I].sum
+ assert_typed_equal(Complex(2.0, 3.0), sum, Complex)
+ assert_typed_equal(2.0, sum.real, Float)
+ assert_typed_equal(3.0, sum.imag, Float)
+
+ sum = [1, 2].sum(10) {|v| v * 2 }
+ assert_typed_equal(16, sum, Integer)
end
def test_index_by
diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
index 99af274614..962d3a30b6 100644
--- a/activesupport/test/core_ext/hash/transform_keys_test.rb
+++ b/activesupport/test/core_ext/hash/transform_keys_test.rb
@@ -43,4 +43,20 @@ class TransformKeysTest < ActiveSupport::TestCase
original.transform_keys!.with_index { |k, i| [k, i].join.to_sym }
assert_equal({ a0: 'a', b1: 'b' }, original)
end
+
+ test "transform_keys returns a Hash instance when self is inherited from Hash" do
+ class HashDescendant < ::Hash
+ def initialize(elements = nil)
+ super(elements)
+ (elements || {}).each_pair{ |key, value| self[key] = value }
+ end
+ end
+
+ original = HashDescendant.new({ a: 'a', b: 'b' })
+ mapped = original.transform_keys { |k| "#{k}!".to_sym }
+
+ assert_equal({ a: 'a', b: 'b' }, original)
+ assert_equal({ a!: 'a', b!: 'b' }, mapped)
+ assert_equal(::Hash, mapped.class)
+ end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index be8583e704..f0a4c4dddc 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -36,12 +36,12 @@ class HashExtTest < ActiveSupport::TestCase
def setup
@strings = { 'a' => 1, 'b' => 2 }
@nested_strings = { 'a' => { 'b' => { 'c' => 3 } } }
- @symbols = { :a => 1, :b => 2 }
+ @symbols = { :a => 1, :b => 2 }
@nested_symbols = { :a => { :b => { :c => 3 } } }
- @mixed = { :a => 1, 'b' => 2 }
- @nested_mixed = { 'a' => { :b => { 'c' => 3 } } }
- @fixnums = { 0 => 1, 1 => 2 }
- @nested_fixnums = { 0 => { 1 => { 2 => 3} } }
+ @mixed = { :a => 1, 'b' => 2 }
+ @nested_mixed = { 'a' => { :b => { 'c' => 3 } } }
+ @integers = { 0 => 1, 1 => 2 }
+ @nested_integers = { 0 => { 1 => { 2 => 3} } }
@illegal_symbols = { [] => 3 }
@nested_illegal_symbols = { [] => { [] => 3} }
@upcase_strings = { 'A' => 1, 'B' => 2 }
@@ -196,14 +196,14 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal @nested_illegal_symbols, @nested_illegal_symbols.deep_dup.deep_symbolize_keys!
end
- def test_symbolize_keys_preserves_fixnum_keys
- assert_equal @fixnums, @fixnums.symbolize_keys
- assert_equal @fixnums, @fixnums.dup.symbolize_keys!
+ def test_symbolize_keys_preserves_integer_keys
+ assert_equal @integers, @integers.symbolize_keys
+ assert_equal @integers, @integers.dup.symbolize_keys!
end
- def test_deep_symbolize_keys_preserves_fixnum_keys
- assert_equal @nested_fixnums, @nested_fixnums.deep_symbolize_keys
- assert_equal @nested_fixnums, @nested_fixnums.deep_dup.deep_symbolize_keys!
+ def test_deep_symbolize_keys_preserves_integer_keys
+ assert_equal @nested_integers, @nested_integers.deep_symbolize_keys
+ assert_equal @nested_integers, @nested_integers.deep_dup.deep_symbolize_keys!
end
def test_stringify_keys
@@ -299,14 +299,14 @@ class HashExtTest < ActiveSupport::TestCase
assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
end
- def test_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
- assert_equal @fixnums, @fixnums.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @fixnums.with_indifferent_access.dup.symbolize_keys! }
+ def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
end
- def test_deep_symbolize_keys_preserves_fixnum_keys_for_hash_with_indifferent_access
- assert_equal @nested_fixnums, @nested_fixnums.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_fixnums.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
end
def test_stringify_keys_for_hash_with_indifferent_access
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index 07c0c0d8cb..380f64c6fd 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -29,7 +29,12 @@ class MarshalTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of EM, Marshal.load(dumped)
+ object = nil
+ assert_nothing_raised do
+ object = Marshal.load(dumped)
+ end
+
+ assert_kind_of EM, object
end
end
@@ -43,7 +48,12 @@ class MarshalTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of ClassFolder::ClassFolderSubclass, Marshal.load(dumped)
+ object = nil
+ assert_nothing_raised do
+ object = Marshal.load(dumped)
+ end
+
+ assert_kind_of ClassFolder::ClassFolderSubclass, object
end
end
@@ -128,7 +138,12 @@ class MarshalTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of EM, Marshal.load(f)
+ object = nil
+ assert_nothing_raised do
+ object = Marshal.load(f)
+ end
+
+ assert_kind_of EM, object
end
end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 5654aeb4f8..69c30a8a9e 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -387,16 +387,9 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
end
def test_to_s__injected_on_proper_types
- assert_equal Fixnum, 1230.class
assert_equal '1.23 Thousand', 1230.to_s(:human)
-
- assert_equal Float, Float(1230).class
assert_equal '1.23 Thousand', Float(1230).to_s(:human)
-
- assert_equal Bignum, (100**10).class
assert_equal '100000 Quadrillion', (100**10).to_s(:human)
-
- assert_equal BigDecimal, BigDecimal("1000010").class
assert_equal '1 Million', BigDecimal("1000010").to_s(:human)
end
diff --git a/activesupport/test/core_ext/object/deep_dup_test.rb b/activesupport/test/core_ext/object/deep_dup_test.rb
index 791b5e7172..aa839201ea 100644
--- a/activesupport/test/core_ext/object/deep_dup_test.rb
+++ b/activesupport/test/core_ext/object/deep_dup_test.rb
@@ -51,7 +51,7 @@ class DeepDupTest < ActiveSupport::TestCase
end
def test_deep_dup_with_hash_class_key
- hash = { Fixnum => 1 }
+ hash = { Integer => 1 }
dup = hash.deep_dup
assert_equal 1, dup.keys.length
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
index 02ab17fb64..2cbb1d590f 100644
--- a/activesupport/test/core_ext/object/json_gem_encoding_test.rb
+++ b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
@@ -10,7 +10,7 @@ require 'json/encoding_test_cases'
# 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
+# we need to require this upfront 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'
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 2e69816364..d68a77680b 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -10,6 +10,7 @@ require 'active_support/core_ext/string/strip'
require 'active_support/core_ext/string/output_safety'
require 'active_support/core_ext/string/indent'
require 'time_zone_test_helpers'
+require 'yaml'
class StringInflectionsTest < ActiveSupport::TestCase
include InflectorTestCases
@@ -76,6 +77,18 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_upcase_first
+ assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first
+ end
+
+ def test_upcase_first_with_one_char
+ assert_equal "W", "w".upcase_first
+ end
+
+ def test_upcase_first_with_empty_string
+ assert_equal "", "".upcase_first
+ end
+
def test_camelize
CamelToUnderscore.each do |camel, underscore|
assert_equal(camel, underscore.camelize)
@@ -332,7 +345,7 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
class StringAccessTest < ActiveSupport::TestCase
- test "#at with Fixnum, returns a substring of one character at that position" do
+ test "#at with Integer, returns a substring of one character at that position" do
assert_equal "h", "hello".at(0)
end
@@ -345,19 +358,19 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal nil, "hello".at(/nonexisting/)
end
- test "#from with positive Fixnum, returns substring from the given position to the end" do
+ test "#from with positive Integer, returns substring from the given position to the end" do
assert_equal "llo", "hello".from(2)
end
- test "#from with negative Fixnum, position is counted from the end" do
+ test "#from with negative Integer, position is counted from the end" do
assert_equal "lo", "hello".from(-2)
end
- test "#to with positive Fixnum, substring from the beginning to the given position" do
+ test "#to with positive Integer, substring from the beginning to the given position" do
assert_equal "hel", "hello".to(2)
end
- test "#to with negative Fixnum, position is counted from the end" do
+ test "#to with negative Integer, position is counted from the end" do
assert_equal "hell", "hello".to(-2)
end
@@ -371,14 +384,14 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.first
end
- test "#first with Fixnum, returns a substring from the beginning to position" do
+ test "#first with Integer, returns a substring from the beginning to position" do
assert_equal "he", "hello".first(2)
assert_equal "", "hello".first(0)
assert_equal "hello", "hello".first(10)
assert_equal 'x', 'x'.first(4)
end
- test "#first with Fixnum >= string length still returns a new string" do
+ test "#first with Integer >= string length still returns a new string" do
string = "hello"
different_string = string.first(5)
assert_not_same different_string, string
@@ -389,14 +402,14 @@ class StringAccessTest < ActiveSupport::TestCase
assert_equal 'x', 'x'.last
end
- test "#last with Fixnum, returns a substring from the end to position" do
+ test "#last with Integer, returns a substring from the end to position" do
assert_equal "llo", "hello".last(3)
assert_equal "hello", "hello".last(10)
assert_equal "", "hello".last(0)
assert_equal 'x', 'x'.last(4)
end
- test "#last with Fixnum >= string length still returns a new string" do
+ test "#last with Integer >= string length still returns a new string" do
string = "hello"
different_string = string.last(5)
assert_not_same different_string, string
@@ -444,16 +457,24 @@ class StringConversionsTest < ActiveSupport::TestCase
assert_equal Time.local(2011, 2, 27, 17, 50), "2011-02-27 13:50 -0100".to_time
assert_equal Time.utc(2011, 2, 27, 23, 50), "2011-02-27 22:50 -0100".to_time(:utc)
assert_equal Time.local(2005, 2, 27, 22, 50), "2005-02-27 14:50 -0500".to_time
+ assert_nil "010".to_time
assert_nil "".to_time
end
end
def test_string_to_time_utc_offset
with_env_tz "US/Eastern" do
- assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset
- assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset)
- assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset
- assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset)
+ if ActiveSupport.to_time_preserves_timezone
+ assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset
+ assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset)
+ assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset
+ assert_equal(-3600, "2005-02-27 22:50 -0100".to_time.utc_offset)
+ else
+ assert_equal 0, "2005-02-27 23:50".to_time(:utc).utc_offset
+ assert_equal(-18000, "2005-02-27 23:50".to_time.utc_offset)
+ assert_equal 0, "2005-02-27 22:50 -0100".to_time(:utc).utc_offset
+ assert_equal(-18000, "2005-02-27 22:50 -0100".to_time.utc_offset)
+ end
end
end
@@ -661,7 +682,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal @string, @string.html_safe
end
- test "A fixnum is safe by default" do
+ test "An integer is safe by default" do
assert 5.html_safe?
end
@@ -792,7 +813,7 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal ["<p>", "<b>", "<h1>"], @other_string
end
- test "Concatting a fixnum to safe always yields safe" do
+ test "Concatting an integer to safe always yields safe" do
string = @string.html_safe
string = string.concat(13)
assert_equal "hello".concat(13), string
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index d8bb38621b..1205797fac 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -107,6 +107,20 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_sec_fraction
+ time = Time.utc(2016, 4, 23, 0, 0, Rational(1,10000000000))
+ assert_equal Rational(1,10000000000), time.sec_fraction
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0.0000000001)
+ assert_equal 0.0000000001.to_r, time.sec_fraction
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0, Rational(1,10000))
+ assert_equal Rational(1,10000000000), time.sec_fraction
+
+ time = Time.utc(2016, 4, 23, 0, 0, 0, 0.0001)
+ assert_equal 0.0001.to_r / 1000000, time.sec_fraction
+ end
+
def test_beginning_of_day
assert_equal Time.local(2005,2,4,0,0,0), Time.local(2005,2,4,10,10,10).beginning_of_day
with_env_tz 'US/Eastern' do
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 7acada011d..d90714acdb 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -11,10 +11,13 @@ class TimeWithZoneTest < ActiveSupport::TestCase
@utc = Time.utc(2000, 1, 1, 0)
@time_zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
@twz = ActiveSupport::TimeWithZone.new(@utc, @time_zone)
+ @dt_twz = ActiveSupport::TimeWithZone.new(@utc.to_datetime, @time_zone)
end
def test_utc
assert_equal @utc, @twz.utc
+ assert_instance_of Time, @twz.utc
+ assert_instance_of Time, @dt_twz.utc
end
def test_time
@@ -47,6 +50,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_localtime
assert_equal @twz.localtime, @twz.utc.getlocal
+ assert_instance_of Time, @twz.localtime
+ assert_instance_of Time, @dt_twz.localtime
end
def test_utc?
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index b7a5747f1b..04e7b24d30 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -269,6 +269,28 @@ class DependenciesTest < ActiveSupport::TestCase
remove_constants(:ModuleFolder)
end
+ def test_raising_discards_autoloaded_constants
+ with_autoloading_fixtures do
+ assert_raises(Exception, 'arbitray exception message') { RaisesArbitraryException }
+ assert_not defined?(A)
+ assert_not defined?(RaisesArbitraryException)
+ end
+ ensure
+ remove_constants(:A, :RaisesArbitraryException)
+ end
+
+ def test_throwing_discards_autoloaded_constants
+ with_autoloading_fixtures do
+ catch :t do
+ Throws
+ end
+ assert_not defined?(A)
+ assert_not defined?(Throws)
+ end
+ ensure
+ remove_constants(:A, :Throws)
+ end
+
def test_doesnt_break_normal_require
path = File.expand_path("../autoloading_fixtures/load_path", __FILE__)
original_path = $:.dup
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 45c88b79cb..ec34bd823d 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -341,7 +341,7 @@ class DeprecationTest < ActiveSupport::TestCase
end
def test_default_deprecation_horizon_should_always_bigger_than_current_rails_version
- assert ActiveSupport::Deprecation.new.deprecation_horizon > ActiveSupport::VERSION::STRING
+ assert_operator ActiveSupport::Deprecation.new.deprecation_horizon, :>, ActiveSupport::VERSION::STRING
end
def test_default_gem_name
diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb
index 6db6db4fa8..d9b389461a 100644
--- a/activesupport/test/executor_test.rb
+++ b/activesupport/test/executor_test.rb
@@ -1,6 +1,9 @@
require 'abstract_unit'
class ExecutorTest < ActiveSupport::TestCase
+ class DummyError < RuntimeError
+ end
+
def test_wrap_invokes_callbacks
called = []
executor.to_run { called << :run }
@@ -35,6 +38,20 @@ class ExecutorTest < ActiveSupport::TestCase
assert_equal [:run, :body, :complete], called
end
+ def test_exceptions_unwind
+ called = []
+ executor.to_run { called << :run_1 }
+ executor.to_run { raise DummyError }
+ executor.to_run { called << :run_2 }
+ executor.to_complete { called << :complete }
+
+ assert_raises(DummyError) do
+ executor.wrap { called << :body }
+ end
+
+ assert_equal [:run_1, :complete], called
+ end
+
def test_avoids_double_wrapping
called = []
executor.to_run { called << :run }
@@ -51,6 +68,96 @@ class ExecutorTest < ActiveSupport::TestCase
assert_equal [:run, :early, :body, :late, :complete], called
end
+ def test_hooks_carry_state
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ :some_state
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.register_hook(hook)
+
+ executor.wrap { }
+
+ assert_equal :some_state, supplied_state
+ end
+
+ def test_nil_state_is_sufficient
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ nil
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.register_hook(hook)
+
+ executor.wrap { }
+
+ assert_equal nil, supplied_state
+ end
+
+ def test_exception_skips_uninvoked_hook
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ :some_state
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.to_run do
+ raise DummyError
+ end
+ executor.register_hook(hook)
+
+ assert_raises(DummyError) do
+ executor.wrap { }
+ end
+
+ assert_equal :none, supplied_state
+ end
+
+ def test_exception_unwinds_invoked_hook
+ supplied_state = :none
+
+ hook = Class.new do
+ define_method(:run) do
+ :some_state
+ end
+
+ define_method(:complete) do |state|
+ supplied_state = state
+ end
+ end.new
+
+ executor.register_hook(hook)
+ executor.to_run do
+ raise DummyError
+ end
+
+ assert_raises(DummyError) do
+ executor.wrap { }
+ end
+
+ assert_equal :some_state, supplied_state
+ end
+
def test_separate_classes_can_wrap
other_executor = Class.new(ActiveSupport::Executor)
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
index 9c07e38fe5..40ae0c7617 100644
--- a/activesupport/test/file_update_checker_shared_tests.rb
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -5,19 +5,21 @@ module FileUpdateCheckerSharedTests
include FileUtils
def tmpdir
- @tmpdir ||= Dir.mktmpdir(nil, __dir__)
+ @tmpdir
end
def tmpfile(name)
- "#{tmpdir}/#{name}"
+ File.join(tmpdir, name)
end
def tmpfiles
@tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) }
end
- def teardown
- FileUtils.rm_rf(@tmpdir) if defined? @tmpdir
+ def run(*args)
+ capture_exceptions do
+ Dir.mktmpdir(nil, __dir__) { |dir| @tmpdir = dir; super }
+ end
end
test 'should not execute the block if no paths are given' do
@@ -134,6 +136,22 @@ module FileUpdateCheckerSharedTests
assert_equal 1, i
end
+ test 'should return max_time for files with mtime = Time.at(0)' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ time = Time.at(0) # wrong mtime from the future
+ File.utime(time, time, tmpfiles[0])
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ touch(tmpfiles[1..-1])
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
test 'should cache updated result until execute' do
i = 0
@@ -209,7 +227,7 @@ module FileUpdateCheckerSharedTests
assert !checker.execute_if_updated
assert_equal 0, i
- touch("#{subdir}/nested.rb")
+ touch(File.join(subdir, "nested.rb"))
assert checker.execute_if_updated
assert_equal 1, i
@@ -229,12 +247,12 @@ module FileUpdateCheckerSharedTests
assert_equal 0, i
# subdir does not look for Ruby files, but its parent tmpdir does.
- touch("#{subdir}/nested.rb")
+ touch(File.join(subdir, "nested.rb"))
assert checker.execute_if_updated
assert_equal 1, i
- touch("#{subdir}/nested.txt")
+ touch(File.join(subdir, "nested.txt"))
assert checker.execute_if_updated
assert_equal 2, i
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 14fe97a986..c7dc1dadb6 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -270,7 +270,8 @@ module InflectorTestCases
"maybe you'll be there" => "Maybe You'll Be There",
"¿por qué?" => '¿Por Qué?',
"Fred’s" => "Fred’s",
- "Fred`s" => "Fred`s"
+ "Fred`s" => "Fred`s",
+ ActiveSupport::SafeBuffer.new("confirmation num") => "Confirmation Num"
}
OrdinalNumbers = {
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 9f4b62fd8b..5fc2e16336 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -422,6 +422,11 @@ EXPECTED
assert_equal '"1999-12-31T19:00:00.000-05:00"', ActiveSupport::JSON.encode(time)
end
+ def test_exception_to_json
+ exception = Exception.new("foo")
+ assert_equal '"foo"', ActiveSupport::JSON.encode(exception)
+ end
+
protected
def object_keys(json_object)
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index 5df8f32e46..9fca47a985 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -28,7 +28,7 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
UNIDATA_FILE = '/NormalizationTest.txt'
- CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+ CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance"
FileUtils.mkdir_p(CACHE_DIR)
RUN_P = begin
Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE)
diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
index 229f24990e..6e2f02abed 100644
--- a/activesupport/test/multibyte_grapheme_break_conformance_test.rb
+++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
@@ -27,7 +27,7 @@ class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase
TEST_DATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd/auxiliary"
TEST_DATA_FILE = '/GraphemeBreakTest.txt'
- CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+ CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance"
def setup
FileUtils.mkdir_p(CACHE_DIR)
diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb
index 8bc91ef708..0d31c9520f 100644
--- a/activesupport/test/multibyte_normalization_conformance_test.rb
+++ b/activesupport/test/multibyte_normalization_conformance_test.rb
@@ -30,7 +30,7 @@ class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
UNIDATA_FILE = '/NormalizationTest.txt'
- CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+ CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance"
def setup
FileUtils.mkdir_p(CACHE_DIR)
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 6696111476..074c872efc 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -57,6 +57,8 @@ module ActiveSupport
assert_equal("+18005551212", number_helper.number_to_phone(8005551212, :country_code => 1, :delimiter => ''))
assert_equal("22-555-1212", number_helper.number_to_phone(225551212))
assert_equal("+45-22-555-1212", number_helper.number_to_phone(225551212, :country_code => 45))
+ assert_equal("(755) 6123-4567", number_helper.number_to_phone(75561234567, pattern: /(\d{3,4})(\d{4})(\d{4})/, area_code: true))
+ assert_equal("133-1234-5678", number_helper.number_to_phone(13312345678, pattern: /(\d{3})(\d{4})(\d{4})/))
end
end
diff --git a/activesupport/test/rescuable_test.rb b/activesupport/test/rescuable_test.rb
index bd43ad0797..e42e6d2973 100644
--- a/activesupport/test/rescuable_test.rb
+++ b/activesupport/test/rescuable_test.rb
@@ -3,9 +3,6 @@ require 'abstract_unit'
class WraithAttack < StandardError
end
-class NuclearExplosion < StandardError
-end
-
class MadRonon < StandardError
end
@@ -19,6 +16,10 @@ module WeirdError
end
class Stargate
+ # Nest this so the 'NuclearExplosion' handler needs a lexical const_get
+ # to find it.
+ class NuclearExplosion < StandardError; end
+
attr_accessor :result
include ActiveSupport::Rescuable
@@ -57,6 +58,14 @@ class Stargate
raise MadRonon.new("dex")
end
+ def fall_back_to_cause
+ # This exception is the cause and has a handler.
+ ronanize
+ rescue
+ # This is the exception we'll handle that doesn't have a cause.
+ raise 'unhandled RuntimeError with a handleable cause'
+ end
+
def weird
StandardError.new.tap do |exc|
def exc.weird?
@@ -74,7 +83,6 @@ class Stargate
def sos_first
@result = 'sos_first'
end
-
end
class CoolStargate < Stargate
@@ -127,4 +135,9 @@ class RescuableTest < ActiveSupport::TestCase
result = @cool_stargate.send(:rescue_handlers).collect(&:first)
assert_equal expected, result
end
+
+ def test_rescue_falls_back_to_exception_cause
+ @stargate.dispatch :fall_back_to_cause
+ assert_equal 'unhandled RuntimeError with a handleable cause', @stargate.result
+ end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 00d40c4497..a15d5c6a0e 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -395,6 +395,17 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal(-18_000, zone.utc_offset)
end
+ def test_utc_offset_is_not_cached_when_current_period_gets_stale
+ tz = ActiveSupport::TimeZone.create('Moscow')
+ travel_to(Time.utc(2014, 10, 25, 21)) do # 1 hour before TZ change
+ assert_equal 14400, tz.utc_offset, 'utc_offset should be initialized according to current_period'
+ end
+
+ travel_to(Time.utc(2014, 10, 25, 22)) do # after TZ change
+ assert_equal 10800, tz.utc_offset, 'utc_offset should not be cached when current_period gets stale'
+ end
+ end
+
def test_seconds_to_utc_offset_with_colon
assert_equal "-06:00", ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600)
assert_equal "+00:00", ActiveSupport::TimeZone.seconds_to_utc_offset(0)
@@ -491,6 +502,11 @@ class TimeZoneTest < ActiveSupport::TestCase
assert !ActiveSupport::TimeZone.us_zones.include?(ActiveSupport::TimeZone["Kuala Lumpur"])
end
+ def test_country_zones
+ assert ActiveSupport::TimeZone.country_zones("ru").include?(ActiveSupport::TimeZone["Moscow"])
+ assert !ActiveSupport::TimeZone.country_zones(:ru).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)
diff --git a/activesupport/test/time_zone_test_helpers.rb b/activesupport/test/time_zone_test_helpers.rb
index 9632b89d09..eb6f7d0f85 100644
--- a/activesupport/test/time_zone_test_helpers.rb
+++ b/activesupport/test/time_zone_test_helpers.rb
@@ -13,4 +13,12 @@ module TimeZoneTestHelpers
ensure
old_tz ? ENV['TZ'] = old_tz : ENV.delete('TZ')
end
+
+ def with_preserve_timezone(value)
+ old_preserve_tz = ActiveSupport.to_time_preserves_timezone
+ ActiveSupport.to_time_preserves_timezone = value
+ yield
+ ensure
+ ActiveSupport.to_time_preserves_timezone = old_preserve_tz
+ end
end
diff --git a/activesupport/test/xml_mini/rexml_engine_test.rb b/activesupport/test/xml_mini/rexml_engine_test.rb
index f0067ca656..6e9ce7ac11 100644
--- a/activesupport/test/xml_mini/rexml_engine_test.rb
+++ b/activesupport/test/xml_mini/rexml_engine_test.rb
@@ -22,14 +22,24 @@ class REXMLEngineTest < ActiveSupport::TestCase
morning
</root>
eoxml
- assert_equal_rexml(io)
+ hash = ActiveSupport::XmlMini.parse(io)
+ assert hash.has_key?('root')
+ assert hash['root'].has_key?('products')
+ assert_match "good", hash['root']['__content__']
+ products = hash['root']['products']
+ assert products.has_key?("__content__")
+ assert_match 'hello everyone', products['__content__']
+ end
+
+ def test_parse_from_empty_string
+ ActiveSupport::XmlMini.backend = 'REXML'
+ assert_equal({}, ActiveSupport::XmlMini.parse(""))
+ end
+
+ def test_parse_from_frozen_string
+ ActiveSupport::XmlMini.backend = 'REXML'
+ xml_string = "<root></root>".freeze
+ assert_equal({"root" => {}}, ActiveSupport::XmlMini.parse(xml_string))
end
- private
- def assert_equal_rexml(xml)
- parsed_xml = ActiveSupport::XmlMini.parse(xml)
- xml.rewind if xml.respond_to?(:rewind)
- hash = ActiveSupport::XmlMini.with_backend('REXML') { ActiveSupport::XmlMini.parse(xml) }
- assert_equal(hash, parsed_xml)
- end
end