aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md438
-rw-r--r--activesupport/MIT-LICENSE4
-rw-r--r--activesupport/README.rdoc3
-rw-r--r--activesupport/Rakefile3
-rw-r--r--activesupport/activesupport.gemspec6
-rwxr-xr-xactivesupport/bin/generate_tables5
-rw-r--r--activesupport/lib/active_support.rb15
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb6
-rw-r--r--activesupport/lib/active_support/benchmarkable.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb136
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb48
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb81
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb35
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb34
-rw-r--r--activesupport/lib/active_support/callbacks.rb21
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb88
-rw-r--r--activesupport/lib/active_support/core_ext/array/access.rb14
-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.rb7
-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.rb12
-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/compact.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/concern.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb7
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb141
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb72
-rw-r--r--activesupport/lib/active_support/core_ext/module/deprecation.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/introspection.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/qualified_const.rb42
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb113
-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/range/conversions.rb17
-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.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb19
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb18
-rw-r--r--activesupport/lib/active_support/dependencies.rb67
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb24
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb6
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb4
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb17
-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.rb155
-rw-r--r--activesupport/lib/active_support/execution_wrapper.rb117
-rw-r--r--activesupport/lib/active_support/executor.rb6
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb26
-rw-r--r--activesupport/lib/active_support/gem_version.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb20
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb6
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb11
-rw-r--r--activesupport/lib/active_support/locale/en.yml2
-rw-r--r--activesupport/lib/active_support/logger.rb33
-rw-r--r--activesupport/lib/active_support/logger_silence.rb12
-rw-r--r--activesupport/lib/active_support/logger_thread_safe_level.rb31
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb4
-rw-r--r--activesupport/lib/active_support/message_verifier.rb8
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb51
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb4
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb10
-rw-r--r--activesupport/lib/active_support/number_helper.rb133
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_currency_converter.rb8
-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_human_size_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/ordered_hash.rb2
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb5
-rw-r--r--activesupport/lib/active_support/reloader.rb129
-rw-r--r--activesupport/lib/active_support/rescuable.rb136
-rw-r--r--activesupport/lib/active_support/security_utils.rb7
-rw-r--r--activesupport/lib/active_support/test_case.rb22
-rw-r--r--activesupport/lib/active_support/testing/composite_filter.rb54
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb23
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb22
-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.rb10
-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/benchmarkable_test.rb14
-rw-r--r--activesupport/test/caching_test.rb100
-rw-r--r--activesupport/test/callbacks_test.rb18
-rw-r--r--activesupport/test/core_ext/array/access_test.rb2
-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_behavior.rb10
-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.rb58
-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.rb44
-rw-r--r--activesupport/test/core_ext/marshal_test.rb34
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb115
-rw-r--r--activesupport/test/core_ext/module/qualified_const_test.rb130
-rw-r--r--activesupport/test/core_ext/module_test.rb67
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb47
-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/range_ext_test.rb6
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb51
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb35
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb5
-rw-r--r--activesupport/test/dependencies_test.rb35
-rw-r--r--activesupport/test/deprecation_test.rb10
-rw-r--r--activesupport/test/evented_file_update_checker_test.rb155
-rw-r--r--activesupport/test/executor_test.rb183
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb260
-rw-r--r--activesupport/test/file_update_checker_test.rb113
-rw-r--r--activesupport/test/inflector_test_cases.rb3
-rw-r--r--activesupport/test/json/encoding_test.rb5
-rw-r--r--activesupport/test/logger_test.rb141
-rw-r--r--activesupport/test/multibyte_chars_test.rb32
-rw-r--r--activesupport/test/multibyte_conformance_test.rb33
-rw-r--r--activesupport/test/multibyte_grapheme_break_conformance_test.rb76
-rw-r--r--activesupport/test/multibyte_normalization_conformance_test.rb129
-rw-r--r--activesupport/test/notifications_test.rb17
-rw-r--r--activesupport/test/number_helper_test.rb17
-rw-r--r--activesupport/test/reloader_test.rb85
-rw-r--r--activesupport/test/rescuable_test.rb21
-rw-r--r--activesupport/test/share_lock_test.rb245
-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
151 files changed, 4391 insertions, 1435 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index d505218c7a..812e18a253 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,419 +1,53 @@
-* Updated `parameterize` to preserve the case of a string, optionally.
+* Introduce Module#delegate_missing_to.
- Example:
+ When building a decorator, a common pattern emerges:
- 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*
+ class Partition
+ def initialize(first_event)
+ @events = [ first_event ]
+ end
-* Add a bang version to `ActiveSupport::OrderedOptions` get methods which will raise
- an `KeyError` if the value is `.blank?`
+ def people
+ if @events.first.detail.people.any?
+ @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ else
+ @events.collect(&:creator).uniq
+ end
+ end
- Before:
+ private
+ def respond_to_missing?(name, include_private = false)
+ @events.respond_to?(name, include_private)
+ end
- if (slack_url = Rails.application.secrets.slack_url).present?
- # Do something worthwhile
- else
- # Raise as important secret password is not specified
+ def method_missing(method, *args, &block)
+ @events.send(method, *args, &block)
+ end
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:
+ With `Module#delegate_missing_to`, the above is condensed to:
- variants = ActiveSupport::ArrayInquirer.new([:phone, :tablet])
+ class Partition
+ delegate_missing_to :@events
- 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
+ def initialize(first_event)
+ @events = [ first_event ]
end
- rescue_from ErrorDispatcher do |error|
- render status: error.status, json: { error: error.to_s }
+ def people
+ if @events.first.detail.people.any?
+ @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ else
+ @events.collect(&:creator).uniq
+ end
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.
+ *Genadi Samokovarov*, *DHH*
- 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.
+* Rescuable: If a handler doesn't match the exception, check for handlers
+ matching the exception's cause.
- *Logan Leger*
+ *Jeremy Daer*
-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/MIT-LICENSE b/activesupport/MIT-LICENSE
index 7bffebb076..40235833ba 100644
--- a/activesupport/MIT-LICENSE
+++ b/activesupport/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2005-2015 David Heinemeier Hansson
+Copyright (c) 2005-2016 David Heinemeier Hansson
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
@@ -17,4 +17,4 @@ 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. \ No newline at end of file
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/activesupport/README.rdoc b/activesupport/README.rdoc
index cd72f53821..14ce204303 100644
--- a/activesupport/README.rdoc
+++ b/activesupport/README.rdoc
@@ -10,7 +10,7 @@ outside of Rails.
The latest version of Active Support can be installed with RubyGems:
- % gem install activesupport
+ $ gem install activesupport
Source code can be downloaded as part of the Rails project on GitHub:
@@ -37,4 +37,3 @@ Bug reports can be filed for the Ruby on Rails project here:
Feature requests should be discussed on the rails-core mailing list here:
* https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core
-
diff --git a/activesupport/Rakefile b/activesupport/Rakefile
index 81c242d4b1..7b56e36abf 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -1,6 +1,9 @@
require 'rake/testtask'
task :default => :test
+
+task :package
+
Rake::TestTask.new do |t|
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 404c16b29f..c6ca969844 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -13,7 +13,7 @@ Gem::Specification.new do |s|
s.author = 'David Heinemeier Hansson'
s.email = 'david@loudthinking.com'
- s.homepage = 'http://www.rubyonrails.org'
+ s.homepage = 'http://rubyonrails.org'
s.files = Dir['CHANGELOG.md', 'MIT-LICENSE', 'README.rdoc', 'lib/**/*']
s.require_path = 'lib'
@@ -21,9 +21,7 @@ Gem::Specification.new do |s|
s.rdoc_options.concat ['--encoding', 'UTF-8']
s.add_dependency 'i18n', '~> 0.7'
- s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
- s.add_dependency 'concurrent-ruby', '~> 1.0.0.pre5', '< 2.0.0'
- s.add_dependency 'method_source'
+ s.add_dependency 'concurrent-ruby', '~> 1.0', '>= 1.0.2'
end
diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables
index 71a6b78652..2193533588 100755
--- a/activesupport/bin/generate_tables
+++ b/activesupport/bin/generate_tables
@@ -50,16 +50,11 @@ module ActiveSupport
([0-9A-F]*); # simple lowercase mapping
([0-9A-F]*)$/ix # simple titlecase mapping
codepoint.code = $1.hex
- #codepoint.name = $2
- #codepoint.category = $3
codepoint.combining_class = Integer($4)
- #codepoint.bidi_class = $5
codepoint.decomp_type = $7
codepoint.decomp_mapping = ($8=='') ? nil : $8.split.collect(&:hex)
- #codepoint.bidi_mirrored = ($13=='Y') ? true : false
codepoint.uppercase_mapping = ($16=='') ? 0 : $16.hex
codepoint.lowercase_mapping = ($17=='') ? 0 : $17.hex
- #codepoint.titlecase_mapping = ($18=='') ? nil : $18.hex
@ucd.codepoints[codepoint.code] = codepoint
end
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 63277a65b4..11569add37 100644
--- a/activesupport/lib/active_support.rb
+++ b/activesupport/lib/active_support.rb
@@ -1,5 +1,5 @@
#--
-# Copyright (c) 2005-2015 David Heinemeier Hansson
+# Copyright (c) 2005-2016 David Heinemeier Hansson
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
@@ -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
@@ -33,9 +34,13 @@ module ActiveSupport
autoload :Concern
autoload :Dependencies
autoload :DescendantsTracker
+ autoload :ExecutionWrapper
+ autoload :Executor
autoload :FileUpdateChecker
+ autoload :EventedFileUpdateChecker
autoload :LogSubscriber
autoload :Notifications
+ autoload :Reloader
eager_autoload do
autoload :BacktraceCleaner
@@ -81,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/benchmarkable.rb b/activesupport/lib/active_support/benchmarkable.rb
index 805b7a714f..3988b147ac 100644
--- a/activesupport/lib/active_support/benchmarkable.rb
+++ b/activesupport/lib/active_support/benchmarkable.rb
@@ -38,7 +38,7 @@ module ActiveSupport
options[:level] ||= :info
result = nil
- ms = Benchmark.ms { result = options[:silence] ? silence { yield } : yield }
+ ms = Benchmark.ms { result = options[:silence] ? logger.silence { yield } : yield }
logger.send(options[:level], '%s (%.1fms)' % [ message, ms ])
result
else
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index d0e53eaf05..bc114e0785 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -8,6 +8,7 @@ require 'active_support/core_ext/numeric/bytes'
require 'active_support/core_ext/numeric/time'
require 'active_support/core_ext/object/to_param'
require 'active_support/core_ext/string/inflections'
+require 'active_support/core_ext/string/strip'
module ActiveSupport
# See ActiveSupport::Cache::Store for documentation.
@@ -157,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
@@ -197,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.
@@ -254,10 +262,11 @@ module ActiveSupport
# end
# end
#
- # # val_1 => "new value 1"
- # # val_2 => "original value"
- # # sleep 10 # First thread extend the life of cache by another 10 seconds
- # # cache.fetch('foo') => "new value 1"
+ # cache.fetch('foo') # => "original value"
+ # sleep 10 # First thread extended the life of cache by another 10 seconds
+ # cache.fetch('foo') # => "new value 1"
+ # val_1 # => "new value 1"
+ # val_2 # => "original value"
#
# Other options will be handled by the specific cache store implementation.
# Internally, #fetch calls #read_entry, and calls #write_entry on a cache
@@ -275,21 +284,23 @@ module ActiveSupport
def fetch(name, options = nil)
if block_given?
options = merged_options(options)
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
+ entry = nil
instrument(:read, name, options) do |payload|
cached_entry = read_entry(key, options) unless options[:force]
- payload[:super_operation] = :fetch if payload
entry = handle_expired_entry(cached_entry, key, options)
+ payload[:super_operation] = :fetch if payload
+ payload[:hit] = !!entry if payload
+ end
- if entry
- payload[:hit] = true if payload
- get_entry_value(entry, name, options)
- else
- payload[:hit] = false if payload
- save_block_result_to_cache(name, options) { |_name| yield _name }
- end
+ if entry
+ get_entry_value(entry, name, options)
+ 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
@@ -302,7 +313,7 @@ module ActiveSupport
# Options are passed to the underlying cache implementation.
def read(name, options = nil)
options = merged_options(options)
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
instrument(:read, name, options) do |payload|
entry = read_entry(key, options)
if entry
@@ -321,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.
@@ -331,21 +342,19 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- instrument_multi(:read, names, options) do |payload|
- results = {}
- names.each do |name|
- key = namespaced_key(name, options)
- entry = read_entry(key, options)
- if entry
- if entry.expired?
- delete_entry(key, options)
- else
- results[name] = entry.value
- end
+ results = {}
+ names.each do |name|
+ key = normalize_key(name, options)
+ entry = read_entry(key, options)
+ if entry
+ if entry.expired?
+ delete_entry(key, options)
+ else
+ results[name] = entry.value
end
end
- results
end
+ results
end
# Fetches data from the cache, using the given keys. If there is data in
@@ -386,7 +395,7 @@ module ActiveSupport
instrument(:write, name, options) do
entry = Entry.new(value, options)
- write_entry(namespaced_key(name, options), entry, options)
+ write_entry(normalize_key(name, options), entry, options)
end
end
@@ -397,7 +406,7 @@ module ActiveSupport
options = merged_options(options)
instrument(:delete, name) do
- delete_entry(namespaced_key(name, options), options)
+ delete_entry(normalize_key(name, options), options)
end
end
@@ -408,12 +417,12 @@ module ActiveSupport
options = merged_options(options)
instrument(:exist?, name) do
- entry = read_entry(namespaced_key(name, options), options)
+ entry = read_entry(normalize_key(name, options), options)
(entry && !entry.expired?) || false
end
end
- # 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,9 +536,9 @@ 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 namespaced_key(key, options)
+ def normalize_key(key, options)
key = expanded_key(key)
namespace = options[:namespace] if options
prefix = namespace.is_a?(Proc) ? namespace.call : namespace
@@ -537,25 +546,22 @@ module ActiveSupport
key
end
+ def namespaced_key(*args)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ `namespaced_key` is deprecated and will be removed from Rails 5.1.
+ Please use `normalize_key` which will return a fully resolved key.
+ MESSAGE
+ normalize_key(*args)
+ end
+
def instrument(operation, key, options = nil)
- log { "Cache #{operation}: #{namespaced_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
+ log { "Cache #{operation}: #{normalize_key(key, options)}#{options.blank? ? "" : " (#{options.inspect})"}" }
payload = { :key => key }
payload.merge!(options) if options.is_a?(Hash)
ActiveSupport::Notifications.instrument("cache_#{operation}.active_support", payload){ yield(payload) }
end
- def instrument_multi(operation, keys, options = nil)
- log do
- formatted_keys = keys.map { |k| "- #{k}" }.join("\n")
- "Caches multi #{operation}:\n#{formatted_keys}#{options.blank? ? "" : " (#{options.inspect})"}"
- end
-
- payload = { key: keys }
- payload.merge!(options) if options.is_a?(Hash)
- ActiveSupport::Notifications.instrument("cache_#{operation}_multi.active_support", payload) { yield(payload) }
- end
-
def log
return unless logger && logger.debug? && !silence?
logger.debug(yield)
@@ -578,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
@@ -601,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)
@@ -620,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
@@ -655,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/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 9a88fc286a..99c55b1aa4 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -17,6 +17,7 @@ module ActiveSupport
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
FILEPATH_MAX_SIZE = 900 # max is 1024, plus some room
EXCLUDED_DIRS = ['.', '..'].freeze
+ GITKEEP_FILES = ['.gitkeep', '.keep'].freeze
def initialize(cache_path, options = nil)
super(options)
@@ -24,10 +25,10 @@ module ActiveSupport
end
# Deletes all items from the cache. In this case it deletes all the entries in the specified
- # file store directory except for .gitkeep. Be careful which directory is specified in your
+ # file store directory except for .keep or .gitkeep. Be careful which directory is specified in your
# config file when using +FileStore+ because everything in that directory will be deleted.
def clear(options = nil)
- root_dirs = Dir.entries(cache_path).reject {|f| (EXCLUDED_DIRS + [".gitkeep"]).include?(f)}
+ root_dirs = exclude_from(cache_path, EXCLUDED_DIRS + GITKEEP_FILES)
FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)})
rescue Errno::ENOENT
end
@@ -60,7 +61,7 @@ module ActiveSupport
matcher = key_matcher(matcher, options)
search_dir(cache_path) do |path|
key = file_path_key(path)
- delete_entry(key, options) if key.match(matcher)
+ delete_entry(path, options) if key.match(matcher)
end
end
end
@@ -68,9 +69,8 @@ module ActiveSupport
protected
def read_entry(key, options)
- file_name = key_file_path(key)
- if File.exist?(file_name)
- File.open(file_name) { |f| Marshal.load(f) }
+ if File.exist?(key)
+ File.open(key) { |f| Marshal.load(f) }
end
rescue => e
logger.error("FileStoreError (#{e}): #{e.message}") if logger
@@ -78,23 +78,21 @@ module ActiveSupport
end
def write_entry(key, entry, options)
- file_name = key_file_path(key)
- return false if options[:unless_exist] && File.exist?(file_name)
- ensure_cache_path(File.dirname(file_name))
- File.atomic_write(file_name, cache_path) {|f| Marshal.dump(entry, f)}
+ return false if options[:unless_exist] && File.exist?(key)
+ ensure_cache_path(File.dirname(key))
+ File.atomic_write(key, cache_path) {|f| Marshal.dump(entry, f)}
true
end
def delete_entry(key, options)
- file_name = key_file_path(key)
- if File.exist?(file_name)
+ if File.exist?(key)
begin
- File.delete(file_name)
- delete_empty_directories(File.dirname(file_name))
+ File.delete(key)
+ delete_empty_directories(File.dirname(key))
true
rescue => e
# Just in case the error was caused by another process deleting the file first.
- raise e if File.exist?(file_name)
+ raise e if File.exist?(key)
false
end
end
@@ -118,7 +116,8 @@ module ActiveSupport
end
# Translate a key into a file path.
- def key_file_path(key)
+ def normalize_key(key, options)
+ key = super
fname = URI.encode_www_form_component(key)
if fname.size > FILEPATH_MAX_SIZE
@@ -139,6 +138,14 @@ module ActiveSupport
File.join(cache_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
end
+ def key_file_path(key)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ `key_file_path` is deprecated and will be removed from Rails 5.1.
+ Please use `normalize_key` which will return a fully resolved key or nothing.
+ MESSAGE
+ key
+ end
+
# Translate a file path into a key.
def file_path_key(path)
fname = path[cache_path.to_s.size..-1].split(File::SEPARATOR, 4).last
@@ -148,7 +155,7 @@ module ActiveSupport
# Delete empty directories in the cache.
def delete_empty_directories(dir)
return if File.realpath(dir) == File.realpath(cache_path)
- if Dir.entries(dir).reject {|f| EXCLUDED_DIRS.include?(f)}.empty?
+ if exclude_from(dir, EXCLUDED_DIRS).empty?
Dir.delete(dir) rescue nil
delete_empty_directories(File.dirname(dir))
end
@@ -175,7 +182,7 @@ module ActiveSupport
# Modifies the amount of an already existing integer value that is stored in the cache.
# If the key is not found nothing is done.
def modify_value(name, amount, options)
- file_name = key_file_path(namespaced_key(name, options))
+ file_name = normalize_key(name, options)
lock_file(file_name) do
options = merged_options(options)
@@ -187,6 +194,11 @@ module ActiveSupport
end
end
end
+
+ # Exclude entries from source directory
+ def exclude_from(source, excludes)
+ Dir.entries(source).reject { |f| excludes.include?(f) }
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index e2f536ef1e..2ca4b51efa 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -36,13 +36,13 @@ module ActiveSupport
end
def write_entry(key, entry, options) # :nodoc:
- retval = super
- if options[:raw] && local_cache && retval
+ if options[:raw] && local_cache
raw_entry = Entry.new(entry.value.to_s)
raw_entry.expires_at = entry.expires_at
- local_cache.write_entry(key, raw_entry, options)
+ super(key, raw_entry, options)
+ else
+ super
end
- retval
end
end
@@ -96,16 +96,14 @@ module ActiveSupport
options = names.extract_options!
options = merged_options(options)
- instrument_multi(:read, names, options) do
- keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}]
- raw_values = @data.get_multi(keys_to_names.keys, :raw => true)
- values = {}
- raw_values.each do |key, value|
- entry = deserialize_entry(value)
- values[keys_to_names[key]] = entry.value unless entry.expired?
- end
- values
+ keys_to_names = Hash[names.map{|name| [normalize_key(name, options), name]}]
+ raw_values = @data.get_multi(keys_to_names.keys, :raw => true)
+ values = {}
+ raw_values.each do |key, value|
+ entry = deserialize_entry(value)
+ values[keys_to_names[key]] = entry.value unless entry.expired?
end
+ values
end
# Increment a cached value. This method uses the memcached incr atomic
@@ -115,11 +113,10 @@ module ActiveSupport
def increment(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
instrument(:increment, name, :amount => amount) do
- @data.incr(escape_key(namespaced_key(name, options)), amount)
+ rescue_error_with nil do
+ @data.incr(normalize_key(name, options), amount)
+ end
end
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
end
# Decrement a cached value. This method uses the memcached decr atomic
@@ -129,20 +126,16 @@ module ActiveSupport
def decrement(name, amount = 1, options = nil) # :nodoc:
options = merged_options(options)
instrument(:decrement, name, :amount => amount) do
- @data.decr(escape_key(namespaced_key(name, options)), amount)
+ rescue_error_with nil do
+ @data.decr(normalize_key(name, options), amount)
+ end
end
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
end
# Clear the entire cache on all memcached servers. This method should
# be used with care when shared cache is being used.
def clear(options = nil)
- @data.flush_all
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
+ rescue_error_with(nil) { @data.flush_all }
end
# Get the statistics from the memcached servers.
@@ -153,10 +146,7 @@ module ActiveSupport
protected
# Read an entry from the cache.
def read_entry(key, options) # :nodoc:
- deserialize_entry(@data.get(escape_key(key), options))
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- nil
+ rescue_error_with(nil) { deserialize_entry(@data.get(key, options)) }
end
# Write an entry to the cache.
@@ -168,18 +158,14 @@ module ActiveSupport
# Set the memcache expire a few minutes in the future to support race condition ttls on read
expires_in += 5.minutes
end
- @data.send(method, escape_key(key), value, expires_in, options)
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- false
+ rescue_error_with false do
+ @data.send(method, key, value, expires_in, options)
+ end
end
# Delete an entry from the cache.
def delete_entry(key, options) # :nodoc:
- @data.delete(escape_key(key))
- rescue Dalli::DalliError => e
- logger.error("DalliError (#{e}): #{e.message}") if logger
- false
+ rescue_error_with(false) { @data.delete(key) }
end
private
@@ -187,22 +173,35 @@ module ActiveSupport
# Memcache keys are binaries. So we need to force their encoding to binary
# before applying the regular expression to ensure we are escaping all
# characters properly.
- def escape_key(key)
- key = key.to_s.dup
+ def normalize_key(key, options)
+ key = super.dup
key = key.force_encoding(Encoding::ASCII_8BIT)
key = key.gsub(ESCAPE_KEY_CHARS){ |match| "%#{match.getbyte(0).to_s(16).upcase}" }
key = "#{key[0, 213]}:md5:#{Digest::MD5.hexdigest(key)}" if key.size > 250
key
end
+ def escape_key(key)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ `escape_key` is deprecated and will be removed from Rails 5.1.
+ Please use `normalize_key` which will return a fully resolved key or nothing.
+ MESSAGE
+ key
+ end
+
def deserialize_entry(raw_value)
if raw_value
entry = Marshal.load(raw_value) rescue raw_value
entry.is_a?(Entry) ? entry : Entry.new(entry)
- else
- nil
end
end
+
+ def rescue_error_with(fallback)
+ yield
+ rescue Dalli::DalliError => e
+ logger.error("DalliError (#{e}): #{e.message}") if logger
+ fallback
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index 90bb2c38c3..896c28ad8b 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -75,30 +75,12 @@ module ActiveSupport
# Increment an integer value in the cache.
def increment(name, amount = 1, options = nil)
- synchronize do
- options = merged_options(options)
- if num = read(name, options)
- num = num.to_i + amount
- write(name, num, options)
- num
- else
- nil
- end
- end
+ modify_value(name, amount, options)
end
# Decrement an integer value in the cache.
def decrement(name, amount = 1, options = nil)
- synchronize do
- options = merged_options(options)
- if num = read(name, options)
- num = num.to_i - amount
- write(name, num, options)
- num
- else
- nil
- end
- end
+ modify_value(name, -amount, options)
end
def delete_matched(matcher, options = nil)
@@ -167,6 +149,19 @@ module ActiveSupport
!!entry
end
end
+
+ private
+
+ def modify_value(name, amount, options)
+ synchronize do
+ options = merged_options(options)
+ if num = read(name, options)
+ num = num.to_i + amount
+ write(name, num, options)
+ num
+ end
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index d521061004..1c678dc2af 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -79,24 +79,28 @@ module ActiveSupport
end
def clear(options = nil) # :nodoc:
- local_cache.clear(options) if local_cache
+ return super unless cache = local_cache
+ cache.clear(options)
super
end
def cleanup(options = nil) # :nodoc:
- local_cache.clear(options) if local_cache
+ return super unless cache = local_cache
+ cache.clear(options)
super
end
def increment(name, amount = 1, options = nil) # :nodoc:
+ return super unless local_cache
value = bypass_local_cache{super}
- set_cache_value(value, name, amount, options)
+ write_cache_value(name, value, options)
value
end
def decrement(name, amount = 1, options = nil) # :nodoc:
+ return super unless local_cache
value = bypass_local_cache{super}
- set_cache_value(value, name, amount, options)
+ write_cache_value(name, value, options)
value
end
@@ -120,13 +124,21 @@ module ActiveSupport
end
def set_cache_value(value, name, amount, options) # :nodoc:
- if local_cache
- local_cache.mute do
- if value
- local_cache.write(name, value, options)
- else
- local_cache.delete(name, options)
- end
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.strip_heredoc)
+ `set_cache_value` is deprecated and will be removed from Rails 5.1.
+ Please use `write_cache_value` instead.
+ MESSAGE
+ write_cache_value name, value, options
+ end
+
+ def write_cache_value(name, value, options) # :nodoc:
+ name = normalize_key(name, options)
+ cache = local_cache
+ cache.mute do
+ if value
+ cache.write(name, value, options)
+ else
+ cache.delete(name, options)
end
end
end
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index d43fde03a9..904d3f0eb0 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -71,7 +71,7 @@ module ActiveSupport
# halt the entire callback chain and display a deprecation message.
# If false, callback chains will only be halted by calling +throw :abort+.
# Defaults to +true+.
- mattr_accessor(:halt_and_display_warning_on_return_false) { true }
+ mattr_accessor(:halt_and_display_warning_on_return_false, instance_writer: false) { true }
# Runs the callbacks for the given event.
#
@@ -295,6 +295,13 @@ module ActiveSupport
class Callback #:nodoc:#
def self.build(chain, filter, kind, options)
+ if filter.is_a?(String)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Passing string to define callback is deprecated and will be removed
+ in Rails 5.1 without replacement.
+ MSG
+ end
+
new chain.name, filter, kind, options, chain.config
end
@@ -564,18 +571,18 @@ 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; or as an
+ # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
# object that responds to a certain method determined by the <tt>:scope</tt>
# argument to +define_callbacks+.
#
@@ -735,7 +742,7 @@ module ActiveSupport
options = names.extract_options!
names.each do |name|
- class_attribute "_#{name}_callbacks"
+ class_attribute "_#{name}_callbacks", instance_writer: false
set_callbacks name, CallbackChain.new(name, options)
module_eval <<-RUBY, __FILE__, __LINE__ + 1
@@ -775,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 ca48164c54..89e63aefd4 100644
--- a/activesupport/lib/active_support/concurrency/share_lock.rb
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -6,12 +6,6 @@ module ActiveSupport
# A share/exclusive lock, otherwise known as a read/write lock.
#
# https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
- #--
- # Note that a pending Exclusive lock attempt does not block incoming
- # Share requests (i.e., we are "read-preferring"). That seems
- # consistent with the behavior of "loose" upgrades, but may be the
- # wrong choice otherwise: it nominally reduces the possibility of
- # deadlock by risking starvation instead.
class ShareLock
include MonitorMixin
@@ -48,17 +42,11 @@ module ActiveSupport
def start_exclusive(purpose: nil, compatible: [], no_wait: false)
synchronize do
unless @exclusive_thread == Thread.current
- if busy?(purpose)
+ if busy_for_exclusive?(purpose)
return false if no_wait
- loose_shares = @sharing.delete(Thread.current)
- @waiting[Thread.current] = compatible if loose_shares
-
- begin
- @cv.wait_while { busy?(purpose) }
- ensure
- @waiting.delete Thread.current
- @sharing[Thread.current] = loose_shares if loose_shares
+ yield_shares(purpose: purpose, compatible: compatible, block_share: true) do
+ @cv.wait_while { busy_for_exclusive?(purpose) }
end
end
@exclusive_thread = Thread.current
@@ -71,13 +59,19 @@ module ActiveSupport
# Relinquish the exclusive lock. Must only be called by the thread
# that called start_exclusive (and currently holds the lock).
- def stop_exclusive
+ def stop_exclusive(compatible: [])
synchronize do
raise "invalid unlock" if @exclusive_thread != Thread.current
@exclusive_depth -= 1
if @exclusive_depth == 0
@exclusive_thread = nil
+
+ if eligible_waiters?(compatible)
+ yield_shares(compatible: compatible, block_share: true) do
+ @cv.wait_while { @exclusive_thread || eligible_waiters?(compatible) }
+ end
+ end
@cv.broadcast
end
end
@@ -85,8 +79,16 @@ module ActiveSupport
def start_sharing
synchronize do
- if @exclusive_thread && @exclusive_thread != Thread.current
+ if @sharing[Thread.current] > 0 || @exclusive_thread == Thread.current
+ # We already hold a lock; nothing to wait for
+ elsif @waiting[Thread.current]
+ # We're nested inside a +yield_shares+ call: we'll resume as
+ # soon as there isn't an exclusive lock in our way
@cv.wait_while { @exclusive_thread }
+ else
+ # This is an initial / outermost share call: any outstanding
+ # requests for an exclusive lock get to go first
+ @cv.wait_while { busy_for_sharing?(false) }
end
@sharing[Thread.current] += 1
end
@@ -109,12 +111,12 @@ module ActiveSupport
# the block.
#
# See +start_exclusive+ for other options.
- def exclusive(purpose: nil, compatible: [], no_wait: false)
+ def exclusive(purpose: nil, compatible: [], after_compatible: [], no_wait: false)
if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
begin
yield
ensure
- stop_exclusive
+ stop_exclusive(compatible: after_compatible)
end
end
end
@@ -129,14 +131,56 @@ module ActiveSupport
end
end
+ # Temporarily give up all held Share locks while executing the
+ # supplied block, allowing any +compatible+ exclusive lock request
+ # to proceed.
+ def yield_shares(purpose: nil, compatible: [], block_share: false)
+ loose_shares = previous_wait = nil
+ synchronize do
+ if loose_shares = @sharing.delete(Thread.current)
+ if previous_wait = @waiting[Thread.current]
+ purpose = nil unless purpose == previous_wait[0]
+ compatible &= previous_wait[1]
+ end
+ compatible |= [false] unless block_share
+ @waiting[Thread.current] = [purpose, compatible]
+ end
+
+ @cv.broadcast
+ end
+
+ begin
+ yield
+ ensure
+ synchronize do
+ @cv.wait_while { @exclusive_thread && @exclusive_thread != Thread.current }
+
+ if previous_wait
+ @waiting[Thread.current] = previous_wait
+ else
+ @waiting.delete Thread.current
+ end
+ @sharing[Thread.current] = loose_shares if loose_shares
+ end
+ end
+ end
+
private
# Must be called within synchronize
- def busy?(purpose)
- (@exclusive_thread && @exclusive_thread != Thread.current) ||
- @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } ||
+ def busy_for_exclusive?(purpose)
+ busy_for_sharing?(purpose) ||
@sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
end
+
+ def busy_for_sharing?(purpose)
+ (@exclusive_thread && @exclusive_thread != Thread.current) ||
+ @waiting.any? { |t, (_, c)| t != Thread.current && !c.include?(purpose) }
+ end
+
+ def eligible_waiters?(compatible)
+ @waiting.any? { |t, (p, _)| compatible.include?(p) && @waiting.all? { |t2, (_, c2)| t == t2 || c2.include?(p) } }
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/array/access.rb b/activesupport/lib/active_support/core_ext/array/access.rb
index 3177d8498e..37d833887a 100644
--- a/activesupport/lib/active_support/core_ext/array/access.rb
+++ b/activesupport/lib/active_support/core_ext/array/access.rb
@@ -73,4 +73,18 @@ class Array
def forty_two
self[41]
end
+
+ # Equal to <tt>self[-3]</tt>.
+ #
+ # %w( a b c d e ).third_to_last # => "c"
+ def third_to_last
+ self[-3]
+ end
+
+ # Equal to <tt>self[-2]</tt>.
+ #
+ # %w( a b c d e ).second_to_last # => "d"
+ def second_to_last
+ self[-2]
+ end
end
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 3c4bfc5f1e..1d8c33b43e 100644
--- a/activesupport/lib/active_support/core_ext/class/subclasses.rb
+++ b/activesupport/lib/active_support/core_ext/class/subclasses.rb
@@ -3,7 +3,8 @@ require 'active_support/core_ext/module/reachable'
class Class
begin
- ObjectSpace.each_object(Class.new) {}
+ # Test if this Ruby supports each_object against singleton_class
+ ObjectSpace.each_object(Numeric.singleton_class) {}
def descendants # :nodoc:
descendants = []
@@ -12,7 +13,7 @@ class Class
end
descendants
end
- rescue StandardError # JRuby
+ rescue StandardError # JRuby 9.0.4.0 and earlier
def descendants # :nodoc:
descendants = []
ObjectSpace.each_object(Class) do |k|
@@ -25,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 e079af594d..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
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/object/try'
+
module DateAndTime
module Calculations
DAYS_INTO_WEEK = {
@@ -51,6 +53,11 @@ module DateAndTime
WEEKEND_DAYS.include?(wday)
end
+ # Returns true if the date/time does not fall on a Saturday or Sunday.
+ def on_weekday?
+ !WEEKEND_DAYS.include?(wday)
+ end
+
# Returns a new date/time the specified number of days ago.
def days_ago(days)
advance(:days => -days)
@@ -287,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 fc7531d088..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 { |sum, element| sum + element } || 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/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb
index 5dc9a05ec7..62ea579c65 100644
--- a/activesupport/lib/active_support/core_ext/hash/compact.rb
+++ b/activesupport/lib/active_support/core_ext/hash/compact.rb
@@ -1,9 +1,9 @@
class Hash
# Returns a hash with non +nil+ values.
#
- # hash = { a: true, b: false, c: nil}
- # hash.compact # => { a: true, b: false}
- # hash # => { a: true, b: false, c: nil}
+ # hash = { a: true, b: false, c: nil }
+ # hash.compact # => { a: true, b: false }
+ # hash # => { a: true, b: false, c: nil }
# { c: nil }.compact # => {}
def compact
self.select { |_, value| !value.nil? }
@@ -11,9 +11,9 @@ class Hash
# Replaces current hash with non +nil+ values.
#
- # hash = { a: true, b: false, c: nil}
- # hash.compact! # => { a: true, b: false}
- # hash # => { a: true, b: false}
+ # hash = { a: true, b: false, c: nil }
+ # hash.compact! # => { a: true, b: false }
+ # hash # => { a: true, b: false }
def compact!
self.reject! { |_, value| value.nil? }
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 8594d9bf2e..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",
@@ -138,6 +137,8 @@ end
module ActiveSupport
class XMLConverter # :nodoc:
+ # Raised if the XML contains attributes with type="yaml" or
+ # type="symbol". Read Hash#from_xml for more details.
class DisallowedType < StandardError
def initialize(type)
super "Disallowed type attribute: #{type.inspect}"
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/kernel/concern.rb b/activesupport/lib/active_support/core_ext/kernel/concern.rb
index bf72caa058..18bcc01fa4 100644
--- a/activesupport/lib/active_support/core_ext/kernel/concern.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/concern.rb
@@ -1,6 +1,8 @@
require 'active_support/core_ext/module/concerning'
module Kernel
+ module_function
+
# A shortcut to define a toplevel concern, not within a module.
#
# See Module::Concerning for more.
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 8afc258df8..d0197af95f 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,4 +1,6 @@
module Kernel
+ module_function
+
# Sets $VERBOSE to nil for the duration of the block and back to its original
# value afterwards.
#
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index e333b26133..edfc8296fe 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -3,9 +3,12 @@ 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
- $1.constantize
+ loaded = $1.constantize
+
+ raise unless $1 == loaded.name
+
# if it is an IO we need to go back to read the object
source.rewind if source.respond_to?(:rewind)
retry
diff --git a/activesupport/lib/active_support/core_ext/module.rb b/activesupport/lib/active_support/core_ext/module.rb
index b4efff8b24..ef038331c2 100644
--- a/activesupport/lib/active_support/core_ext/module.rb
+++ b/activesupport/lib/active_support/core_ext/module.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/module/introspection'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/module/reachable'
require 'active_support/core_ext/module/attribute_accessors'
+require 'active_support/core_ext/module/attribute_accessors_per_thread'
require 'active_support/core_ext/module/attr_internal'
require 'active_support/core_ext/module/concerning'
require 'active_support/core_ext/module/delegation'
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
index bf175a8a70..567ac825e9 100644
--- a/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors.rb
@@ -5,7 +5,7 @@ require 'active_support/core_ext/array/extract_options'
# attributes.
class Module
# Defines a class attribute and creates a class and instance reader methods.
- # The underlying the class variable is set to +nil+, if it is not previously
+ # The underlying class variable is set to +nil+, if it is not previously
# defined.
#
# module HairColors
@@ -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
@@ -49,7 +49,7 @@ class Module
# include HairColors
# end
#
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red]
def mattr_reader(*syms)
options = syms.extract_options!
syms.each do |sym|
@@ -105,7 +105,7 @@ class Module
#
# Also, you can pass a block to set up the attribute with a default value.
#
- # class HairColors
+ # module HairColors
# mattr_writer :hair_colors do
# [:brown, :black, :blonde, :red]
# end
@@ -150,8 +150,8 @@ class Module
# include HairColors
# end
#
- # Person.hair_colors = [:brown, :black, :blonde, :red]
- # Person.hair_colors # => [:brown, :black, :blonde, :red]
+ # HairColors.hair_colors = [:brown, :black, :blonde, :red]
+ # HairColors.hair_colors # => [:brown, :black, :blonde, :red]
# Person.new.hair_colors # => [:brown, :black, :blonde, :red]
#
# If a subclass changes the value then that would also change the value for
@@ -161,8 +161,8 @@ class Module
# class Male < Person
# end
#
- # Male.hair_colors << :blue
- # Person.hair_colors # => [:brown, :black, :blonde, :red, :blue]
+ # Male.new.hair_colors << :blue
+ # Person.new.hair_colors # => [:brown, :black, :blonde, :red, :blue]
#
# To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
# To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
new file mode 100644
index 0000000000..0b3d18301e
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
@@ -0,0 +1,141 @@
+require 'active_support/core_ext/array/extract_options'
+
+# Extends the module object with class/module and instance accessors for
+# class/module attributes, just like the native attr* accessors for instance
+# attributes, but does so on a per-thread basis.
+#
+# So the values are scoped within the Thread.current space under the class name
+# of the module.
+class Module
+ # Defines a per-thread class attribute and creates class and instance reader methods.
+ # The underlying per-thread class variable is set to +nil+, if it is not previously defined.
+ #
+ # module Current
+ # thread_mattr_reader :user
+ # end
+ #
+ # Current.user # => nil
+ # Thread.current[:attr_Current_user] = "DHH"
+ # Current.user # => "DHH"
+ #
+ # The attribute name must be a valid method name in Ruby.
+ #
+ # module Foo
+ # thread_mattr_reader :"1_Badname"
+ # end
+ # # => NameError: invalid attribute name: 1_Badname
+ #
+ # If you want to opt out the creation on the instance reader method, pass
+ # <tt>instance_reader: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # class Current
+ # thread_mattr_reader :user, instance_reader: false
+ # end
+ #
+ # Current.new.user # => NoMethodError
+ def thread_mattr_reader(*syms)
+ options = syms.extract_options!
+
+ syms.each do |sym|
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def self.#{sym}
+ Thread.current[:"attr_#{name}_#{sym}"]
+ end
+ EOS
+
+ unless options[:instance_reader] == false || options[:instance_accessor] == false
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{sym}
+ Thread.current[:"attr_#{name}_#{sym}"]
+ end
+ EOS
+ end
+ end
+ end
+ alias :thread_cattr_reader :thread_mattr_reader
+
+ # Defines a per-thread class attribute and creates a class and instance writer methods to
+ # allow assignment to the attribute.
+ #
+ # module Current
+ # thread_mattr_writer :user
+ # end
+ #
+ # Current.user = "DHH"
+ # Thread.current[:attr_Current_user] # => "DHH"
+ #
+ # If you want to opt out the instance writer method, pass
+ # <tt>instance_writer: false</tt> or <tt>instance_accessor: false</tt>.
+ #
+ # class Current
+ # thread_mattr_writer :user, instance_writer: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ def thread_mattr_writer(*syms)
+ options = syms.extract_options!
+ syms.each do |sym|
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def self.#{sym}=(obj)
+ Thread.current[:"attr_#{name}_#{sym}"] = obj
+ end
+ EOS
+
+ unless options[:instance_writer] == false || options[:instance_accessor] == false
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
+ def #{sym}=(obj)
+ Thread.current[:"attr_#{name}_#{sym}"] = obj
+ end
+ EOS
+ end
+ end
+ end
+ alias :thread_cattr_writer :thread_mattr_writer
+
+ # Defines both class and instance accessors for class attributes.
+ #
+ # class Account
+ # thread_mattr_accessor :user
+ # end
+ #
+ # Account.user = "DHH"
+ # Account.user # => "DHH"
+ # Account.new.user # => "DHH"
+ #
+ # If a subclass changes the value, the parent class' value is not changed.
+ # Similarly, if the parent class changes the value, the value of subclasses
+ # is not changed.
+ #
+ # class Customer < Account
+ # end
+ #
+ # Customer.user = "Rafael"
+ # Customer.user # => "Rafael"
+ # Account.user # => "DHH"
+ #
+ # To opt out of the instance writer method, pass <tt>instance_writer: false</tt>.
+ # To opt out of the instance reader method, pass <tt>instance_reader: false</tt>.
+ #
+ # class Current
+ # thread_mattr_accessor :user, instance_writer: false, instance_reader: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ # Current.new.user # => NoMethodError
+ #
+ # Or pass <tt>instance_accessor: false</tt>, to opt out both instance methods.
+ #
+ # class Current
+ # mattr_accessor :user, instance_accessor: false
+ # end
+ #
+ # Current.new.user = "DHH" # => NoMethodError
+ # Current.new.user # => NoMethodError
+ def thread_mattr_accessor(*syms, &blk)
+ thread_mattr_reader(*syms, &blk)
+ thread_mattr_writer(*syms, &blk)
+ end
+ alias :thread_cattr_accessor :thread_mattr_accessor
+end
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 0d46248582..7f968d10b5 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -148,15 +148,11 @@ class Module
# Foo.new("Bar").name # raises NoMethodError: undefined method `name'
#
# 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
@@ -215,4 +211,68 @@ class Module
module_eval(method_def, file, line)
end
end
+
+ # When building decorators, a common pattern may emerge:
+ #
+ # class Partition
+ # def initialize(first_event)
+ # @events = [ first_event ]
+ # end
+ #
+ # def people
+ # if @events.first.detail.people.any?
+ # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ # else
+ # @events.collect(&:creator).uniq
+ # end
+ # end
+ #
+ # private
+ # def respond_to_missing?(name, include_private = false)
+ # @events.respond_to?(name, include_private)
+ # end
+ #
+ # def method_missing(method, *args, &block)
+ # @events.send(method, *args, &block)
+ # end
+ # end
+ #
+ # With `Module#delegate_missing_to`, the above is condensed to:
+ #
+ # class Partition
+ # delegate_missing_to :@events
+ #
+ # def initialize(first_event)
+ # @events = [ first_event ]
+ # end
+ #
+ # def people
+ # if @events.first.detail.people.any?
+ # @events.collect { |e| Array(e.detail.people) }.flatten.uniq
+ # else
+ # @events.collect(&:creator).uniq
+ # end
+ # end
+ # end
+ #
+ # The target can be anything callable withing the object. E.g. instance
+ # variables, methods, constants ant the likes.
+ def delegate_missing_to(target)
+ target = target.to_s
+ target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
+
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def respond_to_missing?(name, include_private = false)
+ #{target}.respond_to?(name, include_private)
+ end
+
+ def method_missing(method, *args, &block)
+ if #{target}.respond_to?(method)
+ #{target}.public_send(method, *args, &block)
+ else
+ super
+ end
+ end
+ RUBY
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/module/deprecation.rb b/activesupport/lib/active_support/core_ext/module/deprecation.rb
index 56d670fbe8..f3f2e7f5fc 100644
--- a/activesupport/lib/active_support/core_ext/module/deprecation.rb
+++ b/activesupport/lib/active_support/core_ext/module/deprecation.rb
@@ -13,8 +13,8 @@ class Module
#
# class MyLib::Deprecator
# def deprecation_warning(deprecated_method_name, message, caller_backtrace = nil)
- # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
- # Kernel.warn message
+ # message = "#{deprecated_method_name} is deprecated and will be removed from MyLibrary | #{message}"
+ # Kernel.warn message
# end
# end
def deprecate(*method_names)
diff --git a/activesupport/lib/active_support/core_ext/module/introspection.rb b/activesupport/lib/active_support/core_ext/module/introspection.rb
index f1d26ef28f..fa692e1b0e 100644
--- a/activesupport/lib/active_support/core_ext/module/introspection.rb
+++ b/activesupport/lib/active_support/core_ext/module/introspection.rb
@@ -57,6 +57,10 @@ class Module
end
def local_constants #:nodoc:
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ Module#local_constants is deprecated and will be removed in Rails 5.1.
+ Use Module#constants(false) instead.
+ MSG
constants(false)
end
end
diff --git a/activesupport/lib/active_support/core_ext/module/qualified_const.rb b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
index 65525013db..3ea39d4267 100644
--- a/activesupport/lib/active_support/core_ext/module/qualified_const.rb
+++ b/activesupport/lib/active_support/core_ext/module/qualified_const.rb
@@ -3,13 +3,16 @@ require 'active_support/core_ext/string/inflections'
#--
# Allows code reuse in the methods below without polluting Module.
#++
-module QualifiedConstUtils
- def self.raise_if_absolute(path)
- raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
- end
- def self.names(path)
- path.split('::')
+module ActiveSupport
+ module QualifiedConstUtils
+ def self.raise_if_absolute(path)
+ raise NameError.new("wrong constant name #$&") if path =~ /\A::[^:]+/
+ end
+
+ def self.names(path)
+ path.split('::')
+ end
end
end
@@ -24,9 +27,14 @@ end
#++
class Module
def qualified_const_defined?(path, search_parents=true)
- QualifiedConstUtils.raise_if_absolute(path)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Module#qualified_const_defined? is deprecated in favour of the builtin
+ Module#const_defined? and will be removed in Rails 5.1.
+ MESSAGE
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
+ ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
+
+ ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name|
return unless mod.const_defined?(name, search_parents)
mod.const_get(name)
end
@@ -34,19 +42,29 @@ class Module
end
def qualified_const_get(path)
- QualifiedConstUtils.raise_if_absolute(path)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Module#qualified_const_get is deprecated in favour of the builtin
+ Module#const_get and will be removed in Rails 5.1.
+ MESSAGE
+
+ ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
- QualifiedConstUtils.names(path).inject(self) do |mod, name|
+ ActiveSupport::QualifiedConstUtils.names(path).inject(self) do |mod, name|
mod.const_get(name)
end
end
def qualified_const_set(path, value)
- QualifiedConstUtils.raise_if_absolute(path)
+ ActiveSupport::Deprecation.warn(<<-MESSAGE.squish)
+ Module#qualified_const_set is deprecated in favour of the builtin
+ Module#const_set and will be removed in Rails 5.1.
+ MESSAGE
+
+ ActiveSupport::QualifiedConstUtils.raise_if_absolute(path)
const_name = path.demodulize
mod_name = path.deconstantize
- mod = mod_name.empty? ? self : qualified_const_get(mod_name)
+ mod = mod_name.empty? ? self : const_get(mod_name)
mod.const_set(const_name, value)
end
end
diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
index 9a3651f29a..6586a351f8 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/number_helper'
+require 'active_support/core_ext/module/deprecation'
module ActiveSupport::NumericWithFormat
@@ -14,70 +15,72 @@ module ActiveSupport::NumericWithFormat
# ==== Examples
#
# Phone Numbers:
- # 5551234.to_s(:phone) # => 555-1234
- # 1235551234.to_s(:phone) # => 123-555-1234
- # 1235551234.to_s(:phone, area_code: true) # => (123) 555-1234
- # 1235551234.to_s(:phone, delimiter: ' ') # => 123 555 1234
- # 1235551234.to_s(:phone, area_code: true, extension: 555) # => (123) 555-1234 x 555
- # 1235551234.to_s(:phone, country_code: 1) # => +1-123-555-1234
+ # 5551234.to_s(:phone) # => "555-1234"
+ # 1235551234.to_s(:phone) # => "123-555-1234"
+ # 1235551234.to_s(:phone, area_code: true) # => "(123) 555-1234"
+ # 1235551234.to_s(:phone, delimiter: ' ') # => "123 555 1234"
+ # 1235551234.to_s(:phone, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
+ # 1235551234.to_s(:phone, country_code: 1) # => "+1-123-555-1234"
# 1235551234.to_s(:phone, country_code: 1, extension: 1343, delimiter: '.')
- # # => +1.123.555.1234 x 1343
+ # # => "+1.123.555.1234 x 1343"
#
# Currency:
- # 1234567890.50.to_s(:currency) # => $1,234,567,890.50
- # 1234567890.506.to_s(:currency) # => $1,234,567,890.51
- # 1234567890.506.to_s(:currency, precision: 3) # => $1,234,567,890.506
- # 1234567890.506.to_s(:currency, locale: :fr) # => 1 234 567 890,51 €
+ # 1234567890.50.to_s(:currency) # => "$1,234,567,890.50"
+ # 1234567890.506.to_s(:currency) # => "$1,234,567,890.51"
+ # 1234567890.506.to_s(:currency, precision: 3) # => "$1,234,567,890.506"
+ # 1234567890.506.to_s(:currency, locale: :fr) # => "1 234 567 890,51 €"
# -1234567890.50.to_s(:currency, negative_format: '(%u%n)')
- # # => ($1,234,567,890.50)
+ # # => "($1,234,567,890.50)"
# 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '')
- # # => &pound;1234567890,50
+ # # => "&pound;1234567890,50"
# 1234567890.50.to_s(:currency, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
- # # => 1234567890,50 &pound;
+ # # => "1234567890,50 &pound;"
#
# Percentage:
- # 100.to_s(:percentage) # => 100.000%
- # 100.to_s(:percentage, precision: 0) # => 100%
- # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => 1.000,000%
- # 302.24398923423.to_s(:percentage, precision: 5) # => 302.24399%
- # 1000.to_s(:percentage, locale: :fr) # => 1 000,000%
- # 100.to_s(:percentage, format: '%n %') # => 100.000 %
+ # 100.to_s(:percentage) # => "100.000%"
+ # 100.to_s(:percentage, precision: 0) # => "100%"
+ # 1000.to_s(:percentage, delimiter: '.', separator: ',') # => "1.000,000%"
+ # 302.24398923423.to_s(:percentage, precision: 5) # => "302.24399%"
+ # 1000.to_s(:percentage, locale: :fr) # => "1 000,000%"
+ # 100.to_s(:percentage, format: '%n %') # => "100.000 %"
#
# Delimited:
- # 12345678.to_s(:delimited) # => 12,345,678
- # 12345678.05.to_s(:delimited) # => 12,345,678.05
- # 12345678.to_s(:delimited, delimiter: '.') # => 12.345.678
- # 12345678.to_s(:delimited, delimiter: ',') # => 12,345,678
- # 12345678.05.to_s(:delimited, separator: ' ') # => 12,345,678 05
- # 12345678.05.to_s(:delimited, locale: :fr) # => 12 345 678,05
+ # 12345678.to_s(:delimited) # => "12,345,678"
+ # 12345678.05.to_s(:delimited) # => "12,345,678.05"
+ # 12345678.to_s(:delimited, delimiter: '.') # => "12.345.678"
+ # 12345678.to_s(:delimited, delimiter: ',') # => "12,345,678"
+ # 12345678.05.to_s(:delimited, separator: ' ') # => "12,345,678 05"
+ # 12345678.05.to_s(:delimited, locale: :fr) # => "12 345 678,05"
# 98765432.98.to_s(:delimited, delimiter: ' ', separator: ',')
- # # => 98 765 432,98
+ # # => "98 765 432,98"
#
# Rounded:
- # 111.2345.to_s(:rounded) # => 111.235
- # 111.2345.to_s(:rounded, precision: 2) # => 111.23
- # 13.to_s(:rounded, precision: 5) # => 13.00000
- # 389.32314.to_s(:rounded, precision: 0) # => 389
- # 111.2345.to_s(:rounded, significant: true) # => 111
- # 111.2345.to_s(:rounded, precision: 1, significant: true) # => 100
- # 13.to_s(:rounded, precision: 5, significant: true) # => 13.000
- # 111.234.to_s(:rounded, locale: :fr) # => 111,234
+ # 111.2345.to_s(:rounded) # => "111.235"
+ # 111.2345.to_s(:rounded, precision: 2) # => "111.23"
+ # 13.to_s(:rounded, precision: 5) # => "13.00000"
+ # 389.32314.to_s(:rounded, precision: 0) # => "389"
+ # 111.2345.to_s(:rounded, significant: true) # => "111"
+ # 111.2345.to_s(:rounded, precision: 1, significant: true) # => "100"
+ # 13.to_s(:rounded, precision: 5, significant: true) # => "13.000"
+ # 111.234.to_s(:rounded, locale: :fr) # => "111,234"
# 13.to_s(:rounded, precision: 5, significant: true, strip_insignificant_zeros: true)
- # # => 13
- # 389.32314.to_s(:rounded, precision: 4, significant: true) # => 389.3
+ # # => "13"
+ # 389.32314.to_s(:rounded, precision: 4, significant: true) # => "389.3"
# 1111.2345.to_s(:rounded, precision: 2, separator: ',', delimiter: '.')
- # # => 1.111,23
+ # # => "1.111,23"
#
# Human-friendly size in Bytes:
- # 123.to_s(:human_size) # => 123 Bytes
- # 1234.to_s(:human_size) # => 1.21 KB
- # 12345.to_s(:human_size) # => 12.1 KB
- # 1234567.to_s(:human_size) # => 1.18 MB
- # 1234567890.to_s(:human_size) # => 1.15 GB
- # 1234567890123.to_s(:human_size) # => 1.12 TB
- # 1234567.to_s(:human_size, precision: 2) # => 1.2 MB
- # 483989.to_s(:human_size, precision: 2) # => 470 KB
- # 1234567.to_s(:human_size, precision: 2, separator: ',') # => 1,2 MB
+ # 123.to_s(:human_size) # => "123 Bytes"
+ # 1234.to_s(:human_size) # => "1.21 KB"
+ # 12345.to_s(:human_size) # => "12.1 KB"
+ # 1234567.to_s(:human_size) # => "1.18 MB"
+ # 1234567890.to_s(:human_size) # => "1.15 GB"
+ # 1234567890123.to_s(:human_size) # => "1.12 TB"
+ # 1234567890123456.to_s(:human_size) # => "1.1 PB"
+ # 1234567890123456789.to_s(:human_size) # => "1.07 EB"
+ # 1234567.to_s(:human_size, precision: 2) # => "1.2 MB"
+ # 483989.to_s(:human_size, precision: 2) # => "470 KB"
+ # 1234567.to_s(:human_size, precision: 2, separator: ',') # => "1,2 MB"
# 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
# 524288000.to_s(:human_size, precision: 5) # => "500 MB"
#
@@ -117,7 +120,11 @@ module ActiveSupport::NumericWithFormat
when :human_size
return ActiveSupport::NumberHelper.number_to_human_size(self, options)
else
- super
+ if is_a?(Float) || format.is_a?(Symbol)
+ super()
+ else
+ super
+ end
end
end
@@ -127,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/range/conversions.rb b/activesupport/lib/active_support/core_ext/range/conversions.rb
index 83eced50bf..965436c23a 100644
--- a/activesupport/lib/active_support/core_ext/range/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/range/conversions.rb
@@ -1,34 +1,31 @@
-class Range
+module ActiveSupport::RangeWithFormat
RANGE_FORMATS = {
:db => Proc.new { |start, stop| "BETWEEN '#{start.to_s(:db)}' AND '#{stop.to_s(:db)}'" }
}
# Convert range to a formatted string. See RANGE_FORMATS for predefined formats.
#
- # This method is aliased to <tt>to_s</tt>.
- #
# range = (1..100) # => 1..100
#
- # range.to_formatted_s # => "1..100"
# range.to_s # => "1..100"
- #
- # range.to_formatted_s(:db) # => "BETWEEN '1' AND '100'"
# range.to_s(:db) # => "BETWEEN '1' AND '100'"
#
- # == Adding your own range formats to to_formatted_s
+ # == Adding your own range formats to to_s
# You can add your own formats to the Range::RANGE_FORMATS hash.
# Use the format name as the hash key and a Proc instance.
#
# # config/initializers/range_formats.rb
# Range::RANGE_FORMATS[:short] = ->(start, stop) { "Between #{start.to_s(:db)} and #{stop.to_s(:db)}" }
- def to_formatted_s(format = :default)
+ def to_s(format = :default)
if formatter = RANGE_FORMATS[format]
formatter.call(first, last)
else
- to_default_s
+ super()
end
end
alias_method :to_default_s, :to_s
- alias_method :to_s, :to_formatted_s
+ alias_method :to_formatted_s, :to_s
end
+
+Range.prepend(ActiveSupport::RangeWithFormat)
diff --git a/activesupport/lib/active_support/core_ext/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 510fa48189..005ad93b08 100644
--- a/activesupport/lib/active_support/core_ext/string/output_safety.rb
+++ b/activesupport/lib/active_support/core_ext/string/output_safety.rb
@@ -5,7 +5,6 @@ class ERB
module Util
HTML_ESCAPE = { '&' => '&amp;', '>' => '&gt;', '<' => '&lt;', '"' => '&quot;', "'" => '&#39;' }
JSON_ESCAPE = { '&' => '\u0026', '>' => '\u003e', '<' => '\u003c', "\u2028" => '\u2028', "\u2029" => '\u2029' }
- HTML_ESCAPE_REGEXP = /[&"'><]/
HTML_ESCAPE_ONCE_REGEXP = /["><']|&(?!([a-zA-Z]+|(#\d+)|(#[xX][\dA-Fa-f]+));)/
JSON_ESCAPE_REGEXP = /[\u2028\u2029&><]/u
@@ -37,7 +36,7 @@ class ERB
if s.html_safe?
s
else
- ActiveSupport::Multibyte::Unicode.tidy_bytes(s).gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
+ CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
end
end
module_function :unwrapped_html_escape
@@ -142,6 +141,7 @@ module ActiveSupport #:nodoc:
alias_method :original_concat, :concat
private :original_concat
+ # Raised when <tt>ActiveSupport::SafeBuffer#safe_concat</tt> is called on unsafe buffers.
class SafeConcatError < StandardError
def initialize
super 'Could not concatenate to the buffer because it is not html safe.'
@@ -171,7 +171,7 @@ module ActiveSupport #:nodoc:
original_concat(value)
end
- def initialize(*)
+ def initialize(str = '')
@html_safe = true
super
end
@@ -243,15 +243,14 @@ module ActiveSupport #:nodoc:
private
def html_escape_interpolated_argument(arg)
- (!html_safe? || arg.html_safe?) ? arg :
- arg.to_s.gsub(ERB::Util::HTML_ESCAPE_REGEXP, ERB::Util::HTML_ESCAPE)
+ (!html_safe? || arg.html_safe?) ? arg : CGI.escapeHTML(arg.to_s)
end
end
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 82e003fc3b..e81b48ab26 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -26,6 +26,12 @@ class Time
end
end
+ # Returns the number of days in the given year.
+ # If no year is specified, it will use the current year.
+ def days_in_year(year = current.year)
+ days_in_month(2, year) + 337
+ end
+
# Returns <tt>Time.zone.now</tt> when <tt>Time.zone</tt> or <tt>config.time_zone</tt> are set, otherwise just returns <tt>Time.now</tt>.
def current
::Time.zone ? ::Time.zone.now : ::Time.now
@@ -67,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
@@ -156,7 +169,6 @@ class Time
# Returns a new Time representing the start of the day (0:00)
def beginning_of_day
- #(self - seconds_since_midnight).change(usec: 0)
change(:hour => 0)
end
alias :midnight :beginning_of_day
@@ -215,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/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index 877dc84ec8..7a60f94996 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -40,7 +40,23 @@ class Time
Thread.current[:time_zone] = find_zone!(time_zone)
end
- # Allows override of <tt>Time.zone</tt> locally inside supplied block; resets <tt>Time.zone</tt> to existing value when done.
+ # Allows override of <tt>Time.zone</tt> locally inside supplied block;
+ # resets <tt>Time.zone</tt> to existing value when done.
+ #
+ # class ApplicationController < ActionController::Base
+ # around_action :set_time_zone
+ #
+ # private
+ #
+ # def set_time_zone
+ # Time.use_zone(current_user.timezone) { yield }
+ # end
+ # end
+ #
+ # NOTE: This won't affect any <tt>ActiveSupport::TimeWithZone</tt>
+ # objects that have already been created, e.g. any model timestamp
+ # attributes that have been read before the block will remain in
+ # the application's default timezone.
def use_zone(time_zone)
new_zone = find_zone!(time_zone)
begin
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index af18ff746f..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
@@ -143,11 +134,11 @@ module ActiveSupport #:nodoc:
next unless mod.is_a?(Module)
# Get a list of the constants that were added
- new_constants = mod.local_constants - original_constants
+ new_constants = mod.constants(false) - original_constants
- # self[namespace] returns an Array of the constants that are being evaluated
+ # @stack[namespace] returns an Array of the constants that are being evaluated
# for that namespace. For instance, if parent.rb requires child.rb, the first
- # element of self[Object] will be an Array of the constants that were present
+ # element of @stack[Object] will be an Array of the constants that were present
# before parent.rb was required. The second element will be an Array of the
# constants that were present before child.rb was required.
@stack[namespace].each do |namespace_constants|
@@ -171,7 +162,7 @@ module ActiveSupport #:nodoc:
@watching << namespaces.map do |namespace|
module_name = Dependencies.to_constant_name(namespace)
original_constants = Dependencies.qualified_const_defined?(module_name) ?
- Inflector.constantize(module_name).local_constants : []
+ Inflector.constantize(module_name).constants(false) : []
@stack[module_name] << original_constants
module_name
@@ -262,7 +253,7 @@ module ActiveSupport #:nodoc:
end
def load_dependency(file)
- if Dependencies.load? && ActiveSupport::Dependencies.constant_watch_stack.watching?
+ if Dependencies.load? && Dependencies.constant_watch_stack.watching?
Dependencies.new_constants_in(Object) { yield }
else
yield
@@ -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/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
index fbeb904684..f1865ca2f8 100644
--- a/activesupport/lib/active_support/dependencies/interlock.rb
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -8,25 +8,23 @@ module ActiveSupport #:nodoc:
end
def loading
- @lock.exclusive(purpose: :load, compatible: [:load]) do
+ @lock.exclusive(purpose: :load, compatible: [:load], after_compatible: [:load]) do
yield
end
end
def unloading
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload], after_compatible: [:load, :unload]) do
yield
end
end
- # Attempt to obtain an "unloading" (exclusive) lock. If possible,
- # execute the supplied block while holding the lock. If there is
- # concurrent activity, return immediately (without executing the
- # block) instead of waiting.
- def attempt_unloading
- @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do
- yield
- end
+ def start_unloading
+ @lock.start_exclusive(purpose: :unload, compatible: [:load, :unload])
+ end
+
+ def done_unloading
+ @lock.stop_exclusive(compatible: [:load, :unload])
end
def start_running
@@ -42,6 +40,12 @@ module ActiveSupport #:nodoc:
yield
end
end
+
+ def permit_concurrent_loads
+ @lock.yield_shares(compatible: [:load]) do
+ yield
+ end
+ end
end
end
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 46e9996d59..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.0', 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/behaviors.rb b/activesupport/lib/active_support/deprecation/behaviors.rb
index 28d2d78643..dc24e2d0e1 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -1,6 +1,8 @@
require "active_support/notifications"
module ActiveSupport
+ # Raised when <tt>ActiveSupport::Deprecation::Behavior#behavior</tt> is set with <tt>:raise</tt>.
+ # You would set <tt>:raise</tt>, as a behaviour to raise errors and proactively report exceptions from deprecations.
class DeprecationException < StandardError
end
@@ -9,7 +11,7 @@ module ActiveSupport
DEFAULT_BEHAVIORS = {
raise: ->(message, callstack) {
e = DeprecationException.new(message)
- e.set_backtrace(callstack)
+ e.set_backtrace(callstack.map(&:to_s))
raise e
},
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 32fe8025fe..f5ea6669ce 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -21,15 +21,15 @@ module ActiveSupport
# # => [:aaa, :bbb, :ccc]
#
# Fred.aaa
- # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.0. (called from irb_binding at (irb):10)
+ # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
# # => nil
#
# Fred.bbb
- # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.0 (use zzz instead). (called from irb_binding at (irb):11)
+ # # DEPRECATION WARNING: bbb is deprecated and will be removed from Rails 5.1 (use zzz instead). (called from irb_binding at (irb):11)
# # => nil
#
# Fred.ccc
- # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.0 (use Bar#ccc instead). (called from irb_binding at (irb):12)
+ # # DEPRECATION WARNING: ccc is deprecated and will be removed from Rails 5.1 (use Bar#ccc instead). (called from irb_binding at (irb):12)
# # => nil
#
# Passing in a custom deprecator:
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index 6f0ad445fc..0cb2d4d22e 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -80,7 +80,7 @@ module ActiveSupport
# example.old_request.to_s
# # => DEPRECATION WARNING: @request is deprecated! Call request.to_s instead of
# @request.to_s
- # (Bactrace information…)
+ # (Backtrace information…)
# "special_request"
#
# example.request.to_s
@@ -118,7 +118,7 @@ module ActiveSupport
#
# PLANETS.map { |planet| planet.capitalize }
# # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
- # (Bactrace information…)
+ # (Backtrace information…)
# ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
class DeprecatedConstantProxy < DeprecationProxy
def initialize(old_const, new_const, deprecator = ActiveSupport::Deprecation.instance)
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index f89fc0fe14..de5b233679 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -1,3 +1,5 @@
+require 'rbconfig'
+
module ActiveSupport
class Deprecation
module Reporting
@@ -63,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
@@ -81,17 +82,17 @@ module ActiveSupport
def extract_callstack(callstack)
return _extract_callstack(callstack) if callstack.first.is_a? String
- rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
offending_line = callstack.find { |frame|
- frame.absolute_path && !frame.absolute_path.start_with?(rails_gem_root)
+ frame.absolute_path && !ignored_callstack(frame.absolute_path)
} || callstack.first
+
[offending_line.path, offending_line.lineno, offending_line.label]
end
def _extract_callstack(callstack)
warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE
- rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
- offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first
+ offending_line = callstack.find { |line| !ignored_callstack(line) } || callstack.first
+
if offending_line
if md = offending_line.match(/^(.+?):(\d+)(?::in `(.*?)')?/)
md.captures
@@ -100,6 +101,12 @@ module ActiveSupport
end
end
end
+
+ RAILS_GEM_ROOT = File.expand_path("../../../../..", __FILE__) + "/"
+
+ def ignored_callstack(path)
+ path.start_with?(RAILS_GEM_ROOT) || path.start_with?(RbConfig::CONFIG['rubylibdir'])
+ end
end
end
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
new file mode 100644
index 0000000000..21fdf7bb80
--- /dev/null
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -0,0 +1,155 @@
+require 'set'
+require 'pathname'
+require 'concurrent/atomic/atomic_boolean'
+
+module ActiveSupport
+ class EventedFileUpdateChecker #:nodoc: all
+ def initialize(files, dirs = {}, &block)
+ @ph = PathHelper.new
+ @files = files.map { |f| @ph.xpath(f) }.to_set
+
+ @dirs = {}
+ dirs.each do |dir, exts|
+ @dirs[@ph.xpath(dir)] = Array(exts).map { |ext| @ph.normalize_extension(ext) }
+ end
+
+ @block = block
+ @updated = Concurrent::AtomicBoolean.new(false)
+ @lcsp = @ph.longest_common_subpath(@dirs.keys)
+
+ if (dtw = directories_to_watch).any?
+ # Loading listen triggers warnings. These are originated by a legit
+ # usage of attr_* macros for private attributes, but adds a lot of noise
+ # to our test suite. Thus, we lazy load it and disable warnings locally.
+ silence_warnings do
+ begin
+ require 'listen'
+ rescue LoadError => e
+ raise LoadError, "Could not load the 'listen' gem. Add `gem 'listen'` to the development group of your Gemfile", e.backtrace
+ end
+ end
+ Listen.to(*dtw, &method(:changed)).start
+ end
+ end
+
+ def updated?
+ @updated.true?
+ end
+
+ def execute
+ @updated.make_false
+ @block.call
+ end
+
+ def execute_if_updated
+ if updated?
+ yield if block_given?
+ execute
+ true
+ end
+ end
+
+ private
+
+ def changed(modified, added, removed)
+ unless updated?
+ @updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
+ end
+ end
+
+ def watching?(file)
+ file = @ph.xpath(file)
+
+ if @files.member?(file)
+ true
+ elsif file.directory?
+ false
+ else
+ ext = @ph.normalize_extension(file.extname)
+
+ file.dirname.ascend do |dir|
+ if @dirs.fetch(dir, []).include?(ext)
+ break true
+ elsif dir == @lcsp || dir.root?
+ break false
+ end
+ end
+ end
+ end
+
+ def directories_to_watch
+ dtw = (@files + @dirs.keys).map { |f| @ph.existing_parent(f) }
+ dtw.compact!
+ dtw.uniq!
+
+ @ph.filter_out_descendants(dtw)
+ end
+
+ class PathHelper
+ def xpath(path)
+ Pathname.new(path).expand_path
+ end
+
+ def normalize_extension(ext)
+ ext.to_s.sub(/\A\./, '')
+ end
+
+ # Given a collection of Pathname objects returns the longest subpath
+ # common to all of them, or +nil+ if there is none.
+ def longest_common_subpath(paths)
+ return if paths.empty?
+
+ lcsp = Pathname.new(paths[0])
+
+ paths[1..-1].each do |path|
+ until 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
+ # Windows.
+ return
+ else
+ lcsp = lcsp.parent
+ end
+ end
+ end
+
+ lcsp
+ end
+
+ # Returns the deepest existing ascendant, which could be the argument itself.
+ def existing_parent(dir)
+ dir.ascend do |ascendant|
+ break ascendant if ascendant.directory?
+ end
+ end
+
+ # Filters out directories which are descendants of others in the collection (stable).
+ def filter_out_descendants(dirs)
+ return dirs if dirs.length < 2
+
+ dirs_sorted_by_nparts = dirs.sort_by { |dir| dir.each_filename.to_a.length }
+ descendants = []
+
+ until dirs_sorted_by_nparts.empty?
+ dir = dirs_sorted_by_nparts.shift
+
+ dirs_sorted_by_nparts.reject! do |possible_descendant|
+ 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
new file mode 100644
index 0000000000..00c5745a25
--- /dev/null
+++ b/activesupport/lib/active_support/execution_wrapper.rb
@@ -0,0 +1,117 @@
+require 'active_support/callbacks'
+
+module ActiveSupport
+ class ExecutionWrapper
+ include ActiveSupport::Callbacks
+
+ Null = Object.new # :nodoc:
+ def Null.complete! # :nodoc:
+ end
+
+ define_callbacks :run
+ define_callbacks :complete
+
+ def self.to_run(*args, &block)
+ set_callback(:run, *args, &block)
+ end
+
+ def self.to_complete(*args, &block)
+ 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
+ # after the work has been performed.
+ #
+ # Where possible, prefer +wrap+.
+ def self.run!
+ if active?
+ Null
+ else
+ new.tap do |instance|
+ success = nil
+ begin
+ instance.run!
+ success = true
+ ensure
+ instance.complete! unless success
+ end
+ end
+ end
+ end
+
+ # Perform the work in the supplied block as an execution.
+ def self.wrap
+ return yield if active?
+
+ instance = run!
+ begin
+ yield
+ ensure
+ instance.complete!
+ end
+ end
+
+ class << self # :nodoc:
+ attr_accessor :active
+ end
+
+ def self.inherited(other) # :nodoc:
+ super
+ other.active = Concurrent::Hash.new
+ end
+
+ self.active = Concurrent::Hash.new
+
+ def self.active? # :nodoc:
+ @active[Thread.current]
+ end
+
+ def run! # :nodoc:
+ self.class.active[Thread.current] = true
+ run_callbacks(:run)
+ end
+
+ # Complete this in-flight execution. This method *must* be called
+ # exactly once on the result of any call to +run!+.
+ #
+ # Where possible, prefer +wrap+.
+ def complete!
+ run_callbacks(:complete)
+ ensure
+ self.class.active.delete Thread.current
+ end
+
+ private
+ def hook_state
+ @_hook_state ||= {}
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/executor.rb b/activesupport/lib/active_support/executor.rb
new file mode 100644
index 0000000000..602fb11a44
--- /dev/null
+++ b/activesupport/lib/active_support/executor.rb
@@ -0,0 +1,6 @@
+require 'active_support/execution_wrapper'
+
+module ActiveSupport
+ class Executor < ExecutionWrapper
+ end
+end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 78b627c286..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:
@@ -23,7 +25,7 @@ module ActiveSupport
# I18n.reload!
# end
#
- # ActionDispatch::Reloader.to_prepare do
+ # ActiveSupport::Reloader.to_prepare do
# i18n_reloader.execute_if_updated
# end
class FileUpdateChecker
@@ -35,7 +37,7 @@ module ActiveSupport
# This method must also receive a block that will be called once a path
# changes. The array of files and list of directories cannot be changed
# after FileUpdateChecker has been initialized.
- def initialize(files, dirs={}, &block)
+ def initialize(files, dirs = {}, &block)
@files = files.freeze
@glob = compile_glob(dirs)
@block = block
@@ -81,6 +83,7 @@ module ActiveSupport
# Execute the block given if updated.
def execute_if_updated
if updated?
+ yield if block_given?
execute
true
else
@@ -111,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 ece68bbcb6..74f2d8dd4b 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -6,7 +6,7 @@ module ActiveSupport
module VERSION
MAJOR = 5
- MINOR = 0
+ MINOR = 1
TINY = 0
PRE = "alpha"
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 4ff35a45a1..03770a197c 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -68,8 +68,10 @@ module ActiveSupport
end
end
- def default(key = nil)
- if key.is_a?(Symbol) && include?(key = key.to_s)
+ def default(*args)
+ arg_key = args.first
+
+ if include?(key = convert_key(arg_key))
self[key]
else
super
@@ -159,6 +161,20 @@ module ActiveSupport
alias_method :has_key?, :key?
alias_method :member?, :key?
+
+ # Same as <tt>Hash#[]</tt> where the key passed as argument can be
+ # either a string or a symbol:
+ #
+ # counters = ActiveSupport::HashWithIndifferentAccess.new
+ # counters[:foo] = 1
+ #
+ # counters['foo'] # => 1
+ # counters[:foo] # => 1
+ # counters[:zoo] # => nil
+ def [](key)
+ super(convert_key(key))
+ end
+
# Same as <tt>Hash#fetch</tt> where the key passed as argument can be
# either a string or a symbol:
#
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 6775eec34b..6cc7c90c12 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -56,7 +56,7 @@ module I18n
I18n.enforce_available_locales = enforce_available_locales
directories = watched_dirs_with_extensions(reloadable_paths)
- reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup, directories) do
+ reloader = app.config.file_watcher.new(I18n.load_path.dup, directories) do
I18n.load_path.keep_if { |p| File.exist?(p) }
I18n.load_path |= reloadable_paths.map(&:existent).flatten
@@ -64,8 +64,8 @@ module I18n
end
app.reloaders << reloader
- ActionDispatch::Reloader.to_prepare do
- reloader.execute_if_updated
+ app.reloader.to_run do
+ reloader.execute_if_updated { require_unload_lock! }
# TODO: remove the following line as soon as the return value of
# callbacks is ignored, that is, returning `false` does not
# display a deprecation warning or halts the callback chain.
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 595b0339cc..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.
@@ -173,7 +182,7 @@ module ActiveSupport
#
# Singular names are not handled correctly:
#
- # classify('calculus') # => "Calculu"
+ # classify('calculus') # => "Calculus"
def classify(table_name)
# strip out any leading schema name
camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
diff --git a/activesupport/lib/active_support/locale/en.yml b/activesupport/lib/active_support/locale/en.yml
index a4563ace8f..c64b7598ee 100644
--- a/activesupport/lib/active_support/locale/en.yml
+++ b/activesupport/lib/active_support/locale/en.yml
@@ -106,6 +106,8 @@ en:
mb: "MB"
gb: "GB"
tb: "TB"
+ pb: "PB"
+ eb: "EB"
# Used in NumberHelper.number_to_human()
decimal_units:
format: "%n %u"
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 33fccdcf95..de48e717b6 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,11 +1,23 @@
-require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/logger_silence'
+require 'active_support/logger_thread_safe_level'
require 'logger'
module ActiveSupport
class Logger < ::Logger
+ include ActiveSupport::LoggerThreadSafeLevel
include LoggerSilence
+ # Returns true if the logger destination matches one of the sources
+ #
+ # logger = Logger.new(STDOUT)
+ # ActiveSupport::Logger.logger_outputs_to?(logger, STDOUT)
+ # # => true
+ def self.logger_outputs_to?(logger, *sources)
+ logdev = logger.instance_variable_get("@logdev")
+ logger_source = logdev.dev if logdev.respond_to?(:dev)
+ sources.any? { |source| source == logger_source }
+ end
+
# Broadcasts logs to multiple loggers.
def self.broadcast(logger) # :nodoc:
Module.new do
@@ -38,12 +50,31 @@ module ActiveSupport
logger.level = level
super(level)
end
+
+ define_method(:local_level=) do |level|
+ logger.local_level = level if logger.respond_to?(:local_level=)
+ super(level) if respond_to?(:local_level=)
+ end
end
end
def initialize(*args)
super
@formatter = SimpleFormatter.new
+ after_initialize if respond_to? :after_initialize
+ end
+
+ def add(severity, message = nil, progname = nil, &block)
+ return true if @logdev.nil? || (severity || UNKNOWN) < level
+ super
+ end
+
+ Logger::Severity.constants.each do |severity|
+ class_eval(<<-EOT, __FILE__, __LINE__ + 1)
+ def #{severity.downcase}? # def debug?
+ Logger::#{severity} >= level # DEBUG >= level
+ end # end
+ EOT
end
# Simple formatter which only displays the message.
diff --git a/activesupport/lib/active_support/logger_silence.rb b/activesupport/lib/active_support/logger_silence.rb
index a8efdef944..3eb8098c77 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -1,8 +1,10 @@
require 'active_support/concern'
+require 'active_support/core_ext/module/attribute_accessors'
+require 'concurrent'
module LoggerSilence
extend ActiveSupport::Concern
-
+
included do
cattr_accessor :silencer
self.silencer = true
@@ -12,13 +14,15 @@ module LoggerSilence
def silence(temporary_level = Logger::ERROR)
if silencer
begin
- old_logger_level, self.level = level, temporary_level
+ old_local_level = local_level
+ self.local_level = temporary_level
+
yield self
ensure
- self.level = old_logger_level
+ self.local_level = old_local_level
end
else
yield self
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb
new file mode 100644
index 0000000000..5fedb5e689
--- /dev/null
+++ b/activesupport/lib/active_support/logger_thread_safe_level.rb
@@ -0,0 +1,31 @@
+require 'active_support/concern'
+
+module ActiveSupport
+ module LoggerThreadSafeLevel # :nodoc:
+ extend ActiveSupport::Concern
+
+ def after_initialize
+ @local_levels = Concurrent::Map.new(initial_capacity: 2)
+ end
+
+ def local_log_id
+ Thread.current.__id__
+ end
+
+ def local_level
+ @local_levels[local_log_id]
+ end
+
+ def local_level=(level)
+ if level
+ @local_levels[local_log_id] = level
+ else
+ @local_levels.delete(local_log_id)
+ end
+ end
+
+ def level
+ local_level || super
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index c82a13511e..2dde01c844 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -34,8 +34,8 @@ module ActiveSupport
# Initialize a new MessageEncryptor. +secret+ must be at least as long as
# the cipher key size. For the default 'aes-256-cbc' cipher, this is 256
# bits. If you are using a user-entered secret, you can generate a suitable
- # key with <tt>OpenSSL::Digest::SHA256.new(user_secret).digest</tt> or
- # similar.
+ # key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
+ # derivation function.
#
# Options:
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index 64c5232cf4..4c3deffe6e 100644
--- a/activesupport/lib/active_support/message_verifier.rb
+++ b/activesupport/lib/active_support/message_verifier.rb
@@ -15,7 +15,7 @@ module ActiveSupport
# In the authentication filter:
#
# id, time = @verifier.verify(cookies[:remember_me])
- # if time < Time.now
+ # if Time.now < time
# self.current_user = User.find(id)
# end
#
@@ -24,6 +24,12 @@ module ActiveSupport
# hash upon initialization:
#
# @verifier = ActiveSupport::MessageVerifier.new('s3Krit', serializer: YAML)
+ #
+ # +MessageVerifier+ creates HMAC signatures using SHA1 hash algorithm by default.
+ # If you want to use a different hash algorithm, you can change it by providing
+ # `:digest` key as an option while initializing the verifier:
+ #
+ # @verifier = ActiveSupport::MessageVerifier.new('s3Krit', digest: 'SHA256')
class MessageVerifier
class InvalidSignature < StandardError; end
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 586002b03b..72b20fff06 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -87,19 +87,44 @@ module ActiveSupport
pos += 1
previous = codepoints[pos-1]
current = codepoints[pos]
- if (
- # CR X LF
- ( previous == database.boundary[:cr] and current == database.boundary[:lf] ) or
- # L X (L|V|LV|LVT)
- ( database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt]) ) or
- # (LV|V) X (V|T)
- ( in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t]) ) or
- # (LVT|T) X (T)
- ( in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current ) or
- # X Extend
- (database.boundary[:extend] === current)
- )
- else
+
+ should_break =
+ # GB3. CR X LF
+ if previous == database.boundary[:cr] and current == database.boundary[:lf]
+ false
+ # GB4. (Control|CR|LF) ÷
+ elsif previous and in_char_class?(previous, [:control,:cr,:lf])
+ true
+ # GB5. ÷ (Control|CR|LF)
+ elsif in_char_class?(current, [:control,:cr,:lf])
+ true
+ # GB6. L X (L|V|LV|LVT)
+ elsif database.boundary[:l] === previous and in_char_class?(current, [:l,:v,:lv,:lvt])
+ false
+ # GB7. (LV|V) X (V|T)
+ elsif in_char_class?(previous, [:lv,:v]) and in_char_class?(current, [:v,:t])
+ false
+ # GB8. (LVT|T) X (T)
+ elsif in_char_class?(previous, [:lvt,:t]) and database.boundary[:t] === current
+ false
+ # GB8a. Regional_Indicator X Regional_Indicator
+ elsif database.boundary[:regional_indicator] === previous and database.boundary[:regional_indicator] === current
+ false
+ # GB9. X Extend
+ elsif database.boundary[:extend] === current
+ false
+ # GB9a. X SpacingMark
+ elsif database.boundary[:spacingmark] === current
+ false
+ # GB9b. Prepend X
+ elsif database.boundary[:prepend] === previous
+ false
+ # GB10. Any ÷ Any
+ else
+ true
+ end
+
+ if should_break
unpacked << codepoints[marker..pos-1]
marker = pos
end
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 7798c7ec60..c53f9c1039 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -42,8 +42,8 @@ module ActiveSupport
listeners_for(name).each { |s| s.start(name, id, payload) }
end
- def finish(name, id, payload)
- listeners_for(name).each { |s| s.finish(name, id, payload) }
+ def finish(name, id, payload, listeners = listeners_for(name))
+ listeners.each { |s| s.finish(name, id, payload) }
end
def publish(name, *args)
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 075ddc2382..91f94cb2d7 100644
--- a/activesupport/lib/active_support/notifications/instrumenter.rb
+++ b/activesupport/lib/active_support/notifications/instrumenter.rb
@@ -15,14 +15,16 @@ module ActiveSupport
# and publish it. Notice that events get sent even if an error occurs
# in the passed-in block.
def instrument(name, payload={})
- start name, payload
+ # some of the listeners might have state
+ listeners_state = start name, payload
begin
yield payload
rescue Exception => e
payload[:exception] = [e.class.name, e.message]
+ payload[:exception_object] = e
raise e
ensure
- finish name, payload
+ finish_with_state listeners_state, name, payload
end
end
@@ -36,6 +38,10 @@ module ActiveSupport
@notifier.finish name, @id, payload
end
+ def finish_with_state(listeners_state, name, payload)
+ @notifier.finish name, @id, payload, listeners_state
+ end
+
private
def unique_id
diff --git a/activesupport/lib/active_support/number_helper.rb b/activesupport/lib/active_support/number_helper.rb
index 248521e677..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,19 +27,26 @@ 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
- # number_to_phone('5551234') # => 555-1234
- # number_to_phone(1235551234) # => 123-555-1234
- # number_to_phone(1235551234, area_code: true) # => (123) 555-1234
- # number_to_phone(1235551234, delimiter: ' ') # => 123 555 1234
- # number_to_phone(1235551234, area_code: true, extension: 555) # => (123) 555-1234 x 555
- # number_to_phone(1235551234, country_code: 1) # => +1-123-555-1234
- # number_to_phone('123a456') # => 123a456
+ # number_to_phone(5551234) # => "555-1234"
+ # number_to_phone('5551234') # => "555-1234"
+ # number_to_phone(1235551234) # => "123-555-1234"
+ # number_to_phone(1235551234, area_code: true) # => "(123) 555-1234"
+ # number_to_phone(1235551234, delimiter: ' ') # => "123 555 1234"
+ # number_to_phone(1235551234, area_code: true, extension: 555) # => "(123) 555-1234 x 555"
+ # number_to_phone(1235551234, country_code: 1) # => "+1-123-555-1234"
+ # number_to_phone('123a456') # => "123a456"
#
# number_to_phone(1235551234, country_code: 1, extension: 1343, delimiter: '.')
- # # => +1.123.555.1234 x 1343
+ # # => "+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
@@ -47,6 +54,14 @@ module ActiveSupport
# Formats a +number+ into a currency string (e.g., $13.65). You
# can customize the format in the +options+ hash.
#
+ # The currency unit and number formatting of the current locale will be used
+ # unless otherwise specified in the provided options. No currency conversion
+ # is performed. If the user is given a way to change their locale, they will
+ # also be able to change the relative value of the currency displayed with
+ # this helper. If your application will ever support multiple locales, you
+ # may want to specify a constant <tt>:locale</tt> option or consider
+ # using a library capable of currency conversion.
+ #
# ==== Options
#
# * <tt>:locale</tt> - Sets the locale to be used for formatting
@@ -70,18 +85,18 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_currency(1234567890.50) # => $1,234,567,890.50
- # number_to_currency(1234567890.506) # => $1,234,567,890.51
- # number_to_currency(1234567890.506, precision: 3) # => $1,234,567,890.506
- # number_to_currency(1234567890.506, locale: :fr) # => 1 234 567 890,51 €
- # number_to_currency('123a456') # => $123a456
+ # number_to_currency(1234567890.50) # => "$1,234,567,890.50"
+ # number_to_currency(1234567890.506) # => "$1,234,567,890.51"
+ # number_to_currency(1234567890.506, precision: 3) # => "$1,234,567,890.506"
+ # number_to_currency(1234567890.506, locale: :fr) # => "1 234 567 890,51 €"
+ # number_to_currency('123a456') # => "$123a456"
#
# number_to_currency(-1234567890.50, negative_format: '(%u%n)')
- # # => ($1,234,567,890.50)
+ # # => "($1,234,567,890.50)"
# number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '')
- # # => &pound;1234567890,50
+ # # => "&pound;1234567890,50"
# number_to_currency(1234567890.50, unit: '&pound;', separator: ',', delimiter: '', format: '%n %u')
- # # => 1234567890,50 &pound;
+ # # => "1234567890,50 &pound;"
def number_to_currency(number, options = {})
NumberToCurrencyConverter.convert(number, options)
end
@@ -110,15 +125,15 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_percentage(100) # => 100.000%
- # number_to_percentage('98') # => 98.000%
- # number_to_percentage(100, precision: 0) # => 100%
- # number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
- # number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
- # number_to_percentage(1000, locale: :fr) # => 1000,000%
- # number_to_percentage(1000, precision: nil) # => 1000%
- # number_to_percentage('98a') # => 98a%
- # number_to_percentage(100, format: '%n %') # => 100.000 %
+ # number_to_percentage(100) # => "100.000%"
+ # number_to_percentage('98') # => "98.000%"
+ # number_to_percentage(100, precision: 0) # => "100%"
+ # number_to_percentage(1000, delimiter: '.', separator: ',') # => "1.000,000%"
+ # number_to_percentage(302.24398923423, precision: 5) # => "302.24399%"
+ # number_to_percentage(1000, locale: :fr) # => "1000,000%"
+ # number_to_percentage(1000, precision: nil) # => "1000%"
+ # number_to_percentage('98a') # => "98a%"
+ # number_to_percentage(100, format: '%n %') # => "100.000 %"
def number_to_percentage(number, options = {})
NumberToPercentageConverter.convert(number, options)
end
@@ -141,19 +156,19 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_delimited(12345678) # => 12,345,678
- # number_to_delimited('123456') # => 123,456
- # number_to_delimited(12345678.05) # => 12,345,678.05
- # number_to_delimited(12345678, delimiter: '.') # => 12.345.678
- # number_to_delimited(12345678, delimiter: ',') # => 12,345,678
- # number_to_delimited(12345678.05, separator: ' ') # => 12,345,678 05
- # number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05
- # number_to_delimited('112a') # => 112a
+ # number_to_delimited(12345678) # => "12,345,678"
+ # number_to_delimited('123456') # => "123,456"
+ # number_to_delimited(12345678.05) # => "12,345,678.05"
+ # number_to_delimited(12345678, delimiter: '.') # => "12.345.678"
+ # number_to_delimited(12345678, delimiter: ',') # => "12,345,678"
+ # number_to_delimited(12345678.05, separator: ' ') # => "12,345,678 05"
+ # number_to_delimited(12345678.05, locale: :fr) # => "12 345 678,05"
+ # number_to_delimited('112a') # => "112a"
# number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
- # # => 98 765 432,98
+ # # => "98 765 432,98"
# number_to_delimited("123456.78",
# delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/)
- # # => 1,23,456.78
+ # # => "1,23,456.78"
def number_to_delimited(number, options = {})
NumberToDelimitedConverter.convert(number, options)
end
@@ -182,22 +197,22 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_rounded(111.2345) # => 111.235
- # number_to_rounded(111.2345, precision: 2) # => 111.23
- # number_to_rounded(13, precision: 5) # => 13.00000
- # number_to_rounded(389.32314, precision: 0) # => 389
- # number_to_rounded(111.2345, significant: true) # => 111
- # number_to_rounded(111.2345, precision: 1, significant: true) # => 100
- # number_to_rounded(13, precision: 5, significant: true) # => 13.000
- # number_to_rounded(13, precision: nil) # => 13
- # number_to_rounded(111.234, locale: :fr) # => 111,234
+ # number_to_rounded(111.2345) # => "111.235"
+ # number_to_rounded(111.2345, precision: 2) # => "111.23"
+ # number_to_rounded(13, precision: 5) # => "13.00000"
+ # number_to_rounded(389.32314, precision: 0) # => "389"
+ # number_to_rounded(111.2345, significant: true) # => "111"
+ # number_to_rounded(111.2345, precision: 1, significant: true) # => "100"
+ # number_to_rounded(13, precision: 5, significant: true) # => "13.000"
+ # number_to_rounded(13, precision: nil) # => "13"
+ # number_to_rounded(111.234, locale: :fr) # => "111,234"
#
# number_to_rounded(13, precision: 5, significant: true, strip_insignificant_zeros: true)
- # # => 13
+ # # => "13"
#
- # number_to_rounded(389.32314, precision: 4, significant: true) # => 389.3
+ # number_to_rounded(389.32314, precision: 4, significant: true) # => "389.3"
# number_to_rounded(1111.2345, precision: 2, separator: ',', delimiter: '.')
- # # => 1.111,23
+ # # => "1.111,23"
def number_to_rounded(number, options = {})
NumberToRoundedConverter.convert(number, options)
end
@@ -229,15 +244,17 @@ module ActiveSupport
#
# ==== Examples
#
- # number_to_human_size(123) # => 123 Bytes
- # number_to_human_size(1234) # => 1.21 KB
- # number_to_human_size(12345) # => 12.1 KB
- # number_to_human_size(1234567) # => 1.18 MB
- # number_to_human_size(1234567890) # => 1.15 GB
- # number_to_human_size(1234567890123) # => 1.12 TB
- # number_to_human_size(1234567, precision: 2) # => 1.2 MB
- # number_to_human_size(483989, precision: 2) # => 470 KB
- # number_to_human_size(1234567, precision: 2, separator: ',') # => 1,2 MB
+ # number_to_human_size(123) # => "123 Bytes"
+ # number_to_human_size(1234) # => "1.21 KB"
+ # number_to_human_size(12345) # => "12.1 KB"
+ # number_to_human_size(1234567) # => "1.18 MB"
+ # number_to_human_size(1234567890) # => "1.15 GB"
+ # number_to_human_size(1234567890123) # => "1.12 TB"
+ # number_to_human_size(1234567890123456) # => "1.1 PB"
+ # number_to_human_size(1234567890123456789) # => "1.07 EB"
+ # number_to_human_size(1234567, precision: 2) # => "1.2 MB"
+ # number_to_human_size(483989, precision: 2) # => "470 KB"
+ # number_to_human_size(1234567, precision: 2, separator: ',') # => "1,2 MB"
# number_to_human_size(1234567890123, precision: 5) # => "1.1228 TB"
# number_to_human_size(524288000, precision: 5) # => "500 MB"
def number_to_human_size(number, options = {})
diff --git a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
index 7986eb50f0..57f40f33bf 100644
--- a/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_currency_converter.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/numeric/inquiry'
+
module ActiveSupport
module NumberHelper
class NumberToCurrencyConverter < NumberConverter # :nodoc:
@@ -7,7 +9,7 @@ module ActiveSupport
number = self.number.to_s.strip
format = options[:format]
- if is_negative?(number)
+ if number.to_f.negative?
format = options[:negative_format]
number = absolute_value(number)
end
@@ -18,10 +20,6 @@ module ActiveSupport
private
- def is_negative?(number)
- number.to_f.phase != 0
- end
-
def absolute_value(number)
number.respond_to?(:abs) ? number.abs : number.sub(/\A-/, '')
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_human_size_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
index a4a8690bcd..a83b368b7f 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb
@@ -1,7 +1,7 @@
module ActiveSupport
module NumberHelper
class NumberToHumanSizeConverter < NumberConverter #:nodoc:
- STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb]
+ STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb, :pb, :eb]
self.namespace = :human
self.validate_float = true
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/ordered_hash.rb b/activesupport/lib/active_support/ordered_hash.rb
index 4680d5acb7..b1658f0f27 100644
--- a/activesupport/lib/active_support/ordered_hash.rb
+++ b/activesupport/lib/active_support/ordered_hash.rb
@@ -5,7 +5,7 @@ YAML.add_builtin_type("omap") do |type, val|
end
module ActiveSupport
- # <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
+ # DEPRECATED: <tt>ActiveSupport::OrderedHash</tt> implements a hash that preserves
# insertion order.
#
# oh = ActiveSupport::OrderedHash.new
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index 506dd950cb..88e2b12cc7 100644
--- a/activesupport/lib/active_support/per_thread_registry.rb
+++ b/activesupport/lib/active_support/per_thread_registry.rb
@@ -1,4 +1,9 @@
+require 'active_support/core_ext/module/delegation'
+
module ActiveSupport
+ # NOTE: This approach has been deprecated for end-user code in favor of thread_mattr_accessor and friends.
+ # Please use that approach instead.
+ #
# This module is used to encapsulate access to thread local variables.
#
# Instead of polluting the thread locals namespace:
diff --git a/activesupport/lib/active_support/reloader.rb b/activesupport/lib/active_support/reloader.rb
new file mode 100644
index 0000000000..5623bdd349
--- /dev/null
+++ b/activesupport/lib/active_support/reloader.rb
@@ -0,0 +1,129 @@
+require 'active_support/execution_wrapper'
+
+module ActiveSupport
+ #--
+ # This class defines several callbacks:
+ #
+ # to_prepare -- Run once at application startup, and also from
+ # +to_run+.
+ #
+ # to_run -- Run before a work run that is reloading. If
+ # +reload_classes_only_on_change+ is true (the default), the class
+ # unload will have already occurred.
+ #
+ # to_complete -- Run after a work run that has reloaded. If
+ # +reload_classes_only_on_change+ is false, the class unload will
+ # have occurred after the work run, but before this callback.
+ #
+ # before_class_unload -- Run immediately before the classes are
+ # unloaded.
+ #
+ # after_class_unload -- Run immediately after the classes are
+ # unloaded.
+ #
+ class Reloader < ExecutionWrapper
+ define_callbacks :prepare
+
+ define_callbacks :class_unload
+
+ def self.to_prepare(*args, &block)
+ set_callback(:prepare, *args, &block)
+ end
+
+ def self.before_class_unload(*args, &block)
+ set_callback(:class_unload, *args, &block)
+ end
+
+ def self.after_class_unload(*args, &block)
+ set_callback(:class_unload, :after, *args, &block)
+ end
+
+ to_run(:after) { self.class.prepare! }
+
+ # Initiate a manual reload
+ def self.reload!
+ executor.wrap do
+ new.tap do |instance|
+ begin
+ instance.run!
+ ensure
+ instance.complete!
+ end
+ end
+ end
+ prepare!
+ end
+
+ def self.run! # :nodoc:
+ if check!
+ super
+ else
+ Null
+ end
+ end
+
+ # Run the supplied block as a work unit, reloading code as needed
+ def self.wrap
+ executor.wrap do
+ super
+ end
+ end
+
+ class_attribute :executor
+ class_attribute :check
+
+ self.executor = Executor
+ self.check = lambda { false }
+
+ def self.check! # :nodoc:
+ @should_reload ||= check.call
+ end
+
+ def self.reloaded! # :nodoc:
+ @should_reload = false
+ end
+
+ def self.prepare! # :nodoc:
+ new.run_callbacks(:prepare)
+ end
+
+ def initialize
+ super
+ @locked = false
+ end
+
+ # Acquire the ActiveSupport::Dependencies::Interlock unload lock,
+ # ensuring it will be released automatically
+ def require_unload_lock!
+ unless @locked
+ ActiveSupport::Dependencies.interlock.start_unloading
+ @locked = true
+ end
+ end
+
+ # Release the unload lock if it has been previously obtained
+ def release_unload_lock!
+ if @locked
+ @locked = false
+ ActiveSupport::Dependencies.interlock.done_unloading
+ end
+ end
+
+ def run! # :nodoc:
+ super
+ release_unload_lock!
+ end
+
+ def class_unload!(&block) # :nodoc:
+ require_unload_lock!
+ run_callbacks(:class_unload, &block)
+ end
+
+ def complete! # :nodoc:
+ super
+ self.class.reloaded!
+ ensure
+ release_unload_lock!
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/rescuable.rb b/activesupport/lib/active_support/rescuable.rb
index fcf5553061..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,55 +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
- 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
+
+ # 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/security_utils.rb b/activesupport/lib/active_support/security_utils.rb
index 64c4801179..9be8613ada 100644
--- a/activesupport/lib/active_support/security_utils.rb
+++ b/activesupport/lib/active_support/security_utils.rb
@@ -1,3 +1,5 @@
+require 'digest'
+
module ActiveSupport
module SecurityUtils
# Constant time string comparison.
@@ -16,5 +18,10 @@ module ActiveSupport
res == 0
end
module_function :secure_compare
+
+ def variable_size_secure_compare(a, b) # :nodoc:
+ secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b))
+ end
+ module_function :variable_size_secure_compare
end
end
diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb
index ae6f00b861..1fc12d0bc1 100644
--- a/activesupport/lib/active_support/test_case.rb
+++ b/activesupport/lib/active_support/test_case.rb
@@ -9,7 +9,6 @@ require 'active_support/testing/isolation'
require 'active_support/testing/constant_lookup'
require 'active_support/testing/time_helpers'
require 'active_support/testing/file_fixtures'
-require 'active_support/testing/composite_filter'
require 'active_support/core_ext/kernel/reporting'
module ActiveSupport
@@ -39,15 +38,6 @@ module ActiveSupport
def test_order
ActiveSupport.test_order ||= :random
end
-
- def run(reporter, options = {})
- if options[:patterns] && options[:patterns].any? { |p| p =~ /:\d+/ }
- options[:filter] = \
- Testing::CompositeFilter.new(self, options[:filter], options[:patterns])
- end
-
- super
- end
end
alias_method :method_name, :name
@@ -76,12 +66,20 @@ module ActiveSupport
alias :assert_not_respond_to :refute_respond_to
alias :assert_not_same :refute_same
- # Reveals the intention that the block should not raise any exception.
+
+ # Assertion that the block should not raise an exception.
+ #
+ # Passes if evaluated code in the yielded block raises no exception.
#
# assert_nothing_raised do
- # ...
+ # perform_service(param: 'no_exception')
# end
def assert_nothing_raised(*args)
+ if args.present?
+ ActiveSupport::Deprecation.warn(
+ "Passing arguments to assert_nothing_raised " \
+ "is deprecated and will be removed in Rails 5.1.")
+ end
yield
end
end
diff --git a/activesupport/lib/active_support/testing/composite_filter.rb b/activesupport/lib/active_support/testing/composite_filter.rb
deleted file mode 100644
index bde723e30b..0000000000
--- a/activesupport/lib/active_support/testing/composite_filter.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'method_source'
-
-module ActiveSupport
- module Testing
- class CompositeFilter # :nodoc:
- def initialize(runnable, filter, patterns)
- @runnable = runnable
- @filters = [ derive_regexp(filter), *derive_line_filters(patterns) ].compact
- end
-
- def ===(method)
- @filters.any? { |filter| filter === method }
- end
-
- private
- def derive_regexp(filter)
- filter =~ %r%/(.*)/% ? Regexp.new($1) : filter
- end
-
- def derive_line_filters(patterns)
- patterns.map do |file_and_line|
- file, line = file_and_line.split(':')
- Filter.new(@runnable, file, line) if file
- end
- end
-
- class Filter # :nodoc:
- def initialize(runnable, file, line)
- @runnable, @file = runnable, File.expand_path(file)
- @line = line.to_i if line
- end
-
- def ===(method)
- return unless @runnable.method_defined?(method)
-
- if @line
- test_file, test_range = definition_for(@runnable.instance_method(method))
- test_file == @file && test_range.include?(@line)
- else
- @runnable.instance_method(method).source_location.first == @file
- end
- end
-
- private
- def definition_for(method)
- file, start_line = method.source_location
- end_line = method.source.count("\n") + start_line - 1
-
- return file, start_line..end_line
- end
- end
- end
- end
-end
diff --git a/activesupport/lib/active_support/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 6404f65612..19420cee5e 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,7 +1,6 @@
require 'tzinfo'
require 'concurrent/map'
require 'active_support/core_ext/object/blank'
-require 'active_support/core_ext/object/try'
module ActiveSupport
# The TimeZone class serves as a wrapper around TZInfo::Timezone instances.
@@ -86,7 +85,8 @@ module ActiveSupport
"Paris" => "Europe/Paris",
"Amsterdam" => "Europe/Amsterdam",
"Berlin" => "Europe/Berlin",
- "Bern" => "Europe/Berlin",
+ "Bern" => "Europe/Zurich",
+ "Zurich" => "Europe/Zurich",
"Rome" => "Europe/Rome",
"Stockholm" => "Europe/Stockholm",
"Vienna" => "Europe/Vienna",
@@ -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 c0e23e89f7..bab7440397 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -1,12 +1,5 @@
ORIG_ARGV = ARGV.dup
-begin
- old, $VERBOSE = $VERBOSE, nil
- require File.expand_path('../../../load_paths', __FILE__)
-ensure
- $VERBOSE = old
-end
-
require 'active_support/core_ext/kernel/reporting'
silence_warnings do
@@ -25,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/benchmarkable_test.rb b/activesupport/test/benchmarkable_test.rb
index 04d4f5e503..5af041f458 100644
--- a/activesupport/test/benchmarkable_test.rb
+++ b/activesupport/test/benchmarkable_test.rb
@@ -41,6 +41,20 @@ class BenchmarkableTest < ActiveSupport::TestCase
assert_last_logged 'test_run'
end
+ def test_with_silence
+ assert_difference 'buffer.count', +2 do
+ benchmark('test_run') do
+ logger.info "SOMETHING"
+ end
+ end
+
+ assert_difference 'buffer.count', +1 do
+ benchmark('test_run', silence: true) do
+ logger.info "NOTHING"
+ end
+ end
+ end
+
def test_within_level
logger.level = ActiveSupport::Logger::DEBUG
benchmark('included_debug_run', :level => :debug) { }
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 3629f5e64b..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'))
@@ -416,7 +430,7 @@ module CacheStoreBehavior
def test_race_condition_protection_skipped_if_not_defined
@cache.write('foo', 'bar')
- time = @cache.send(:read_entry, 'foo', {}).expires_at
+ time = @cache.send(:read_entry, @cache.send(:normalize_key, 'foo', {}), {}).expires_at
Time.stub(:now, Time.at(time)) do
result = @cache.fetch('foo') do
@@ -493,31 +507,41 @@ module CacheStoreBehavior
def test_cache_hit_instrumentation
key = "test_key"
- subscribe_executed = false
- ActiveSupport::Notifications.subscribe "cache_read.active_support" do |name, start, finish, id, payload|
- subscribe_executed = true
- assert_equal :fetch, payload[:super_operation]
- assert payload[:hit]
+ @events = []
+ ActiveSupport::Notifications.subscribe "cache_read.active_support" do |*args|
+ @events << ActiveSupport::Notifications::Event.new(*args)
end
assert @cache.write(key, "1", :raw => true)
assert @cache.fetch(key) {}
- assert subscribe_executed
+ assert_equal 1, @events.length
+ assert_equal 'cache_read.active_support', @events[0].name
+ assert_equal :fetch, @events[0].payload[:super_operation]
+ assert @events[0].payload[:hit]
ensure
ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
end
def test_cache_miss_instrumentation
- subscribe_executed = false
- ActiveSupport::Notifications.subscribe "cache_read.active_support" do |name, start, finish, id, payload|
- subscribe_executed = true
- assert_equal :fetch, payload[:super_operation]
- assert_not payload[:hit]
+ @events = []
+ ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args|
+ @events << ActiveSupport::Notifications::Event.new(*args)
end
assert_not @cache.fetch("bad_key") {}
- assert subscribe_executed
+ assert_equal 3, @events.length
+ assert_equal 'cache_read.active_support', @events[0].name
+ assert_equal 'cache_generate.active_support', @events[1].name
+ assert_equal 'cache_write.active_support', @events[2].name
+ assert_equal :fetch, @events[0].payload[:super_operation]
+ assert_not @events[0].payload[:hit]
ensure
ActiveSupport::Notifications.unsubscribe "cache_read.active_support"
end
+
+ def test_can_call_deprecated_namesaced_key
+ assert_deprecated "`namespaced_key` is deprecated" do
+ @cache.send(:namespaced_key, 111, {})
+ end
+ end
end
# https://rails.lighthouseapp.com/projects/8994/tickets/6225-memcachestore-cant-deal-with-umlauts-and-special-characters
@@ -693,6 +717,15 @@ module LocalCacheBehavior
app = @cache.middleware.new(app)
app.call({})
end
+
+ def test_can_call_deprecated_set_cache_value
+ @cache.with_local_cache do
+ assert_deprecated "`set_cache_value` is deprecated" do
+ @cache.send(:set_cache_value, 1, 'foo', :ignored, {})
+ end
+ assert_equal 1, @cache.read('foo')
+ end
+ end
end
module AutoloadingCacheBehavior
@@ -761,10 +794,12 @@ class FileStoreTest < ActiveSupport::TestCase
include AutoloadingCacheBehavior
def test_clear
- filepath = File.join(cache_dir, ".gitkeep")
- FileUtils.touch(filepath)
+ gitkeep = File.join(cache_dir, ".gitkeep")
+ keep = File.join(cache_dir, ".keep")
+ FileUtils.touch([gitkeep, keep])
@cache.clear
- assert File.exist?(filepath)
+ assert File.exist?(gitkeep)
+ assert File.exist?(keep)
end
def test_clear_without_cache_dir
@@ -783,13 +818,13 @@ class FileStoreTest < ActiveSupport::TestCase
end
def test_key_transformation
- key = @cache.send(:key_file_path, "views/index?id=1")
+ key = @cache.send(:normalize_key, "views/index?id=1", {})
assert_equal "views/index?id=1", @cache.send(:file_path_key, key)
end
def test_key_transformation_with_pathname
FileUtils.touch(File.join(cache_dir, "foo"))
- key = @cache_with_pathname.send(:key_file_path, "views/index?id=1")
+ key = @cache_with_pathname.send(:normalize_key, "views/index?id=1", {})
assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key)
end
@@ -797,7 +832,7 @@ class FileStoreTest < ActiveSupport::TestCase
# remain valid
def test_filename_max_size
key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}"
- path = @cache.send(:key_file_path, key)
+ path = @cache.send(:normalize_key, key, {})
Dir::Tmpname.create(path) do |tmpname, n, opts|
assert File.basename(tmpname+'.lock').length <= 255, "Temp filename too long: #{File.basename(tmpname+'.lock').length}"
end
@@ -807,7 +842,7 @@ class FileStoreTest < ActiveSupport::TestCase
# If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B
def test_key_transformation_max_filename_size
key = "#{'A' * ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}B"
- path = @cache.send(:key_file_path, key)
+ path = @cache.send(:normalize_key, key, {})
assert path.split('/').all? { |dir_name| dir_name.size <= ActiveSupport::Cache::FileStore::FILENAME_MAX_SIZE}
assert_equal 'B', File.basename(path)
end
@@ -815,7 +850,7 @@ class FileStoreTest < ActiveSupport::TestCase
# If nothing has been stored in the cache, there is a chance the cache directory does not yet exist
# Ensure delete_matched gracefully handles this case
def test_delete_matched_when_cache_directory_does_not_exist
- assert_nothing_raised(Exception) do
+ assert_nothing_raised do
ActiveSupport::Cache::FileStore.new('/test/cache/directory').delete_matched(/does_not_exist/)
end
end
@@ -823,7 +858,7 @@ class FileStoreTest < ActiveSupport::TestCase
def test_delete_does_not_delete_empty_parent_dir
sub_cache_dir = File.join(cache_dir, 'subdir/')
sub_cache_store = ActiveSupport::Cache::FileStore.new(sub_cache_dir)
- assert_nothing_raised(Exception) do
+ assert_nothing_raised do
assert sub_cache_store.write('foo', 'bar')
assert sub_cache_store.delete('foo')
end
@@ -858,6 +893,12 @@ class FileStoreTest < ActiveSupport::TestCase
@cache.write(1, nil)
assert_equal false, @cache.write(1, "aaaaaaaaaa", unless_exist: true)
end
+
+ def test_can_call_deprecated_key_file_path
+ assert_deprecated "`key_file_path` is deprecated" do
+ assert_equal 111, @cache.send(:key_file_path, 111)
+ end
+ end
end
class MemoryStoreTest < ActiveSupport::TestCase
@@ -1032,6 +1073,12 @@ class MemCacheStoreTest < ActiveSupport::TestCase
value << 'bingo'
assert_not_equal value, @cache.read('foo')
end
+
+ def test_can_call_deprecated_escape_key
+ assert_deprecated "`escape_key` is deprecated" do
+ assert_equal 111, @cache.send(:escape_key, 111)
+ end
+ end
end
class NullStoreTest < ActiveSupport::TestCase
@@ -1118,15 +1165,6 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase
@cache.mute { @cache.fetch('foo') { 'bar' } }
assert @buffer.string.blank?
end
-
- def test_multi_read_loggin
- @cache.write 'hello', 'goodbye'
- @cache.write 'world', 'earth'
-
- @cache.read_multi('hello', 'world')
-
- assert_match "Caches multi read:\n- hello\n- world", @buffer.string
- end
end
class CacheEntryTest < ActiveSupport::TestCase
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index 3b00ff87a0..a624473f46 100644
--- a/activesupport/test/callbacks_test.rb
+++ b/activesupport/test/callbacks_test.rb
@@ -59,7 +59,7 @@ module CallbacksTest
[:before_save, :after_save].each do |callback_method|
callback_method_sym = callback_method.to_sym
send(callback_method, callback_symbol(callback_method_sym))
- send(callback_method, callback_string(callback_method_sym))
+ ActiveSupport::Deprecation.silence { send(callback_method, callback_string(callback_method_sym)) }
send(callback_method, callback_proc(callback_method_sym))
send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, '')))
send(callback_method, CallbackClass)
@@ -228,7 +228,7 @@ module CallbacksTest
set_callback :save, :before, :nope, :if => :no
set_callback :save, :before, :nope, :unless => :yes
set_callback :save, :after, :tweedle
- set_callback :save, :before, "tweedle_dee"
+ ActiveSupport::Deprecation.silence { set_callback :save, :before, "tweedle_dee" }
set_callback :save, :before, proc {|m| m.history << "yup" }
set_callback :save, :before, :nope, :if => proc { false }
set_callback :save, :before, :nope, :unless => proc { true }
@@ -1046,7 +1046,7 @@ module CallbacksTest
def test_add_eval
calls = []
- klass = build_class("bar")
+ klass = ActiveSupport::Deprecation.silence { build_class("bar") }
klass.class_eval { define_method(:bar) { calls << klass } }
klass.new.run
assert_equal 1, calls.length
@@ -1086,7 +1086,7 @@ module CallbacksTest
def test_skip_string # raises error
calls = []
- klass = build_class("bar")
+ klass = ActiveSupport::Deprecation.silence { build_class("bar") }
klass.class_eval { define_method(:bar) { calls << klass } }
assert_raises(ArgumentError) { klass.skip "bar" }
klass.new.run
@@ -1111,4 +1111,14 @@ module CallbacksTest
assert_equal 1, calls.length
end
end
+
+ class DeprecatedWarningTest < ActiveSupport::TestCase
+ def test_deprecate_string_callback
+ klass = Class.new(Record)
+
+ assert_deprecated do
+ klass.send :before_save, "tweedle_dee"
+ end
+ end
+ end
end
diff --git a/activesupport/test/core_ext/array/access_test.rb b/activesupport/test/core_ext/array/access_test.rb
index 3f1e0c4cb4..1d834667f0 100644
--- a/activesupport/test/core_ext/array/access_test.rb
+++ b/activesupport/test/core_ext/array/access_test.rb
@@ -26,6 +26,8 @@ class AccessTest < ActiveSupport::TestCase
assert_equal array[3], array.fourth
assert_equal array[4], array.fifth
assert_equal array[41], array.forty_two
+ assert_equal array[-3], array.third_to_last
+ assert_equal array[-2], array.second_to_last
end
def test_without
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_behavior.rb b/activesupport/test/core_ext/date_and_time_behavior.rb
index 784547bdf8..54df87def8 100644
--- a/activesupport/test/core_ext/date_and_time_behavior.rb
+++ b/activesupport/test/core_ext/date_and_time_behavior.rb
@@ -301,6 +301,16 @@ module DateAndTimeBehavior
assert_not date_time_init(2015,1,5,15,15,10).on_weekend?
end
+ def test_on_weekday_on_sunday
+ assert_not date_time_init(2015,1,4,0,0,0).on_weekday?
+ assert_not date_time_init(2015,1,4,15,15,10).on_weekday?
+ end
+
+ def test_on_weekday_on_monday
+ assert date_time_init(2015,1,5,0,0,0).on_weekday?
+ assert date_time_init(2015,1,5,15,15,10).on_weekday?
+ end
+
def with_bw_default(bw = :monday)
old_bw = Date.beginning_of_week
Date.beginning_of_week = bw
diff --git a/activesupport/test/core_ext/date_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 6fe38c45ec..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
@@ -186,6 +210,10 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2006,11,15), DateTime.civil(2006,11,23,0,0,0).last_week(:wednesday)
end
+ def test_date_time_should_have_correct_last_week_for_leap_year
+ assert_equal DateTime.civil(2016, 2, 29), DateTime.civil(2016, 3, 7).last_week
+ end
+
def test_last_month_on_31st
assert_equal DateTime.civil(2004, 2, 29), DateTime.civil(2004, 3, 31).last_month
end
@@ -306,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
@@ -350,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
@@ -370,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 2119352df0..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
@@ -702,6 +702,12 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal h.class, h.dup.class
end
+ def test_nested_dig_indifferent_access
+ skip if RUBY_VERSION < "2.3.0"
+ data = {"this" => {"views" => 1234}}.with_indifferent_access
+ assert_equal 1234, data.dig(:this, :views)
+ end
+
def test_assert_valid_keys
assert_nothing_raised do
{ :failure => "stuff", :funny => "business" }.assert_valid_keys([ :failure, :funny ])
@@ -1587,9 +1593,9 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_equal 3, hash_wia[:new_key]
end
- def test_should_use_default_proc_if_no_key_is_supplied
+ def test_should_return_nil_if_no_key_is_supplied
hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_equal 3, hash_wia.default
+ assert_equal nil, hash_wia.default
end
def test_should_use_default_value_for_unknown_key
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index 825df439a5..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
@@ -64,6 +74,17 @@ class MarshalTest < ActiveSupport::TestCase
end
end
+ test "when one constant resolves to another" do
+ class Parent; C = Class.new; end
+ class Child < Parent; C = Class.new; end
+
+ dump = Marshal.dump(Child::C.new)
+
+ Child.send(:remove_const, :C)
+
+ assert_raise(ArgumentError) { Marshal.load(dump) }
+ end
+
test "that a real missing class is causing an exception" do
dumped = nil
with_autoloading_fixtures do
@@ -96,7 +117,7 @@ class MarshalTest < ActiveSupport::TestCase
Marshal.load(dumped)
end
- assert_nothing_raised("EM failed to load while we expect only SomeClass to fail loading") do
+ assert_nothing_raised do
EM.new
end
@@ -117,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/module/attribute_accessor_per_thread_test.rb b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
new file mode 100644
index 0000000000..a9fd878b80
--- /dev/null
+++ b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
@@ -0,0 +1,115 @@
+require 'abstract_unit'
+require 'active_support/core_ext/module/attribute_accessors_per_thread'
+
+class ModuleAttributeAccessorPerThreadTest < ActiveSupport::TestCase
+ def setup
+ @class = Class.new do
+ thread_mattr_accessor :foo
+ thread_mattr_accessor :bar, instance_writer: false
+ thread_mattr_reader :shaq, instance_reader: false
+ thread_mattr_accessor :camp, instance_accessor: false
+ end
+
+ @object = @class.new
+ end
+
+ def test_should_use_mattr_default
+ Thread.new do
+ assert_nil @class.foo
+ assert_nil @object.foo
+ end.join
+ end
+
+ def test_should_set_mattr_value
+ Thread.new do
+ @class.foo = :test
+ assert_equal :test, @class.foo
+
+ @class.foo = :test2
+ assert_equal :test2, @class.foo
+ end.join
+ end
+
+ def test_should_not_create_instance_writer
+ Thread.new do
+ assert_respond_to @class, :foo
+ assert_respond_to @class, :foo=
+ assert_respond_to @object, :bar
+ assert !@object.respond_to?(:bar=)
+ end.join
+ end
+
+ def test_should_not_create_instance_reader
+ Thread.new do
+ assert_respond_to @class, :shaq
+ assert !@object.respond_to?(:shaq)
+ end.join
+ end
+
+ def test_should_not_create_instance_accessors
+ Thread.new do
+ assert_respond_to @class, :camp
+ assert !@object.respond_to?(:camp)
+ assert !@object.respond_to?(:camp=)
+ end.join
+ end
+
+ def test_values_should_not_bleed_between_threads
+ threads = []
+ threads << Thread.new do
+ @class.foo = 'things'
+ sleep 1
+ assert_equal 'things', @class.foo
+ end
+
+ threads << Thread.new do
+ @class.foo = 'other things'
+ sleep 1
+ assert_equal 'other things', @class.foo
+ end
+
+ threads << Thread.new do
+ @class.foo = 'really other things'
+ sleep 1
+ assert_equal 'really other things', @class.foo
+ end
+
+ threads.each { |t| t.join }
+ end
+
+ def test_should_raise_name_error_if_attribute_name_is_invalid
+ exception = assert_raises NameError do
+ Class.new do
+ thread_cattr_reader "1nvalid"
+ end
+ end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
+
+ exception = assert_raises NameError do
+ Class.new do
+ thread_cattr_writer "1nvalid"
+ end
+ end
+ assert_equal "invalid attribute name: 1nvalid", exception.message
+
+ exception = assert_raises NameError do
+ Class.new do
+ thread_mattr_reader "1valid_part"
+ end
+ end
+ assert_equal "invalid attribute name: 1valid_part", exception.message
+
+ exception = assert_raises NameError do
+ Class.new do
+ thread_mattr_writer "2valid_part"
+ end
+ end
+ assert_equal "invalid attribute name: 2valid_part", exception.message
+ end
+
+ def test_should_return_same_value_by_class_or_instance_accessor
+ @class.foo = 'fries'
+
+ assert_equal @class.foo, @object.foo
+ end
+end
diff --git a/activesupport/test/core_ext/module/qualified_const_test.rb b/activesupport/test/core_ext/module/qualified_const_test.rb
index 37c9228a64..a3146cabe1 100644
--- a/activesupport/test/core_ext/module/qualified_const_test.rb
+++ b/activesupport/test/core_ext/module/qualified_const_test.rb
@@ -19,84 +19,94 @@ end
class QualifiedConstTest < ActiveSupport::TestCase
test "Object.qualified_const_defined?" do
- assert Object.qualified_const_defined?("QualifiedConstTestMod")
- assert !Object.qualified_const_defined?("NonExistingQualifiedConstTestMod")
-
- assert Object.qualified_const_defined?("QualifiedConstTestMod::X")
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::Y")
-
- assert Object.qualified_const_defined?("QualifiedConstTestMod::M::X")
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::M::Y")
-
- if Module.method(:const_defined?).arity == 1
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
- else
- assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
- assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X", false)
- assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X", true)
+ assert_deprecated do
+ assert Object.qualified_const_defined?("QualifiedConstTestMod")
+ assert !Object.qualified_const_defined?("NonExistingQualifiedConstTestMod")
+
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::X")
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::Y")
+
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::M::X")
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::M::Y")
+
+ if Module.method(:const_defined?).arity == 1
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
+ else
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X")
+ assert !Object.qualified_const_defined?("QualifiedConstTestMod::N::X", false)
+ assert Object.qualified_const_defined?("QualifiedConstTestMod::N::X", true)
+ end
end
end
test "mod.qualified_const_defined?" do
- assert QualifiedConstTestMod.qualified_const_defined?("M")
- assert !QualifiedConstTestMod.qualified_const_defined?("NonExistingM")
-
- assert QualifiedConstTestMod.qualified_const_defined?("M::X")
- assert !QualifiedConstTestMod.qualified_const_defined?("M::Y")
-
- assert QualifiedConstTestMod.qualified_const_defined?("M::C::X")
- assert !QualifiedConstTestMod.qualified_const_defined?("M::C::Y")
-
- if Module.method(:const_defined?).arity == 1
- assert !QualifiedConstTestMod.qualified_const_defined?("QualifiedConstTestMod::N::X")
- else
- assert QualifiedConstTestMod.qualified_const_defined?("N::X")
- assert !QualifiedConstTestMod.qualified_const_defined?("N::X", false)
- assert QualifiedConstTestMod.qualified_const_defined?("N::X", true)
+ assert_deprecated do
+ assert QualifiedConstTestMod.qualified_const_defined?("M")
+ assert !QualifiedConstTestMod.qualified_const_defined?("NonExistingM")
+
+ assert QualifiedConstTestMod.qualified_const_defined?("M::X")
+ assert !QualifiedConstTestMod.qualified_const_defined?("M::Y")
+
+ assert QualifiedConstTestMod.qualified_const_defined?("M::C::X")
+ assert !QualifiedConstTestMod.qualified_const_defined?("M::C::Y")
+
+ if Module.method(:const_defined?).arity == 1
+ assert !QualifiedConstTestMod.qualified_const_defined?("QualifiedConstTestMod::N::X")
+ else
+ assert QualifiedConstTestMod.qualified_const_defined?("N::X")
+ assert !QualifiedConstTestMod.qualified_const_defined?("N::X", false)
+ assert QualifiedConstTestMod.qualified_const_defined?("N::X", true)
+ end
end
end
test "qualified_const_get" do
- assert_equal false, Object.qualified_const_get("QualifiedConstTestMod::X")
- assert_equal false, QualifiedConstTestMod.qualified_const_get("X")
- assert_equal 1, QualifiedConstTestMod.qualified_const_get("M::X")
- assert_equal 1, QualifiedConstTestMod.qualified_const_get("N::X")
- assert_equal 2, QualifiedConstTestMod.qualified_const_get("M::C::X")
-
- assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y")}
+ assert_deprecated do
+ assert_equal false, Object.qualified_const_get("QualifiedConstTestMod::X")
+ assert_equal false, QualifiedConstTestMod.qualified_const_get("X")
+ assert_equal 1, QualifiedConstTestMod.qualified_const_get("M::X")
+ assert_equal 1, QualifiedConstTestMod.qualified_const_get("N::X")
+ assert_equal 2, QualifiedConstTestMod.qualified_const_get("M::C::X")
+
+ assert_raise(NameError) { QualifiedConstTestMod.qualified_const_get("M::C::Y")}
+ end
end
test "qualified_const_set" do
- begin
- m = Module.new
- assert_equal m, Object.qualified_const_set("QualifiedConstTestMod2", m)
- assert_equal m, ::QualifiedConstTestMod2
-
- # We are going to assign to existing constants on purpose, so silence warnings.
- silence_warnings do
- assert_equal true, QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", true)
- assert_equal true, QualifiedConstTestMod::X
-
- assert_equal 10, QualifiedConstTestMod::M.qualified_const_set("X", 10)
- assert_equal 10, QualifiedConstTestMod::M::X
- end
- ensure
- silence_warnings do
- QualifiedConstTestMod.qualified_const_set('QualifiedConstTestMod::X', false)
- QualifiedConstTestMod::M.qualified_const_set('X', 1)
+ assert_deprecated do
+ begin
+ m = Module.new
+ assert_equal m, Object.qualified_const_set("QualifiedConstTestMod2", m)
+ assert_equal m, ::QualifiedConstTestMod2
+
+ # We are going to assign to existing constants on purpose, so silence warnings.
+ silence_warnings do
+ assert_equal true, QualifiedConstTestMod.qualified_const_set("QualifiedConstTestMod::X", true)
+ assert_equal true, QualifiedConstTestMod::X
+
+ assert_equal 10, QualifiedConstTestMod::M.qualified_const_set("X", 10)
+ assert_equal 10, QualifiedConstTestMod::M::X
+ end
+ ensure
+ silence_warnings do
+ QualifiedConstTestMod.qualified_const_set('QualifiedConstTestMod::X', false)
+ QualifiedConstTestMod::M.qualified_const_set('X', 1)
+ end
end
end
end
test "reject absolute paths" do
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y")}
+ assert_deprecated do
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X")}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_defined?("::X::Y")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y")}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X")}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_get("::X::Y")}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil)}
- assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil)}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X", nil)}
+ assert_raise_with_message(NameError, "wrong constant name ::X") { Object.qualified_const_set("::X::Y", nil)}
+ end
end
private
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index 0ed66f8c37..566f29b470 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -40,6 +40,12 @@ class Someone < Struct.new(:name, :place)
FAILED_DELEGATE_LINE_2 = __LINE__ + 1
delegate :bar, :to => :place, :allow_nil => true
+
+ private
+
+ def private_name
+ "Private"
+ end
end
Invoice = Struct.new(:client) do
@@ -83,6 +89,20 @@ Product = Struct.new(:name) do
end
end
+DecoratedTester = Struct.new(:client) do
+ delegate_missing_to :client
+end
+
+class DecoratedReserved
+ delegate_missing_to :case
+
+ attr_reader :case
+
+ def initialize(kase)
+ @case = kase
+ end
+end
+
class Block
def hello?
true
@@ -173,6 +193,21 @@ class ModuleTest < ActiveSupport::TestCase
end
end
+ def test_delegation_target_when_prefix_is_true
+ assert_nothing_raised do
+ Name.send :delegate, :go, to: :you, prefix: true
+ end
+ assert_nothing_raised do
+ Name.send :delegate, :go, to: :_you, prefix: true
+ end
+ assert_raise(ArgumentError) do
+ Name.send :delegate, :go, to: :You, prefix: true
+ end
+ assert_raise(ArgumentError) do
+ Name.send :delegate, :go, to: :@you, prefix: true
+ end
+ end
+
def test_delegation_prefix
invoice = Invoice.new(@david)
assert_equal invoice.client_name, "David"
@@ -316,6 +351,30 @@ class ModuleTest < ActiveSupport::TestCase
assert has_block.hello?
end
+ def test_delegate_to_missing_with_method
+ assert_equal "David", DecoratedTester.new(@david).name
+ end
+
+ def test_delegate_to_missing_with_reserved_methods
+ assert_equal "David", DecoratedReserved.new(@david).name
+ end
+
+ def test_delegate_to_missing_does_not_delegate_to_private_methods
+ e = assert_raises(NoMethodError) do
+ DecoratedReserved.new(@david).private_name
+ end
+
+ assert_match(/undefined method `private_name' for/, e.message)
+ end
+
+ def test_delegate_to_missing_does_not_delegate_to_fake_methods
+ e = assert_raises(NoMethodError) do
+ DecoratedReserved.new(@david).my_fake_method
+ end
+
+ assert_match(/undefined method `my_fake_method' for/, e.message)
+ end
+
def test_parent
assert_equal Yz::Zy, Yz::Zy::Cd.parent
assert_equal Yz, Yz::Zy.parent
@@ -328,7 +387,13 @@ class ModuleTest < ActiveSupport::TestCase
end
def test_local_constants
- assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s)
+ ActiveSupport::Deprecation.silence do
+ assert_equal %w(Constant1 Constant3), Ab.local_constants.sort.map(&:to_s)
+ end
+ end
+
+ def test_local_constants_is_deprecated
+ assert_deprecated { Ab.local_constants.sort.map(&:to_s) }
end
end
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 0ff8f0f89b..69c30a8a9e 100644
--- a/activesupport/test/core_ext/numeric_ext_test.rb
+++ b/activesupport/test/core_ext/numeric_ext_test.rb
@@ -143,6 +143,14 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
gigabytes(number) * 1024
end
+ def petabytes(number)
+ terabytes(number) * 1024
+ end
+
+ def exabytes(number)
+ petabytes(number) * 1024
+ end
+
def test_to_s__phone
assert_equal("555-1234", 5551234.to_s(:phone))
assert_equal("800-555-1212", 8005551212.to_s(:phone))
@@ -266,7 +274,9 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1.18 MB', 1234567.to_s(:human_size)
assert_equal '1.15 GB', 1234567890.to_s(:human_size)
assert_equal '1.12 TB', 1234567890123.to_s(:human_size)
- assert_equal '1030 TB', terabytes(1026).to_s(:human_size)
+ assert_equal '1.1 PB', 1234567890123456.to_s(:human_size)
+ assert_equal '1.07 EB', 1234567890123456789.to_s(:human_size)
+ assert_equal '1030 EB', exabytes(1026).to_s(:human_size)
assert_equal '444 KB', kilobytes(444).to_s(:human_size)
assert_equal '1020 MB', megabytes(1023).to_s(:human_size)
assert_equal '3 TB', terabytes(3).to_s(:human_size)
@@ -289,6 +299,8 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 PB', 1234567890123456.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 EB', 1234567890123456789.to_s(:human_size, :prefix => :si)
end
end
@@ -375,19 +387,38 @@ 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
+ def test_to_formatted_s_is_deprecated
+ assert_deprecated do
+ 5551234.to_formatted_s(:phone)
+ end
+ end
+
+ def test_to_s_with_invalid_formatter
+ assert_equal '123', 123.to_s(:invalid)
+ assert_equal '2.5', 2.5.to_s(:invalid)
+ assert_equal '100000000000000000000', (100**10).to_s(:invalid)
+ assert_equal '1000010.0', BigDecimal("1000010").to_s(:invalid)
+ end
+
+ def test_default_to_s
+ assert_equal '123', 123.to_s
+ assert_equal '1111011', 123.to_s(2)
+
+ assert_equal '2.5', 2.5.to_s
+
+ assert_equal '100000000000000000000', (100**10).to_s
+ assert_equal '1010110101111000111010111100010110101100011000100000000000000000000', (100**10).to_s(2)
+
+ assert_equal '1000010.0', BigDecimal("1000010").to_s
+ assert_equal '10000 10.0', BigDecimal("1000010").to_s('5F')
+ end
+
def test_in_milliseconds
assert_equal 10_000, 10.seconds.in_milliseconds
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/range_ext_test.rb b/activesupport/test/core_ext/range_ext_test.rb
index f096328cee..f28cebda3d 100644
--- a/activesupport/test/core_ext/range_ext_test.rb
+++ b/activesupport/test/core_ext/range_ext_test.rb
@@ -1,5 +1,6 @@
require 'abstract_unit'
require 'active_support/time'
+require 'active_support/core_ext/numeric'
require 'active_support/core_ext/range'
class RangeTest < ActiveSupport::TestCase
@@ -13,6 +14,11 @@ class RangeTest < ActiveSupport::TestCase
assert_equal "BETWEEN '2005-12-10 15:30:00' AND '2005-12-10 17:30:00'", date_range.to_s(:db)
end
+ def test_to_s_with_numeric
+ number_range = (1..100)
+ assert_equal "BETWEEN '1' AND '100'", number_range.to_s(:db)
+ end
+
def test_date_range
assert_instance_of Range, DateTime.new..DateTime.new
assert_instance_of Range, DateTime::Infinity.new..DateTime::Infinity.new
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 2d0fb70a6b..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
@@ -392,7 +406,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(:usec => 8)
assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,2).change(:nsec => 8000)
assert_raise(ArgumentError) { Time.local(2005,1,2,11,22,33, 8).change(:usec => 1, :nsec => 1) }
- assert_nothing_raised(ArgumentError) { Time.new(2015, 5, 9, 10, 00, 00, '+03:00').change(nsec: 999999999) }
+ assert_nothing_raised { Time.new(2015, 5, 9, 10, 00, 00, '+03:00').change(nsec: 999999999) }
end
def test_utc_change
@@ -617,6 +631,25 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
end
+ def test_days_in_year_with_year
+ assert_equal 365, Time.days_in_year(2005)
+ assert_equal 366, Time.days_in_year(2004)
+ assert_equal 366, Time.days_in_year(2000)
+ assert_equal 365, Time.days_in_year(1900)
+ end
+
+ def test_days_in_year_in_common_year_without_year_arg
+ Time.stub(:now, Time.utc(2007)) do
+ assert_equal 365, Time.days_in_year
+ end
+ end
+
+ def test_days_in_year_in_leap_year_without_year_arg
+ Time.stub(:now, Time.utc(2008)) do
+ assert_equal 366, Time.days_in_year
+ end
+ end
+
def test_last_month_on_31st
assert_equal Time.local(2004, 2, 29), Time.local(2004, 3, 31).last_month
end
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 757e600646..04e7b24d30 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -76,6 +76,7 @@ class DependenciesTest < ActiveSupport::TestCase
def test_dependency_which_raises_exception_isnt_added_to_loaded_set
with_loading do
filename = 'dependencies/raises_exception'
+ expanded = File.expand_path(filename)
$raises_exception_load_count = 0
5.times do |count|
@@ -86,8 +87,8 @@ class DependenciesTest < ActiveSupport::TestCase
assert_equal 'Loading me failed, so do not add to loaded or history.', e.message
assert_equal count + 1, $raises_exception_load_count
- assert_not ActiveSupport::Dependencies.loaded.include?(filename)
- assert_not ActiveSupport::Dependencies.history.include?(filename)
+ assert_not ActiveSupport::Dependencies.loaded.include?(expanded)
+ assert_not ActiveSupport::Dependencies.history.include?(expanded)
end
end
end
@@ -268,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
@@ -1047,12 +1070,4 @@ class DependenciesTest < ActiveSupport::TestCase
ensure
ActiveSupport::Dependencies.hook!
end
-
- def test_unhook
- ActiveSupport::Dependencies.unhook!
- assert !Module.new.respond_to?(:const_missing_without_dependencies)
- assert !Module.new.respond_to?(:load_without_new_constant_marking)
- ensure
- ActiveSupport::Dependencies.hook!
- end
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index cd02ad3f3f..ec34bd823d 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -105,13 +105,13 @@ class DeprecationTest < ActiveSupport::TestCase
ActiveSupport::Deprecation.behavior = :raise
message = 'Revise this deprecated stuff now!'
- callstack = %w(foo bar baz)
+ callstack = caller_locations
e = assert_raise ActiveSupport::DeprecationException do
ActiveSupport::Deprecation.behavior.first.call(message, callstack)
end
assert_equal message, e.message
- assert_equal callstack, e.backtrace
+ assert_equal callstack.map(&:to_s), e.backtrace.map(&:to_s)
end
def test_default_stderr_behavior
@@ -199,7 +199,7 @@ class DeprecationTest < ActiveSupport::TestCase
end
def test_assert_deprecated_warn_work_with_default_behavior
- ActiveSupport::Deprecation.instance_variable_set('@behavior' , nil)
+ ActiveSupport::Deprecation.instance_variable_set('@behavior', nil)
assert_deprecated('abc') do
ActiveSupport::Deprecation.warn 'abc'
end
@@ -340,6 +340,10 @@ class DeprecationTest < ActiveSupport::TestCase
assert_match(/You are calling deprecated method/, object.last_message)
end
+ def test_default_deprecation_horizon_should_always_bigger_than_current_rails_version
+ assert_operator ActiveSupport::Deprecation.new.deprecation_horizon, :>, ActiveSupport::VERSION::STRING
+ end
+
def test_default_gem_name
deprecator = ActiveSupport::Deprecation.new
diff --git a/activesupport/test/evented_file_update_checker_test.rb b/activesupport/test/evented_file_update_checker_test.rb
new file mode 100644
index 0000000000..bc3f77bd54
--- /dev/null
+++ b/activesupport/test/evented_file_update_checker_test.rb
@@ -0,0 +1,155 @@
+require 'abstract_unit'
+require 'pathname'
+require 'file_update_checker_shared_tests'
+
+class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
+ include FileUpdateCheckerSharedTests
+
+ def setup
+ skip if ENV['LISTEN'] == '0'
+ super
+ end
+
+ def new_checker(files = [], dirs = {}, &block)
+ ActiveSupport::EventedFileUpdateChecker.new(files, dirs, &block).tap do
+ wait
+ end
+ end
+
+ def teardown
+ super
+ Listen.stop
+ end
+
+ def wait
+ sleep 1
+ end
+
+ def touch(files)
+ super
+ wait # wait for the events to fire
+ end
+
+ def rm_f(files)
+ super
+ wait
+ end
+end
+
+class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase
+ def pn(path)
+ Pathname.new(path)
+ end
+
+ setup do
+ @ph = ActiveSupport::EventedFileUpdateChecker::PathHelper.new
+ end
+
+ test '#xpath returns the expanded path as a Pathname object' do
+ assert_equal pn(__FILE__).expand_path, @ph.xpath(__FILE__)
+ end
+
+ test '#normalize_extension returns a bare extension as is' do
+ assert_equal 'rb', @ph.normalize_extension('rb')
+ end
+
+ test '#normalize_extension removes a leading dot' do
+ assert_equal 'rb', @ph.normalize_extension('.rb')
+ end
+
+ test '#normalize_extension supports symbols' do
+ assert_equal 'rb', @ph.normalize_extension(:rb)
+ end
+
+ test '#longest_common_subpath finds the longest common subpath, if there is one' do
+ paths = %w(
+ /foo/bar
+ /foo/baz
+ /foo/bar/baz/woo/zoo
+ ).map { |path| pn(path) }
+
+ assert_equal pn('/foo'), @ph.longest_common_subpath(paths)
+ end
+
+ test '#longest_common_subpath returns the root directory as an edge case' do
+ paths = %w(
+ /foo/bar
+ /foo/baz
+ /foo/bar/baz/woo/zoo
+ /wadus
+ ).map { |path| pn(path) }
+
+ assert_equal pn('/'), @ph.longest_common_subpath(paths)
+ end
+
+ test '#longest_common_subpath returns nil for an empty collection' do
+ assert_nil @ph.longest_common_subpath([])
+ end
+
+ test '#existing_parent returns the most specific existing ascendant' do
+ wd = Pathname.getwd
+
+ assert_equal wd, @ph.existing_parent(wd)
+ assert_equal wd, @ph.existing_parent(wd.join('non-existing/directory'))
+ assert_equal pn('/'), @ph.existing_parent(pn('/non-existing/directory'))
+ end
+
+ test '#filter_out_descendants returns the same collection if there are no descendants (empty)' do
+ assert_equal [], @ph.filter_out_descendants([])
+ end
+
+ test '#filter_out_descendants returns the same collection if there are no descendants (one)' do
+ assert_equal ['/foo'], @ph.filter_out_descendants(['/foo'])
+ end
+
+ test '#filter_out_descendants returns the same collection if there are no descendants (several)' do
+ paths = %w(
+ /Rails.root/app/controllers
+ /Rails.root/app/models
+ /Rails.root/app/helpers
+ ).map { |path| pn(path) }
+
+ assert_equal paths, @ph.filter_out_descendants(paths)
+ end
+
+ test '#filter_out_descendants filters out descendants preserving order' do
+ paths = %w(
+ /Rails.root/app/controllers
+ /Rails.root/app/controllers/concerns
+ /Rails.root/app/models
+ /Rails.root/app/models/concerns
+ /Rails.root/app/helpers
+ ).map { |path| pn(path) }
+
+ assert_equal paths.values_at(0, 2, 4), @ph.filter_out_descendants(paths)
+ end
+
+ test '#filter_out_descendants works on path units' do
+ paths = %w(
+ /foo/bar
+ /foo/barrrr
+ ).map { |path| pn(path) }
+
+ assert_equal paths, @ph.filter_out_descendants(paths)
+ end
+
+ test '#filter_out_descendants deals correctly with the root directory' do
+ paths = %w(
+ /
+ /foo
+ /foo/bar
+ ).map { |path| pn(path) }
+
+ assert_equal paths.values_at(0), @ph.filter_out_descendants(paths)
+ end
+
+ test '#filter_out_descendants preserves duplicates' do
+ paths = %w(
+ /foo
+ /foo/bar
+ /foo
+ ).map { |path| pn(path) }
+
+ assert_equal paths.values_at(0, 2), @ph.filter_out_descendants(paths)
+ end
+end
diff --git a/activesupport/test/executor_test.rb b/activesupport/test/executor_test.rb
new file mode 100644
index 0000000000..d9b389461a
--- /dev/null
+++ b/activesupport/test/executor_test.rb
@@ -0,0 +1,183 @@
+require 'abstract_unit'
+
+class ExecutorTest < ActiveSupport::TestCase
+ class DummyError < RuntimeError
+ end
+
+ def test_wrap_invokes_callbacks
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+
+ executor.wrap do
+ called << :body
+ end
+
+ assert_equal [:run, :body, :complete], called
+ end
+
+ def test_callbacks_share_state
+ result = false
+ executor.to_run { @foo = true }
+ executor.to_complete { result = @foo }
+
+ executor.wrap { }
+
+ assert result
+ end
+
+ def test_separated_calls_invoke_callbacks
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+
+ state = executor.run!
+ called << :body
+ state.complete!
+
+ 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 }
+ executor.to_complete { called << :complete }
+
+ executor.wrap do
+ called << :early
+ executor.wrap do
+ called << :body
+ end
+ called << :late
+ end
+
+ 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)
+
+ called = []
+ executor.to_run { called << :run }
+ executor.to_complete { called << :complete }
+ other_executor.to_run { called << :other_run }
+ other_executor.to_complete { called << :other_complete }
+
+ executor.wrap do
+ other_executor.wrap do
+ called << :body
+ end
+ end
+
+ assert_equal [:run, :other_run, :body, :other_complete, :complete], called
+ end
+
+ private
+ def executor
+ @executor ||= Class.new(ActiveSupport::Executor)
+ end
+end
diff --git a/activesupport/test/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
new file mode 100644
index 0000000000..40ae0c7617
--- /dev/null
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -0,0 +1,260 @@
+require 'fileutils'
+
+module FileUpdateCheckerSharedTests
+ extend ActiveSupport::Testing::Declarative
+ include FileUtils
+
+ def tmpdir
+ @tmpdir
+ end
+
+ def tmpfile(name)
+ File.join(tmpdir, name)
+ end
+
+ def tmpfiles
+ @tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) }
+ end
+
+ 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
+ silence_warnings { require 'listen' }
+ i = 0
+
+ checker = new_checker { i += 1 }
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ test 'should not execute the block if no files change' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ test 'should execute the block once when files are created' do
+ i = 0
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ touch(tmpfiles)
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'should execute the block once when files are modified' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ touch(tmpfiles)
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'should execute the block once when files are deleted' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+
+ rm_f(tmpfiles)
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+
+ test 'updated should become true when watched files are created' do
+ i = 0
+
+ checker = new_checker(tmpfiles) { i += 1 }
+ assert !checker.updated?
+
+ touch(tmpfiles)
+
+ assert checker.updated?
+ end
+
+
+ test 'updated should become true when watched files are modified' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+ assert !checker.updated?
+
+ touch(tmpfiles)
+
+ assert checker.updated?
+ end
+
+ test 'updated should become true when watched files are deleted' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ checker = new_checker(tmpfiles) { i += 1 }
+ assert !checker.updated?
+
+ rm_f(tmpfiles)
+
+ assert checker.updated?
+ end
+
+ test 'should be robust to handle files with wrong modified time' do
+ i = 0
+
+ FileUtils.touch(tmpfiles)
+
+ now = Time.now
+ time = Time.mktime(now.year + 1, now.month, now.day) # 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 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
+
+ checker = new_checker(tmpfiles) { i += 1 }
+ assert !checker.updated?
+
+ touch(tmpfiles)
+
+ assert checker.updated?
+ checker.execute
+ assert !checker.updated?
+ end
+
+ test 'should execute the block if files change in a watched directory one extension' do
+ i = 0
+
+ checker = new_checker([], tmpdir => :rb) { i += 1 }
+
+ touch(tmpfile('foo.rb'))
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'should execute the block if files change in a watched directory several extensions' do
+ i = 0
+
+ checker = new_checker([], tmpdir => [:rb, :txt]) { i += 1 }
+
+ touch(tmpfile('foo.rb'))
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+
+ touch(tmpfile('foo.txt'))
+
+ assert checker.execute_if_updated
+ assert_equal 2, i
+ end
+
+ test 'should not execute the block if the file extension is not watched' do
+ i = 0
+
+ checker = new_checker([], tmpdir => :txt) { i += 1 }
+
+ touch(tmpfile('foo.rb'))
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+ end
+
+ test 'does not assume files exist on instantiation' do
+ i = 0
+
+ non_existing = tmpfile('non_existing.rb')
+ checker = new_checker([non_existing]) { i += 1 }
+
+ touch(non_existing)
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'detects files in new subdirectories' do
+ i = 0
+
+ checker = new_checker([], tmpdir => :rb) { i += 1 }
+
+ subdir = tmpfile('subdir')
+ mkdir(subdir)
+ wait
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+
+ touch(File.join(subdir, "nested.rb"))
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+ end
+
+ test 'looked up extensions are inherited in subdirectories not listening to them' do
+ i = 0
+
+ subdir = tmpfile('subdir')
+ mkdir(subdir)
+
+ checker = new_checker([], tmpdir => :rb, subdir => :txt) { i += 1 }
+
+ touch(tmpfile('new.txt'))
+
+ assert !checker.execute_if_updated
+ assert_equal 0, i
+
+ # subdir does not look for Ruby files, but its parent tmpdir does.
+ touch(File.join(subdir, "nested.rb"))
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+
+ touch(File.join(subdir, "nested.txt"))
+
+ assert checker.execute_if_updated
+ assert_equal 2, i
+ end
+end
diff --git a/activesupport/test/file_update_checker_test.rb b/activesupport/test/file_update_checker_test.rb
index bd1df0f858..752f7836cd 100644
--- a/activesupport/test/file_update_checker_test.rb
+++ b/activesupport/test/file_update_checker_test.rb
@@ -1,112 +1,19 @@
require 'abstract_unit'
-require 'fileutils'
-require 'thread'
+require 'file_update_checker_shared_tests'
-MTIME_FIXTURES_PATH = File.expand_path("../fixtures", __FILE__)
+class FileUpdateCheckerTest < ActiveSupport::TestCase
+ include FileUpdateCheckerSharedTests
-class FileUpdateCheckerWithEnumerableTest < ActiveSupport::TestCase
- FILES = %w(1.txt 2.txt 3.txt)
-
- def setup
- FileUtils.mkdir_p("tmp_watcher")
- FileUtils.touch(FILES)
- end
-
- def teardown
- FileUtils.rm_rf("tmp_watcher")
- FileUtils.rm_rf(FILES)
- end
-
- def test_should_not_execute_the_block_if_no_paths_are_given
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([]){ i += 1 }
- checker.execute_if_updated
- assert_equal 0, i
- end
-
- def test_should_not_invoke_the_block_if_no_file_has_changed
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- 5.times { assert !checker.execute_if_updated }
- assert_equal 0, i
- end
-
- def test_should_invoke_the_block_if_a_file_has_changed
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- sleep(1)
- FileUtils.touch(FILES)
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_be_robust_enough_to_handle_deleted_files
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- FileUtils.rm(FILES)
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_be_robust_to_handle_files_with_wrong_modified_time
- i = 0
- now = Time.now
- time = Time.mktime(now.year + 1, now.month, now.day) # wrong mtime from the future
- File.utime time, time, FILES[2]
-
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
-
- sleep(1)
- FileUtils.touch(FILES[0..1])
-
- assert checker.execute_if_updated
- assert_equal 1, i
- end
-
- def test_should_cache_updated_result_until_execute
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new(FILES){ i += 1 }
- assert !checker.updated?
-
- sleep(1)
- FileUtils.touch(FILES)
-
- assert checker.updated?
- checker.execute
- assert !checker.updated?
- end
-
- def test_should_invoke_the_block_if_a_watched_dir_changed_its_glob
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => [:txt]){ i += 1 }
- FileUtils.cd "tmp_watcher" do
- FileUtils.touch(FILES)
- end
- assert checker.execute_if_updated
- assert_equal 1, i
+ def new_checker(files = [], dirs = {}, &block)
+ ActiveSupport::FileUpdateChecker.new(files, dirs, &block)
end
- def test_should_not_invoke_the_block_if_a_watched_dir_changed_its_glob
- i = 0
- checker = ActiveSupport::FileUpdateChecker.new([], "tmp_watcher" => :rb){ i += 1 }
- FileUtils.cd "tmp_watcher" do
- FileUtils.touch(FILES)
- end
- assert !checker.execute_if_updated
- assert_equal 0, i
+ def wait
+ # noop
end
- def test_should_not_block_if_a_strange_filename_used
- FileUtils.mkdir_p("tmp_watcher/valid,yetstrange,path,")
- FileUtils.touch(FILES.map { |file_name| "tmp_watcher/valid,yetstrange,path,/#{file_name}" })
-
- test = Thread.new do
- ActiveSupport::FileUpdateChecker.new([],"tmp_watcher/valid,yetstrange,path," => :txt) { i += 1 }
- Thread.exit
- end
- test.priority = -1
- test.join(5)
-
- assert !test.alive?
+ def touch(files)
+ sleep 1 # let's wait a bit to ensure there's a new mtime
+ super
end
end
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/logger_test.rb b/activesupport/test/logger_test.rb
index d2801849ca..5a91420f1e 100644
--- a/activesupport/test/logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -3,6 +3,7 @@ require 'multibyte_test_helpers'
require 'stringio'
require 'fileutils'
require 'tempfile'
+require 'concurrent/atomics'
class LoggerTest < ActiveSupport::TestCase
include MultibyteTestHelpers
@@ -16,6 +17,14 @@ class LoggerTest < ActiveSupport::TestCase
@logger = Logger.new(@output)
end
+ def test_log_outputs_to
+ assert Logger.logger_outputs_to?(@logger, @output), "Expected logger_outputs_to? @output to return true but was false"
+ assert Logger.logger_outputs_to?(@logger, @output, STDOUT), "Expected logger_outputs_to? @output or STDOUT to return true but was false"
+
+ assert_not Logger.logger_outputs_to?(@logger, STDOUT), "Expected logger_outputs_to? to STDOUT to return false, but was true"
+ assert_not Logger.logger_outputs_to?(@logger, STDOUT, STDERR), "Expected logger_outputs_to? to STDOUT or STDERR to return false, but was true"
+ end
+
def test_write_binary_data_to_existing_file
t = Tempfile.new ['development', 'log']
t.binmode
@@ -64,7 +73,7 @@ class LoggerTest < ActiveSupport::TestCase
def test_should_not_log_debug_messages_when_log_level_is_info
@logger.level = Logger::INFO
@logger.add(Logger::DEBUG, @message)
- assert ! @output.string.include?(@message)
+ assert_not @output.string.include?(@message)
end
def test_should_add_message_passed_as_block_when_using_add
@@ -113,6 +122,7 @@ class LoggerTest < ActiveSupport::TestCase
end
def test_buffer_multibyte
+ @logger.level = Logger::INFO
@logger.info(UNICODE_STRING)
@logger.info(BYTE_STRING)
assert @output.string.include?(UNICODE_STRING)
@@ -120,14 +130,137 @@ class LoggerTest < ActiveSupport::TestCase
byte_string.force_encoding("ASCII-8BIT")
assert byte_string.include?(BYTE_STRING)
end
-
+
def test_silencing_everything_but_errors
@logger.silence do
@logger.debug "NOT THERE"
@logger.error "THIS IS HERE"
end
-
- assert !@output.string.include?("NOT THERE")
+
+ assert_not @output.string.include?("NOT THERE")
assert @output.string.include?("THIS IS HERE")
end
+
+ def test_logger_silencing_works_for_broadcast
+ another_output = StringIO.new
+ another_logger = Logger.new(another_output)
+
+ @logger.extend Logger.broadcast(another_logger)
+
+ @logger.debug "CORRECT DEBUG"
+ @logger.silence do
+ @logger.debug "FAILURE"
+ @logger.error "CORRECT ERROR"
+ end
+
+ assert @output.string.include?("CORRECT DEBUG")
+ assert @output.string.include?("CORRECT ERROR")
+ assert_not @output.string.include?("FAILURE")
+
+ assert another_output.string.include?("CORRECT DEBUG")
+ assert another_output.string.include?("CORRECT ERROR")
+ assert_not another_output.string.include?("FAILURE")
+ end
+
+ def test_broadcast_silencing_does_not_break_plain_ruby_logger
+ another_output = StringIO.new
+ another_logger = ::Logger.new(another_output)
+
+ @logger.extend Logger.broadcast(another_logger)
+
+ @logger.debug "CORRECT DEBUG"
+ @logger.silence do
+ @logger.debug "FAILURE"
+ @logger.error "CORRECT ERROR"
+ end
+
+ assert @output.string.include?("CORRECT DEBUG")
+ assert @output.string.include?("CORRECT ERROR")
+ assert_not @output.string.include?("FAILURE")
+
+ assert another_output.string.include?("CORRECT DEBUG")
+ assert another_output.string.include?("CORRECT ERROR")
+ assert another_output.string.include?("FAILURE")
+ # We can't silence plain ruby Logger cause with thread safety
+ # but at least we don't break it
+ end
+
+ def test_logger_level_per_object_thread_safety
+ logger1 = Logger.new(StringIO.new)
+ logger2 = Logger.new(StringIO.new)
+
+ level = Logger::DEBUG
+ assert_equal level, logger1.level, "Expected level #{level_name(level)}, got #{level_name(logger1.level)}"
+ assert_equal level, logger2.level, "Expected level #{level_name(level)}, got #{level_name(logger2.level)}"
+
+ logger1.level = Logger::ERROR
+ assert_equal level, logger2.level, "Expected level #{level_name(level)}, got #{level_name(logger2.level)}"
+ end
+
+ def test_logger_level_main_thread_safety
+ @logger.level = Logger::INFO
+ assert_level(Logger::INFO)
+
+ latch = Concurrent::CountDownLatch.new
+ latch2 = Concurrent::CountDownLatch.new
+
+ t = Thread.new do
+ latch.wait
+ assert_level(Logger::INFO)
+ latch2.count_down
+ end
+
+ @logger.silence(Logger::ERROR) do
+ assert_level(Logger::ERROR)
+ latch.count_down
+ latch2.wait
+ end
+
+ t.join
+ end
+
+ def test_logger_level_local_thread_safety
+ @logger.level = Logger::INFO
+ assert_level(Logger::INFO)
+
+ thread_1_latch = Concurrent::CountDownLatch.new
+ thread_2_latch = Concurrent::CountDownLatch.new
+
+ threads = (1..2).collect do |thread_number|
+ Thread.new do
+ # force thread 2 to wait until thread 1 is already in @logger.silence
+ thread_2_latch.wait if thread_number == 2
+
+ @logger.silence(Logger::ERROR) do
+ assert_level(Logger::ERROR)
+ @logger.silence(Logger::DEBUG) do
+ # allow thread 2 to finish but hold thread 1
+ if thread_number == 1
+ thread_2_latch.count_down
+ thread_1_latch.wait
+ end
+ assert_level(Logger::DEBUG)
+ end
+ end
+
+ # allow thread 1 to finish
+ assert_level(Logger::INFO)
+ thread_1_latch.count_down if thread_number == 2
+ end
+ end
+
+ threads.each(&:join)
+ assert_level(Logger::INFO)
+ end
+
+ private
+ def level_name(level)
+ ::Logger::Severity.constants.find do |severity|
+ Logger.const_get(severity) == level
+ end.to_s
+ end
+
+ def assert_level(level)
+ assert_equal level, @logger.level, "Expected level #{level_name(level)}, got #{level_name(@logger.level)}"
+ end
end
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index 8d4d9d736c..c1e0b19248 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -612,28 +612,54 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
['abc', 3],
['こにちわ', 4],
[[0x0924, 0x094D, 0x0930].pack('U*'), 2],
+ # GB3
[%w(cr lf), 1],
+ # GB4
+ [%w(cr n), 2],
+ [%w(lf n), 2],
+ [%w(control n), 2],
+ [%w(cr extend), 2],
+ [%w(lf extend), 2],
+ [%w(control extend), 2],
+ # GB 5
+ [%w(n cr), 2],
+ [%w(n lf), 2],
+ [%w(n control), 2],
+ [%w(extend cr), 2],
+ [%w(extend lf), 2],
+ [%w(extend control), 2],
+ # GB 6
[%w(l l), 1],
[%w(l v), 1],
[%w(l lv), 1],
[%w(l lvt), 1],
+ # GB7
[%w(lv v), 1],
[%w(lv t), 1],
[%w(v v), 1],
[%w(v t), 1],
+ # GB8
[%w(lvt t), 1],
[%w(t t), 1],
+ # GB8a
+ [%w(r r), 1],
+ # GB9
[%w(n extend), 1],
+ # GB9a
+ [%w(n spacingmark), 1],
+ # GB10
[%w(n n), 2],
+ # Other
[%w(n cr lf n), 3],
- [%w(n l v t), 2]
+ [%w(n l v t), 2],
+ [%w(cr extend n), 3],
].each do |input, expected_length|
if input.kind_of?(Array)
str = string_from_classes(input)
else
str = input
end
- assert_equal expected_length, chars(str).grapheme_length
+ assert_equal expected_length, chars(str).grapheme_length, input.inspect
end
end
@@ -698,7 +724,7 @@ class MultibyteCharsExtrasTest < ActiveSupport::TestCase
# Characters from the character classes as described in UAX #29
character_from_class = {
:l => 0x1100, :v => 0x1160, :t => 0x11A8, :lv => 0xAC00, :lvt => 0xAC01, :cr => 0x000D, :lf => 0x000A,
- :extend => 0x094D, :n => 0x64
+ :extend => 0x094D, :n => 0x64, :spacingmark => 0x0903, :r => 0x1F1E6, :control => 0x0001
}
classes.collect do |k|
character_from_class[k.intern]
diff --git a/activesupport/test/multibyte_conformance_test.rb b/activesupport/test/multibyte_conformance_test.rb
index 2a885e32bf..9fca47a985 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -5,30 +5,30 @@ require 'fileutils'
require 'open-uri'
require 'tmpdir'
-class Downloader
- def self.download(from, to)
- unless File.exist?(to)
- unless File.exist?(File.dirname(to))
- system "mkdir -p #{File.dirname(to)}"
- end
- open(from) do |source|
- File.open(to, 'w') do |target|
- source.each_line do |l|
- target.write l
+class MultibyteConformanceTest < ActiveSupport::TestCase
+ class Downloader
+ def self.download(from, to)
+ unless File.exist?(to)
+ unless File.exist?(File.dirname(to))
+ system "mkdir -p #{File.dirname(to)}"
+ end
+ open(from) do |source|
+ File.open(to, 'w') do |target|
+ source.each_line do |l|
+ target.write l
+ end
end
end
end
+ true
end
- true
end
-end
-class MultibyteConformanceTest < ActiveSupport::TestCase
include MultibyteTestHelpers
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)
@@ -104,11 +104,8 @@ class MultibyteConformanceTest < ActiveSupport::TestCase
protected
def each_line_of_norm_tests(&block)
- lines = 0
- max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile
File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f |
- until f.eof? || (max_test_lines > 38 and lines > max_test_lines)
- lines += 1
+ until f.eof?
line = f.gets.chomp!
next if (line.empty? || line =~ /^\#/)
diff --git a/activesupport/test/multibyte_grapheme_break_conformance_test.rb b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
new file mode 100644
index 0000000000..6e2f02abed
--- /dev/null
+++ b/activesupport/test/multibyte_grapheme_break_conformance_test.rb
@@ -0,0 +1,76 @@
+# encoding: utf-8
+
+require 'abstract_unit'
+
+require 'fileutils'
+require 'open-uri'
+require 'tmpdir'
+
+class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase
+ class Downloader
+ def self.download(from, to)
+ unless File.exist?(to)
+ $stderr.puts "Downloading #{from} to #{to}"
+ unless File.exist?(File.dirname(to))
+ system "mkdir -p #{File.dirname(to)}"
+ end
+ open(from) do |source|
+ File.open(to, 'w') do |target|
+ source.each_line do |l|
+ target.write l
+ end
+ end
+ end
+ end
+ end
+ end
+
+ TEST_DATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd/auxiliary"
+ TEST_DATA_FILE = '/GraphemeBreakTest.txt'
+ CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance"
+
+ def setup
+ FileUtils.mkdir_p(CACHE_DIR)
+ Downloader.download(TEST_DATA_URL + TEST_DATA_FILE, CACHE_DIR + TEST_DATA_FILE)
+ end
+
+ def test_breaks
+ each_line_of_break_tests do |*cols|
+ *clusters, comment = *cols
+ packed = ActiveSupport::Multibyte::Unicode.pack_graphemes(clusters)
+ assert_equal clusters, ActiveSupport::Multibyte::Unicode.unpack_graphemes(packed), comment
+ end
+ end
+
+ protected
+ def each_line_of_break_tests(&block)
+ lines = 0
+ max_test_lines = 0 # Don't limit below 21, because that's the header of the testfile
+ File.open(File.join(CACHE_DIR, TEST_DATA_FILE), 'r') do | f |
+ until f.eof? || (max_test_lines > 21 and lines > max_test_lines)
+ lines += 1
+ line = f.gets.chomp!
+ next if (line.empty? || line =~ /^\#/)
+
+ cols, comment = line.split("#")
+ # Cluster breaks are represented by ÷
+ clusters = cols.split("÷").map{|e| e.strip}.reject{|e| e.empty? }
+ clusters = clusters.map do |cluster|
+ # Codepoints within each cluster are separated by ×
+ codepoints = cluster.split("×").map{|e| e.strip}.reject{|e| e.empty? }
+ # codepoints are in hex in the test suite, pack wants them as integers
+ codepoints.map{|codepoint| codepoint.to_i(16)}
+ end
+
+ # The tests contain a solitary U+D800 <Non Private Use High
+ # Surrogate, First> character, which Ruby does not allow to stand
+ # alone in a UTF-8 string. So we'll just skip it.
+ next if clusters.flatten.include?(0xd800)
+
+ clusters << comment.strip
+
+ yield(*clusters)
+ end
+ end
+ end
+end
diff --git a/activesupport/test/multibyte_normalization_conformance_test.rb b/activesupport/test/multibyte_normalization_conformance_test.rb
new file mode 100644
index 0000000000..0d31c9520f
--- /dev/null
+++ b/activesupport/test/multibyte_normalization_conformance_test.rb
@@ -0,0 +1,129 @@
+# encoding: utf-8
+
+require 'abstract_unit'
+require 'multibyte_test_helpers'
+
+require 'fileutils'
+require 'open-uri'
+require 'tmpdir'
+
+class MultibyteNormalizationConformanceTest < ActiveSupport::TestCase
+ class Downloader
+ def self.download(from, to)
+ unless File.exist?(to)
+ $stderr.puts "Downloading #{from} to #{to}"
+ unless File.exist?(File.dirname(to))
+ system "mkdir -p #{File.dirname(to)}"
+ end
+ open(from) do |source|
+ File.open(to, 'w') do |target|
+ source.each_line do |l|
+ target.write l
+ end
+ end
+ end
+ end
+ end
+ end
+
+ include MultibyteTestHelpers
+
+ UNIDATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd"
+ UNIDATA_FILE = '/NormalizationTest.txt'
+ CACHE_DIR = "#{Dir.tmpdir}/cache/unicode_conformance"
+
+ def setup
+ FileUtils.mkdir_p(CACHE_DIR)
+ Downloader.download(UNIDATA_URL + UNIDATA_FILE, CACHE_DIR + UNIDATA_FILE)
+ @proxy = ActiveSupport::Multibyte::Chars
+ end
+
+ def test_normalizations_C
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
+
+ # CONFORMANCE:
+ # 1. The following invariants must be true for all conformant implementations
+ #
+ # NFC
+ # c2 == NFC(c1) == NFC(c2) == NFC(c3)
+ assert_equal_codepoints col2, @proxy.new(col1).normalize(:c), "Form C - Col 2 has to be NFC(1) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col2).normalize(:c), "Form C - Col 2 has to be NFC(2) - #{comment}"
+ assert_equal_codepoints col2, @proxy.new(col3).normalize(:c), "Form C - Col 2 has to be NFC(3) - #{comment}"
+ #
+ # c4 == NFC(c4) == NFC(c5)
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:c), "Form C - Col 4 has to be C(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:c), "Form C - Col 4 has to be C(5) - #{comment}"
+ end
+ end
+
+ def test_normalizations_D
+ each_line_of_norm_tests do |*cols|
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFD
+ # c3 == NFD(c1) == NFD(c2) == NFD(c3)
+ assert_equal_codepoints col3, @proxy.new(col1).normalize(:d), "Form D - Col 3 has to be NFD(1) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col2).normalize(:d), "Form D - Col 3 has to be NFD(2) - #{comment}"
+ assert_equal_codepoints col3, @proxy.new(col3).normalize(:d), "Form D - Col 3 has to be NFD(3) - #{comment}"
+ # c5 == NFD(c4) == NFD(c5)
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:d), "Form D - Col 5 has to be NFD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:d), "Form D - Col 5 has to be NFD(5) - #{comment}"
+ end
+ end
+
+ def test_normalizations_KC
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKC
+ # c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5)
+ assert_equal_codepoints col4, @proxy.new(col1).normalize(:kc), "Form D - Col 4 has to be NFKC(1) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col2).normalize(:kc), "Form D - Col 4 has to be NFKC(2) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col3).normalize(:kc), "Form D - Col 4 has to be NFKC(3) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col4).normalize(:kc), "Form D - Col 4 has to be NFKC(4) - #{comment}"
+ assert_equal_codepoints col4, @proxy.new(col5).normalize(:kc), "Form D - Col 4 has to be NFKC(5) - #{comment}"
+ end
+ end
+
+ def test_normalizations_KD
+ each_line_of_norm_tests do | *cols |
+ col1, col2, col3, col4, col5, comment = *cols
+ #
+ # NFKD
+ # c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5)
+ assert_equal_codepoints col5, @proxy.new(col1).normalize(:kd), "Form KD - Col 5 has to be NFKD(1) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col2).normalize(:kd), "Form KD - Col 5 has to be NFKD(2) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col3).normalize(:kd), "Form KD - Col 5 has to be NFKD(3) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col4).normalize(:kd), "Form KD - Col 5 has to be NFKD(4) - #{comment}"
+ assert_equal_codepoints col5, @proxy.new(col5).normalize(:kd), "Form KD - Col 5 has to be NFKD(5) - #{comment}"
+ end
+ end
+
+ protected
+ def each_line_of_norm_tests(&block)
+ lines = 0
+ max_test_lines = 0 # Don't limit below 38, because that's the header of the testfile
+ File.open(File.join(CACHE_DIR, UNIDATA_FILE), 'r') do | f |
+ until f.eof? || (max_test_lines > 38 and lines > max_test_lines)
+ lines += 1
+ line = f.gets.chomp!
+ next if (line.empty? || line =~ /^\#/)
+
+ cols, comment = line.split("#")
+ cols = cols.split(";").map{|e| e.strip}.reject{|e| e.empty? }
+ next unless cols.length == 5
+
+ # codepoints are in hex in the test suite, pack wants them as integers
+ cols.map!{|c| c.split.map{|codepoint| codepoint.to_i(16)}.pack("U*") }
+ cols << comment
+
+ yield(*cols)
+ end
+ end
+ end
+
+ def inspect_codepoints(str)
+ str.to_s.unpack("U*").map{|cp| cp.to_s(16) }.join(' ')
+ end
+end
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index f729f0a95b..1cb17e6197 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -42,6 +42,21 @@ module Notifications
ActiveSupport::Notifications.instrument(name)
assert_equal expected, events
end
+
+ def test_subsribing_to_instrumentation_while_inside_it
+ # the repro requires that there are no evented subscribers for the "foo" event,
+ # so we have to duplicate some of the setup code
+ old_notifier = ActiveSupport::Notifications.notifier
+ ActiveSupport::Notifications.notifier = ActiveSupport::Notifications::Fanout.new
+
+ ActiveSupport::Notifications.subscribe('foo', TestSubscriber.new)
+
+ ActiveSupport::Notifications.instrument('foo') do
+ ActiveSupport::Notifications.subscribe('foo') {}
+ end
+ ensure
+ ActiveSupport::Notifications.notifier = old_notifier
+ end
end
class UnsubscribeTest < TestCase
@@ -217,7 +232,7 @@ module Notifications
assert_equal 1, @events.size
assert_equal Hash[:payload => "notifications",
- :exception => ["RuntimeError", "FAIL"]], @events.last.payload
+ :exception => ["RuntimeError", "FAIL"], :exception_object => e], @events.last.payload
end
def test_event_is_pushed_even_without_block
diff --git a/activesupport/test/number_helper_test.rb b/activesupport/test/number_helper_test.rb
index 7f62d7c0b3..074c872efc 100644
--- a/activesupport/test/number_helper_test.rb
+++ b/activesupport/test/number_helper_test.rb
@@ -34,6 +34,14 @@ module ActiveSupport
gigabytes(number) * 1024
end
+ def petabytes(number)
+ terabytes(number) * 1024
+ end
+
+ def exabytes(number)
+ petabytes(number) * 1024
+ end
+
def test_number_to_phone
[@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
assert_equal("555-1234", number_helper.number_to_phone(5551234))
@@ -49,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
@@ -66,7 +76,6 @@ module ActiveSupport
assert_equal("1,234,567,890.50 K&#269;", number_helper.number_to_currency("1234567890.50", {:unit => "K&#269;", :format => "%n %u"}))
assert_equal("1,234,567,890.50 - K&#269;", number_helper.number_to_currency("-1234567890.50", {:unit => "K&#269;", :format => "%n %u", :negative_format => "%n - %u"}))
assert_equal("0.00", number_helper.number_to_currency(+0.0, {:unit => "", :negative_format => "(%n)"}))
- assert_equal("(0.00)", number_helper.number_to_currency(-0.0, {:unit => "", :negative_format => "(%n)"}))
end
end
@@ -219,7 +228,9 @@ module ActiveSupport
assert_equal '1.18 MB', number_helper.number_to_human_size(1234567)
assert_equal '1.15 GB', number_helper.number_to_human_size(1234567890)
assert_equal '1.12 TB', number_helper.number_to_human_size(1234567890123)
- assert_equal '1030 TB', number_helper.number_to_human_size(terabytes(1026))
+ assert_equal '1.1 PB', number_helper.number_to_human_size(1234567890123456)
+ assert_equal '1.07 EB', number_helper.number_to_human_size(1234567890123456789)
+ assert_equal '1030 EB', number_helper.number_to_human_size(exabytes(1026))
assert_equal '444 KB', number_helper.number_to_human_size(kilobytes(444))
assert_equal '1020 MB', number_helper.number_to_human_size(megabytes(1023))
assert_equal '3 TB', number_helper.number_to_human_size(terabytes(3))
@@ -245,6 +256,8 @@ module ActiveSupport
assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ assert_equal '1.23 PB', number_helper.number_to_human_size(1234567890123456, :prefix => :si)
+ assert_equal '1.23 EB', number_helper.number_to_human_size(1234567890123456789, :prefix => :si)
end
end
end
diff --git a/activesupport/test/reloader_test.rb b/activesupport/test/reloader_test.rb
new file mode 100644
index 0000000000..958cb49993
--- /dev/null
+++ b/activesupport/test/reloader_test.rb
@@ -0,0 +1,85 @@
+require 'abstract_unit'
+
+class ReloaderTest < ActiveSupport::TestCase
+ def test_prepare_callback
+ prepared = false
+ reloader.to_prepare { prepared = true }
+
+ assert !prepared
+ reloader.prepare!
+ assert prepared
+
+ prepared = false
+ reloader.wrap do
+ assert prepared
+ prepared = false
+ end
+ assert !prepared
+ end
+
+ def test_only_run_when_check_passes
+ r = new_reloader { true }
+ invoked = false
+ r.to_run { invoked = true }
+ r.wrap { }
+ assert invoked
+
+ r = new_reloader { false }
+ invoked = false
+ r.to_run { invoked = true }
+ r.wrap { }
+ assert !invoked
+ end
+
+ def test_full_reload_sequence
+ called = []
+ reloader.to_prepare { called << :prepare }
+ reloader.to_run { called << :reloader_run }
+ reloader.to_complete { called << :reloader_complete }
+ reloader.executor.to_run { called << :executor_run }
+ reloader.executor.to_complete { called << :executor_complete }
+
+ reloader.wrap { }
+ assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete], called
+
+ called = []
+ reloader.reload!
+ assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called
+
+ reloader.check = lambda { false }
+
+ called = []
+ reloader.wrap { }
+ assert_equal [:executor_run, :executor_complete], called
+
+ called = []
+ reloader.reload!
+ assert_equal [:executor_run, :reloader_run, :prepare, :reloader_complete, :executor_complete, :prepare], called
+ end
+
+ def test_class_unload_block
+ called = []
+ reloader.before_class_unload { called << :before_unload }
+ reloader.after_class_unload { called << :after_unload }
+ reloader.to_run do
+ class_unload! do
+ called << :unload
+ end
+ end
+ reloader.wrap { called << :body }
+
+ assert_equal [:before_unload, :unload, :after_unload, :body], called
+ end
+
+ private
+ def new_reloader(&check)
+ Class.new(ActiveSupport::Reloader).tap do |r|
+ r.check = check
+ r.executor = Class.new(ActiveSupport::Executor)
+ end
+ end
+
+ def reloader
+ @reloader ||= new_reloader { true }
+ 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/share_lock_test.rb b/activesupport/test/share_lock_test.rb
index 465a657308..acefa185a8 100644
--- a/activesupport/test/share_lock_test.rb
+++ b/activesupport/test/share_lock_test.rb
@@ -114,14 +114,17 @@ class ShareLockTest < ActiveSupport::TestCase
[true, false].each do |use_upgrading|
with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
begin
+ together = Concurrent::CyclicBarrier.new(2)
conflicting_exclusive_threads = [
Thread.new do
@lock.send(use_upgrading ? :sharing : :tap) do
+ together.wait
@lock.exclusive(purpose: :red, compatible: [:green, :purple]) {}
end
end,
Thread.new do
@lock.send(use_upgrading ? :sharing : :tap) do
+ together.wait
@lock.exclusive(purpose: :blue, compatible: [:green]) {}
end
end
@@ -183,11 +186,14 @@ class ShareLockTest < ActiveSupport::TestCase
load_params = [:load, [:load]]
unload_params = [:unload, [:unload, :load]]
+ all_sharing = Concurrent::CyclicBarrier.new(4)
+
[load_params, load_params, unload_params, unload_params].permutation do |thread_params|
with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
threads = thread_params.map do |purpose, compatible|
Thread.new do
@lock.sharing do
+ all_sharing.wait
@lock.exclusive(purpose: purpose, compatible: compatible) do
scratch_pad_mutex.synchronize { scratch_pad << purpose }
end
@@ -209,6 +215,245 @@ class ShareLockTest < ActiveSupport::TestCase
end
end
+ def test_new_share_attempts_block_on_waiting_exclusive
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ release_exclusive = Concurrent::CountDownLatch.new
+
+ waiting_exclusive = Thread.new do
+ @lock.sharing do
+ @lock.exclusive do
+ release_exclusive.wait
+ end
+ end
+ end
+ assert_threads_stuck waiting_exclusive
+
+ late_share_attempt = Thread.new do
+ @lock.sharing {}
+ end
+ assert_threads_stuck late_share_attempt
+
+ sharing_thread_release_latch.count_down
+ assert_threads_stuck late_share_attempt
+
+ release_exclusive.count_down
+ assert_threads_not_stuck late_share_attempt
+ end
+ end
+
+ def test_share_remains_reentrant_ignoring_a_waiting_exclusive
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ ready = Concurrent::CyclicBarrier.new(2)
+ attempt_reentrancy = Concurrent::CountDownLatch.new
+
+ sharer = Thread.new do
+ @lock.sharing do
+ ready.wait
+ attempt_reentrancy.wait
+ @lock.sharing {}
+ end
+ end
+
+ exclusive = Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive {}
+ end
+ end
+
+ assert_threads_stuck exclusive
+
+ attempt_reentrancy.count_down
+
+ assert_threads_not_stuck sharer
+ assert_threads_stuck exclusive
+ end
+ end
+
+ def test_compatible_exclusives_cooperate_to_both_proceed
+ ready = Concurrent::CyclicBarrier.new(2)
+ done = Concurrent::CyclicBarrier.new(2)
+
+ threads = 2.times.map do
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x, compatible: [:x], after_compatible: [:x]) {}
+ done.wait
+ end
+ end
+ end
+
+ assert_threads_not_stuck threads
+ end
+
+ def test_manual_yield
+ ready = Concurrent::CyclicBarrier.new(2)
+ done = Concurrent::CyclicBarrier.new(2)
+
+ threads = [
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x) {}
+ done.wait
+ end
+ end,
+
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.yield_shares(compatible: [:x]) do
+ done.wait
+ end
+ end
+ end,
+ ]
+
+ assert_threads_not_stuck threads
+ end
+
+ def test_manual_incompatible_yield
+ ready = Concurrent::CyclicBarrier.new(2)
+ done = Concurrent::CyclicBarrier.new(2)
+
+ threads = [
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x) {}
+ done.wait
+ end
+ end,
+
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.yield_shares(compatible: [:y]) do
+ done.wait
+ end
+ end
+ end,
+ ]
+
+ assert_threads_stuck threads
+ ensure
+ threads.each(&:kill) if threads
+ end
+
+ def test_manual_recursive_yield
+ ready = Concurrent::CyclicBarrier.new(2)
+ done = Concurrent::CyclicBarrier.new(2)
+ do_nesting = Concurrent::CountDownLatch.new
+
+ threads = [
+ Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x) {}
+ done.wait
+ end
+ end,
+
+ Thread.new do
+ @lock.sharing do
+ @lock.yield_shares(compatible: [:x]) do
+ @lock.sharing do
+ ready.wait
+ do_nesting.wait
+ @lock.yield_shares(compatible: [:x, :y]) do
+ done.wait
+ end
+ end
+ end
+ end
+ end
+ ]
+
+ assert_threads_stuck threads
+ do_nesting.count_down
+
+ assert_threads_not_stuck threads
+ end
+
+ def test_manual_recursive_yield_cannot_expand_outer_compatible
+ ready = Concurrent::CyclicBarrier.new(2)
+ do_compatible_nesting = Concurrent::CountDownLatch.new
+ in_compatible_nesting = Concurrent::CountDownLatch.new
+
+ incompatible_thread = Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.exclusive(purpose: :x) {}
+ end
+ end
+
+ yield_shares_thread = Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.yield_shares(compatible: [:y]) do
+ do_compatible_nesting.wait
+ @lock.sharing do
+ @lock.yield_shares(compatible: [:x, :y]) do
+ in_compatible_nesting.wait
+ end
+ end
+ end
+ end
+ end
+
+ assert_threads_stuck incompatible_thread
+ do_compatible_nesting.count_down
+ assert_threads_stuck incompatible_thread
+ in_compatible_nesting.count_down
+ assert_threads_not_stuck [yield_shares_thread, incompatible_thread]
+ end
+
+ def test_manual_recursive_yield_restores_previous_compatible
+ ready = Concurrent::CyclicBarrier.new(2)
+ do_nesting = Concurrent::CountDownLatch.new
+ after_nesting = Concurrent::CountDownLatch.new
+
+ incompatible_thread = Thread.new do
+ ready.wait
+ @lock.exclusive(purpose: :z) {}
+ end
+
+ recursive_yield_shares_thread = Thread.new do
+ @lock.sharing do
+ ready.wait
+ @lock.yield_shares(compatible: [:y]) do
+ do_nesting.wait
+ @lock.sharing do
+ @lock.yield_shares(compatible: [:x, :y]) {}
+ end
+ after_nesting.wait
+ end
+ end
+ end
+
+ assert_threads_stuck incompatible_thread
+ do_nesting.count_down
+ assert_threads_stuck incompatible_thread
+
+ compatible_thread = Thread.new do
+ @lock.exclusive(purpose: :y) {}
+ end
+ assert_threads_not_stuck compatible_thread
+
+ post_nesting_incompatible_thread = Thread.new do
+ @lock.exclusive(purpose: :x) {}
+ end
+ assert_threads_stuck post_nesting_incompatible_thread
+
+ after_nesting.count_down
+ assert_threads_not_stuck recursive_yield_shares_thread
+ # post_nesting_incompatible_thread can now proceed
+ assert_threads_not_stuck post_nesting_incompatible_thread
+ # assert_threads_not_stuck can now proceed
+ assert_threads_not_stuck incompatible_thread
+ end
+
def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads
scratch_pad = []
scratch_pad_mutex = Mutex.new
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