aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md293
-rw-r--r--activesupport/MIT-LICENSE4
-rw-r--r--activesupport/README.rdoc3
-rw-r--r--activesupport/Rakefile14
-rw-r--r--activesupport/activesupport.gemspec3
-rwxr-xr-xactivesupport/bin/generate_tables5
-rwxr-xr-xactivesupport/bin/test4
-rw-r--r--activesupport/lib/active_support.rb7
-rw-r--r--activesupport/lib/active_support/array_inquirer.rb2
-rw-r--r--activesupport/lib/active_support/backtrace_cleaner.rb2
-rw-r--r--activesupport/lib/active_support/cache.rb52
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb57
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb107
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb35
-rw-r--r--activesupport/lib/active_support/cache/null_store.rb5
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache.rb47
-rw-r--r--activesupport/lib/active_support/callbacks.rb162
-rw-r--r--activesupport/lib/active_support/concern.rb2
-rw-r--r--activesupport/lib/active_support/concurrency/latch.rb22
-rw-r--r--activesupport/lib/active_support/concurrency/share_lock.rb142
-rw-r--r--activesupport/lib/active_support/core_ext/array/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/big_decimal/conversions.rb15
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/attribute.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb45
-rw-r--r--activesupport/lib/active_support/core_ext/class/subclasses.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/date.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/blank.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/calculations.rb45
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/zones.rb3
-rw-r--r--activesupport/lib/active_support/core_ext/date_time.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/blank.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/enumerable.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb56
-rw-r--r--activesupport/lib/active_support/core_ext/hash/conversions.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/except.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/hash/indifferent_access.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/hash/keys.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/hash/transform_values.rb16
-rw-r--r--activesupport/lib/active_support/core_ext/integer/time.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/kernel/reporting.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/load_error.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/module/anonymous.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/module/attribute_accessors.rb24
-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/concerning.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb13
-rw-r--r--activesupport/lib/active_support/core_ext/module/qualified_const.rb42
-rw-r--r--activesupport/lib/active_support/core_ext/module/remove_method.rb20
-rw-r--r--activesupport/lib/active_support/core_ext/numeric/conversions.rb46
-rw-r--r--activesupport/lib/active_support/core_ext/object/blank.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/object/deep_dup.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/object/inclusion.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/object/try.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/range/conversions.rb17
-rw-r--r--activesupport/lib/active_support/core_ext/securerandom.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/string/conversions.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/string/multibyte.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/string/output_safety.rb10
-rw-r--r--activesupport/lib/active_support/core_ext/string/strip.rb9
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb26
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb6
-rw-r--r--activesupport/lib/active_support/core_ext/time/zones.rb5
-rw-r--r--activesupport/lib/active_support/core_ext/uri.rb4
-rw-r--r--activesupport/lib/active_support/dependencies.rb115
-rw-r--r--activesupport/lib/active_support/dependencies/interlock.rb47
-rw-r--r--activesupport/lib/active_support/deprecation.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/behaviors.rb6
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb56
-rw-r--r--activesupport/lib/active_support/deprecation/proxy_wrappers.rb6
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb15
-rw-r--r--activesupport/lib/active_support/duration.rb4
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb150
-rw-r--r--activesupport/lib/active_support/file_update_checker.rb2
-rw-r--r--activesupport/lib/active_support/gem_version.rb4
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb61
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb19
-rw-r--r--activesupport/lib/active_support/inflector/inflections.rb41
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb38
-rw-r--r--activesupport/lib/active_support/inflector/transliterate.rb47
-rw-r--r--activesupport/lib/active_support/json/encoding.rb4
-rw-r--r--activesupport/lib/active_support/key_generator.rb8
-rw-r--r--activesupport/lib/active_support/locale/en.yml2
-rw-r--r--activesupport/lib/active_support/log_subscriber.rb2
-rw-r--r--activesupport/lib/active_support/log_subscriber/test_helper.rb6
-rw-r--r--activesupport/lib/active_support/logger.rb26
-rw-r--r--activesupport/lib/active_support/logger_silence.rb27
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb6
-rw-r--r--activesupport/lib/active_support/message_verifier.rb8
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb17
-rw-r--r--activesupport/lib/active_support/multibyte/unicode.rb62
-rw-r--r--activesupport/lib/active_support/notifications.rb4
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb12
-rw-r--r--activesupport/lib/active_support/notifications/instrumenter.rb10
-rw-r--r--activesupport/lib/active_support/number_helper.rb26
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb9
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_converter.rb6
-rw-r--r--activesupport/lib/active_support/number_helper/number_to_human_size_converter.rb6
-rw-r--r--activesupport/lib/active_support/ordered_hash.rb2
-rw-r--r--activesupport/lib/active_support/ordered_options.rb16
-rw-r--r--activesupport/lib/active_support/per_thread_registry.rb11
-rw-r--r--activesupport/lib/active_support/railtie.rb2
-rw-r--r--activesupport/lib/active_support/testing/assertions.rb8
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb9
-rw-r--r--activesupport/lib/active_support/testing/deprecation.rb17
-rw-r--r--activesupport/lib/active_support/testing/file_fixtures.rb2
-rw-r--r--activesupport/lib/active_support/testing/isolation.rb18
-rw-r--r--activesupport/lib/active_support/testing/method_call_assertions.rb41
-rw-r--r--activesupport/lib/active_support/testing/time_helpers.rb2
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb81
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb27
-rw-r--r--activesupport/lib/active_support/values/unicode_tables.datbin1001806 -> 1068675 bytes
-rw-r--r--activesupport/lib/active_support/xml_mini.rb3
-rw-r--r--activesupport/lib/active_support/xml_mini/jdom.rb11
-rw-r--r--activesupport/lib/active_support/xml_mini/rexml.rb11
-rw-r--r--activesupport/test/abstract_unit.rb5
-rw-r--r--activesupport/test/array_inquirer_test.rb9
-rw-r--r--activesupport/test/autoloading_fixtures/a/c/e/f.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/a/c/em/f.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/d.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/e.rb2
-rw-r--r--activesupport/test/autoloading_fixtures/em.rb2
-rw-r--r--activesupport/test/caching_test.rb296
-rw-r--r--activesupport/test/callbacks_test.rb42
-rw-r--r--activesupport/test/concern_test.rb25
-rw-r--r--activesupport/test/constantize_test_cases.rb4
-rw-r--r--activesupport/test/core_ext/class/delegating_attributes_test.rb122
-rw-r--r--activesupport/test/core_ext/date_ext_test.rb33
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb75
-rw-r--r--activesupport/test/core_ext/duration_test.rb34
-rw-r--r--activesupport/test/core_ext/enumerable_test.rb14
-rw-r--r--activesupport/test/core_ext/hash/transform_keys_test.rb16
-rw-r--r--activesupport/test/core_ext/hash/transform_values_test.rb16
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb75
-rw-r--r--activesupport/test/core_ext/marshal_test.rb32
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb109
-rw-r--r--activesupport/test/core_ext/module/attribute_accessor_test.rb20
-rw-r--r--activesupport/test/core_ext/module/qualified_const_test.rb130
-rw-r--r--activesupport/test/core_ext/module/remove_method_test.rb38
-rw-r--r--activesupport/test/core_ext/module_test.rb15
-rw-r--r--activesupport/test/core_ext/numeric_ext_test.rb58
-rw-r--r--activesupport/test/core_ext/object/blank_test.rb1
-rw-r--r--activesupport/test/core_ext/object/json_gem_encoding_test.rb66
-rw-r--r--activesupport/test/core_ext/object/try_test.rb2
-rw-r--r--activesupport/test/core_ext/range_ext_test.rb6
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb226
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb123
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb111
-rw-r--r--activesupport/test/dependencies_test.rb41
-rw-r--r--activesupport/test/deprecation/method_wrappers_test.rb34
-rw-r--r--activesupport/test/deprecation_test.rb30
-rw-r--r--activesupport/test/evented_file_update_checker_test.rb155
-rw-r--r--activesupport/test/file_update_checker_shared_tests.rb242
-rw-r--r--activesupport/test/file_update_checker_test.rb113
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb11
-rw-r--r--activesupport/test/inflector_test.rb176
-rw-r--r--activesupport/test/inflector_test_cases.rb35
-rw-r--r--activesupport/test/json/encoding_test.rb141
-rw-r--r--activesupport/test/json/encoding_test_cases.rb88
-rw-r--r--activesupport/test/log_subscriber_test.rb9
-rw-r--r--activesupport/test/logger_test.rb97
-rw-r--r--activesupport/test/message_verifier_test.rb1
-rw-r--r--activesupport/test/multibyte_chars_test.rb44
-rw-r--r--activesupport/test/multibyte_conformance_test.rb6
-rw-r--r--activesupport/test/multibyte_grapheme_break_conformance.rb76
-rw-r--r--activesupport/test/multibyte_normalization_conformance.rb129
-rw-r--r--activesupport/test/multibyte_proxy_test.rb1
-rw-r--r--activesupport/test/multibyte_test_helpers.rb1
-rw-r--r--activesupport/test/multibyte_unicode_database_test.rb5
-rw-r--r--activesupport/test/notifications_test.rb17
-rw-r--r--activesupport/test/number_helper_test.rb37
-rw-r--r--activesupport/test/ordered_options_test.rb15
-rw-r--r--activesupport/test/share_lock_test.rb333
-rw-r--r--activesupport/test/tagged_logging_test.rb12
-rw-r--r--activesupport/test/test_case_test.rb8
-rw-r--r--activesupport/test/testing/method_call_assertions_test.rb123
-rw-r--r--activesupport/test/time_travel_test.rb100
-rw-r--r--activesupport/test/time_zone_test.rb15
-rw-r--r--activesupport/test/xml_mini_test.rb1
187 files changed, 4889 insertions, 1786 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index d6fa651e39..21c79949ca 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,274 @@
+* Match `HashWithIndifferentAccess#default`'s behaviour with `Hash#default`.
+
+ *David Cornu*
+
+* Adds `:exception_object` key to `ActiveSupport::Notifications::Instrumenter`
+ payload when an exception is raised.
+
+ Adds new key/value pair to payload when an exception is raised:
+ e.g. `:exception_object => #<RuntimeError: FAIL>`.
+
+ *Ryan T. Hosford*
+
+* Support extended grapheme clusters and UAX 29.
+
+ *Adam Roben*
+
+* Add petabyte and exabyte numeric conversion.
+
+ *Akshay Vishnoi*
+
+## Rails 5.0.0.beta1 (December 18, 2015) ##
+
+* Add thread_m/cattr_accessor/reader/writer suite of methods for declaring class and module variables that live per-thread.
+ This makes it easy to declare per-thread globals that are encapsulated. Note: This is a sharp edge. A wild proliferation
+ of globals is A Bad Thing. But like other sharp tools, when it's right, it's right.
+
+ Here's an example of a simple event tracking system where the object being tracked needs not pass a creator that it
+ doesn't need itself along:
+
+ module Current
+ thread_mattr_accessor :account
+ thread_mattr_accessor :user
+
+ def self.reset() self.account = self.user = nil end
+ end
+
+ class ApplicationController < ActionController::Base
+ before_action :set_current
+ after_action { Current.reset }
+
+ private
+ def set_current
+ Current.account = Account.find(params[:account_id])
+ Current.user = Current.account.users.find(params[:user_id])
+ end
+ end
+
+ class MessagesController < ApplicationController
+ def create
+ @message = Message.create!(message_params)
+ end
+ end
+
+ class Message < ApplicationRecord
+ has_many :events
+ after_create :track_created
+
+ private
+ def track_created
+ events.create! origin: self, action: :create
+ end
+ end
+
+ class Event < ApplicationRecord
+ belongs_to :creator, class_name: 'User'
+ before_validation { self.creator ||= Current.user }
+ end
+
+ *DHH*
+
+
+* Deprecated `Module#qualified_const_` in favour of the builtin Module#const_
+ methods.
+
+ *Genadi Samokovarov*
+
+* Deprecate passing string to define callback.
+
+ *Yuichiro Kaneko*
+
+* `ActiveSupport::Cache::Store#namespaced_key`,
+ `ActiveSupport::Cache::MemCachedStore#escape_key`, and
+ `ActiveSupport::Cache::FileStore#key_file_path`
+ are deprecated and replaced with `normalize_key` that now calls `super`.
+
+ `ActiveSupport::Cache::LocaleCache#set_cache_value` is deprecated and replaced with `write_cache_value`.
+
+ *Michael Grosser*
+
+* Implements an evented file watcher to asynchronously detect changes in the
+ application source code, routes, locales, etc.
+
+ This watcher is disabled by default, applications my enable it in the configuration:
+
+ # config/environments/development.rb
+ config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+
+ This feature depends on the [listen](https://github.com/guard/listen) gem:
+
+ group :development do
+ gem 'listen', '~> 3.0.5'
+ end
+
+ *Puneet Agarwal* and *Xavier Noria*
+
+* Added `Time.days_in_year` to return the number of days in the given year, or the
+ current year if no argument is provided.
+
+ *Jon Pascoe*
+
+* Updated `parameterize` to preserve the case of a string, optionally.
+
+ Example:
+
+ parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
+ parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
+
+ *Swaathi Kakarla*
+
+* `HashWithIndifferentAccess.new` respects the default value or proc on objects
+ that respond to `#to_hash`. `.new_from_hash_copying_default` simply invokes `.new`.
+ All calls to `.new_from_hash_copying_default` are replaced with `.new`.
+
+ *Gordon Chan*
+
+* Change Integer#year to return a Fixnum instead of a Float to improve
+ consistency.
+
+ Integer#years returned a Float while the rest of the accompanying methods
+ (days, weeks, months, etc.) return a Fixnum.
+
+ Before:
+
+ 1.year # => 31557600.0
+
+ After:
+
+ 1.year # => 31557600
+
+ *Konstantinos Rousis*
+
+* Handle invalid UTF-8 strings when HTML escaping.
+
+ Use `ActiveSupport::Multibyte::Unicode.tidy_bytes` to handle invalid UTF-8
+ strings in `ERB::Util.unwrapped_html_escape` and `ERB::Util.html_escape_once`.
+ Prevents user-entered input passed from a querystring into a form field from
+ causing invalid byte sequence errors.
+
+ *Grey Baker*
+
+* Update `ActiveSupport::Multibyte::Chars#slice!` to return `nil` if the
+ arguments are out of bounds, to mirror the behavior of `String#slice!`
+
+ *Gourav Tiwari*
+
+* Fix `number_to_human` so that 999999999 rounds to "1 Billion" instead of
+ "1000 Million".
+
+ *Max Jacobson*
+
+* Fix `ActiveSupport::Deprecation#deprecate_methods` to report using the
+ current deprecator instance, where applicable.
+
+ *Brandon Dunne*
+
+* `Cache#fetch` instrumentation marks whether it was a `:hit`.
+
+ *Robin Clowers*
+
+* `assert_difference` and `assert_no_difference` now returns the result of the
+ yielded block.
+
+ Example:
+
+ post = assert_difference -> { Post.count }, 1 do
+ Post.create
+ end
+
+ *Lucas Mazza*
+
+* Short-circuit `blank?` on date and time values since they are never blank.
+
+ Fixes #21657.
+
+ *Andrew White*
+
+* Replaced deprecated `ThreadSafe::Cache` with its successor `Concurrent::Map` now that
+ the thread_safe gem has been merged into concurrent-ruby.
+
+ *Jerry D'Antonio*
+
+* Updated Unicode version to 8.0.0
+
+ *Anshul Sharma*
+
+* `number_to_currency` and `number_with_delimiter` now accept custom `delimiter_pattern` option
+ to handle placement of delimiter, to support currency formats like INR
+
+ Example:
+
+ number_to_currency(1230000, delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/, unit: '₹', format: "%u %n")
+ # => '₹ 12,30,000.00'
+
+ *Vipul A M*
+
+* Deprecate `:prefix` option of `number_to_human_size` with no replacement.
+
+ *Jean Boussier*
+
+* Fix `TimeWithZone#eql?` to properly handle `TimeWithZone` created from `DateTime`:
+ twz = DateTime.now.in_time_zone
+ twz.eql?(twz.dup) => true
+
+ Fixes #14178.
+
+ *Roque Pinel*
+
+* ActiveSupport::HashWithIndifferentAccess `select` and `reject` will now return
+ enumerator if called without block.
+
+ Fixes #20095.
+
+ *Bernard Potocki*
+
+* Removed `ActiveSupport::Concurrency::Latch`, superseded by `Concurrent::CountDownLatch`
+ from the concurrent-ruby gem.
+
+ *Jerry D'Antonio*
+
+* Fix not calling `#default` on `HashWithIndifferentAccess#to_hash` when only
+ `default_proc` is set, which could raise.
+
+ *Simon Eskildsen*
+
+* Fix setting `default_proc` on `HashWithIndifferentAccess#dup`.
+
+ *Simon Eskildsen*
+
+* Fix a range of values for parameters of the Time#change.
+
+ *Nikolay Kondratyev*
+
+* Add `Enumerable#pluck` to get the same values from arrays as from ActiveRecord
+ associations.
+
+ Fixes #20339.
+
+ *Kevin Deisz*
+
+* Add a bang version to `ActiveSupport::OrderedOptions` get methods which will raise
+ an `KeyError` if the value is `.blank?`.
+
+ Before:
+
+ if (slack_url = Rails.application.secrets.slack_url).present?
+ # Do something worthwhile
+ else
+ # Raise as important secret password is not specified
+ end
+
+ After:
+
+ slack_url = Rails.application.secrets.slack_url!
+
+ *Aditya Sanghi*, *Gaurish Sharma*
+
+* Remove deprecated `Class#superclass_delegating_accessor`.
+ Use `Class#class_attribute` instead.
+
+ *Akshay Vishnoi*
+
* Patch `Delegator` to work with `#try`.
Fixes #5790.
@@ -186,30 +457,24 @@
The preferred method to halt a callback chain from now on is to explicitly
`throw(:abort)`.
- In the past, returning `false` in an ActiveSupport callback had the side
- effect of halting the callback chain. This is not recommended anymore and,
- depending on the value of
- `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`, will
- either not work at all or display a deprecation warning.
+ 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 `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
+* Add `ActiveSupport.halt_callback_chains_on_return_false`
- Setting `Callbacks::CallbackChain.halt_and_display_warning_on_return_false`
- to `true` will let an app support the deprecated way of halting callback
- chains by returning `false`.
+ 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 callbacks, and only halt the chain upon `throw(:abort)`.
-
- The value can also be set with the Rails configuration option
- `config.active_support.halt_callback_chains_on_return_false`.
+ 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*
+ *claudiob*, *Roque Pinel*
* Changes arguments and default value of CallbackChain's `:terminator` option
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 7c40df8dc8..81c242d4b1 100644
--- a/activesupport/Rakefile
+++ b/activesupport/Rakefile
@@ -1,5 +1,4 @@
require 'rake/testtask'
-require 'rubygems/package_task'
task :default => :test
Rake::TestTask.new do |t|
@@ -17,16 +16,3 @@ namespace :test do
end or raise "Failures"
end
end
-
-spec = eval(File.read('activesupport.gemspec'))
-
-Gem::PackageTask.new(spec) do |p|
- p.gem_spec = spec
-end
-
-desc "Release to rubygems"
-task :release => :package do
- require 'rake/gemcutter'
- Rake::Gemcutter::Tasks.new(spec).define
- Rake::Task['gem:push'].invoke
-end
diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec
index 12c6a4d449..32e28c0212 100644
--- a/activesupport/activesupport.gemspec
+++ b/activesupport/activesupport.gemspec
@@ -24,5 +24,6 @@ Gem::Specification.new do |s|
s.add_dependency 'json', '~> 1.7', '>= 1.7.7'
s.add_dependency 'tzinfo', '~> 1.1'
s.add_dependency 'minitest', '~> 5.1'
- s.add_dependency 'thread_safe','~> 0.3', '>= 0.3.4'
+ s.add_dependency 'concurrent-ruby', '~> 1.0'
+ s.add_dependency 'method_source'
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/bin/test b/activesupport/bin/test
new file mode 100755
index 0000000000..404cabba51
--- /dev/null
+++ b/activesupport/bin/test
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+COMPONENT_ROOT = File.expand_path("../../", __FILE__)
+require File.expand_path("../tools/test", COMPONENT_ROOT)
+exit Minitest.run(ARGV)
diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb
index 588d6c49f9..94fe893149 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
@@ -34,6 +34,7 @@ module ActiveSupport
autoload :Dependencies
autoload :DescendantsTracker
autoload :FileUpdateChecker
+ autoload :EventedFileUpdateChecker
autoload :LogSubscriber
autoload :Notifications
@@ -75,11 +76,11 @@ module ActiveSupport
cattr_accessor :test_order # :nodoc:
def self.halt_callback_chains_on_return_false
- Callbacks::CallbackChain.halt_and_display_warning_on_return_false
+ Callbacks.halt_and_display_warning_on_return_false
end
def self.halt_callback_chains_on_return_false=(value)
- Callbacks::CallbackChain.halt_and_display_warning_on_return_false = value
+ Callbacks.halt_and_display_warning_on_return_false = value
end
end
diff --git a/activesupport/lib/active_support/array_inquirer.rb b/activesupport/lib/active_support/array_inquirer.rb
index e7188d7adb..f59ddf5403 100644
--- a/activesupport/lib/active_support/array_inquirer.rb
+++ b/activesupport/lib/active_support/array_inquirer.rb
@@ -23,7 +23,7 @@ module ActiveSupport
super
else
candidates.any? do |candidate|
- include?(candidate) || include?(candidate.to_sym)
+ include?(candidate.to_sym) || include?(candidate.to_s)
end
end
end
diff --git a/activesupport/lib/active_support/backtrace_cleaner.rb b/activesupport/lib/active_support/backtrace_cleaner.rb
index d06f22ad5c..e161ec4cca 100644
--- a/activesupport/lib/active_support/backtrace_cleaner.rb
+++ b/activesupport/lib/active_support/backtrace_cleaner.rb
@@ -25,7 +25,7 @@ module ActiveSupport
# of the backtrace, you can call <tt>BacktraceCleaner#remove_filters!</tt>
# These two methods will give you a completely untouched backtrace.
#
- # Inspired by the Quiet Backtrace gem by Thoughtbot.
+ # Inspired by the Quiet Backtrace gem by thoughtbot.
class BacktraceCleaner
def initialize
@filters, @silencers = [], []
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 837974bc85..610105f41c 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.
@@ -26,7 +27,7 @@ module ActiveSupport
end
class << self
- # Creates a new CacheStore object according to the given options.
+ # Creates a new Store object according to the given options.
#
# If no arguments are passed to this method, then a new
# ActiveSupport::Cache::MemoryStore object will be returned.
@@ -254,10 +255,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,10 +277,15 @@ module ActiveSupport
def fetch(name, options = nil)
if block_given?
options = merged_options(options)
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
- cached_entry = find_cached_entry(key, name, options) unless options[:force]
- entry = handle_expired_entry(cached_entry, key, options)
+ entry = nil
+ instrument(:read, name, options) do |payload|
+ cached_entry = read_entry(key, options) unless options[:force]
+ entry = handle_expired_entry(cached_entry, key, options)
+ payload[:super_operation] = :fetch if payload
+ payload[:hit] = !!entry if payload
+ end
if entry
get_entry_value(entry, name, options)
@@ -297,7 +304,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
@@ -329,7 +336,7 @@ module ActiveSupport
instrument_multi(:read, names, options) do |payload|
results = {}
names.each do |name|
- key = namespaced_key(name, options)
+ key = normalize_key(name, options)
entry = read_entry(key, options)
if entry
if entry.expired?
@@ -381,7 +388,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
@@ -392,7 +399,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
@@ -403,7 +410,7 @@ 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
@@ -524,7 +531,7 @@ module ActiveSupport
# Prefix 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
@@ -532,8 +539,16 @@ 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}: #{key}#{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)
@@ -556,13 +571,6 @@ module ActiveSupport
logger.debug(yield)
end
- def find_cached_entry(key, name, options)
- instrument(:read, name, options) do |payload|
- payload[:super_operation] = :fetch if payload
- read_entry(key, options)
- end
- end
-
def handle_expired_entry(entry, key, options)
if entry && entry.expired?
race_ttl = options[:race_condition_ttl].to_i
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index e6a8b84214..99c55b1aa4 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -10,24 +10,25 @@ module ActiveSupport
# FileStore implements the Strategy::LocalCache strategy which implements
# an in-memory cache inside of a block.
class FileStore < Store
+ prepend Strategy::LocalCache
attr_reader :cache_path
DIR_FORMATTER = "%03X"
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)
@cache_path = cache_path.to_s
- extend Strategy::LocalCache
end
# Deletes all items from the cache. In this case it deletes all the entries in the specified
- # file store directory except for .gitkeep. Be careful which directory is specified in your
+ # 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,12 +116,14 @@ module ActiveSupport
end
# Translate a key into a file path.
- def key_file_path(key)
- if key.size > FILEPATH_MAX_SIZE
- key = Digest::MD5.hexdigest(key)
+ def normalize_key(key, options)
+ key = super
+ fname = URI.encode_www_form_component(key)
+
+ if fname.size > FILEPATH_MAX_SIZE
+ fname = Digest::MD5.hexdigest(key)
end
- fname = URI.encode_www_form_component(key)
hash = Zlib.adler32(fname)
hash, dir_1 = hash.divmod(0x1000)
dir_2 = hash.modulo(0x1000)
@@ -138,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
@@ -147,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
@@ -174,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)
@@ -186,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 47133bf550..174913365a 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -24,6 +24,31 @@ module ActiveSupport
# MemCacheStore implements the Strategy::LocalCache strategy which implements
# an in-memory cache inside of a block.
class MemCacheStore < Store
+ # Provide support for raw values in the local cache strategy.
+ module LocalCacheWithRaw # :nodoc:
+ protected
+ def read_entry(key, options)
+ entry = super
+ if options[:raw] && local_cache && entry
+ entry = deserialize_entry(entry.value)
+ end
+ entry
+ end
+
+ def write_entry(key, entry, options) # :nodoc:
+ if options[:raw] && local_cache
+ raw_entry = Entry.new(entry.value.to_s)
+ raw_entry.expires_at = entry.expires_at
+ super(key, raw_entry, options)
+ else
+ super
+ end
+ end
+ end
+
+ prepend Strategy::LocalCache
+ prepend LocalCacheWithRaw
+
ESCAPE_KEY_CHARS = /[\x00-\x20%\x7F-\xFF]/n
# Creates a new Dalli::Client instance with specified addresses and options.
@@ -63,9 +88,6 @@ module ActiveSupport
UNIVERSAL_OPTIONS.each{|name| mem_cache_options.delete(name)}
@data = self.class.build_mem_cache(*(addresses + [mem_cache_options]))
end
-
- extend Strategy::LocalCache
- extend LocalCacheWithRaw
end
# Reads multiple values from the cache using a single call to the
@@ -75,7 +97,7 @@ module ActiveSupport
options = merged_options(options)
instrument_multi(:read, names, options) do
- keys_to_names = Hash[names.map{|name| [escape_key(namespaced_key(name, options)), name]}]
+ 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|
@@ -93,11 +115,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
@@ -107,20 +128,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.
@@ -131,10 +148,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.
@@ -146,18 +160,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
@@ -165,44 +175,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
- # Provide support for raw values in the local cache strategy.
- module LocalCacheWithRaw # :nodoc:
- protected
- def read_entry(key, options)
- entry = super
- if options[:raw] && local_cache && entry
- entry = deserialize_entry(entry.value)
- end
- entry
- end
-
- def write_entry(key, entry, options) # :nodoc:
- retval = super
- if options[:raw] && local_cache && retval
- raw_entry = Entry.new(entry.value.to_s)
- raw_entry.expires_at = entry.expires_at
- local_cache.write_entry(key, raw_entry, options)
- end
- retval
- 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/null_store.rb b/activesupport/lib/active_support/cache/null_store.rb
index 4427eaafcd..0564ce5312 100644
--- a/activesupport/lib/active_support/cache/null_store.rb
+++ b/activesupport/lib/active_support/cache/null_store.rb
@@ -8,10 +8,7 @@ module ActiveSupport
# be cached inside blocks that utilize this strategy. See
# ActiveSupport::Cache::Strategy::LocalCache for more details.
class NullStore < Store
- def initialize(options = nil)
- super(options)
- extend Strategy::LocalCache
- end
+ prepend Strategy::LocalCache
def clear(options = nil)
end
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache.rb b/activesupport/lib/active_support/cache/strategy/local_cache.rb
index fe5bc82c30..df38dbcf11 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache.rb
@@ -60,6 +60,10 @@ module ActiveSupport
def delete_entry(key, options)
!!@data.delete(key)
end
+
+ def fetch_entry(key, options = nil) # :nodoc:
+ @data.fetch(key) { @data[key] = yield }
+ end
end
# Use a local cache for the duration of block.
@@ -75,36 +79,35 @@ 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
protected
def read_entry(key, options) # :nodoc:
- if local_cache
- entry = local_cache.read_entry(key, options)
- unless entry
- entry = super
- local_cache.write_entry(key, entry, options)
- end
- entry
+ if cache = local_cache
+ cache.fetch_entry(key) { super }
else
super
end
@@ -121,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`
+ 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 e8ab3a7db5..bf560ec1fa 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -4,6 +4,7 @@ require 'active_support/core_ext/array/extract_options'
require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/kernel/singleton_class'
+require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/core_ext/string/filters'
require 'active_support/deprecation'
require 'thread'
@@ -66,6 +67,12 @@ module ActiveSupport
CALLBACK_FILTER_TYPES = [:before, :after, :around]
+ # If true, Active Record and Active Model callbacks returning +false+ will
+ # halt the entire callback chain and display a deprecation message.
+ # If false, callback chains will only be halted by calling +throw :abort+.
+ # Defaults to +true+.
+ mattr_accessor(:halt_and_display_warning_on_return_false) { true }
+
# Runs the callbacks for the given event.
#
# Calls the before and around callbacks in the order they were set, yields
@@ -80,8 +87,12 @@ module ActiveSupport
# save
# end
def run_callbacks(kind, &block)
- callbacks = send("_#{kind}_callbacks")
+ send "_run_#{kind}_callbacks", &block
+ end
+
+ private
+ def __run_callbacks__(callbacks, &block)
if callbacks.empty?
yield if block_given?
else
@@ -91,8 +102,6 @@ module ActiveSupport
end
end
- private
-
# A hook invoked every time a before callback is halted.
# This can be overridden in AS::Callback implementors in order
# to provide better debugging/logging.
@@ -124,14 +133,10 @@ module ActiveSupport
def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
halted_lambda = chain_config[:terminator]
- if chain_config.key?(:terminator) && user_conditions.any?
+ if user_conditions.any?
halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
- elsif chain_config.key? :terminator
- halting(callback_sequence, user_callback, halted_lambda, filter)
- elsif user_conditions.any?
- conditional(callback_sequence, user_callback, user_conditions)
else
- simple callback_sequence, user_callback
+ halting(callback_sequence, user_callback, halted_lambda, filter)
end
end
@@ -173,42 +178,15 @@ module ActiveSupport
end
end
private_class_method :halting
-
- def self.conditional(callback_sequence, user_callback, user_conditions)
- callback_sequence.before do |env|
- target = env.target
- value = env.value
-
- if user_conditions.all? { |c| c.call(target, value) }
- user_callback.call target, value
- end
-
- env
- end
- end
- private_class_method :conditional
-
- def self.simple(callback_sequence, user_callback)
- callback_sequence.before do |env|
- user_callback.call env.target, env.value
-
- env
- end
- end
- private_class_method :simple
end
class After
def self.build(callback_sequence, user_callback, user_conditions, chain_config)
if chain_config[:skip_after_callbacks_if_terminated]
- if chain_config.key?(:terminator) && user_conditions.any?
+ if user_conditions.any?
halting_and_conditional(callback_sequence, user_callback, user_conditions)
- elsif chain_config.key?(:terminator)
- halting(callback_sequence, user_callback)
- elsif user_conditions.any?
- conditional callback_sequence, user_callback, user_conditions
else
- simple callback_sequence, user_callback
+ halting(callback_sequence, user_callback)
end
else
if user_conditions.any?
@@ -271,14 +249,10 @@ module ActiveSupport
class Around
def self.build(callback_sequence, user_callback, user_conditions, chain_config)
- if chain_config.key?(:terminator) && user_conditions.any?
+ if user_conditions.any?
halting_and_conditional(callback_sequence, user_callback, user_conditions)
- elsif chain_config.key? :terminator
- halting(callback_sequence, user_callback)
- elsif user_conditions.any?
- conditional(callback_sequence, user_callback, user_conditions)
else
- simple(callback_sequence, user_callback)
+ halting(callback_sequence, user_callback)
end
end
@@ -316,38 +290,18 @@ module ActiveSupport
end
end
private_class_method :halting
-
- def self.conditional(callback_sequence, user_callback, user_conditions)
- callback_sequence.around do |env, &run|
- target = env.target
- value = env.value
-
- if user_conditions.all? { |c| c.call(target, value) }
- user_callback.call(target, value) {
- run.call.value
- }
- env
- else
- run.call
- end
- end
- end
- private_class_method :conditional
-
- def self.simple(callback_sequence, user_callback)
- callback_sequence.around do |env, &run|
- user_callback.call(env.target, env.value) {
- run.call.value
- }
- env
- end
- end
- private_class_method :simple
end
end
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
@@ -510,12 +464,6 @@ module ActiveSupport
attr_reader :name, :config
- # If true, any callback returning +false+ will halt the entire callback
- # chain and display a deprecation message. If false, callback chains will
- # only be halted by calling +throw :abort+. Defaults to +true+.
- class_attribute :halt_and_display_warning_on_return_false
- self.halt_and_display_warning_on_return_false = true
-
def initialize(name, config)
@name = name
@config = {
@@ -596,23 +544,12 @@ module ActiveSupport
Proc.new do |target, result_lambda|
terminate = true
catch(:abort) do
- result = result_lambda.call if result_lambda.is_a?(Proc)
- if halt_and_display_warning_on_return_false && result == false
- display_deprecation_warning_for_false_terminator
- else
- terminate = false
- end
+ result_lambda.call if result_lambda.is_a?(Proc)
+ terminate = false
end
terminate
end
end
-
- def display_deprecation_warning_for_false_terminator
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
- Returning `false` in a callback will not implicitly halt a callback chain in the next release of Rails.
- To explicitly halt a callback chain, please use `throw :abort` instead.
- MSG
- end
end
module ClassMethods
@@ -645,7 +582,7 @@ module ActiveSupport
# set_callback :save, :before_meth
#
# 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+.
#
@@ -739,14 +676,15 @@ module ActiveSupport
# callback chain, preventing following before and around callbacks from
# being called and the event from being triggered.
# This should be a lambda to be executed.
- # The current object and the return result of the callback will be called
- # with the lambda.
+ # The current object and the result lambda of the callback will be provided
+ # to the terminator lambda.
#
- # define_callbacks :validate, terminator: ->(target, result) { result == false }
+ # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
#
# In this example, if any before validate callbacks returns +false+,
# any successive before and around callback is not executed.
- # Defaults to +false+, meaning no value halts the chain.
+ #
+ # The default terminator halts the chain when a callback throws +:abort+.
#
# * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
# callbacks should be terminated by the <tt>:terminator</tt> option. By
@@ -806,18 +744,48 @@ module ActiveSupport
names.each do |name|
class_attribute "_#{name}_callbacks"
set_callbacks name, CallbackChain.new(name, options)
+
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ def _run_#{name}_callbacks(&block)
+ __run_callbacks__(_#{name}_callbacks, &block)
+ end
+ RUBY
end
end
protected
- def get_callbacks(name)
+ def get_callbacks(name) # :nodoc:
send "_#{name}_callbacks"
end
- def set_callbacks(name, callbacks)
+ def set_callbacks(name, callbacks) # :nodoc:
send "_#{name}_callbacks=", callbacks
end
+
+ def deprecated_false_terminator # :nodoc:
+ Proc.new do |target, result_lambda|
+ terminate = true
+ catch(:abort) do
+ result = result_lambda.call if result_lambda.is_a?(Proc)
+ if Callbacks.halt_and_display_warning_on_return_false && result == false
+ display_deprecation_warning_for_false_terminator
+ else
+ terminate = false
+ end
+ end
+ terminate
+ end
+ end
+
+ private
+
+ 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.
+ To explicitly halt the callback chain, please use `throw :abort` instead.
+ MSG
+ end
end
end
end
diff --git a/activesupport/lib/active_support/concern.rb b/activesupport/lib/active_support/concern.rb
index 4082d2d464..0403eb70ca 100644
--- a/activesupport/lib/active_support/concern.rb
+++ b/activesupport/lib/active_support/concern.rb
@@ -132,7 +132,7 @@ module ActiveSupport
end
def class_methods(&class_methods_module_definition)
- mod = const_defined?(:ClassMethods) ?
+ mod = const_defined?(:ClassMethods, false) ?
const_get(:ClassMethods) :
const_set(:ClassMethods, Module.new)
diff --git a/activesupport/lib/active_support/concurrency/latch.rb b/activesupport/lib/active_support/concurrency/latch.rb
index 1507de433e..4abe5ece6f 100644
--- a/activesupport/lib/active_support/concurrency/latch.rb
+++ b/activesupport/lib/active_support/concurrency/latch.rb
@@ -1,26 +1,18 @@
-require 'thread'
-require 'monitor'
+require 'concurrent/atomic/count_down_latch'
module ActiveSupport
module Concurrency
- class Latch
+ class Latch < Concurrent::CountDownLatch
+
def initialize(count = 1)
- @count = count
- @lock = Monitor.new
- @cv = @lock.new_cond
+ ActiveSupport::Deprecation.warn("ActiveSupport::Concurrency::Latch is deprecated. Please use Concurrent::CountDownLatch instead.")
+ super(count)
end
- def release
- @lock.synchronize do
- @count -= 1 if @count > 0
- @cv.broadcast if @count.zero?
- end
- end
+ alias_method :release, :count_down
def await
- @lock.synchronize do
- @cv.wait_while { @count > 0 }
- end
+ wait(nil)
end
end
end
diff --git a/activesupport/lib/active_support/concurrency/share_lock.rb b/activesupport/lib/active_support/concurrency/share_lock.rb
new file mode 100644
index 0000000000..ca48164c54
--- /dev/null
+++ b/activesupport/lib/active_support/concurrency/share_lock.rb
@@ -0,0 +1,142 @@
+require 'thread'
+require 'monitor'
+
+module ActiveSupport
+ module Concurrency
+ # A share/exclusive lock, otherwise known as a read/write lock.
+ #
+ # https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock
+ #--
+ # Note that a pending Exclusive lock attempt does not block incoming
+ # Share requests (i.e., we are "read-preferring"). That seems
+ # consistent with the behavior of "loose" upgrades, but may be the
+ # wrong choice otherwise: it nominally reduces the possibility of
+ # deadlock by risking starvation instead.
+ class ShareLock
+ include MonitorMixin
+
+ # We track Thread objects, instead of just using counters, because
+ # we need exclusive locks to be reentrant, and we need to be able
+ # to upgrade share locks to exclusive.
+
+
+ def initialize
+ super()
+
+ @cv = new_cond
+
+ @sharing = Hash.new(0)
+ @waiting = {}
+ @exclusive_thread = nil
+ @exclusive_depth = 0
+ end
+
+ # Returns false if +no_wait+ is set and the lock is not
+ # immediately available. Otherwise, returns true after the lock
+ # has been acquired.
+ #
+ # +purpose+ and +compatible+ work together; while this thread is
+ # waiting for the exclusive lock, it will yield its share (if any)
+ # to any other attempt whose +purpose+ appears in this attempt's
+ # +compatible+ list. This allows a "loose" upgrade, which, being
+ # less strict, prevents some classes of deadlocks.
+ #
+ # For many resources, loose upgrades are sufficient: if a thread
+ # is awaiting a lock, it is not running any other code. With
+ # +purpose+ matching, it is possible to yield only to other
+ # threads whose activity will not interfere.
+ def start_exclusive(purpose: nil, compatible: [], no_wait: false)
+ synchronize do
+ unless @exclusive_thread == Thread.current
+ if busy?(purpose)
+ return false if no_wait
+
+ loose_shares = @sharing.delete(Thread.current)
+ @waiting[Thread.current] = compatible if loose_shares
+
+ begin
+ @cv.wait_while { busy?(purpose) }
+ ensure
+ @waiting.delete Thread.current
+ @sharing[Thread.current] = loose_shares if loose_shares
+ end
+ end
+ @exclusive_thread = Thread.current
+ end
+ @exclusive_depth += 1
+
+ true
+ end
+ end
+
+ # Relinquish the exclusive lock. Must only be called by the thread
+ # that called start_exclusive (and currently holds the lock).
+ def stop_exclusive
+ synchronize do
+ raise "invalid unlock" if @exclusive_thread != Thread.current
+
+ @exclusive_depth -= 1
+ if @exclusive_depth == 0
+ @exclusive_thread = nil
+ @cv.broadcast
+ end
+ end
+ end
+
+ def start_sharing
+ synchronize do
+ if @exclusive_thread && @exclusive_thread != Thread.current
+ @cv.wait_while { @exclusive_thread }
+ end
+ @sharing[Thread.current] += 1
+ end
+ end
+
+ def stop_sharing
+ synchronize do
+ if @sharing[Thread.current] > 1
+ @sharing[Thread.current] -= 1
+ else
+ @sharing.delete Thread.current
+ @cv.broadcast
+ end
+ end
+ end
+
+ # Execute the supplied block while holding the Exclusive lock. If
+ # +no_wait+ is set and the lock is not immediately available,
+ # returns +nil+ without yielding. Otherwise, returns the result of
+ # the block.
+ #
+ # See +start_exclusive+ for other options.
+ def exclusive(purpose: nil, compatible: [], no_wait: false)
+ if start_exclusive(purpose: purpose, compatible: compatible, no_wait: no_wait)
+ begin
+ yield
+ ensure
+ stop_exclusive
+ end
+ end
+ end
+
+ # Execute the supplied block while holding the Share lock.
+ def sharing
+ start_sharing
+ begin
+ yield
+ ensure
+ stop_sharing
+ end
+ end
+
+ private
+
+ # Must be called within synchronize
+ def busy?(purpose)
+ (@exclusive_thread && @exclusive_thread != Thread.current) ||
+ @waiting.any? { |k, v| k != Thread.current && !v.include?(purpose) } ||
+ @sharing.size > (@sharing[Thread.current] > 0 ? 1 : 0)
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/array/conversions.rb b/activesupport/lib/active_support/core_ext/array/conversions.rb
index d80df21e7d..8718b7e1e5 100644
--- a/activesupport/lib/active_support/core_ext/array/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/array/conversions.rb
@@ -32,7 +32,7 @@ class Array
# ['one', 'two', 'three'].to_sentence # => "one, two, and three"
#
# ['one', 'two'].to_sentence(passing: 'invalid option')
- # # => ArgumentError: Unknown key :passing
+ # # => ArgumentError: Unknown key: :passing. Valid keys are: :words_connector, :two_words_connector, :last_word_connector, :locale
#
# ['one', 'two'].to_sentence(two_words_connector: '-')
# # => "one-two"
@@ -85,7 +85,9 @@ class Array
# Extends <tt>Array#to_s</tt> to convert a collection of elements into a
# comma separated id list if <tt>:db</tt> argument is given as the format.
#
- # Blog.all.to_formatted_s(:db) # => "1,2,3"
+ # Blog.all.to_formatted_s(:db) # => "1,2,3"
+ # Blog.none.to_formatted_s(:db) # => "null"
+ # [1,2].to_formatted_s # => "[1, 2]"
def to_formatted_s(format = :default)
case format
when :db
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 234283e792..22fc7ecf92 100644
--- a/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/big_decimal/conversions.rb
@@ -1,15 +1,14 @@
require 'bigdecimal'
require 'bigdecimal/util'
-class BigDecimal
- DEFAULT_STRING_FORMAT = 'F'
- alias_method :to_default_s, :to_s
+module ActiveSupport
+ module BigDecimalWithDefaultFormat #:nodoc:
+ DEFAULT_STRING_FORMAT = 'F'
- def to_s(format = nil, options = nil)
- if format.is_a?(Symbol)
- to_formatted_s(format, options || {})
- else
- to_default_s(format || DEFAULT_STRING_FORMAT)
+ def to_s(format = nil)
+ super(format || DEFAULT_STRING_FORMAT)
end
end
end
+
+BigDecimal.prepend(ActiveSupport::BigDecimalWithDefaultFormat)
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index c750a10bb2..ef903d59b5 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,3 +1,2 @@
require 'active_support/core_ext/class/attribute'
-require 'active_support/core_ext/class/delegating_attributes'
require 'active_support/core_ext/class/subclasses'
diff --git a/activesupport/lib/active_support/core_ext/class/attribute.rb b/activesupport/lib/active_support/core_ext/class/attribute.rb
index f2b7bb3ef1..802d988af2 100644
--- a/activesupport/lib/active_support/core_ext/class/attribute.rb
+++ b/activesupport/lib/active_support/core_ext/class/attribute.rb
@@ -75,11 +75,15 @@ class Class
instance_predicate = options.fetch(:instance_predicate, true)
attrs.each do |name|
+ remove_possible_singleton_method(name)
define_singleton_method(name) { nil }
+
+ remove_possible_singleton_method("#{name}?")
define_singleton_method("#{name}?") { !!public_send(name) } if instance_predicate
ivar = "@#{name}"
+ remove_possible_singleton_method("#{name}=")
define_singleton_method("#{name}=") do |val|
singleton_class.class_eval do
remove_possible_method(name)
@@ -110,10 +114,15 @@ class Class
self.class.public_send name
end
end
+
+ remove_possible_method "#{name}?"
define_method("#{name}?") { !!public_send(name) } if instance_predicate
end
- attr_writer name if instance_writer
+ if instance_writer
+ remove_possible_method "#{name}="
+ attr_writer name
+ end
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
deleted file mode 100644
index 1c305c5970..0000000000
--- a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-require 'active_support/core_ext/kernel/singleton_class'
-require 'active_support/core_ext/module/remove_method'
-require 'active_support/core_ext/module/deprecation'
-
-
-class Class
- def superclass_delegating_accessor(name, options = {})
- # Create private _name and _name= methods that can still be used if the public
- # methods are overridden.
- _superclass_delegating_accessor("_#{name}", options)
-
- # Generate the public methods name, name=, and name?.
- # These methods dispatch to the private _name, and _name= methods, making them
- # overridable.
- singleton_class.send(:define_method, name) { send("_#{name}") }
- singleton_class.send(:define_method, "#{name}?") { !!send("_#{name}") }
- singleton_class.send(:define_method, "#{name}=") { |value| send("_#{name}=", value) }
-
- # If an instance_reader is needed, generate public instance methods name and name?.
- if options[:instance_reader] != false
- define_method(name) { send("_#{name}") }
- define_method("#{name}?") { !!send("#{name}") }
- end
- end
-
- deprecate superclass_delegating_accessor: :class_attribute
-
- private
- # Take the object being set and store it in a method. This gives us automatic
- # inheritance behavior, without having to store the object in an instance
- # variable and look up the superclass chain manually.
- def _stash_object_in_method(object, method, instance_reader = true)
- singleton_class.remove_possible_method(method)
- singleton_class.send(:define_method, method) { object }
- remove_possible_method(method)
- define_method(method) { object } if instance_reader
- end
-
- def _superclass_delegating_accessor(name, options = {})
- singleton_class.send(:define_method, "#{name}=") do |value|
- _stash_object_in_method(value, name, options[:instance_reader] != false)
- end
- send("#{name}=", nil)
- end
-end
diff --git a/activesupport/lib/active_support/core_ext/class/subclasses.rb b/activesupport/lib/active_support/core_ext/class/subclasses.rb
index 3c4bfc5f1e..b0f9a8be34 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|
diff --git a/activesupport/lib/active_support/core_ext/date.rb b/activesupport/lib/active_support/core_ext/date.rb
index 465fedda80..7f0f4639a2 100644
--- a/activesupport/lib/active_support/core_ext/date.rb
+++ b/activesupport/lib/active_support/core_ext/date.rb
@@ -1,5 +1,5 @@
require 'active_support/core_ext/date/acts_like'
+require 'active_support/core_ext/date/blank'
require 'active_support/core_ext/date/calculations'
require 'active_support/core_ext/date/conversions'
require 'active_support/core_ext/date/zones'
-
diff --git a/activesupport/lib/active_support/core_ext/date/blank.rb b/activesupport/lib/active_support/core_ext/date/blank.rb
new file mode 100644
index 0000000000..71627b6a6f
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date/blank.rb
@@ -0,0 +1,12 @@
+require 'date'
+
+class Date #:nodoc:
+ # No Date is blank:
+ #
+ # Date.today.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date/calculations.rb b/activesupport/lib/active_support/core_ext/date/calculations.rb
index c60e833441..d589b67bf7 100644
--- a/activesupport/lib/active_support/core_ext/date/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date/calculations.rb
@@ -26,7 +26,7 @@ class Date
Thread.current[:beginning_of_week] = find_beginning_of_week!(week_start)
end
- # Returns week start day symbol (e.g. :monday), or raises an ArgumentError for invalid day symbol.
+ # Returns week start day symbol (e.g. :monday), or raises an +ArgumentError+ for invalid day symbol.
def find_beginning_of_week!(week_start)
raise ArgumentError, "Invalid beginning of week: #{week_start}" unless ::Date::DAYS_INTO_WEEK.key?(week_start)
week_start
diff --git a/activesupport/lib/active_support/core_ext/date/conversions.rb b/activesupport/lib/active_support/core_ext/date/conversions.rb
index 31479a1269..ed8bca77ac 100644
--- a/activesupport/lib/active_support/core_ext/date/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date/conversions.rb
@@ -75,10 +75,10 @@ class Date
#
# date = Date.new(2007, 11, 10) # => Sat, 10 Nov 2007
#
- # date.to_time # => Sat Nov 10 00:00:00 0800 2007
- # date.to_time(:local) # => Sat Nov 10 00:00:00 0800 2007
+ # date.to_time # => 2007-11-10 00:00:00 0800
+ # date.to_time(:local) # => 2007-11-10 00:00:00 0800
#
- # date.to_time(:utc) # => Sat Nov 10 00:00:00 UTC 2007
+ # date.to_time(:utc) # => 2007-11-10 00:00:00 UTC
def to_time(form = :local)
::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 01153606c9..e079af594d 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
@@ -92,15 +92,28 @@ module DateAndTime
end
# Returns a new date/time at the start of the month.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Thu, 18 Jun 2015
+ # today.beginning_of_month # => Mon, 01 Jun 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Thu, 18 Jun 2015 15:23:13 +0000
+ # now.beginning_of_month # => Mon, 01 Jun 2015 00:00:00 +0000
def beginning_of_month
first_hour(change(:day => 1))
end
alias :at_beginning_of_month :beginning_of_month
# Returns a new date/time at the start of the quarter.
- # Example: 1st January, 1st July, 1st October.
- # DateTime objects will have a time set to 0:00.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_quarter # => Wed, 01 Jul 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_quarter # => Wed, 01 Jul 2015 00:00:00 +0000
def beginning_of_quarter
first_quarter_month = [10, 7, 4, 1].detect { |m| m <= month }
beginning_of_month.change(:month => first_quarter_month)
@@ -108,17 +121,29 @@ module DateAndTime
alias :at_beginning_of_quarter :beginning_of_quarter
# Returns a new date/time at the end of the quarter.
- # Example: 31st March, 30th June, 30th September.
- # DateTime objects will have a time set to 23:59:59.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.end_of_quarter # => Wed, 30 Sep 2015
+ #
+ # +DateTime+ objects will have a time set to 23:59:59.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.end_of_quarter # => Wed, 30 Sep 2015 23:59:59 +0000
def end_of_quarter
last_quarter_month = [3, 6, 9, 12].detect { |m| m >= month }
beginning_of_month.change(:month => last_quarter_month).end_of_month
end
alias :at_end_of_quarter :end_of_quarter
- # Return a new date/time at the beginning of the year.
- # Example: 1st January.
- # DateTime objects will have a time set to 0:00.
+ # Returns a new date/time at the beginning of the year.
+ #
+ # today = Date.today # => Fri, 10 Jul 2015
+ # today.beginning_of_year # => Thu, 01 Jan 2015
+ #
+ # +DateTime+ objects will have a time set to 0:00.
+ #
+ # now = DateTime.current # => Fri, 10 Jul 2015 18:41:29 +0000
+ # now.beginning_of_year # => Thu, 01 Jan 2015 00:00:00 +0000
def beginning_of_year
change(:month => 1).beginning_of_month
end
@@ -138,8 +163,8 @@ module DateAndTime
#
# +DateTime+ objects have their time set to 0:00 unless +same_time+ is true.
#
- # now = Time.current # => Thu, 07 May 2015 13:31:16 UTC +00:00
- # now.next_week # => Mon, 11 May 2015 00:00:00 UTC +00:00
+ # now = DateTime.current # => Thu, 07 May 2015 13:31:16 +0000
+ # now.next_week # => Mon, 11 May 2015 00:00:00 +0000
def next_week(given_day_in_next_week = Date.beginning_of_week, same_time: false)
result = first_hour(weeks_since(1).beginning_of_week.days_since(days_span(given_day_in_next_week)))
same_time ? copy_time_to(result) : result
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 96c6df9407..d29a8db5cf 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
@@ -4,7 +4,7 @@ module DateAndTime
# if Time.zone_default is set. Otherwise, it returns the current time.
#
# Time.zone = 'Hawaii' # => 'Hawaii'
- # DateTime.utc(2000).in_time_zone # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ # 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
#
# This method is similar to Time#localtime, except that it uses <tt>Time.zone</tt> as the local zone
@@ -14,7 +14,6 @@ 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
- # DateTime.utc(2000).in_time_zone('Alaska') # => Fri, 31 Dec 1999 15:00:00 AKST -09:00
# Date.new(2000).in_time_zone('Alaska') # => Sat, 01 Jan 2000 00:00:00 AKST -09:00
def in_time_zone(zone = ::Time.zone)
time_zone = ::Time.find_zone! zone
diff --git a/activesupport/lib/active_support/core_ext/date_time.rb b/activesupport/lib/active_support/core_ext/date_time.rb
index e8a27b9f38..bcb228b09a 100644
--- a/activesupport/lib/active_support/core_ext/date_time.rb
+++ b/activesupport/lib/active_support/core_ext/date_time.rb
@@ -1,4 +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/conversions'
require 'active_support/core_ext/date_time/zones'
diff --git a/activesupport/lib/active_support/core_ext/date_time/blank.rb b/activesupport/lib/active_support/core_ext/date_time/blank.rb
new file mode 100644
index 0000000000..56981b75fb
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/date_time/blank.rb
@@ -0,0 +1,12 @@
+require 'date'
+
+class DateTime #:nodoc:
+ # No DateTime is ever blank:
+ #
+ # DateTime.now.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 55ad384f4f..95617fb8c2 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -168,7 +168,7 @@ class DateTime
if other.kind_of?(Infinity)
super
elsif other.respond_to? :to_datetime
- super other.to_datetime
+ super other.to_datetime rescue nil
else
nil
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/conversions.rb b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
index 2a9c09fc29..f59d05b214 100644
--- a/activesupport/lib/active_support/core_ext/date_time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/conversions.rb
@@ -40,6 +40,8 @@ class DateTime
alias_method :to_default_s, :to_s if instance_methods(false).include?(:to_s)
alias_method :to_s, :to_formatted_s
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
#
# datetime = DateTime.civil(2000, 1, 1, 0, 0, 0, Rational(-6, 24))
# datetime.formatted_offset # => "-06:00"
diff --git a/activesupport/lib/active_support/core_ext/enumerable.rb b/activesupport/lib/active_support/core_ext/enumerable.rb
index 7a893292b3..8a74ad4d66 100644
--- a/activesupport/lib/active_support/core_ext/enumerable.rb
+++ b/activesupport/lib/active_support/core_ext/enumerable.rb
@@ -21,7 +21,7 @@ module Enumerable
if block_given?
map(&block).sum(identity)
else
- inject { |sum, element| sum + element } || identity
+ inject(:+) || identity
end
end
@@ -71,6 +71,21 @@ module Enumerable
def without(*elements)
reject { |element| elements.include?(element) }
end
+
+ # Convert an enumerable to an array based on the given key.
+ #
+ # [{ name: "David" }, { name: "Rafael" }, { name: "Aaron" }].pluck(:name)
+ # => ["David", "Rafael", "Aaron"]
+ #
+ # [{ id: 1, name: "David" }, { id: 2, name: "Rafael" }].pluck(:id, :name)
+ # => [[1, "David"], [2, "Rafael"]]
+ def pluck(*keys)
+ if keys.many?
+ map { |element| keys.map { |key| element[key] } }
+ else
+ map { |element| element[keys.first] }
+ end
+ end
end
class Range #:nodoc:
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
index fad6fa8d9d..463fd78412 100644
--- a/activesupport/lib/active_support/core_ext/file/atomic.rb
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -8,43 +8,45 @@ class File
# file.write('hello')
# end
#
- # If your temp directory is not on the same filesystem as the file you're
- # trying to write, you can provide a different temporary directory.
+ # This method needs to create a temporary file. By default it will create it
+ # in the same directory as the destination file. If you don't like this
+ # behavior you can provide a different directory but it must be on the
+ # same physical filesystem as the file you're trying to write.
#
# File.atomic_write('/data/something.important', '/data/tmp') do |file|
# file.write('hello')
# end
- def self.atomic_write(file_name, temp_dir = Dir.tmpdir)
+ def self.atomic_write(file_name, temp_dir = dirname(file_name))
require 'tempfile' unless defined?(Tempfile)
- require 'fileutils' unless defined?(FileUtils)
- temp_file = Tempfile.new(basename(file_name), temp_dir)
- temp_file.binmode
- return_val = yield temp_file
- temp_file.close
+ Tempfile.open(".#{basename(file_name)}", temp_dir) do |temp_file|
+ temp_file.binmode
+ return_val = yield temp_file
+ temp_file.close
- if File.exist?(file_name)
- # Get original file permissions
- old_stat = stat(file_name)
- else
- # If not possible, probe which are the default permissions in the
- # destination directory.
- old_stat = probe_stat_in(dirname(file_name))
- end
-
- # Overwrite original file with temp file
- FileUtils.mv(temp_file.path, file_name)
+ old_stat = if exist?(file_name)
+ # Get original file permissions
+ stat(file_name)
+ elsif temp_dir != dirname(file_name)
+ # If not possible, probe which are the default permissions in the
+ # destination directory.
+ probe_stat_in(dirname(file_name))
+ end
- # Set correct permissions on new file
- begin
- chown(old_stat.uid, old_stat.gid, file_name)
- # This operation will affect filesystem ACL's
- chmod(old_stat.mode, file_name)
+ if old_stat
+ # Set correct permissions on new file
+ begin
+ chown(old_stat.uid, old_stat.gid, temp_file.path)
+ # This operation will affect filesystem ACL's
+ chmod(old_stat.mode, temp_file.path)
+ rescue Errno::EPERM, Errno::EACCES
+ # Changing file ownership failed, moving on.
+ end
+ end
- # Make sure we return the result of the yielded block
+ # Overwrite original file with temp file
+ rename(temp_file.path, file_name)
return_val
- rescue Errno::EPERM, Errno::EACCES
- # Changing file ownership failed, moving on.
end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/conversions.rb b/activesupport/lib/active_support/core_ext/hash/conversions.rb
index 2149d4439d..8594d9bf2e 100644
--- a/activesupport/lib/active_support/core_ext/hash/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/hash/conversions.rb
@@ -106,7 +106,25 @@ class Hash
# # => {"hash"=>{"foo"=>1, "bar"=>2}}
#
# +DisallowedType+ is raised if the XML contains attributes with <tt>type="yaml"</tt> or
- # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to parse this XML.
+ # <tt>type="symbol"</tt>. Use <tt>Hash.from_trusted_xml</tt> to
+ # parse this XML.
+ #
+ # Custom +disallowed_types+ can also be passed in the form of an
+ # array.
+ #
+ # xml = <<-XML
+ # <?xml version="1.0" encoding="UTF-8"?>
+ # <hash>
+ # <foo type="integer">1</foo>
+ # <bar type="string">"David"</bar>
+ # </hash>
+ # XML
+ #
+ # hash = Hash.from_xml(xml, ['integer'])
+ # # => ActiveSupport::XMLConverter::DisallowedType: Disallowed type attribute: "integer"
+ #
+ # Note that passing custom disallowed types will override the default types,
+ # which are Symbol and YAML.
def from_xml(xml, disallowed_types = nil)
ActiveSupport::XMLConverter.new(xml, disallowed_types).to_h
end
diff --git a/activesupport/lib/active_support/core_ext/hash/except.rb b/activesupport/lib/active_support/core_ext/hash/except.rb
index 6e397abf51..2f6d38c1f6 100644
--- a/activesupport/lib/active_support/core_ext/hash/except.rb
+++ b/activesupport/lib/active_support/core_ext/hash/except.rb
@@ -1,8 +1,9 @@
class Hash
- # Returns a hash that includes everything but the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false, c: nil}
+ # Returns a hash that includes everything except given keys.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except(:c) # => { a: true, b: false }
+ # hash.except(:a, :b) # => { c: nil }
+ # hash # => { a: true, b: false, c: nil }
#
# This is useful for limiting a set of parameters to everything but a few known toggles:
# @person.update(params[:person].except(:admin))
@@ -10,10 +11,10 @@ class Hash
dup.except!(*keys)
end
- # Replaces the hash without the given keys.
- # hash = { a: true, b: false, c: nil}
- # hash.except!(:c) # => { a: true, b: false}
- # hash # => { a: true, b: false }
+ # Removes the given keys from hash and returns it.
+ # hash = { a: true, b: false, c: nil }
+ # hash.except!(:c) # => { a: true, b: false }
+ # hash # => { a: true, b: false }
def except!(*keys)
keys.each { |key| delete(key) }
self
diff --git a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
index 28cb3e2a3b..6df7b4121b 100644
--- a/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
+++ b/activesupport/lib/active_support/core_ext/hash/indifferent_access.rb
@@ -6,7 +6,7 @@ class Hash
#
# { a: 1 }.with_indifferent_access['a'] # => 1
def with_indifferent_access
- ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default(self)
+ ActiveSupport::HashWithIndifferentAccess.new(self)
end
# Called when object is nested under an object that receives
diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb
index c30044b9ff..8b2366c4b3 100644
--- a/activesupport/lib/active_support/core_ext/hash/keys.rb
+++ b/activesupport/lib/active_support/core_ext/hash/keys.rb
@@ -1,12 +1,16 @@
class Hash
- # Returns a new hash with all keys converted using the block operation.
+ # Returns a new hash with all keys converted using the +block+ operation.
#
# hash = { name: 'Rob', age: '28' }
#
- # hash.transform_keys{ |key| key.to_s.upcase }
- # # => {"NAME"=>"Rob", "AGE"=>"28"}
+ # hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"}
+ #
+ # If you do not provide a +block+, it will return an Enumerator
+ # for chaining with other methods:
+ #
+ # hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"}
def transform_keys
- return enum_for(:transform_keys) unless block_given?
+ return enum_for(:transform_keys) { size } unless block_given?
result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
@@ -14,10 +18,10 @@ class Hash
result
end
- # Destructively converts all keys using the block operations.
- # Same as transform_keys but modifies +self+.
+ # Destructively converts all keys using the +block+ operations.
+ # Same as +transform_keys+ but modifies +self+.
def transform_keys!
- return enum_for(:transform_keys!) unless block_given?
+ return enum_for(:transform_keys!) { size } unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
@@ -60,7 +64,7 @@ class Hash
alias_method :to_options!, :symbolize_keys!
# Validates all keys in a hash match <tt>*valid_keys</tt>, raising
- # ArgumentError on a mismatch.
+ # +ArgumentError+ on a mismatch.
#
# Note that keys are treated differently than HashWithIndifferentAccess,
# meaning that string and symbol keys will not match.
diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
index e9bcce761f..7d507ac998 100644
--- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb
+++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb
@@ -2,10 +2,15 @@ class Hash
# Returns a new hash with the results of running +block+ once for every value.
# The keys are unchanged.
#
- # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 }
- # # => { a: 2, b: 4, c: 6 }
+ # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 } # => { a: 2, b: 4, c: 6 }
+ #
+ # If you do not provide a +block+, it will return an Enumerator
+ # for chaining with other methods:
+ #
+ # { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 }
def transform_values
- return enum_for(:transform_values) unless block_given?
+ return enum_for(:transform_values) { size } unless block_given?
+ return {} if empty?
result = self.class.new
each do |key, value|
result[key] = yield(value)
@@ -13,9 +18,10 @@ class Hash
result
end
- # Destructive +transform_values+
+ # Destructively converts all values using the +block+ operations.
+ # Same as +transform_values+ but modifies +self+.
def transform_values!
- return enum_for(:transform_values!) unless block_given?
+ return enum_for(:transform_values!) { size } unless block_given?
each do |key, value|
self[key] = yield(value)
end
diff --git a/activesupport/lib/active_support/core_ext/integer/time.rb b/activesupport/lib/active_support/core_ext/integer/time.rb
index f0b7382ef3..87185b024f 100644
--- a/activesupport/lib/active_support/core_ext/integer/time.rb
+++ b/activesupport/lib/active_support/core_ext/integer/time.rb
@@ -23,7 +23,7 @@ class Integer
alias :month :months
def years
- ActiveSupport::Duration.new(self * 365.25.days, [[:years, self]])
+ ActiveSupport::Duration.new(self * 365.25.days.to_i, [[:years, self]])
end
alias :year :years
end
diff --git a/activesupport/lib/active_support/core_ext/kernel/reporting.rb b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
index 9189e6d977..8afc258df8 100644
--- a/activesupport/lib/active_support/core_ext/kernel/reporting.rb
+++ b/activesupport/lib/active_support/core_ext/kernel/reporting.rb
@@ -1,5 +1,3 @@
-require 'tempfile'
-
module Kernel
# 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/load_error.rb b/activesupport/lib/active_support/core_ext/load_error.rb
index d9fb392752..60732eb41a 100644
--- a/activesupport/lib/active_support/core_ext/load_error.rb
+++ b/activesupport/lib/active_support/core_ext/load_error.rb
@@ -23,7 +23,7 @@ class LoadError
# Returns true if the given path name (except perhaps for the ".rb"
# extension) is the missing file which caused the exception to be raised.
def is_missing?(location)
- location.sub(/\.rb$/, '') == path.sub(/\.rb$/, '')
+ location.sub(/\.rb$/, ''.freeze) == path.sub(/\.rb$/, ''.freeze)
end
end
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index 20a0856e71..e333b26133 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -6,7 +6,7 @@ module ActiveSupport
if exc.message.match(%r|undefined class/module (.+)|)
# try loading the class/module
$1.constantize
- # if it is a IO we need to go back to read the object
+ # if it is an IO we need to go back to read the object
source.rewind if source.respond_to?(:rewind)
retry
else
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/anonymous.rb b/activesupport/lib/active_support/core_ext/module/anonymous.rb
index 0ecc67a855..510c9a5430 100644
--- a/activesupport/lib/active_support/core_ext/module/anonymous.rb
+++ b/activesupport/lib/active_support/core_ext/module/anonymous.rb
@@ -7,7 +7,7 @@ class Module
# m = Module.new
# m.name # => nil
#
- # +anonymous?+ method returns true if module does not have a name:
+ # +anonymous?+ method returns true if module does not have a name, false otherwise:
#
# Module.new.anonymous? # => true
#
@@ -18,8 +18,10 @@ class Module
# via the +module+ or +class+ keyword or by an explicit assignment:
#
# m = Module.new # creates an anonymous module
- # M = m # => m gets a name here as a side-effect
+ # m.anonymous? # => true
+ # M = m # m gets a name here as a side-effect
# m.name # => "M"
+ # m.anonymous? # => false
def anonymous?
name.nil?
end
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 d4e6b5a1ac..76825862d7 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
@@ -19,9 +19,9 @@ class Module
# The attribute name must be a valid method name in Ruby.
#
# module Foo
- # mattr_reader :"1_Badname "
+ # mattr_reader :"1_Badname"
# end
- # # => NameError: invalid attribute name
+ # # => 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>.
@@ -49,11 +49,11 @@ 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|
- raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{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
@@ -119,7 +119,7 @@ class Module
def mattr_writer(*syms)
options = syms.extract_options!
syms.each do |sym|
- raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /^[_A-Za-z]\w*$/
+ raise NameError.new("invalid attribute name: #{sym}") unless sym =~ /\A[_A-Za-z]\w*\z/
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
@@#{sym} = nil unless defined? @@#{sym}
@@ -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>.
@@ -206,7 +206,7 @@ class Module
# Person.class_variable_get("@@hair_colors") # => [:brown, :black, :blonde, :red]
def mattr_accessor(*syms, &blk)
mattr_reader(*syms, &blk)
- mattr_writer(*syms, &blk)
+ mattr_writer(*syms)
end
alias :cattr_accessor :mattr_accessor
end
diff --git a/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb b/activesupport/lib/active_support/core_ext/module/attribute_accessors_per_thread.rb
new file mode 100644
index 0000000000..8a7e6776da
--- /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_#{self.class.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_#{self.class.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/concerning.rb b/activesupport/lib/active_support/core_ext/module/concerning.rb
index e26b594fc4..65b88b9bbd 100644
--- a/activesupport/lib/active_support/core_ext/module/concerning.rb
+++ b/activesupport/lib/active_support/core_ext/module/concerning.rb
@@ -99,7 +99,7 @@ class Module
# end
#
# Todo.ancestors
- # # => Todo, Todo::EventTracking, Object
+ # # => [Todo, Todo::EventTracking, Object]
#
# This small step has some wonderful ripple effects. We can
# * grok the behavior of our class in one glance,
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index 9b7a429db9..0d46248582 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -5,10 +5,11 @@ class Module
# option is not used.
class DelegationError < NoMethodError; end
- RUBY_RESERVED_WORDS = Set.new(
- %w(alias and BEGIN begin break case class def defined? do else elsif END
- end ensure false for if in module next nil not or redo rescue retry
- return self super then true undef unless until when while yield)
+ DELEGATION_RESERVED_METHOD_NAMES = Set.new(
+ %w(_ arg args alias and BEGIN begin block break case class def defined? do
+ else elsif END end ensure false for if in module next nil not or redo
+ rescue retry return self super then true undef unless until when while
+ yield)
).freeze
# Provides a +delegate+ class method to easily expose contained objects'
@@ -167,11 +168,11 @@ class Module
''
end
- file, line = caller(1, 1).first.split(':', 2)
+ file, line = caller(1, 1).first.split(':'.freeze, 2)
line = line.to_i
to = to.to_s
- to = "self.#{to}" if RUBY_RESERVED_WORDS.include?(to)
+ to = "self.#{to}" if DELEGATION_RESERVED_METHOD_NAMES.include?(to)
methods.each do |method|
# Attribute writer methods only accept one argument. Makes sure []=
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/module/remove_method.rb b/activesupport/lib/active_support/core_ext/module/remove_method.rb
index 52632d2c6b..d5ec16d68a 100644
--- a/activesupport/lib/active_support/core_ext/module/remove_method.rb
+++ b/activesupport/lib/active_support/core_ext/module/remove_method.rb
@@ -6,10 +6,30 @@ class Module
end
end
+ # Removes the named singleton method, if it exists.
+ def remove_possible_singleton_method(method)
+ singleton_class.instance_eval do
+ remove_possible_method(method)
+ end
+ end
+
# Replaces the existing method definition, if there is one, with the passed
# block as its body.
def redefine_method(method, &block)
+ visibility = method_visibility(method)
remove_possible_method(method)
define_method(method, &block)
+ send(visibility, method)
+ end
+
+ def method_visibility(method) # :nodoc:
+ case
+ when private_method_defined?(method)
+ :private
+ when protected_method_defined?(method)
+ :protected
+ else
+ :public
+ end
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 0c8ff79237..9d832897ed 100644
--- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb
@@ -1,7 +1,8 @@
require 'active_support/core_ext/big_decimal/conversions'
require 'active_support/number_helper'
+require 'active_support/core_ext/module/deprecation'
-class Numeric
+module ActiveSupport::NumericWithFormat
# Provides options for converting numbers into formatted strings.
# Options are provided for phone numbers, currency, percentage,
@@ -41,7 +42,7 @@ class Numeric
# 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 %
+ # 100.to_s(:percentage, format: '%n %') # => 100.000 %
#
# Delimited:
# 12345678.to_s(:delimited) # => 12,345,678
@@ -75,10 +76,12 @@ class Numeric
# 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.1229 TB"
+ # 1234567890123.to_s(:human_size, precision: 5) # => "1.1228 TB"
# 524288000.to_s(:human_size, precision: 5) # => "500 MB"
#
# Human-friendly format:
@@ -97,7 +100,10 @@ class Numeric
# 1234567.to_s(:human, precision: 1,
# separator: ',',
# significant: false) # => "1,2 Million"
- def to_formatted_s(format = :default, options = {})
+ def to_s(*args)
+ format, options = args
+ options ||= {}
+
case format
when :phone
return ActiveSupport::NumberHelper.number_to_phone(self, options)
@@ -114,32 +120,20 @@ class Numeric
when :human_size
return ActiveSupport::NumberHelper.number_to_human_size(self, options)
else
- self.to_default_s
- end
- end
-
- [Fixnum, Bignum].each do |klass|
- klass.class_eval do
- alias_method :to_default_s, :to_s
- def to_s(base_or_format = 10, options = nil)
- if base_or_format.is_a?(Symbol)
- to_formatted_s(base_or_format, options || {})
- else
- to_default_s(base_or_format)
- end
+ if is_a?(Float) || format.is_a?(Symbol)
+ super()
+ else
+ super
end
end
end
- Float.class_eval do
- alias_method :to_default_s, :to_s
- def to_s(*args)
- if args.empty?
- to_default_s
- else
- to_formatted_s(*args)
- end
- end
+ def to_formatted_s(*args)
+ to_s(*args)
end
+ deprecate to_formatted_s: :to_s
+end
+[Fixnum, Bignum, Float, BigDecimal].each do |klass|
+ klass.prepend(ActiveSupport::NumericWithFormat)
end
diff --git a/activesupport/lib/active_support/core_ext/object/blank.rb b/activesupport/lib/active_support/core_ext/object/blank.rb
index 38e43478df..039c50a4a2 100644
--- a/activesupport/lib/active_support/core_ext/object/blank.rb
+++ b/activesupport/lib/active_support/core_ext/object/blank.rb
@@ -1,12 +1,10 @@
-# encoding: utf-8
-
class Object
# An object is blank if it's false, empty, or a whitespace string.
- # For example, '', ' ', +nil+, [], and {} are all blank.
+ # For example, +false+, '', ' ', +nil+, [], and {} are all blank.
#
# This simplifies
#
- # address.nil? || address.empty?
+ # !address || address.empty?
#
# to
#
@@ -129,3 +127,14 @@ class Numeric #:nodoc:
false
end
end
+
+class Time #:nodoc:
+ # No Time is blank:
+ #
+ # Time.now.blank? # => false
+ #
+ # @return [false]
+ def blank?
+ false
+ end
+end
diff --git a/activesupport/lib/active_support/core_ext/object/deep_dup.rb b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
index ad5b2af161..8dfeed0066 100644
--- a/activesupport/lib/active_support/core_ext/object/deep_dup.rb
+++ b/activesupport/lib/active_support/core_ext/object/deep_dup.rb
@@ -39,9 +39,15 @@ class Hash
# hash[:a][:c] # => nil
# dup[:a][:c] # => "c"
def deep_dup
- each_with_object(dup) do |(key, value), hash|
- hash.delete(key)
- hash[key.deep_dup] = value.deep_dup
+ hash = dup
+ each_pair do |key, value|
+ if key.frozen? && ::String === key
+ hash[key] = value.deep_dup
+ else
+ hash.delete(key)
+ hash[key.deep_dup] = value.deep_dup
+ end
end
+ hash
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 620f7b6561..befa5aee21 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -19,7 +19,7 @@
class Object
# Can you safely dup this object?
#
- # False for +nil+, +false+, +true+, symbol, number objects;
+ # False for +nil+, +false+, +true+, symbol, number, method objects;
# true otherwise.
def duplicable?
true
@@ -78,6 +78,10 @@ end
require 'bigdecimal'
class BigDecimal
+ # BigDecimals are duplicable:
+ #
+ # BigDecimal.new("1.2").duplicable? # => true
+ # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
diff --git a/activesupport/lib/active_support/core_ext/object/inclusion.rb b/activesupport/lib/active_support/core_ext/object/inclusion.rb
index 55f281b213..d4c17dfb07 100644
--- a/activesupport/lib/active_support/core_ext/object/inclusion.rb
+++ b/activesupport/lib/active_support/core_ext/object/inclusion.rb
@@ -5,7 +5,7 @@ class Object
# characters = ["Konata", "Kagami", "Tsukasa"]
# "Konata".in?(characters) # => true
#
- # This will throw an ArgumentError if the argument doesn't respond
+ # This will throw an +ArgumentError+ if the argument doesn't respond
# to +#include?+.
def in?(another_object)
another_object.include?(self)
@@ -18,7 +18,7 @@ class Object
#
# params[:bucket_type].presence_in %w( project calendar )
#
- # This will throw an ArgumentError if the argument doesn't respond to +#include?+.
+ # This will throw an +ArgumentError+ if the argument doesn't respond to +#include?+.
#
# @return [Object]
def presence_in(another_object)
diff --git a/activesupport/lib/active_support/core_ext/object/try.rb b/activesupport/lib/active_support/core_ext/object/try.rb
index 69be6c4abc..8c16d95b62 100644
--- a/activesupport/lib/active_support/core_ext/object/try.rb
+++ b/activesupport/lib/active_support/core_ext/object/try.rb
@@ -8,7 +8,7 @@ module ActiveSupport
def try!(*a, &b)
if a.empty? && block_given?
- if b.arity.zero?
+ if b.arity == 0
instance_eval(&b)
else
yield self
@@ -94,7 +94,7 @@ class Object
# :call-seq:
# try!(*a, &b)
#
- # Same as #try, but raises a NoMethodError exception if the receiver is
+ # Same as #try, but raises a +NoMethodError+ exception if the receiver is
# not +nil+ and does not implement the tried method.
#
# "a".try!(:upcase) # => "A"
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/securerandom.rb b/activesupport/lib/active_support/core_ext/securerandom.rb
index 6cdbea1f37..98cf7430f7 100644
--- a/activesupport/lib/active_support/core_ext/securerandom.rb
+++ b/activesupport/lib/active_support/core_ext/securerandom.rb
@@ -10,8 +10,8 @@ module SecureRandom
#
# The result may contain alphanumeric characters except 0, O, I and l
#
- # p SecureRandom.base58 #=> "4kUgL2pdQMSCQtjE"
- # p SecureRandom.base58(24) #=> "77TMHrHJFvFDwodq8w7Ev2m7"
+ # p SecureRandom.base58 # => "4kUgL2pdQMSCQtjE"
+ # p SecureRandom.base58(24) # => "77TMHrHJFvFDwodq8w7Ev2m7"
#
def self.base58(n = 16)
SecureRandom.random_bytes(n).unpack("C*").map do |byte|
diff --git a/activesupport/lib/active_support/core_ext/string/conversions.rb b/activesupport/lib/active_support/core_ext/string/conversions.rb
index 3e0cb8a7ac..fd79a40e31 100644
--- a/activesupport/lib/active_support/core_ext/string/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/string/conversions.rb
@@ -14,7 +14,7 @@ class String
# "06:12".to_time # => 2012-12-13 06:12:00 +0100
# "2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
# "2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
- # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 05:12:00 UTC
+ # "2012-12-13T06:12".to_time(:utc) # => 2012-12-13 06:12:00 UTC
# "12/13/2012".to_time # => ArgumentError: argument out of range
def to_time(form = :local)
parts = Date._parse(self, false)
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 97f9720b2b..cc71b8155d 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -164,15 +164,33 @@ class String
#
# <%= link_to(@person.name, person_path) %>
# # => <a href="/person/1-donald-e-knuth">Donald E. Knuth</a>
- def parameterize(sep = '-')
- ActiveSupport::Inflector.parameterize(self, sep)
+ #
+ # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ #
+ # class Person
+ # def to_param
+ # "#{id}-#{name.parameterize(preserve_case: true)}"
+ # end
+ # end
+ #
+ # @person = Person.find(1)
+ # # => #<Person id: 1, name: "Donald E. Knuth">
+ #
+ # <%= link_to(@person.name, person_path) %>
+ # # => <a href="/person/1-Donald-E-Knuth">Donald E. Knuth</a>
+ def parameterize(sep = :unused, separator: '-', preserve_case: false)
+ unless sep == :unused
+ ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.")
+ separator = sep
+ end
+ ActiveSupport::Inflector.parameterize(self, separator: separator, preserve_case: preserve_case)
end
# Creates the name of a table like Rails does for models to table names. This method
# uses the +pluralize+ method on the last word in the string.
#
# 'RawScaledScorer'.tableize # => "raw_scaled_scorers"
- # 'egg_and_ham'.tableize # => "egg_and_hams"
+ # 'ham_and_egg'.tableize # => "ham_and_eggs"
# 'fancyCategory'.tableize # => "fancy_categories"
def tableize
ActiveSupport::Inflector.tableize(self)
@@ -182,7 +200,7 @@ class String
# Note that this returns a string and not a class. (To convert to an actual class
# follow +classify+ with +constantize+.)
#
- # 'egg_and_hams'.classify # => "EggAndHam"
+ # 'ham_and_eggs'.classify # => "HamAndEgg"
# 'posts'.classify # => "Post"
def classify
ActiveSupport::Inflector.classify(self)
diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb
index 7055f7f699..cc6f2158e7 100644
--- a/activesupport/lib/active_support/core_ext/string/multibyte.rb
+++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb
@@ -9,12 +9,10 @@ class String
# encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy
# class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string.
#
- # name = 'Claus Müller'
- # name.reverse # => "rell??M sualC"
- # name.length # => 13
- #
- # name.mb_chars.reverse.to_s # => "rellüM sualC"
- # name.mb_chars.length # => 12
+ # >> "lj".upcase
+ # => "lj"
+ # >> "lj".mb_chars.upcase.to_s
+ # => "LJ"
#
# == Method chaining
#
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 c676b26b06..04ed8e7cd8 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
- s.gsub(HTML_ESCAPE_REGEXP, HTML_ESCAPE)
+ CGI.escapeHTML(ActiveSupport::Multibyte::Unicode.tidy_bytes(s))
end
end
module_function :unwrapped_html_escape
@@ -50,7 +49,7 @@ class ERB
# html_escape_once('&lt;&lt; Accept & Checkout')
# # => "&lt;&lt; Accept &amp; Checkout"
def html_escape_once(s)
- result = s.to_s.gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
+ result = ActiveSupport::Multibyte::Unicode.tidy_bytes(s.to_s).gsub(HTML_ESCAPE_ONCE_REGEXP, HTML_ESCAPE)
s.html_safe? ? result.html_safe : result
end
@@ -86,7 +85,7 @@ class ERB
# use inside HTML attributes.
#
# If your JSON is being used downstream for insertion into the DOM, be aware of
- # whether or not it is being inserted via +html()+. Most JQuery plugins do this.
+ # whether or not it is being inserted via +html()+. Most jQuery plugins do this.
# If that is the case, be sure to +html_escape+ or +sanitize+ any user-generated
# content returned by your JSON.
#
@@ -243,8 +242,7 @@ 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
diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb
index 086c610976..55b9b87352 100644
--- a/activesupport/lib/active_support/core_ext/string/strip.rb
+++ b/activesupport/lib/active_support/core_ext/string/strip.rb
@@ -1,5 +1,3 @@
-require 'active_support/core_ext/object/try'
-
class String
# Strips indentation in heredocs.
#
@@ -17,10 +15,9 @@ class String
#
# the user would see the usage message aligned against the left margin.
#
- # Technically, it looks for the least indented line in the whole string, and removes
- # that amount of leading whitespace.
+ # Technically, it looks for the least indented non-empty line
+ # in the whole string, and removes that amount of leading whitespace.
def strip_heredoc
- indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
- gsub(/^[ \t]{#{indent}}/, '')
+ gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze)
end
end
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index 1ce68ea7c7..768c9a1b2c 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -3,6 +3,7 @@ require 'active_support/core_ext/time/conversions'
require 'active_support/time_with_zone'
require 'active_support/core_ext/time/zones'
require 'active_support/core_ext/date_and_time/calculations'
+require 'active_support/core_ext/date/calculations'
class Time
include DateAndTime::Calculations
@@ -15,9 +16,9 @@ class Time
super || (self == Time && other.is_a?(ActiveSupport::TimeWithZone))
end
- # Return the number of days in the given month.
+ # Returns the number of days in the given month.
# If no year is specified, it will use the current year.
- def days_in_month(month, year = now.year)
+ def days_in_month(month, year = current.year)
if month == 2 && ::Date.gregorian_leap?(year)
29
else
@@ -25,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
@@ -50,9 +57,9 @@ class Time
# Returns the number of seconds since 00:00:00.
#
- # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0
- # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296
- # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399
+ # Time.new(2012, 8, 29, 0, 0, 0).seconds_since_midnight # => 0.0
+ # Time.new(2012, 8, 29, 12, 34, 56).seconds_since_midnight # => 45296.0
+ # Time.new(2012, 8, 29, 23, 59, 59).seconds_since_midnight # => 86399.0
def seconds_since_midnight
to_i - change(:hour => 0).to_i + (usec / 1.0e+6)
end
@@ -98,7 +105,7 @@ class Time
elsif zone
::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
else
- raise ArgumentError, 'argument out of range' if new_usec > 999999
+ raise ArgumentError, 'argument out of range' if new_usec >= 1000000
::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
end
end
@@ -108,6 +115,12 @@ class Time
# takes a hash with any of these keys: <tt>:years</tt>, <tt>:months</tt>,
# <tt>:weeks</tt>, <tt>:days</tt>, <tt>:hours</tt>, <tt>:minutes</tt>,
# <tt>:seconds</tt>.
+ #
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(seconds: 1) # => 2015-08-01 14:35:01 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(minutes: 1) # => 2015-08-01 14:36:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(hours: 1) # => 2015-08-01 15:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(days: 1) # => 2015-08-02 14:35:00 -0700
+ # Time.new(2015, 8, 1, 14, 35, 0).advance(weeks: 1) # => 2015-08-08 14:35:00 -0700
def advance(options)
unless options[:weeks].nil?
options[:weeks], partial_weeks = options[:weeks].divmod(1)
@@ -149,7 +162,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
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index dbf1f2f373..536c4bf525 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -6,6 +6,7 @@ class Time
:db => '%Y-%m-%d %H:%M:%S',
:number => '%Y%m%d%H%M%S',
:nsec => '%Y%m%d%H%M%S%9N',
+ :usec => '%Y%m%d%H%M%S%6N',
:time => '%H:%M',
:short => '%d %b %H:%M',
:long => '%B %d, %Y %H:%M',
@@ -24,7 +25,7 @@ class Time
#
# This method is aliased to <tt>to_s</tt>.
#
- # time = Time.now # => Thu Jan 18 06:10:17 CST 2007
+ # time = Time.now # => 2007-01-18 06:10:17 -06:00
#
# time.to_formatted_s(:time) # => "06:10"
# time.to_s(:time) # => "06:10"
@@ -55,7 +56,8 @@ class Time
alias_method :to_default_s, :to_s
alias_method :to_s, :to_formatted_s
- # Returns the UTC offset as an +HH:MM formatted string.
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
#
# Time.local(2000).formatted_offset # => "-06:00"
# Time.local(2000).formatted_offset(false) # => "-0600"
diff --git a/activesupport/lib/active_support/core_ext/time/zones.rb b/activesupport/lib/active_support/core_ext/time/zones.rb
index d683e7c777..877dc84ec8 100644
--- a/activesupport/lib/active_support/core_ext/time/zones.rb
+++ b/activesupport/lib/active_support/core_ext/time/zones.rb
@@ -53,7 +53,7 @@ class Time
# Returns a TimeZone instance matching the time zone provided.
# Accepts the time zone in any format supported by <tt>Time.zone=</tt>.
- # Raises an ArgumentError for invalid time zones.
+ # Raises an +ArgumentError+ for invalid time zones.
#
# Time.find_zone! "America/New_York" # => #<ActiveSupport::TimeZone @name="America/New_York" ...>
# Time.find_zone! "EST" # => #<ActiveSupport::TimeZone @name="EST" ...>
@@ -65,7 +65,8 @@ class Time
if !time_zone || time_zone.is_a?(ActiveSupport::TimeZone)
time_zone
else
- # lookup timezone based on identifier (unless we've been passed a TZInfo::Timezone)
+ # Look up the timezone based on the identifier (unless we've been
+ # passed a TZInfo::Timezone)
unless time_zone.respond_to?(:period_for_local)
time_zone = ActiveSupport::TimeZone[time_zone] || TZInfo::Timezone.get(time_zone)
end
diff --git a/activesupport/lib/active_support/core_ext/uri.rb b/activesupport/lib/active_support/core_ext/uri.rb
index bfe0832b37..c6c183edd9 100644
--- a/activesupport/lib/active_support/core_ext/uri.rb
+++ b/activesupport/lib/active_support/core_ext/uri.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
require 'uri'
str = "\xE6\x97\xA5\xE6\x9C\xAC\xE8\xAA\x9E" # Ni-ho-nn-go in UTF-8, means Japanese.
parser = URI::Parser.new
@@ -12,7 +10,7 @@ unless str == parser.unescape(parser.escape(str))
# YK: My initial experiments say yes, but let's be sure please
enc = str.encoding
enc = Encoding::UTF_8 if enc == Encoding::US_ASCII
- str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(enc)
+ str.gsub(escaped) { |match| [match[1, 2].hex].pack('C') }.force_encoding(enc)
end
end
end
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 664cc15a29..af18ff746f 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -1,6 +1,6 @@
require 'set'
require 'thread'
-require 'thread_safe'
+require 'concurrent/map'
require 'pathname'
require 'active_support/core_ext/module/aliasing'
require 'active_support/core_ext/module/attribute_accessors'
@@ -12,12 +12,40 @@ require 'active_support/core_ext/kernel/reporting'
require 'active_support/core_ext/load_error'
require 'active_support/core_ext/name_error'
require 'active_support/core_ext/string/starts_ends_with'
+require "active_support/dependencies/interlock"
require 'active_support/inflector'
module ActiveSupport #:nodoc:
module Dependencies #:nodoc:
extend self
+ mattr_accessor :interlock
+ self.interlock = Interlock.new
+
+ # :doc:
+
+ # Execute the supplied block without interference from any
+ # concurrent loads.
+ def self.run_interlock
+ Dependencies.interlock.running { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time.
+ def self.load_interlock
+ Dependencies.interlock.loading { yield }
+ end
+
+ # Execute the supplied block while holding an exclusive lock,
+ # preventing any other thread from being inside a #run_interlock
+ # block at the same time.
+ def self.unload_interlock
+ Dependencies.interlock.unloading { yield }
+ end
+
+ # :nodoc:
+
# Should we turn on Ruby warnings on the first load of dependent files?
mattr_accessor :warnings_on_first_load
self.warnings_on_first_load = false
@@ -128,7 +156,7 @@ module ActiveSupport #:nodoc:
# Normalize the list of new constants, and add them to the list we will return
new_constants.each do |suffix|
- constants << ([namespace, suffix] - ["Object"]).join("::")
+ constants << ([namespace, suffix] - ["Object"]).join("::".freeze)
end
end
constants
@@ -325,9 +353,11 @@ module ActiveSupport #:nodoc:
def clear
log_call
- loaded.clear
- loading.clear
- remove_unloadable_constants!
+ Dependencies.unload_interlock do
+ loaded.clear
+ loading.clear
+ remove_unloadable_constants!
+ end
end
def require_or_load(file_name, const_path = nil)
@@ -336,39 +366,44 @@ module ActiveSupport #:nodoc:
expanded = File.expand_path(file_name)
return if loaded.include?(expanded)
- # Record that we've seen this file *before* loading it to avoid an
- # infinite loop with mutual dependencies.
- loaded << expanded
- loading << expanded
+ Dependencies.load_interlock do
+ # Maybe it got loaded while we were waiting for our lock:
+ return if loaded.include?(expanded)
- begin
- if load?
- log "loading #{file_name}"
+ # Record that we've seen this file *before* loading it to avoid an
+ # infinite loop with mutual dependencies.
+ loaded << expanded
+ loading << expanded
- # Enable warnings if this file has not been loaded before and
- # warnings_on_first_load is set.
- load_args = ["#{file_name}.rb"]
- load_args << const_path unless const_path.nil?
-
- if !warnings_on_first_load or history.include?(expanded)
- result = load_file(*load_args)
+ begin
+ if load?
+ log "loading #{file_name}"
+
+ # Enable warnings if this file has not been loaded before and
+ # warnings_on_first_load is set.
+ load_args = ["#{file_name}.rb"]
+ load_args << const_path unless const_path.nil?
+
+ if !warnings_on_first_load or history.include?(expanded)
+ result = load_file(*load_args)
+ else
+ enable_warnings { result = load_file(*load_args) }
+ end
else
- enable_warnings { result = load_file(*load_args) }
+ log "requiring #{file_name}"
+ result = require file_name
end
- else
- log "requiring #{file_name}"
- result = require file_name
+ rescue Exception
+ loaded.delete expanded
+ raise
+ ensure
+ loading.pop
end
- rescue Exception
- loaded.delete expanded
- raise
- ensure
- loading.pop
- end
- # Record history *after* loading so first load gets warnings.
- history << expanded
- result
+ # Record history *after* loading so first load gets warnings.
+ history << expanded
+ result
+ end
end
# Is the provided constant path defined?
@@ -386,13 +421,13 @@ module ActiveSupport #:nodoc:
bases.each do |root|
expanded_root = File.expand_path(root)
- next unless %r{\A#{Regexp.escape(expanded_root)}(/|\\)} =~ expanded_path
+ next unless expanded_path.start_with?(expanded_root)
- nesting = expanded_path[(expanded_root.size)..-1]
- nesting = nesting[1..-1] if nesting && nesting[0] == ?/
- next if nesting.blank?
+ root_size = expanded_root.size
+ next if expanded_path[root_size] != ?/.freeze
- paths << nesting.camelize
+ nesting = expanded_path[(root_size + 1)..-1]
+ paths << nesting.camelize unless nesting.blank?
end
paths.uniq!
@@ -401,7 +436,7 @@ module ActiveSupport #:nodoc:
# Search for a file in autoload_paths matching the provided suffix.
def search_for_file(path_suffix)
- path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb")
+ path_suffix = path_suffix.sub(/(\.rb)?$/, ".rb".freeze)
autoload_paths.each do |root|
path = File.join(root, path_suffix)
@@ -486,7 +521,7 @@ module ActiveSupport #:nodoc:
if file_path
expanded = File.expand_path(file_path)
- expanded.sub!(/\.rb\z/, '')
+ expanded.sub!(/\.rb\z/, ''.freeze)
if loading.include?(expanded)
raise "Circular dependency detected while autoloading constant #{qualified_name}"
@@ -550,7 +585,7 @@ module ActiveSupport #:nodoc:
class ClassCache
def initialize
- @store = ThreadSafe::Cache.new
+ @store = Concurrent::Map.new
end
def empty?
diff --git a/activesupport/lib/active_support/dependencies/interlock.rb b/activesupport/lib/active_support/dependencies/interlock.rb
new file mode 100644
index 0000000000..fbeb904684
--- /dev/null
+++ b/activesupport/lib/active_support/dependencies/interlock.rb
@@ -0,0 +1,47 @@
+require 'active_support/concurrency/share_lock'
+
+module ActiveSupport #:nodoc:
+ module Dependencies #:nodoc:
+ class Interlock
+ def initialize # :nodoc:
+ @lock = ActiveSupport::Concurrency::ShareLock.new
+ end
+
+ def loading
+ @lock.exclusive(purpose: :load, compatible: [:load]) do
+ yield
+ end
+ end
+
+ def unloading
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload]) do
+ yield
+ end
+ end
+
+ # Attempt to obtain an "unloading" (exclusive) lock. If possible,
+ # execute the supplied block while holding the lock. If there is
+ # concurrent activity, return immediately (without executing the
+ # block) instead of waiting.
+ def attempt_unloading
+ @lock.exclusive(purpose: :unload, compatible: [:load, :unload], no_wait: true) do
+ yield
+ end
+ end
+
+ def start_running
+ @lock.start_sharing
+ end
+
+ def done_running
+ @lock.stop_sharing
+ end
+
+ def running
+ @lock.sharing do
+ yield
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 46e9996d59..24545d766c 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.1', 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 0cdc7e96f7..28d2d78643 100644
--- a/activesupport/lib/active_support/deprecation/behaviors.rb
+++ b/activesupport/lib/active_support/deprecation/behaviors.rb
@@ -39,8 +39,8 @@ module ActiveSupport
}
# Behavior module allows to determine how to display deprecation messages.
- # You can set any behaviors from +DEFAULT_BEHAVIORS+ constant or create
- # custom behavior. Available behaviors:
+ # You can create a custom behavior or set any from the +DEFAULT_BEHAVIORS+
+ # constant. Available behaviors are:
#
# [+raise+] Raise <tt>ActiveSupport::DeprecationException</tt>.
# [+stderr+] Log all deprecation warnings to +$stderr+.
@@ -49,7 +49,7 @@ module ActiveSupport
# [+silence+] Do nothing.
#
# Setting behaviors only affects deprecations that happen after boot time.
- # For more information you can read documentation for +behavior=+ method.
+ # For more information you can read the documentation of the +behavior=+ method.
module Behavior
# Whether to print a backtrace along with the warning.
attr_accessor :debug
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index c74e9c40ac..f5ea6669ce 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -9,37 +9,61 @@ module ActiveSupport
# module Fred
# extend self
#
- # def foo; end
- # def bar; end
- # def baz; end
+ # def aaa; end
+ # def bbb; end
+ # def ccc; end
+ # def ddd; end
+ # def eee; end
# end
#
- # ActiveSupport::Deprecation.deprecate_methods(Fred, :foo, bar: :qux, baz: 'use Bar#baz instead')
- # # => [:foo, :bar, :baz]
+ # Using the default deprecator:
+ # ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
+ # # => [:aaa, :bbb, :ccc]
#
- # Fred.foo
- # # => "DEPRECATION WARNING: foo is deprecated and will be removed from Rails 4.1."
+ # Fred.aaa
+ # # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
+ # # => nil
#
- # Fred.bar
- # # => "DEPRECATION WARNING: bar is deprecated and will be removed from Rails 4.1 (use qux instead)."
+ # Fred.bbb
+ # # 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.baz
- # # => "DEPRECATION WARNING: baz is deprecated and will be removed from Rails 4.1 (use Bar#baz instead)."
+ # Fred.ccc
+ # # 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:
+ # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem')
+ # ActiveSupport::Deprecation.deprecate_methods(Fred, ddd: :zzz, deprecator: custom_deprecator)
+ # # => [:ddd]
+ #
+ # Fred.ddd
+ # DEPRECATION WARNING: ddd is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):15)
+ # # => nil
+ #
+ # Using a custom deprecator directly:
+ # custom_deprecator = ActiveSupport::Deprecation.new('next-release', 'MyGem')
+ # custom_deprecator.deprecate_methods(Fred, eee: :zzz)
+ # # => [:eee]
+ #
+ # Fred.eee
+ # DEPRECATION WARNING: eee is deprecated and will be removed from MyGem next-release (use zzz instead). (called from irb_binding at (irb):18)
+ # # => nil
def deprecate_methods(target_module, *method_names)
options = method_names.extract_options!
- deprecator = options.delete(:deprecator) || ActiveSupport::Deprecation.instance
+ deprecator = options.delete(:deprecator) || self
method_names += options.keys
- method_names.each do |method_name|
- mod = Module.new do
+ mod = Module.new do
+ method_names.each do |method_name|
define_method(method_name) do |*args, &block|
deprecator.deprecation_warning(method_name, options[method_name])
super(*args, &block)
end
end
-
- target_module.prepend(mod)
end
+
+ target_module.prepend(mod)
end
end
end
diff --git a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
index c6d2b5e795..6f0ad445fc 100644
--- a/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/proxy_wrappers.rb
@@ -20,7 +20,7 @@ module ActiveSupport
private
def method_missing(called, *args, &block)
- warn caller, called, args
+ warn caller_locations, called, args
target.__send__(called, *args, &block)
end
end
@@ -111,7 +111,7 @@ module ActiveSupport
#
# PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
#
- # (In a later update, the orignal implementation of `PLANETS` has been removed.)
+ # (In a later update, the original implementation of `PLANETS` has been removed.)
#
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
@@ -127,7 +127,7 @@ module ActiveSupport
@deprecator = deprecator
end
- # Returns class of a new constant.
+ # Returns the class of the new constant.
#
# PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
# PLANETS = ActiveSupport::Deprecation::DeprecatedConstantProxy.new('PLANETS', 'PLANETS_POST_2006')
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index a7d265d732..f89fc0fe14 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -14,7 +14,7 @@ module ActiveSupport
def warn(message = nil, callstack = nil)
return if silenced
- callstack ||= caller(2)
+ callstack ||= caller_locations(2)
deprecation_message(callstack, message).tap do |m|
behavior.each { |b| b.call(m, callstack) }
end
@@ -37,7 +37,7 @@ module ActiveSupport
end
def deprecation_warning(deprecated_method_name, message = nil, caller_backtrace = nil)
- caller_backtrace ||= caller(2)
+ caller_backtrace ||= caller_locations(2)
deprecated_method_warning(deprecated_method_name, message).tap do |msg|
warn(msg, caller_backtrace)
end
@@ -79,6 +79,17 @@ module ActiveSupport
end
def extract_callstack(callstack)
+ return _extract_callstack(callstack) if callstack.first.is_a? String
+
+ rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
+ offending_line = callstack.find { |frame|
+ frame.absolute_path && !frame.absolute_path.start_with?(rails_gem_root)
+ } || callstack.first
+ [offending_line.path, offending_line.lineno, offending_line.label]
+ end
+
+ def _extract_callstack(callstack)
+ warn "Please pass `caller_locations` to the deprecation API" if $VERBOSE
rails_gem_root = File.expand_path("../../../../..", __FILE__) + "/"
offending_line = callstack.find { |line| !line.start_with?(rails_gem_root) } || callstack.first
if offending_line
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 4c0d1197fe..c63b61e97a 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -52,6 +52,10 @@ module ActiveSupport
end
end
+ # Returns the amount of seconds a duration covers as a string.
+ # For more information check to_i method.
+ #
+ # 1.day.to_s # => "86400"
def to_s
@value.to_s
end
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..315be85fb3
--- /dev/null
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -0,0 +1,150 @@
+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 { require 'listen' }
+ 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?
+ 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
+ using Module.new {
+ refine Pathname do
+ def ascendant_of?(other)
+ self != other && other.ascend do |ascendant|
+ break true if self == ascendant
+ end
+ end
+ end
+ }
+
+ def xpath(path)
+ Pathname.new(path).expand_path
+ end
+
+ 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 lcsp.ascendant_of?(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|
+ dir.ascendant_of?(possible_descendant) && descendants << possible_descendant
+ end
+ end
+
+ # Array#- preserves order.
+ dirs - descendants
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/file_update_checker.rb b/activesupport/lib/active_support/file_update_checker.rb
index 78b627c286..1fa9335080 100644
--- a/activesupport/lib/active_support/file_update_checker.rb
+++ b/activesupport/lib/active_support/file_update_checker.rb
@@ -35,7 +35,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
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 7068f09d87..7790a9b2c0 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -1,5 +1,5 @@
module ActiveSupport
- # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>
+ # Returns the version of the currently loaded Active Support as a <tt>Gem::Version</tt>.
def self.gem_version
Gem::Version.new VERSION::STRING
end
@@ -8,7 +8,7 @@ module ActiveSupport
MAJOR = 5
MINOR = 0
TINY = 0
- PRE = "alpha"
+ PRE = "beta1"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 4f71f13971..b878f31e75 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -59,25 +59,28 @@ module ActiveSupport
if constructor.respond_to?(:to_hash)
super()
update(constructor)
+
+ hash = constructor.to_hash
+ self.default = hash.default if hash.default
+ self.default_proc = hash.default_proc if hash.default_proc
else
super(constructor)
end
end
- def default(key = nil)
- if key.is_a?(Symbol) && include?(key = key.to_s)
- self[key]
- else
- super
- end
+ def default(*args)
+ key = args.first
+ args[0] = key.to_s if key.is_a?(Symbol)
+ super(*args)
end
def self.new_from_hash_copying_default(hash)
- hash = hash.to_hash
- new(hash).tap do |new_hash|
- new_hash.default = hash.default
- new_hash.default_proc = hash.default_proc if hash.default_proc
- end
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`
+ has been deprecated, and will be removed in Rails 5.1. The behavior of
+ this method is now identical to the behavior of `.new`.
+ MSG
+ new(hash)
end
def self.[](*args)
@@ -92,7 +95,7 @@ module ActiveSupport
# hash = ActiveSupport::HashWithIndifferentAccess.new
# hash[:key] = 'value'
#
- # This value can be later fetched using either +:key+ or +'key'+.
+ # This value can be later fetched using either +:key+ or <tt>'key'</tt>.
def []=(key, value)
regular_writer(convert_key(key), convert_value(value, for: :assignment))
end
@@ -154,6 +157,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:
#
@@ -188,7 +205,7 @@ module ActiveSupport
# dup[:a][:c] # => "c"
def dup
self.class.new(self).tap do |new_hash|
- new_hash.default = default
+ set_defaults(new_hash)
end
end
@@ -206,7 +223,7 @@ module ActiveSupport
# hash['a'] = nil
# hash.reverse_merge(a: 0, b: 1) # => {"a"=>nil, "b"=>1}
def reverse_merge(other_hash)
- super(self.class.new_from_hash_copying_default(other_hash))
+ super(self.class.new(other_hash))
end
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
@@ -219,7 +236,7 @@ module ActiveSupport
# h = { "a" => 100, "b" => 200 }
# h.replace({ "c" => 300, "d" => 400 }) # => {"c"=>300, "d"=>400}
def replace(other_hash)
- super(self.class.new_from_hash_copying_default(other_hash))
+ super(self.class.new(other_hash))
end
# Removes the specified key from the hash.
@@ -238,16 +255,20 @@ module ActiveSupport
def to_options!; self end
def select(*args, &block)
+ return to_enum(:select) unless block_given?
dup.tap { |hash| hash.select!(*args, &block) }
end
def reject(*args, &block)
+ return to_enum(:reject) unless block_given?
dup.tap { |hash| hash.reject!(*args, &block) }
end
# Convert to a regular hash with string keys.
def to_hash
- _new_hash = Hash.new(default)
+ _new_hash = Hash.new
+ set_defaults(_new_hash)
+
each do |key, value|
_new_hash[key] = convert_value(value, for: :to_hash)
end
@@ -275,6 +296,14 @@ module ActiveSupport
value
end
end
+
+ def set_defaults(target)
+ if default_proc
+ target.default_proc = default_proc.dup
+ else
+ target.default = default
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index 95f3f6255a..82aacf3b24 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -37,10 +37,12 @@ module I18n
enforce_available_locales = I18n.enforce_available_locales if enforce_available_locales.nil?
I18n.enforce_available_locales = false
+ reloadable_paths = []
app.config.i18n.each do |setting, value|
case setting
when :railties_load_path
- app.config.i18n.load_path.unshift(*value)
+ reloadable_paths = value
+ app.config.i18n.load_path.unshift(*value.map(&:existent).flatten)
when :load_path
I18n.load_path += value
else
@@ -53,7 +55,14 @@ module I18n
# Restore available locales check so it will take place from now on.
I18n.enforce_available_locales = enforce_available_locales
- reloader = ActiveSupport::FileUpdateChecker.new(I18n.load_path.dup){ I18n.reload! }
+ directories = watched_dirs_with_extensions(reloadable_paths)
+ reloader = 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
+
+ I18n.reload!
+ end
+
app.reloaders << reloader
ActionDispatch::Reloader.to_prepare do
reloader.execute_if_updated
@@ -96,5 +105,11 @@ module I18n
raise "Unexpected fallback type #{fallbacks.inspect}"
end
end
+
+ def self.watched_dirs_with_extensions(paths)
+ paths.each_with_object({}) do |path, result|
+ result[path.absolute_current] = path.extensions
+ end
+ end
end
end
diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb
index 486838bd15..f3e52b48ac 100644
--- a/activesupport/lib/active_support/inflector/inflections.rb
+++ b/activesupport/lib/active_support/inflector/inflections.rb
@@ -1,4 +1,4 @@
-require 'thread_safe'
+require 'concurrent/map'
require 'active_support/core_ext/array/prepend_and_append'
require 'active_support/i18n'
@@ -25,7 +25,38 @@ module ActiveSupport
# singularization rules that is runs. This guarantees that your rules run
# before any of the rules that may already have been loaded.
class Inflections
- @__instance__ = ThreadSafe::Cache.new
+ @__instance__ = Concurrent::Map.new
+
+ class Uncountables < Array
+ def initialize
+ @regex_array = []
+ super
+ end
+
+ def delete(entry)
+ super entry
+ @regex_array.delete(to_regex(entry))
+ end
+
+ def <<(*word)
+ add(word)
+ end
+
+ def add(words)
+ self.concat(words.flatten.map(&:downcase))
+ @regex_array += self.map {|word| to_regex(word) }
+ self
+ end
+
+ def uncountable?(str)
+ @regex_array.any? { |regex| regex === str }
+ end
+
+ private
+ def to_regex(string)
+ /\b#{::Regexp.escape(string)}\Z/i
+ end
+ end
def self.instance(locale = :en)
@__instance__[locale] ||= new
@@ -34,7 +65,7 @@ module ActiveSupport
attr_reader :plurals, :singulars, :uncountables, :humans, :acronyms, :acronym_regex
def initialize
- @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], [], [], {}, /(?=a)b/
+ @plurals, @singulars, @uncountables, @humans, @acronyms, @acronym_regex = [], [], Uncountables.new, [], {}, /(?=a)b/
end
# Private, for the test suite.
@@ -160,7 +191,7 @@ module ActiveSupport
# uncountable 'money', 'information'
# uncountable %w( money information rice )
def uncountable(*words)
- @uncountables += words.flatten.map(&:downcase)
+ @uncountables.add(words)
end
# Specifies a humanized form of a string by a regular expression rule or
@@ -185,7 +216,7 @@ module ActiveSupport
def clear(scope = :all)
case scope
when :all
- @plurals, @singulars, @uncountables, @humans = [], [], [], []
+ @plurals, @singulars, @uncountables, @humans = [], [], Uncountables.new, []
else
instance_variable_set "@#{scope}", []
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index bde70d772d..f741c0bfb8 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -1,5 +1,3 @@
-# encoding: utf-8
-
require 'active_support/inflections'
module ActiveSupport
@@ -68,9 +66,9 @@ module ActiveSupport
def camelize(term, uppercase_first_letter = true)
string = term.to_s
if uppercase_first_letter
- string = string.sub(/^[a-z\d]*/) { inflections.acronyms[$&] || $&.capitalize }
+ string = string.sub(/^[a-z\d]*/) { |match| inflections.acronyms[match] || match.capitalize }
else
- string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { $&.downcase }
+ string = string.sub(/^(?:#{inflections.acronym_regex}(?=\b|[A-Z_])|\w)/) { |match| match.downcase }
end
string.gsub!(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{inflections.acronyms[$2] || $2.capitalize}" }
string.gsub!('/'.freeze, '::'.freeze)
@@ -91,10 +89,10 @@ module ActiveSupport
def underscore(camel_cased_word)
return camel_cased_word unless camel_cased_word =~ /[A-Z-]|::/
word = camel_cased_word.to_s.gsub('::'.freeze, '/'.freeze)
- word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'}#{$2.downcase}" }
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
- word.tr!("-", "_")
+ word.gsub!(/(?:(?<=([A-Za-z\d]))|\b)(#{inflections.acronym_regex})(?=\b|[^a-z])/) { "#{$1 && '_'.freeze }#{$2.downcase}" }
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2'.freeze)
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2'.freeze)
+ word.tr!("-".freeze, "_".freeze)
word.downcase!
word
end
@@ -127,9 +125,9 @@ module ActiveSupport
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
- result.sub!(/\A_+/, '')
- result.sub!(/_id\z/, '')
- result.tr!('_', ' ')
+ result.sub!(/\A_+/, ''.freeze)
+ result.sub!(/_id\z/, ''.freeze)
+ result.tr!('_'.freeze, ' '.freeze)
result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
@@ -153,14 +151,14 @@ module ActiveSupport
# titleize('TheManWithoutAPast') # => "The Man Without A Past"
# titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
def titleize(word)
- humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { $&.capitalize }
+ humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }
end
# Creates the name of a table like Rails does for models to table names.
# This method uses the #pluralize method on the last word in the string.
#
# tableize('RawScaledScorer') # => "raw_scaled_scorers"
- # tableize('egg_and_ham') # => "egg_and_hams"
+ # tableize('ham_and_egg') # => "ham_and_eggs"
# tableize('fancyCategory') # => "fancy_categories"
def tableize(class_name)
pluralize(underscore(class_name))
@@ -170,22 +168,22 @@ module ActiveSupport
# names to models. Note that this returns a string and not a Class (To
# convert to an actual class follow +classify+ with #constantize).
#
- # classify('egg_and_hams') # => "EggAndHam"
+ # classify('ham_and_eggs') # => "HamAndEgg"
# classify('posts') # => "Post"
#
# 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(/.*\./, '')))
+ camelize(singularize(table_name.to_s.sub(/.*\./, ''.freeze)))
end
# Replaces underscores with dashes in the string.
#
# dasherize('puni_puni') # => "puni-puni"
def dasherize(underscored_word)
- underscored_word.tr('_', '-')
+ underscored_word.tr('_'.freeze, '-'.freeze)
end
# Removes the module part from the expression in the string.
@@ -248,7 +246,7 @@ module ActiveSupport
# NameError is raised when the name is not in CamelCase or the constant is
# unknown.
def constantize(camel_cased_word)
- names = camel_cased_word.split('::')
+ names = camel_cased_word.split('::'.freeze)
# Trigger a built-in NameError exception including the ill-formed constant in the message.
Object.const_get(camel_cased_word) if names.empty?
@@ -354,7 +352,7 @@ module ActiveSupport
# const_regexp("Foo::Bar::Baz") # => "Foo(::Bar(::Baz)?)?"
# const_regexp("::") # => "::"
def const_regexp(camel_cased_word) #:nodoc:
- parts = camel_cased_word.split("::")
+ parts = camel_cased_word.split("::".freeze)
return Regexp.escape(camel_cased_word) if parts.blank?
@@ -372,7 +370,7 @@ module ActiveSupport
def apply_inflections(word, rules)
result = word.to_s.dup
- if word.empty? || inflections.uncountables.include?(result.downcase[/\b\w+\Z/])
+ if word.empty? || inflections.uncountables.uncountable?(result)
result
else
rules.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
diff --git a/activesupport/lib/active_support/inflector/transliterate.rb b/activesupport/lib/active_support/inflector/transliterate.rb
index edea142e82..871cfb8a72 100644
--- a/activesupport/lib/active_support/inflector/transliterate.rb
+++ b/activesupport/lib/active_support/inflector/transliterate.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/core_ext/string/multibyte'
require 'active_support/i18n'
@@ -58,7 +57,7 @@ module ActiveSupport
# I18n.locale = :de
# transliterate('Jürgen')
# # => "Juergen"
- def transliterate(string, replacement = "?")
+ def transliterate(string, replacement = "?".freeze)
I18n.transliterate(ActiveSupport::Multibyte::Unicode.normalize(
ActiveSupport::Multibyte::Unicode.tidy_bytes(string), :c),
:replacement => replacement)
@@ -69,19 +68,45 @@ module ActiveSupport
#
# parameterize("Donald E. Knuth") # => "donald-e-knuth"
# parameterize("^trés|Jolie-- ") # => "tres-jolie"
- def parameterize(string, sep = '-')
- # replace accented chars with their ascii equivalents
+ #
+ # To use a custom separator, override the `separator` argument.
+ #
+ # parameterize("Donald E. Knuth", separator: '_') # => "donald_e_knuth"
+ # parameterize("^trés|Jolie-- ", separator: '_') # => "tres_jolie"
+ #
+ # To preserve the case of the characters in a string, use the `preserve_case` argument.
+ #
+ # parameterize("Donald E. Knuth", preserve_case: true) # => "Donald-E-Knuth"
+ # parameterize("^trés|Jolie-- ", preserve_case: true) # => "tres-Jolie"
+ #
+ def parameterize(string, sep = :unused, separator: '-', preserve_case: false)
+ unless sep == :unused
+ ActiveSupport::Deprecation.warn("Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '#{sep}'` instead.")
+ separator = sep
+ end
+ # Replace accented chars with their ASCII equivalents.
parameterized_string = transliterate(string)
- # Turn unwanted chars into the separator
- parameterized_string.gsub!(/[^a-z0-9\-_]+/i, sep)
- unless sep.nil? || sep.empty?
- re_sep = Regexp.escape(sep)
+
+ # Turn unwanted chars into the separator.
+ parameterized_string.gsub!(/[^a-z0-9\-_]+/i, separator)
+
+ unless separator.nil? || separator.empty?
+ if separator == "-".freeze
+ re_duplicate_separator = /-{2,}/
+ re_leading_trailing_separator = /^-|-$/i
+ else
+ re_sep = Regexp.escape(separator)
+ re_duplicate_separator = /#{re_sep}{2,}/
+ re_leading_trailing_separator = /^#{re_sep}|#{re_sep}$/i
+ end
# No more than one of the separator in a row.
- parameterized_string.gsub!(/#{re_sep}{2,}/, sep)
+ parameterized_string.gsub!(re_duplicate_separator, separator)
# Remove leading/trailing separator.
- parameterized_string.gsub!(/^#{re_sep}|#{re_sep}$/i, '')
+ parameterized_string.gsub!(re_leading_trailing_separator, ''.freeze)
end
- parameterized_string.downcase
+
+ parameterized_string.downcase! unless preserve_case
+ parameterized_string
end
end
end
diff --git a/activesupport/lib/active_support/json/encoding.rb b/activesupport/lib/active_support/json/encoding.rb
index 48f4967892..031c5e9339 100644
--- a/activesupport/lib/active_support/json/encoding.rb
+++ b/activesupport/lib/active_support/json/encoding.rb
@@ -57,6 +57,10 @@ module ActiveSupport
super.gsub ESCAPE_REGEX_WITHOUT_HTML_ENTITIES, ESCAPED_CHARS
end
end
+
+ def to_s
+ self
+ end
end
# Mark these as private so we don't leak encoding-specific constructs
diff --git a/activesupport/lib/active_support/key_generator.rb b/activesupport/lib/active_support/key_generator.rb
index 51d2da3a79..7f73f9ddfc 100644
--- a/activesupport/lib/active_support/key_generator.rb
+++ b/activesupport/lib/active_support/key_generator.rb
@@ -1,8 +1,8 @@
-require 'thread_safe'
+require 'concurrent/map'
require 'openssl'
module ActiveSupport
- # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2
+ # KeyGenerator is a simple wrapper around OpenSSL's implementation of PBKDF2.
# It can be used to derive a number of keys for various purposes from a given secret.
# This lets Rails applications have a single secure secret, but avoid reusing that
# key in multiple incompatible contexts.
@@ -24,11 +24,11 @@ module ActiveSupport
# CachingKeyGenerator is a wrapper around KeyGenerator which allows users to avoid
# re-executing the key generation process when it's called using the same salt and
- # key_size
+ # key_size.
class CachingKeyGenerator
def initialize(key_generator)
@key_generator = key_generator
- @cache_keys = ThreadSafe::Cache.new
+ @cache_keys = Concurrent::Map.new
end
# Returns a derived key suitable for use. The default key_size is chosen
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/log_subscriber.rb b/activesupport/lib/active_support/log_subscriber.rb
index e95dc5a866..e782cd2d4b 100644
--- a/activesupport/lib/active_support/log_subscriber.rb
+++ b/activesupport/lib/active_support/log_subscriber.rb
@@ -95,7 +95,7 @@ module ActiveSupport
METHOD
end
- # Set color by using a string or one of the defined constants. If a third
+ # Set color by using a symbol or one of the defined constants. If a third
# option is set to +true+, it also adds bold to the string. This is based
# on the Highline implementation and will automatically append CLEAR to the
# end of the returned String.
diff --git a/activesupport/lib/active_support/log_subscriber/test_helper.rb b/activesupport/lib/active_support/log_subscriber/test_helper.rb
index 75f353f62c..588ed67c81 100644
--- a/activesupport/lib/active_support/log_subscriber/test_helper.rb
+++ b/activesupport/lib/active_support/log_subscriber/test_helper.rb
@@ -10,7 +10,7 @@ module ActiveSupport
# class SyncLogSubscriberTest < ActiveSupport::TestCase
# include ActiveSupport::LogSubscriber::TestHelper
#
- # def setup
+ # setup do
# ActiveRecord::LogSubscriber.attach_to(:active_record)
# end
#
@@ -33,7 +33,7 @@ module ActiveSupport
# you can collect them doing @logger.logged(level), where level is the level
# used in logging, like info, debug, warn and so on.
module TestHelper
- def setup
+ def setup # :nodoc:
@logger = MockLogger.new
@notifier = ActiveSupport::Notifications::Fanout.new
@@ -44,7 +44,7 @@ module ActiveSupport
ActiveSupport::Notifications.notifier = @notifier
end
- def teardown
+ def teardown # :nodoc:
set_logger(nil)
ActiveSupport::Notifications.notifier = @old_notifier
end
diff --git a/activesupport/lib/active_support/logger.rb b/activesupport/lib/active_support/logger.rb
index 33fccdcf95..7626b28108 100644
--- a/activesupport/lib/active_support/logger.rb
+++ b/activesupport/lib/active_support/logger.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/module/attribute_accessors'
require 'active_support/logger_silence'
require 'logger'
@@ -6,6 +5,17 @@ module ActiveSupport
class Logger < ::Logger
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
@@ -44,6 +54,20 @@ module ActiveSupport
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..125d81d973 100644
--- a/activesupport/lib/active_support/logger_silence.rb
+++ b/activesupport/lib/active_support/logger_silence.rb
@@ -1,21 +1,42 @@
require 'active_support/concern'
+require 'active_support/core_ext/module/attribute_accessors'
+require 'concurrent'
module LoggerSilence
extend ActiveSupport::Concern
-
+
included do
cattr_accessor :silencer
+ attr_reader :local_levels
self.silencer = true
end
+ def after_initialize
+ @local_levels = Concurrent::Map.new(:initial_capacity => 2)
+ end
+
+ def local_log_id
+ Thread.current.__id__
+ end
+
+ def level
+ local_levels[local_log_id] || super
+ end
+
# Silences the logger for the duration of the block.
def silence(temporary_level = Logger::ERROR)
if silencer
begin
- old_logger_level, self.level = level, temporary_level
+ old_local_level = local_levels[local_log_id]
+ local_levels[local_log_id] = temporary_level
+
yield self
ensure
- self.level = old_logger_level
+ if old_local_level
+ local_levels[local_log_id] = old_local_level
+ else
+ local_levels.delete(local_log_id)
+ end
end
else
yield self
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 92ab6fe648..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
@@ -82,7 +82,7 @@ module ActiveSupport
def _decrypt(encrypted_message)
cipher = new_cipher
- encrypted_data, iv = encrypted_message.split("--").map {|v| ::Base64.strict_decode64(v)}
+ encrypted_data, iv = encrypted_message.split("--".freeze).map {|v| ::Base64.strict_decode64(v)}
cipher.decrypt
cipher.key = @secret
diff --git a/activesupport/lib/active_support/message_verifier.rb b/activesupport/lib/active_support/message_verifier.rb
index eee9bbaead..854029bf83 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
#
@@ -44,9 +44,9 @@ module ActiveSupport
# tampered_message = signed_message.chop # editing the message invalidates the signature
# verifier.valid_message?(tampered_message) # => false
def valid_message?(signed_message)
- return if signed_message.blank?
+ return if signed_message.nil? || !signed_message.valid_encoding? || signed_message.blank?
- data, digest = signed_message.split("--")
+ data, digest = signed_message.split("--".freeze)
data.present? && digest.present? && ActiveSupport::SecurityUtils.secure_compare(digest, generate_digest(data))
end
@@ -74,7 +74,7 @@ module ActiveSupport
def verified(signed_message)
if valid_message?(signed_message)
begin
- data = signed_message.split("--")[0]
+ data = signed_message.split("--".freeze)[0]
@serializer.load(decode(data))
rescue ArgumentError => argument_error
return if argument_error.message =~ %r{invalid base64}
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 3c0cf9f137..707cf200b5 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
require 'active_support/json'
require 'active_support/core_ext/string/access'
require 'active_support/core_ext/string/behavior'
@@ -86,10 +85,20 @@ module ActiveSupport #:nodoc:
@wrapped_string.split(*args).map { |i| self.class.new(i) }
end
- # Works like like <tt>String#slice!</tt>, but returns an instance of
- # Chars, or nil if the string was not modified.
+ # Works like <tt>String#slice!</tt>, but returns an instance of
+ # Chars, or nil if the string was not modified. The string will not be
+ # modified if the range given is out of bounds
+ #
+ # string = 'Welcome'
+ # string.mb_chars.slice!(3) # => #<ActiveSupport::Multibyte::Chars:0x000000038109b8 @wrapped_string="c">
+ # string # => 'Welome'
+ # string.mb_chars.slice!(0..3) # => #<ActiveSupport::Multibyte::Chars:0x00000002eb80a0 @wrapped_string="Welo">
+ # string # => 'me'
def slice!(*args)
- chars(@wrapped_string.slice!(*args))
+ string_sliced = @wrapped_string.slice!(*args)
+ if string_sliced
+ chars(string_sliced)
+ end
end
# Reverses all characters in the string.
diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb
index 35efebc65f..72b20fff06 100644
--- a/activesupport/lib/active_support/multibyte/unicode.rb
+++ b/activesupport/lib/active_support/multibyte/unicode.rb
@@ -1,4 +1,3 @@
-# encoding: utf-8
module ActiveSupport
module Multibyte
module Unicode
@@ -11,7 +10,7 @@ module ActiveSupport
NORMALIZATION_FORMS = [:c, :kc, :d, :kd]
# The Unicode version that is supported by the implementation
- UNICODE_VERSION = '7.0.0'
+ UNICODE_VERSION = '8.0.0'
# The default normalization used for operations that require
# normalization. It can be set to any of the normalizations
@@ -58,7 +57,7 @@ module ActiveSupport
# Returns a regular expression pattern that matches the passed Unicode
# codepoints.
def self.codepoints_to_pattern(array_of_codepoints) #:nodoc:
- array_of_codepoints.collect{ |e| [e].pack 'U*' }.join('|')
+ array_of_codepoints.collect{ |e| [e].pack 'U*'.freeze }.join('|'.freeze)
end
TRAILERS_PAT = /(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+\Z/u
LEADERS_PAT = /\A(#{codepoints_to_pattern(LEADERS_AND_TRAILERS)})+/u
@@ -88,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
@@ -257,7 +281,7 @@ module ActiveSupport
# * <tt>string</tt> - The string to perform normalization on.
# * <tt>form</tt> - The form you want to normalize in. Should be one of
# the following: <tt>:c</tt>, <tt>:kc</tt>, <tt>:d</tt>, or <tt>:kd</tt>.
- # Default is ActiveSupport::Multibyte.default_normalization_form.
+ # Default is ActiveSupport::Multibyte::Unicode.default_normalization_form.
def normalize(string, form=nil)
form ||= @default_normalization_form
# See http://www.unicode.org/reports/tr15, Table 1
@@ -273,7 +297,7 @@ module ActiveSupport
compose(reorder_characters(decompose(:compatibility, codepoints)))
else
raise ArgumentError, "#{form} is not a valid normalization variant", caller
- end.pack('U*')
+ end.pack('U*'.freeze)
end
def downcase(string)
@@ -338,7 +362,7 @@ module ActiveSupport
end
# Redefine the === method so we can write shorter rules for grapheme cluster breaks
- @boundary.each do |k,_|
+ @boundary.each_key do |k|
@boundary[k].instance_eval do
def ===(other)
detect { |i| i === other } ? true : false
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index b9f8e1ab2c..823d68e507 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -69,8 +69,8 @@ module ActiveSupport
# is able to take the arguments as they come and provide an object-oriented
# interface to that data.
#
- # It is also possible to pass an object as the second parameter passed to the
- # <tt>subscribe</tt> method instead of a block:
+ # It is also possible to pass an object which responds to <tt>call</tt> method
+ # as the second parameter to the <tt>subscribe</tt> method instead of a block:
#
# module ActionController
# class PageRequest
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index 6bf8c7d5de..c53f9c1039 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -1,5 +1,5 @@
require 'mutex_m'
-require 'thread_safe'
+require 'concurrent/map'
module ActiveSupport
module Notifications
@@ -12,7 +12,7 @@ module ActiveSupport
def initialize
@subscribers = []
- @listeners_for = ThreadSafe::Cache.new
+ @listeners_for = Concurrent::Map.new
super
end
@@ -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)
@@ -51,7 +51,7 @@ module ActiveSupport
end
def listeners_for(name)
- # this is correctly done double-checked locking (ThreadSafe::Cache's lookups have volatile semantics)
+ # this is correctly done double-checked locking (Concurrent::Map's lookups have volatile semantics)
@listeners_for[name] || synchronize do
# use synchronisation when accessing @subscribers
@listeners_for[name] ||= @subscribers.select { |s| s.subscribed_to?(name) }
@@ -111,7 +111,7 @@ module ActiveSupport
end
end
- class Timed < Evented
+ class Timed < Evented # :nodoc:
def publish(name, *args)
@delegate.call name, *args
end
diff --git a/activesupport/lib/active_support/notifications/instrumenter.rb b/activesupport/lib/active_support/notifications/instrumenter.rb
index 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 258d9b34e1..64d9e71f37 100644
--- a/activesupport/lib/active_support/number_helper.rb
+++ b/activesupport/lib/active_support/number_helper.rb
@@ -47,6 +47,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
@@ -115,10 +123,10 @@ module ActiveSupport
# number_to_percentage(100, precision: 0) # => 100%
# number_to_percentage(1000, delimiter: '.', separator: ',') # => 1.000,000%
# number_to_percentage(302.24398923423, precision: 5) # => 302.24399%
- # number_to_percentage(1000, locale: :fr) # => 1 000,000%
- # number_to_percentage:(1000, precision: nil) # => 1000%
+ # 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 %
+ # number_to_percentage(100, format: '%n %') # => 100.000 %
def number_to_percentage(number, options = {})
NumberToPercentageConverter.convert(number, options)
end
@@ -135,6 +143,9 @@ module ActiveSupport
# to ",").
# * <tt>:separator</tt> - Sets the separator between the
# fractional and integer digits (defaults to ".").
+ # * <tt>:delimiter_pattern</tt> - Sets a custom regular expression used for
+ # deriving the placement of delimiter. Helpful when using currency formats
+ # like INR.
#
# ==== Examples
#
@@ -147,7 +158,10 @@ module ActiveSupport
# number_to_delimited(12345678.05, locale: :fr) # => 12 345 678,05
# number_to_delimited('112a') # => 112a
# number_to_delimited(98765432.98, delimiter: ' ', separator: ',')
- # # => 98 765 432,98
+ # # => 98 765 432,98
+ # number_to_delimited("123456.78",
+ # delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/)
+ # # => 1,23,456.78
def number_to_delimited(number, options = {})
NumberToDelimitedConverter.convert(number, options)
end
@@ -220,8 +234,6 @@ module ActiveSupport
# * <tt>:strip_insignificant_zeros</tt> - If +true+ removes
# insignificant zeros after the decimal separator (defaults to
# +true+)
- # * <tt>:prefix</tt> - If +:si+ formats the number using the SI
- # prefix (defaults to :binary)
#
# ==== Examples
#
@@ -231,6 +243,8 @@ module ActiveSupport
# 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
diff --git a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
index d85cc086d7..45ae8f1a93 100644
--- a/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_delimited_converter.rb
@@ -3,7 +3,7 @@ module ActiveSupport
class NumberToDelimitedConverter < NumberConverter #:nodoc:
self.validate_float = true
- DELIMITED_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
+ DEFAULT_DELIMITER_REGEX = /(\d)(?=(\d\d\d)+(?!\d))/
def convert
parts.join(options[:separator])
@@ -13,11 +13,16 @@ module ActiveSupport
def parts
left, right = number.to_s.split('.')
- left.gsub!(DELIMITED_REGEX) do |digit_to_delimit|
+ left.gsub!(delimiter_pattern) do |digit_to_delimit|
"#{digit_to_delimit}#{options[:delimiter]}"
end
[left, right].compact
end
+
+ def delimiter_pattern
+ options.fetch(:delimiter_pattern, DEFAULT_DELIMITER_REGEX)
+ end
+
end
end
end
diff --git a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
index 5c6fe2df83..7a1f8171c0 100644
--- a/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
+++ b/activesupport/lib/active_support/number_helper/number_to_human_converter.rb
@@ -20,9 +20,11 @@ module ActiveSupport
exponent = calculate_exponent(units)
@number = number / (10 ** exponent)
+ until (rounded_number = NumberToRoundedConverter.convert(number, options)) != NumberToRoundedConverter.convert(1000, options)
+ @number = number / 1000.0
+ exponent += 3
+ end
unit = determine_unit(units, exponent)
-
- rounded_number = NumberToRoundedConverter.convert(number, options)
format.gsub('%n'.freeze, rounded_number).gsub('%u'.freeze, unit).strip
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 ac0d20b454..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,12 +1,16 @@
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
def convert
+ if opts.key?(:prefix)
+ ActiveSupport::Deprecation.warn('The :prefix option of `number_to_human_size` is deprecated and will be removed in Rails 5.1 with no replacement.')
+ end
+
@number = Float(number)
# for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files
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/ordered_options.rb b/activesupport/lib/active_support/ordered_options.rb
index a33e2c58a9..53a55bd986 100644
--- a/activesupport/lib/active_support/ordered_options.rb
+++ b/activesupport/lib/active_support/ordered_options.rb
@@ -6,6 +6,7 @@ module ActiveSupport
# h[:girl] = 'Mary'
# h[:boy] # => 'John'
# h[:girl] # => 'Mary'
+ # h[:dog] # => nil
#
# Using +OrderedOptions+, the above code could be reduced to:
#
@@ -14,6 +15,13 @@ module ActiveSupport
# h.girl = 'Mary'
# h.boy # => 'John'
# h.girl # => 'Mary'
+ # h.dog # => nil
+ #
+ # To raise an exception when the value is blank, append a
+ # bang to the key name, like:
+ #
+ # h.dog! # => raises KeyError: key not found: :dog
+ #
class OrderedOptions < Hash
alias_method :_get, :[] # preserve the original #[] method
protected :_get # make it protected
@@ -31,7 +39,13 @@ module ActiveSupport
if name_string.chomp!('=')
self[name_string] = args.first
else
- self[name]
+ bangs = name_string.chomp!('!')
+
+ if bangs
+ fetch(name_string.to_sym).presence || raise(KeyError.new("#{name_string} is blank."))
+ else
+ self[name_string]
+ end
end
end
diff --git a/activesupport/lib/active_support/per_thread_registry.rb b/activesupport/lib/active_support/per_thread_registry.rb
index ca2e4d5625..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:
@@ -43,9 +48,9 @@ module ActiveSupport
protected
def method_missing(name, *args, &block) # :nodoc:
# Caches the method definition as a singleton method of the receiver.
- define_singleton_method(name) do |*a, &b|
- instance.public_send(name, *a, &b)
- end
+ #
+ # By letting #delegate handle it, we avoid an enclosure that'll capture args.
+ singleton_class.delegate name, to: :instance
send(name, *args, &block)
end
diff --git a/activesupport/lib/active_support/railtie.rb b/activesupport/lib/active_support/railtie.rb
index cd0fb51009..845788b669 100644
--- a/activesupport/lib/active_support/railtie.rb
+++ b/activesupport/lib/active_support/railtie.rb
@@ -26,7 +26,7 @@ module ActiveSupport
unless zone_default
raise 'Value assigned to config.time_zone not recognized. ' \
- 'Run "rake -D time" for a list of tasks for finding appropriate time zone names.'
+ 'Run "rake time:zones:all" for a time zone names list.'
end
Time.zone_default = zone_default
diff --git a/activesupport/lib/active_support/testing/assertions.rb b/activesupport/lib/active_support/testing/assertions.rb
index 411dd29df5..29305e0082 100644
--- a/activesupport/lib/active_support/testing/assertions.rb
+++ b/activesupport/lib/active_support/testing/assertions.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/object/blank'
module ActiveSupport
module Testing
module Assertions
- # Assert that an expression is not truthy. Passes if <tt>object</tt> is
+ # Asserts that an expression is not truthy. Passes if <tt>object</tt> is
# +nil+ or +false+. "Truthy" means "considered true in a conditional"
# like <tt>if foo</tt>.
#
@@ -28,7 +28,7 @@ module ActiveSupport
#
# An arbitrary expression is passed in and evaluated.
#
- # assert_difference 'assigns(:article).comments(:reload).size' do
+ # assert_difference 'Article.last.comments(:reload).size' do
# post :create, params: { comment: {...} }
# end
#
@@ -68,13 +68,15 @@ module ActiveSupport
}
before = exps.map(&:call)
- yield
+ retval = yield
expressions.zip(exps).each_with_index do |(code, e), i|
error = "#{code.inspect} didn't change by #{difference}"
error = "#{message}.\n#{error}" if message
assert_equal(before[i] + difference, e.call, error)
end
+
+ retval
end
# Assertion that the numeric result of evaluating an expression is not
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
index 5aa5f46310..84c6b89340 100644
--- a/activesupport/lib/active_support/testing/autorun.rb
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -2,4 +2,11 @@ gem 'minitest'
require 'minitest'
-Minitest.autorun
+if Minitest.respond_to?(:run_with_rails_extension)
+ unless Minitest.run_with_rails_extension
+ Minitest.run_with_autorun = true
+ Minitest.autorun
+ end
+else
+ Minitest.autorun
+end
diff --git a/activesupport/lib/active_support/testing/deprecation.rb b/activesupport/lib/active_support/testing/deprecation.rb
index 6c94c611b6..5dfa14eeba 100644
--- a/activesupport/lib/active_support/testing/deprecation.rb
+++ b/activesupport/lib/active_support/testing/deprecation.rb
@@ -3,8 +3,8 @@ require 'active_support/deprecation'
module ActiveSupport
module Testing
module Deprecation #:nodoc:
- def assert_deprecated(match = nil, &block)
- result, warnings = collect_deprecations(&block)
+ def assert_deprecated(match = nil, deprecator = nil, &block)
+ result, warnings = collect_deprecations(deprecator, &block)
assert !warnings.empty?, "Expected a deprecation warning within the block but received none"
if match
match = Regexp.new(Regexp.escape(match)) unless match.is_a?(Regexp)
@@ -13,22 +13,23 @@ module ActiveSupport
result
end
- def assert_not_deprecated(&block)
- result, deprecations = collect_deprecations(&block)
+ def assert_not_deprecated(deprecator = nil, &block)
+ result, deprecations = collect_deprecations(deprecator, &block)
assert deprecations.empty?, "Expected no deprecation warning within the block but received #{deprecations.size}: \n #{deprecations * "\n "}"
result
end
- def collect_deprecations
- old_behavior = ActiveSupport::Deprecation.behavior
+ def collect_deprecations(deprecator = nil)
+ deprecator ||= ActiveSupport::Deprecation
+ old_behavior = deprecator.behavior
deprecations = []
- ActiveSupport::Deprecation.behavior = Proc.new do |message, callstack|
+ deprecator.behavior = Proc.new do |message, callstack|
deprecations << message
end
result = yield
[result, deprecations]
ensure
- ActiveSupport::Deprecation.behavior = old_behavior
+ deprecator.behavior = old_behavior
end
end
end
diff --git a/activesupport/lib/active_support/testing/file_fixtures.rb b/activesupport/lib/active_support/testing/file_fixtures.rb
index 4c6a0801b8..affb84cda5 100644
--- a/activesupport/lib/active_support/testing/file_fixtures.rb
+++ b/activesupport/lib/active_support/testing/file_fixtures.rb
@@ -18,7 +18,7 @@ module ActiveSupport
# Returns a +Pathname+ to the fixture file named +fixture_name+.
#
- # Raises ArgumentError if +fixture_name+ can't be found.
+ # Raises +ArgumentError+ if +fixture_name+ can't be found.
def file_fixture(fixture_name)
path = Pathname.new(File.join(file_fixture_path, fixture_name))
diff --git a/activesupport/lib/active_support/testing/isolation.rb b/activesupport/lib/active_support/testing/isolation.rb
index 1de0a19998..edf8b30a0a 100644
--- a/activesupport/lib/active_support/testing/isolation.rb
+++ b/activesupport/lib/active_support/testing/isolation.rb
@@ -41,7 +41,23 @@ module ActiveSupport
pid = fork do
read.close
yield
- write.puts [Marshal.dump(self.dup)].pack("m")
+ begin
+ if error?
+ failures.map! { |e|
+ begin
+ Marshal.dump e
+ e
+ rescue TypeError
+ ex = Exception.new e.message
+ ex.set_backtrace e.backtrace
+ Minitest::UnexpectedError.new ex
+ end
+ }
+ end
+ result = Marshal.dump(self.dup)
+ end
+
+ write.puts [result].pack("m")
exit!
end
diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb
new file mode 100644
index 0000000000..fccaa54f40
--- /dev/null
+++ b/activesupport/lib/active_support/testing/method_call_assertions.rb
@@ -0,0 +1,41 @@
+require 'minitest/mock'
+
+module ActiveSupport
+ module Testing
+ module MethodCallAssertions # :nodoc:
+ private
+ def assert_called(object, method_name, message = nil, times: 1, returns: nil)
+ times_called = 0
+
+ object.stub(method_name, proc { times_called += 1; returns }) { yield }
+
+ error = "Expected #{method_name} to be called #{times} times, " \
+ "but was called #{times_called} times"
+ error = "#{message}.\n#{error}" if message
+ assert_equal times, times_called, error
+ end
+
+ def assert_called_with(object, method_name, args = [], returns: nil)
+ mock = Minitest::Mock.new
+
+ if args.all? { |arg| arg.is_a?(Array) }
+ args.each { |arg| mock.expect(:call, returns, arg) }
+ else
+ mock.expect(:call, returns, args)
+ end
+
+ object.stub(method_name, mock) { yield }
+
+ mock.verify
+ end
+
+ def assert_not_called(object, method_name, message = nil, &block)
+ assert_called(object, method_name, message, times: 0, &block)
+ end
+
+ def stub_any_instance(klass, instance: klass.new)
+ klass.stub(:new, instance) { yield instance }
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb
index 3478b09423..fca0947c5b 100644
--- a/activesupport/lib/active_support/testing/time_helpers.rb
+++ b/activesupport/lib/active_support/testing/time_helpers.rb
@@ -39,7 +39,7 @@ module ActiveSupport
end
end
- # Contain helpers that help you test passage of time.
+ # Contains helpers that help you test passage of time.
module TimeHelpers
# Changes current time to the time in the future or in the past by a given time difference by
# stubbing +Time.now+, +Date.today+, and +DateTime.now+.
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index b0d7f3299f..79cc748cf5 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -1,3 +1,4 @@
+require 'active_support/duration'
require 'active_support/values/time_zone'
require 'active_support/core_ext/object/acts_like'
@@ -13,7 +14,7 @@ module ActiveSupport
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.local(2007, 2, 10, 15, 30, 45) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
# Time.zone.parse('2007-02-10 15:30:45') # => Sat, 10 Feb 2007 15:30:45 EST -05:00
- # Time.zone.at(1170361845) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
+ # Time.zone.at(1171139445) # => Sat, 10 Feb 2007 15:30:45 EST -05:00
# Time.zone.now # => Sun, 18 May 2008 13:07:55 EDT -04:00
# Time.utc(2007, 2, 10, 20, 30, 45).in_time_zone # => Sat, 10 Feb 2007 15:30:45 EST -05:00
#
@@ -40,6 +41,9 @@ module ActiveSupport
'Time'
end
+ PRECISIONS = Hash.new { |h, n| h[n] = "%FT%T.%#{n}N".freeze }
+ PRECISIONS[0] = '%FT%T'.freeze
+
include Comparable
attr_reader :time_zone
@@ -98,7 +102,7 @@ module ActiveSupport
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
# Time.zone.now.utc? # => false
def utc?
- time_zone.name == 'UTC'
+ period.offset.abbreviation == :UTC || period.offset.abbreviation == :UCT
end
alias_method :gmt?, :utc?
@@ -131,7 +135,7 @@ module ActiveSupport
# Returns a string of the object's date, time, zone and offset from UTC.
#
- # Time.zone.now.httpdate # => "Thu, 04 Dec 2014 11:00:25 EST -05:00"
+ # Time.zone.now.inspect # => "Thu, 04 Dec 2014 11:00:25 EST -05:00"
def inspect
"#{time.strftime('%a, %d %b %Y %H:%M:%S')} #{zone} #{formatted_offset}"
end
@@ -141,11 +145,7 @@ module ActiveSupport
#
# Time.zone.now.xmlschema # => "2014-12-04T11:02:37-05:00"
def xmlschema(fraction_digits = 0)
- fraction = if fraction_digits.to_i > 0
- (".%06i" % time.usec)[0, fraction_digits.to_i + 1]
- end
-
- "#{time.strftime("%Y-%m-%dT%H:%M:%S")}#{fraction}#{formatted_offset(true, 'Z')}"
+ "#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z'.freeze)}"
end
alias_method :iso8601, :xmlschema
@@ -245,8 +245,9 @@ module ActiveSupport
utc.future?
end
+ # Returns +true+ if +other+ is equal to current object.
def eql?(other)
- utc.eql?(other)
+ other.eql?(utc)
end
def hash
@@ -283,8 +284,8 @@ module ActiveSupport
# the current object's time and the +other+ time.
#
# Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
- # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EST -05:00
- # now - 1000 # => Sun, 02 Nov 2014 01:09:48 EST -05:00
+ # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00
+ # now - 1000 # => Mon, 03 Nov 2014 00:09:48 EST -05:00
#
# If subtracting a Duration of variable length (i.e., years, months, days),
# move backward from #time, otherwise move backward from #utc, for accuracy
@@ -293,8 +294,8 @@ module ActiveSupport
# For instance, a time - 24.hours will go subtract exactly 24 hours, while a
# time - 1.day will subtract 23-25 hours, depending on the day.
#
- # now - 24.hours # => Sat, 01 Nov 2014 02:26:28 EDT -04:00
- # now - 1.day # => Sat, 01 Nov 2014 01:26:28 EDT -04:00
+ # now - 24.hours # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now - 1.day # => Sun, 02 Nov 2014 00:26:28 EDT -04:00
def -(other)
if other.acts_like?(:time)
to_time - other.to_time
@@ -306,10 +307,48 @@ module ActiveSupport
end
end
+ # Subtracts an interval of time from the current object's time and returns
+ # the result as a new TimeWithZone object.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Mon, 03 Nov 2014 00:26:28 EST -05:00
+ # now.ago(1000) # => Mon, 03 Nov 2014 00:09:48 EST -05:00
+ #
+ # If we're subtracting a Duration of variable length (i.e., years, months,
+ # days), move backward from #time, otherwise move backward from #utc, for
+ # accuracy when moving across DST boundaries.
+ #
+ # For instance, <tt>time.ago(24.hours)</tt> will move back exactly 24 hours,
+ # while <tt>time.ago(1.day)</tt> will move back 23-25 hours, depending on
+ # the day.
+ #
+ # now.ago(24.hours) # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now.ago(1.day) # => Sun, 02 Nov 2014 00:26:28 EDT -04:00
def ago(other)
since(-other)
end
+ # Uses Date to provide precise Time calculations for years, months, and days
+ # according to the proleptic Gregorian calendar. The result is returned as a
+ # new TimeWithZone object.
+ #
+ # The +options+ parameter takes a hash with any of these keys:
+ # <tt>:years</tt>, <tt>:months</tt>, <tt>:weeks</tt>, <tt>:days</tt>,
+ # <tt>:hours</tt>, <tt>:minutes</tt>, <tt>:seconds</tt>.
+ #
+ # If advancing by a value of variable length (i.e., years, weeks, months,
+ # days), move forward from #time, otherwise move forward from #utc, for
+ # accuracy when moving across DST boundaries.
+ #
+ # Time.zone = 'Eastern Time (US & Canada)' # => 'Eastern Time (US & Canada)'
+ # now = Time.zone.now # => Sun, 02 Nov 2014 01:26:28 EDT -04:00
+ # now.advance(seconds: 1) # => Sun, 02 Nov 2014 01:26:29 EDT -04:00
+ # now.advance(minutes: 1) # => Sun, 02 Nov 2014 01:27:28 EDT -04:00
+ # now.advance(hours: 1) # => Sun, 02 Nov 2014 01:26:28 EST -05:00
+ # now.advance(days: 1) # => Mon, 03 Nov 2014 01:26:28 EST -05:00
+ # now.advance(weeks: 1) # => Sun, 09 Nov 2014 01:26:28 EST -05:00
+ # now.advance(months: 1) # => Tue, 02 Dec 2014 01:26:28 EST -05:00
+ # now.advance(years: 1) # => Mon, 02 Nov 2015 01:26:28 EST -05:00
def advance(options)
# If we're advancing a value of variable length (i.e., years, weeks, months, days), advance from #time,
# otherwise advance from #utc, for accuracy when moving across DST boundaries
@@ -328,6 +367,11 @@ module ActiveSupport
EOV
end
+ # Returns Array of parts of Time in sequence of
+ # [seconds, minutes, hours, day, month, year, weekday, yearday, dst?, zone].
+ #
+ # now = Time.zone.now # => Tue, 18 Aug 2015 02:29:27 UTC +00:00
+ # now.to_a # => [27, 29, 2, 18, 8, 2015, 2, 230, false, "UTC"]
def to_a
[time.sec, time.min, time.hour, time.day, time.mon, time.year, time.wday, time.yday, dst?, zone]
end
@@ -357,11 +401,15 @@ module ActiveSupport
utc.to_r
end
- # Return an instance of Time in the system timezone.
+ # Returns an instance of Time in the system timezone.
def to_time
utc.to_time
end
+ # Returns an instance of DateTime with the timezone's UTC offset
+ #
+ # Time.zone.now.to_datetime # => Tue, 18 Aug 2015 02:32:20 +0000
+ # Time.current.in_time_zone('Hawaii').to_datetime # => Mon, 17 Aug 2015 16:32:20 -1000
def to_datetime
utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
end
@@ -377,6 +425,11 @@ module ActiveSupport
end
alias_method :kind_of?, :is_a?
+ # An instance of ActiveSupport::TimeWithZone is never blank
+ def blank?
+ false
+ end
+
def freeze
period; utc; time # preload instance variables before freezing
super
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 2699a064d7..7ca3592520 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -1,5 +1,5 @@
require 'tzinfo'
-require 'thread_safe'
+require 'concurrent/map'
require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/object/try'
@@ -23,15 +23,9 @@ module ActiveSupport
# config.time_zone = 'Eastern Time (US & Canada)'
# end
#
- # Time.zone # => #<TimeZone:0x514834...>
+ # Time.zone # => #<ActiveSupport::TimeZone:0x514834...>
# Time.zone.name # => "Eastern Time (US & Canada)"
# Time.zone.now # => Sun, 18 May 2008 14:30:44 EDT -04:00
- #
- # The version of TZInfo bundled with Active Support only includes the
- # definitions necessary to support the zones defined by the TimeZone class.
- # If you need to use zones that aren't defined by TimeZone, you'll need to
- # install the TZInfo gem (if a recent version of the gem is installed locally,
- # this will be used instead of the bundled version.)
class TimeZone
# Keys are Rails TimeZone names, values are TZInfo identifiers.
MAPPING = {
@@ -92,7 +86,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",
@@ -189,13 +184,13 @@ module ActiveSupport
UTC_OFFSET_WITH_COLON = '%s%02d:%02d'
UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(':', '')
- @lazy_zones_map = ThreadSafe::Cache.new
+ @lazy_zones_map = Concurrent::Map.new
class << self
# Assumes self represents an offset from UTC in seconds (as returned from
# Time#utc_offset) and turns this into an +HH:MM formatted string.
#
- # TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
+ # ActiveSupport::TimeZone.seconds_to_utc_offset(-21_600) # => "-06:00"
def seconds_to_utc_offset(seconds, colon = true)
format = colon ? UTC_OFFSET_WITH_COLON : UTC_OFFSET_WITHOUT_COLON
sign = (seconds < 0 ? '-' : '+')
@@ -285,8 +280,12 @@ module ActiveSupport
end
end
- # Returns the offset of this time zone as a formatted string, of the
- # format "+HH:MM".
+ # Returns a formatted string of the offset from UTC, or an alternative
+ # string if the time zone is already UTC.
+ #
+ # zone = ActiveSupport::TimeZone['Central Time (US & Canada)']
+ # zone.formatted_offset # => "-06:00"
+ # zone.formatted_offset(false) # => "-0600"
def formatted_offset(colon=true, alternate_utc_string = nil)
utc_offset == 0 && alternate_utc_string || self.class.seconds_to_utc_offset(utc_offset, colon)
end
@@ -384,7 +383,7 @@ module ActiveSupport
time_now.utc.in_time_zone(self)
end
- # Return the current date in this time zone.
+ # Returns the current date in this time zone.
def today
tzinfo.now.to_date
end
diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat
index 760be4c07a..dd2c178fb6 100644
--- a/activesupport/lib/active_support/values/unicode_tables.dat
+++ b/activesupport/lib/active_support/values/unicode_tables.dat
Binary files differ
diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb
index 009ee4db90..df7b081993 100644
--- a/activesupport/lib/active_support/xml_mini.rb
+++ b/activesupport/lib/active_support/xml_mini.rb
@@ -78,6 +78,9 @@ module ActiveSupport
)
end
+ attr_accessor :depth
+ self.depth = 100
+
delegate :parse, :to => :backend
def backend
diff --git a/activesupport/lib/active_support/xml_mini/jdom.rb b/activesupport/lib/active_support/xml_mini/jdom.rb
index f303daa1a7..94751bbc04 100644
--- a/activesupport/lib/active_support/xml_mini/jdom.rb
+++ b/activesupport/lib/active_support/xml_mini/jdom.rb
@@ -46,7 +46,7 @@ module ActiveSupport
xml_string_reader = StringReader.new(data)
xml_input_source = InputSource.new(xml_string_reader)
doc = @dbf.new_document_builder.parse(xml_input_source)
- merge_element!({CONTENT_KEY => ''}, doc.document_element)
+ merge_element!({CONTENT_KEY => ''}, doc.document_element, XmlMini.depth)
end
end
@@ -58,9 +58,10 @@ module ActiveSupport
# Hash to merge the converted element into.
# element::
# XML element to merge into hash
- def merge_element!(hash, element)
+ def merge_element!(hash, element, depth)
+ raise 'Document too deep!' if depth == 0
delete_empty(hash)
- merge!(hash, element.tag_name, collapse(element))
+ merge!(hash, element.tag_name, collapse(element, depth))
end
def delete_empty(hash)
@@ -71,14 +72,14 @@ module ActiveSupport
#
# element::
# The document element to be collapsed.
- def collapse(element)
+ def collapse(element, depth)
hash = get_attributes(element)
child_nodes = element.child_nodes
if child_nodes.length > 0
(0...child_nodes.length).each do |i|
child = child_nodes.item(i)
- merge_element!(hash, child) unless child.node_type == Node.TEXT_NODE
+ merge_element!(hash, child, depth - 1) unless child.node_type == Node.TEXT_NODE
end
merge_texts!(hash, element) unless empty_content?(element)
hash
diff --git a/activesupport/lib/active_support/xml_mini/rexml.rb b/activesupport/lib/active_support/xml_mini/rexml.rb
index 5c7c78bf70..924ed72345 100644
--- a/activesupport/lib/active_support/xml_mini/rexml.rb
+++ b/activesupport/lib/active_support/xml_mini/rexml.rb
@@ -29,7 +29,7 @@ module ActiveSupport
doc = REXML::Document.new(data)
if doc.root
- merge_element!({}, doc.root)
+ merge_element!({}, doc.root, XmlMini.depth)
else
raise REXML::ParseException,
"The document #{doc.to_s.inspect} does not have a valid root"
@@ -44,19 +44,20 @@ module ActiveSupport
# Hash to merge the converted element into.
# element::
# XML element to merge into hash
- def merge_element!(hash, element)
- merge!(hash, element.name, collapse(element))
+ def merge_element!(hash, element, depth)
+ raise REXML::ParseException, "The document is too deep" if depth == 0
+ merge!(hash, element.name, collapse(element, depth))
end
# Actually converts an XML document element into a data structure.
#
# element::
# The document element to be collapsed.
- def collapse(element)
+ def collapse(element, depth)
hash = get_attributes(element)
if element.has_elements?
- element.each_element {|child| merge_element!(hash, child) }
+ element.each_element {|child| merge_element!(hash, child, depth - 1) }
merge_texts!(hash, element) unless empty_content?(element)
hash
else
diff --git a/activesupport/test/abstract_unit.rb b/activesupport/test/abstract_unit.rb
index 7ffcae6007..c0e23e89f7 100644
--- a/activesupport/test/abstract_unit.rb
+++ b/activesupport/test/abstract_unit.rb
@@ -15,6 +15,7 @@ silence_warnings do
end
require 'active_support/testing/autorun'
+require 'active_support/testing/method_call_assertions'
ENV['NO_RELOAD'] = '1'
require 'active_support'
@@ -37,4 +38,6 @@ def jruby_skip(message = '')
skip message if defined?(JRUBY_VERSION)
end
-require 'mocha/setup' # FIXME: stop using mocha
+class ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+end
diff --git a/activesupport/test/array_inquirer_test.rb b/activesupport/test/array_inquirer_test.rb
index b25e5cca86..263ab3802b 100644
--- a/activesupport/test/array_inquirer_test.rb
+++ b/activesupport/test/array_inquirer_test.rb
@@ -3,7 +3,7 @@ require 'active_support/core_ext/array'
class ArrayInquirerTest < ActiveSupport::TestCase
def setup
- @array_inquirer = ActiveSupport::ArrayInquirer.new([:mobile, :tablet])
+ @array_inquirer = ActiveSupport::ArrayInquirer.new([:mobile, :tablet, 'api'])
end
def test_individual
@@ -18,6 +18,11 @@ class ArrayInquirerTest < ActiveSupport::TestCase
assert_not @array_inquirer.any?(:desktop, :watch)
end
+ def test_any_string_symbol_mismatch
+ assert @array_inquirer.any?('mobile')
+ assert @array_inquirer.any?(:api)
+ end
+
def test_any_with_block
assert @array_inquirer.any? { |v| v == :mobile }
assert_not @array_inquirer.any? { |v| v == :desktop }
@@ -28,7 +33,7 @@ class ArrayInquirerTest < ActiveSupport::TestCase
end
def test_inquiry
- result = [:mobile, :tablet].inquiry
+ result = [:mobile, :tablet, 'api'].inquiry
assert_instance_of ActiveSupport::ArrayInquirer, result
assert_equal @array_inquirer, result
diff --git a/activesupport/test/autoloading_fixtures/a/c/e/f.rb b/activesupport/test/autoloading_fixtures/a/c/e/f.rb
deleted file mode 100644
index 57dba5a307..0000000000
--- a/activesupport/test/autoloading_fixtures/a/c/e/f.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class A::C::E::F
-end \ No newline at end of file
diff --git a/activesupport/test/autoloading_fixtures/a/c/em/f.rb b/activesupport/test/autoloading_fixtures/a/c/em/f.rb
new file mode 100644
index 0000000000..8b28e19148
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/a/c/em/f.rb
@@ -0,0 +1,2 @@
+class A::C::EM::F
+end \ No newline at end of file
diff --git a/activesupport/test/autoloading_fixtures/d.rb b/activesupport/test/autoloading_fixtures/d.rb
new file mode 100644
index 0000000000..45c794d4ca
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/d.rb
@@ -0,0 +1,2 @@
+class D
+end \ No newline at end of file
diff --git a/activesupport/test/autoloading_fixtures/e.rb b/activesupport/test/autoloading_fixtures/e.rb
deleted file mode 100644
index 2f59e4fb75..0000000000
--- a/activesupport/test/autoloading_fixtures/e.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-class E
-end \ No newline at end of file
diff --git a/activesupport/test/autoloading_fixtures/em.rb b/activesupport/test/autoloading_fixtures/em.rb
new file mode 100644
index 0000000000..16a1838667
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/em.rb
@@ -0,0 +1,2 @@
+class EM
+end \ No newline at end of file
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 527538ed9a..4a299429f3 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -133,37 +133,42 @@ class CacheStoreSettingTest < ActiveSupport::TestCase
end
def test_mem_cache_fragment_cache_store
- Dalli::Client.expects(:new).with(%w[localhost], {})
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_called_with(Dalli::Client, :new, [%w[localhost], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost"
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
end
def test_mem_cache_fragment_cache_store_with_given_mem_cache
mem_cache = Dalli::Client.new
- Dalli::Client.expects(:new).never
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_not_called(Dalli::Client, :new) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, mem_cache
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
end
def test_mem_cache_fragment_cache_store_with_not_dalli_client
- Dalli::Client.expects(:new).never
- memcache = Object.new
- assert_raises(ArgumentError) do
- ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ assert_not_called(Dalli::Client, :new) do
+ memcache = Object.new
+ assert_raises(ArgumentError) do
+ ActiveSupport::Cache.lookup_store :mem_cache_store, memcache
+ end
end
end
def test_mem_cache_fragment_cache_store_with_multiple_servers
- Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], {})
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1'
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], {}]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1'
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ end
end
def test_mem_cache_fragment_cache_store_with_options
- Dalli::Client.expects(:new).with(%w[localhost 192.168.1.1], { :timeout => 10 })
- store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10
- assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
- assert_equal 'foo', store.options[:namespace]
+ assert_called_with(Dalli::Client, :new, [%w[localhost 192.168.1.1], { :timeout => 10 }]) do
+ store = ActiveSupport::Cache.lookup_store :mem_cache_store, "localhost", '192.168.1.1', :namespace => 'foo', :timeout => 10
+ assert_kind_of(ActiveSupport::Cache::MemCacheStore, store)
+ assert_equal 'foo', store.options[:namespace]
+ end
end
def test_object_assigned_fragment_cache_store
@@ -224,13 +229,15 @@ module CacheStoreBehavior
def test_fetch_without_cache_miss
@cache.write('foo', 'bar')
- @cache.expects(:write).never
- assert_equal 'bar', @cache.fetch('foo') { 'baz' }
+ assert_not_called(@cache, :write) do
+ assert_equal 'bar', @cache.fetch('foo') { 'baz' }
+ end
end
def test_fetch_with_cache_miss
- @cache.expects(:write).with('foo', 'baz', @cache.options)
- assert_equal 'baz', @cache.fetch('foo') { 'baz' }
+ assert_called_with(@cache, :write, ['foo', 'baz', @cache.options]) do
+ assert_equal 'baz', @cache.fetch('foo') { 'baz' }
+ end
end
def test_fetch_with_cache_miss_passes_key_to_block
@@ -245,15 +252,18 @@ module CacheStoreBehavior
def test_fetch_with_forced_cache_miss
@cache.write('foo', 'bar')
- @cache.expects(:read).never
- @cache.expects(:write).with('foo', 'bar', @cache.options.merge(:force => true))
- @cache.fetch('foo', :force => true) { 'bar' }
+ assert_not_called(@cache, :read) do
+ assert_called_with(@cache, :write, ['foo', 'bar', @cache.options.merge(:force => true)]) do
+ @cache.fetch('foo', :force => true) { 'bar' }
+ end
+ end
end
def test_fetch_with_cached_nil
@cache.write('foo', nil)
- @cache.expects(:write).never
- assert_nil @cache.fetch('foo') { 'baz' }
+ assert_not_called(@cache, :write) do
+ assert_nil @cache.fetch('foo') { 'baz' }
+ end
end
def test_should_read_and_write_hash
@@ -288,8 +298,9 @@ module CacheStoreBehavior
@cache.write('foo', 'bar', :expires_in => 10)
@cache.write('fu', 'baz')
@cache.write('fud', 'biz')
- Time.stubs(:now).returns(time + 11)
- assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
+ Time.stub(:now, time + 11) do
+ assert_equal({"fu" => "baz"}, @cache.read_multi('foo', 'fu'))
+ end
end
def test_fetch_multi
@@ -303,8 +314,9 @@ module CacheStoreBehavior
end
def test_multi_with_objects
- foo = stub(:title => 'FOO!', :cache_key => 'foo')
- bar = stub(:cache_key => 'bar')
+ cache_struct = Struct.new(:cache_key, :title)
+ foo = cache_struct.new('foo', 'FOO!')
+ bar = cache_struct.new('bar')
@cache.write('bar', 'BAM!')
@@ -387,66 +399,74 @@ module CacheStoreBehavior
def test_expires_in
time = Time.local(2008, 4, 24)
- Time.stubs(:now).returns(time)
- @cache.write('foo', 'bar')
- assert_equal 'bar', @cache.read('foo')
+ Time.stub(:now, time) do
+ @cache.write('foo', 'bar')
+ assert_equal 'bar', @cache.read('foo')
+ end
- Time.stubs(:now).returns(time + 30)
- assert_equal 'bar', @cache.read('foo')
+ Time.stub(:now, time + 30) do
+ assert_equal 'bar', @cache.read('foo')
+ end
- Time.stubs(:now).returns(time + 61)
- assert_nil @cache.read('foo')
+ Time.stub(:now, time + 61) do
+ assert_nil @cache.read('foo')
+ end
end
def test_race_condition_protection_skipped_if_not_defined
@cache.write('foo', 'bar')
- time = @cache.send(:read_entry, 'foo', {}).expires_at
- Time.stubs(:now).returns(Time.at(time))
+ time = @cache.send(:read_entry, @cache.send(:normalize_key, 'foo', {}), {}).expires_at
- result = @cache.fetch('foo') do
- assert_equal nil, @cache.read('foo')
- 'baz'
+ Time.stub(:now, Time.at(time)) do
+ result = @cache.fetch('foo') do
+ assert_equal nil, @cache.read('foo')
+ 'baz'
+ end
+ assert_equal 'baz', result
end
- assert_equal 'baz', result
end
def test_race_condition_protection_is_limited
time = Time.now
@cache.write('foo', 'bar', :expires_in => 60)
- Time.stubs(:now).returns(time + 71)
- result = @cache.fetch('foo', :race_condition_ttl => 10) do
- assert_equal nil, @cache.read('foo')
- "baz"
+ Time.stub(:now, time + 71) do
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
+ assert_equal nil, @cache.read('foo')
+ "baz"
+ end
+ assert_equal "baz", result
end
- assert_equal "baz", result
end
def test_race_condition_protection_is_safe
time = Time.now
@cache.write('foo', 'bar', :expires_in => 60)
- Time.stubs(:now).returns(time + 61)
- begin
- @cache.fetch('foo', :race_condition_ttl => 10) do
- assert_equal 'bar', @cache.read('foo')
- raise ArgumentError.new
+ Time.stub(:now, time + 61) do
+ begin
+ @cache.fetch('foo', :race_condition_ttl => 10) do
+ assert_equal 'bar', @cache.read('foo')
+ raise ArgumentError.new
+ end
+ rescue ArgumentError
end
- rescue ArgumentError
+ assert_equal "bar", @cache.read('foo')
+ end
+ Time.stub(:now, time + 91) do
+ assert_nil @cache.read('foo')
end
- assert_equal "bar", @cache.read('foo')
- Time.stubs(:now).returns(time + 91)
- assert_nil @cache.read('foo')
end
def test_race_condition_protection
time = Time.now
@cache.write('foo', 'bar', :expires_in => 60)
- Time.stubs(:now).returns(time + 61)
- result = @cache.fetch('foo', :race_condition_ttl => 10) do
- assert_equal 'bar', @cache.read('foo')
- "baz"
+ Time.stub(:now, time + 61) do
+ result = @cache.fetch('foo', :race_condition_ttl => 10) do
+ assert_equal 'bar', @cache.read('foo')
+ "baz"
+ end
+ assert_equal "baz", result
end
- assert_equal "baz", result
end
def test_crazy_key_characters
@@ -470,6 +490,44 @@ module CacheStoreBehavior
assert_equal({key => "bar"}, @cache.read_multi(key))
assert @cache.delete(key)
end
+
+ def test_cache_hit_instrumentation
+ key = "test_key"
+ @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_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
+ @events = []
+ ActiveSupport::Notifications.subscribe(/^cache_(.*)\.active_support$/) do |*args|
+ @events << ActiveSupport::Notifications::Event.new(*args)
+ end
+ assert_not @cache.fetch("bad_key") {}
+ 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
@@ -577,6 +635,21 @@ module LocalCacheBehavior
end
end
+ def test_local_cache_of_read_nil
+ @cache.with_local_cache do
+ assert_equal nil, @cache.read('foo')
+ @cache.send(:bypass_local_cache) { @cache.write 'foo', 'bar' }
+ assert_equal nil, @cache.read('foo')
+ end
+ end
+
+ def test_local_cache_fetch
+ @cache.with_local_cache do
+ @cache.send(:local_cache).write 'foo', 'bar'
+ assert_equal 'bar', @cache.send(:local_cache).fetch('foo')
+ end
+ end
+
def test_local_cache_of_write_nil
@cache.with_local_cache do
assert @cache.write('foo', nil)
@@ -630,43 +703,52 @@ 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
include DependenciesTestHelpers
def test_simple_autoloading
with_autoloading_fixtures do
- @cache.write('foo', E.new)
+ @cache.write('foo', EM.new)
end
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of E, @cache.read('foo')
+ assert_kind_of EM, @cache.read('foo')
end
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
end
def test_two_classes_autoloading
with_autoloading_fixtures do
- @cache.write('foo', [E.new, ClassFolder.new])
+ @cache.write('foo', [EM.new, ClassFolder.new])
end
- remove_constants(:E, :ClassFolder)
+ remove_constants(:EM, :ClassFolder)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
loaded = @cache.read('foo')
assert_kind_of Array, loaded
assert_equal 2, loaded.size
- assert_kind_of E, loaded[0]
+ assert_kind_of EM, loaded[0]
assert_kind_of ClassFolder, loaded[1]
end
- remove_constants(:E, :ClassFolder)
+ remove_constants(:EM, :ClassFolder)
ActiveSupport::Dependencies.clear
end
end
@@ -698,10 +780,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
@@ -714,14 +798,19 @@ class FileStoreTest < ActiveSupport::TestCase
assert_equal 1, @cache.read("a"*10000)
end
+ def test_long_uri_encoded_keys
+ @cache.write("%"*870, 1)
+ assert_equal 1, @cache.read("%"*870)
+ 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
@@ -729,7 +818,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
@@ -739,7 +828,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
@@ -765,9 +854,10 @@ class FileStoreTest < ActiveSupport::TestCase
end
def test_log_exception_when_cache_read_fails
- File.expects(:exist?).raises(StandardError, "failed")
- @cache.send(:read_entry, "winston", {})
- assert @buffer.string.present?
+ File.stub(:exist?, -> { raise StandardError.new("failed") }) do
+ @cache.send(:read_entry, "winston", {})
+ assert @buffer.string.present?
+ end
end
def test_cleanup_removes_all_expired_entries
@@ -775,11 +865,12 @@ class FileStoreTest < ActiveSupport::TestCase
@cache.write('foo', 'bar', expires_in: 10)
@cache.write('baz', 'qux')
@cache.write('quux', 'corge', expires_in: 20)
- Time.stubs(:now).returns(time + 15)
- @cache.cleanup
- assert_not @cache.exist?('foo')
- assert @cache.exist?('baz')
- assert @cache.exist?('quux')
+ Time.stub(:now, time + 15) do
+ @cache.cleanup
+ assert_not @cache.exist?('foo')
+ assert @cache.exist?('baz')
+ assert @cache.exist?('quux')
+ end
end
def test_write_with_unless_exist
@@ -788,6 +879,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
@@ -962,6 +1059,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
@@ -1016,10 +1119,6 @@ class NullStoreTest < ActiveSupport::TestCase
end
assert_nil @cache.read("name")
end
-
- def test_setting_nil_cache_store
- assert ActiveSupport::Cache.lookup_store.class.name, ActiveSupport::Cache::NullStore.name
- end
end
class CacheStoreLoggerTest < ActiveSupport::TestCase
@@ -1035,6 +1134,19 @@ class CacheStoreLoggerTest < ActiveSupport::TestCase
assert @buffer.string.present?
end
+ def test_log_with_string_namespace
+ @cache.fetch('foo', {namespace: 'string_namespace'}) { 'bar' }
+ assert_match %r{string_namespace:foo}, @buffer.string
+ end
+
+ def test_log_with_proc_namespace
+ proc = Proc.new do
+ "proc_namespace"
+ end
+ @cache.fetch('foo', {:namespace => proc}) { 'bar' }
+ assert_match %r{proc_namespace:foo}, @buffer.string
+ end
+
def test_mute_logging
@cache.mute { @cache.fetch('foo') { 'bar' } }
assert @buffer.string.blank?
@@ -1056,9 +1168,9 @@ class CacheEntryTest < ActiveSupport::TestCase
assert !entry.expired?, 'entry not expired'
entry = ActiveSupport::Cache::Entry.new("value", :expires_in => 60)
assert !entry.expired?, 'entry not expired'
- time = Time.now + 61
- Time.stubs(:now).returns(time)
- assert entry.expired?, 'entry is expired'
+ Time.stub(:now, Time.now + 61) do
+ assert entry.expired?, 'entry is expired'
+ end
end
def test_compress_values
diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb
index cda9732cae..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 }
@@ -766,34 +766,30 @@ module CallbacksTest
end
class CallbackFalseTerminatorWithoutConfigTest < ActiveSupport::TestCase
- def test_returning_false_halts_callback_if_config_variable_is_not_set
+ def test_returning_false_does_not_halt_callback_if_config_variable_is_not_set
obj = CallbackFalseTerminator.new
- assert_deprecated do
- obj.save
- assert_equal :second, obj.halted
- assert !obj.saved
- end
+ obj.save
+ assert_equal nil, obj.halted
+ assert obj.saved
end
end
class CallbackFalseTerminatorWithConfigTrueTest < ActiveSupport::TestCase
def setup
- ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = true
+ ActiveSupport::Callbacks.halt_and_display_warning_on_return_false = true
end
- def test_returning_false_halts_callback_if_config_variable_is_true
+ def test_returning_false_does_not_halt_callback_if_config_variable_is_true
obj = CallbackFalseTerminator.new
- assert_deprecated do
- obj.save
- assert_equal :second, obj.halted
- assert !obj.saved
- end
+ obj.save
+ assert_equal nil, obj.halted
+ assert obj.saved
end
end
class CallbackFalseTerminatorWithConfigFalseTest < ActiveSupport::TestCase
def setup
- ActiveSupport::Callbacks::CallbackChain.halt_and_display_warning_on_return_false = false
+ ActiveSupport::Callbacks.halt_and_display_warning_on_return_false = false
end
def test_returning_false_does_not_halt_callback_if_config_variable_is_false
@@ -1050,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
@@ -1090,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
@@ -1115,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/concern_test.rb b/activesupport/test/concern_test.rb
index 253c1adc23..8ea701cfb7 100644
--- a/activesupport/test/concern_test.rb
+++ b/activesupport/test/concern_test.rb
@@ -54,6 +54,11 @@ class ConcernTest < ActiveSupport::TestCase
include Bar, Baz
end
+ module Qux
+ module ClassMethods
+ end
+ end
+
def setup
@klass = Class.new
end
@@ -70,6 +75,26 @@ class ConcernTest < ActiveSupport::TestCase
assert_equal ConcernTest::Baz::ClassMethods, (class << @klass; self.included_modules; end)[0]
end
+ def test_class_methods_are_extended_only_on_expected_objects
+ ::Object.__send__(:include, Qux)
+ Object.extend(Qux::ClassMethods)
+ # module needs to be created after Qux is included in Object or bug won't
+ # be triggered
+ test_module = Module.new do
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def test
+ end
+ end
+ end
+ @klass.include test_module
+ assert_equal false, Object.respond_to?(:test)
+ Qux.class_eval do
+ remove_const :ClassMethods
+ end
+ end
+
def test_included_block_is_ran
@klass.include(Baz)
assert_equal true, @klass.included_ran
diff --git a/activesupport/test/constantize_test_cases.rb b/activesupport/test/constantize_test_cases.rb
index 366e4e5ef0..1115bc0fd8 100644
--- a/activesupport/test/constantize_test_cases.rb
+++ b/activesupport/test/constantize_test_cases.rb
@@ -100,6 +100,10 @@ module ConstantizeTestCases
assert_nil yield("Ace::Gas::ConstantizeTestCases")
assert_nil yield("#<Class:0x7b8b718b>::Nested_1")
assert_nil yield("Ace::gas")
+ assert_nil yield('Object::ABC')
+ assert_nil yield('Object::Object::Object::ABC')
+ assert_nil yield('A::Object::B')
+ assert_nil yield('A::Object::Object::Object::B')
assert_raises(NameError) do
with_autoloading_fixtures do
diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb
deleted file mode 100644
index 447b1d10ad..0000000000
--- a/activesupport/test/core_ext/class/delegating_attributes_test.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-require 'abstract_unit'
-require 'active_support/core_ext/class/delegating_attributes'
-
-module DelegatingFixtures
- class Parent
- end
-
- class Child < Parent
- ActiveSupport::Deprecation.silence do
- superclass_delegating_accessor :some_attribute
- end
- end
-
- class Mokopuna < Child
- end
-
- class PercysMom
- ActiveSupport::Deprecation.silence do
- superclass_delegating_accessor :superpower
- end
- end
-
- class Percy < PercysMom
- end
-end
-
-class DelegatingAttributesTest < ActiveSupport::TestCase
- include DelegatingFixtures
- attr_reader :single_class
-
- def setup
- @single_class = Class.new(Object)
- end
-
- def test_simple_accessor_declaration
- assert_deprecated do
- single_class.superclass_delegating_accessor :both
- end
-
- # Class should have accessor and mutator
- # the instance should have an accessor only
- assert_respond_to single_class, :both
- assert_respond_to single_class, :both=
- assert single_class.public_instance_methods.map(&:to_s).include?("both")
- assert !single_class.public_instance_methods.map(&:to_s).include?("both=")
- end
-
- def test_simple_accessor_declaration_with_instance_reader_false
- _instance_methods = single_class.public_instance_methods
-
- assert_deprecated do
- single_class.superclass_delegating_accessor :no_instance_reader, :instance_reader => false
- end
-
- assert_respond_to single_class, :no_instance_reader
- assert_respond_to single_class, :no_instance_reader=
- assert !_instance_methods.include?(:no_instance_reader)
- assert !_instance_methods.include?(:no_instance_reader?)
- assert !_instance_methods.include?(:_no_instance_reader)
- end
-
- def test_working_with_simple_attributes
- assert_deprecated do
- single_class.superclass_delegating_accessor :both
- end
-
- single_class.both = "HMMM"
-
- assert_equal "HMMM", single_class.both
- assert_equal true, single_class.both?
-
- assert_equal "HMMM", single_class.new.both
- assert_equal true, single_class.new.both?
-
- single_class.both = false
- assert_equal false, single_class.both?
- end
-
- def test_child_class_delegates_to_parent_but_can_be_overridden
- parent = Class.new
-
- assert_deprecated do
- parent.superclass_delegating_accessor :both
- end
-
- child = Class.new(parent)
- parent.both = "1"
- assert_equal "1", child.both
-
- child.both = "2"
- assert_equal "1", parent.both
- assert_equal "2", child.both
-
- parent.both = "3"
- assert_equal "3", parent.both
- assert_equal "2", child.both
- end
-
- def test_delegation_stops_at_the_right_level
- assert_nil Percy.superpower
- assert_nil PercysMom.superpower
-
- PercysMom.superpower = :heatvision
- assert_equal :heatvision, Percy.superpower
- end
-
- def test_delegation_stops_for_nil
- Mokopuna.some_attribute = nil
- Child.some_attribute="1"
-
- assert_equal "1", Child.some_attribute
- assert_nil Mokopuna.some_attribute
- ensure
- Child.some_attribute=nil
- end
-
- def test_deprecation_warning
- assert_deprecated(/superclass_delegating_accessor is deprecated/) do
- single_class.superclass_delegating_accessor :test_attribute
- end
- end
-end
diff --git a/activesupport/test/core_ext/date_ext_test.rb b/activesupport/test/core_ext/date_ext_test.rb
index e89be25b53..0fc3f765f5 100644
--- a/activesupport/test/core_ext/date_ext_test.rb
+++ b/activesupport/test/core_ext/date_ext_test.rb
@@ -191,8 +191,9 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
def test_yesterday_constructor_when_zone_is_set
with_env_tz 'UTC' do
with_tz_default ActiveSupport::TimeZone['Eastern Time (US & Canada)'] do # UTC -5
- Time.stubs(:now).returns Time.local(2000, 1, 1)
- assert_equal Date.new(1999, 12, 30), Date.yesterday
+ Time.stub(:now, Time.local(2000, 1, 1)) do
+ assert_equal Date.new(1999, 12, 30), Date.yesterday
+ end
end
end
end
@@ -212,8 +213,9 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
def test_tomorrow_constructor_when_zone_is_set
with_env_tz 'UTC' do
with_tz_default ActiveSupport::TimeZone['Europe/Paris'] do # UTC +1
- Time.stubs(:now).returns Time.local(1999, 12, 31, 23)
- assert_equal Date.new(2000, 1, 2), Date.tomorrow
+ Time.stub(:now, Time.local(1999, 12, 31, 23)) do
+ assert_equal Date.new(2000, 1, 2), Date.tomorrow
+ end
end
end
end
@@ -317,23 +319,26 @@ class DateExtCalculationsTest < ActiveSupport::TestCase
end
def test_past
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- assert_equal true, Date.new(1999, 12, 31).past?
- assert_equal false, Date.new(2000,1,1).past?
- assert_equal false, Date.new(2000,1,2).past?
+ Date.stub(:current, Date.new(2000, 1, 1)) do
+ assert_equal true, Date.new(1999, 12, 31).past?
+ assert_equal false, Date.new(2000,1,1).past?
+ assert_equal false, Date.new(2000,1,2).past?
+ end
end
def test_future
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- assert_equal false, Date.new(1999, 12, 31).future?
- assert_equal false, Date.new(2000,1,1).future?
- assert_equal true, Date.new(2000,1,2).future?
+ Date.stub(:current, Date.new(2000, 1, 1)) do
+ assert_equal false, Date.new(1999, 12, 31).future?
+ assert_equal false, Date.new(2000,1,1).future?
+ assert_equal true, Date.new(2000,1,2).future?
+ end
end
def test_current_returns_date_today_when_zone_not_set
with_env_tz 'US/Central' do
- Time.stubs(:now).returns Time.local(1999, 12, 31, 23)
- assert_equal Date.today, Date.current
+ Time.stub(:now, Time.local(1999, 12, 31, 23)) do
+ assert_equal Date.today, Date.current
+ end
end
end
diff --git a/activesupport/test/core_ext/date_time_ext_test.rb b/activesupport/test/core_ext/date_time_ext_test.rb
index 74319ecd09..6fe38c45ec 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -204,61 +204,69 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_today_with_offset
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- assert_equal false, DateTime.civil(1999,12,31,23,59,59, Rational(-18000, 86400)).today?
- assert_equal true, DateTime.civil(2000,1,1,0,0,0, Rational(-18000, 86400)).today?
- assert_equal true, DateTime.civil(2000,1,1,23,59,59, Rational(-18000, 86400)).today?
- assert_equal false, DateTime.civil(2000,1,2,0,0,0, Rational(-18000, 86400)).today?
+ Date.stub(:current, Date.new(2000, 1, 1)) do
+ assert_equal false, DateTime.civil(1999,12,31,23,59,59, Rational(-18000, 86400)).today?
+ assert_equal true, DateTime.civil(2000,1,1,0,0,0, Rational(-18000, 86400)).today?
+ assert_equal true, DateTime.civil(2000,1,1,23,59,59, Rational(-18000, 86400)).today?
+ assert_equal false, DateTime.civil(2000,1,2,0,0,0, Rational(-18000, 86400)).today?
+ end
end
def test_today_without_offset
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- assert_equal false, DateTime.civil(1999,12,31,23,59,59).today?
- assert_equal true, DateTime.civil(2000,1,1,0).today?
- assert_equal true, DateTime.civil(2000,1,1,23,59,59).today?
- assert_equal false, DateTime.civil(2000,1,2,0).today?
+ Date.stub(:current, Date.new(2000, 1, 1)) do
+ assert_equal false, DateTime.civil(1999,12,31,23,59,59).today?
+ assert_equal true, DateTime.civil(2000,1,1,0).today?
+ assert_equal true, DateTime.civil(2000,1,1,23,59,59).today?
+ assert_equal false, DateTime.civil(2000,1,2,0).today?
+ end
end
def test_past_with_offset
- DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
- assert_equal true, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).past?
- assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).past?
- assert_equal false, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).past?
+ DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do
+ assert_equal true, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).past?
+ assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).past?
+ assert_equal false, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).past?
+ end
end
def test_past_without_offset
- DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
- assert_equal true, DateTime.civil(2005,2,10,20,30,44).past?
- assert_equal false, DateTime.civil(2005,2,10,20,30,45).past?
- assert_equal false, DateTime.civil(2005,2,10,20,30,46).past?
+ DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do
+ assert_equal true, DateTime.civil(2005,2,10,20,30,44).past?
+ assert_equal false, DateTime.civil(2005,2,10,20,30,45).past?
+ assert_equal false, DateTime.civil(2005,2,10,20,30,46).past?
+ end
end
def test_future_with_offset
- DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
- assert_equal false, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).future?
- assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).future?
- assert_equal true, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).future?
+ DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do
+ assert_equal false, DateTime.civil(2005,2,10,15,30,44, Rational(-18000, 86400)).future?
+ assert_equal false, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)).future?
+ assert_equal true, DateTime.civil(2005,2,10,15,30,46, Rational(-18000, 86400)).future?
+ end
end
def test_future_without_offset
- DateTime.stubs(:current).returns(DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400)))
- assert_equal false, DateTime.civil(2005,2,10,20,30,44).future?
- assert_equal false, DateTime.civil(2005,2,10,20,30,45).future?
- assert_equal true, DateTime.civil(2005,2,10,20,30,46).future?
+ DateTime.stub(:current, DateTime.civil(2005,2,10,15,30,45, Rational(-18000, 86400))) do
+ assert_equal false, DateTime.civil(2005,2,10,20,30,44).future?
+ assert_equal false, DateTime.civil(2005,2,10,20,30,45).future?
+ assert_equal true, DateTime.civil(2005,2,10,20,30,46).future?
+ end
end
def test_current_returns_date_today_when_zone_is_not_set
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59)
- assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ Time.stub(:now, Time.local(1999, 12, 31, 23, 59, 59)) do
+ assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ end
end
end
def test_current_returns_time_zone_today_when_zone_is_set
Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(1999, 12, 31, 23, 59, 59)
- assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ Time.stub(:now, Time.local(1999, 12, 31, 23, 59, 59)) do
+ assert_equal DateTime.new(1999, 12, 31, 23, 59, 59, Rational(-18000, 86400)), DateTime.current
+ end
end
ensure
Time.zone = nil
@@ -335,6 +343,13 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal(-1, DateTime.civil(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] ))
end
+ def test_compare_with_string
+ assert_equal 1, DateTime.civil(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59).to_s
+ assert_equal 0, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s
+ assert_equal( -1, DateTime.civil(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1).to_s)
+ assert_equal nil, DateTime.civil(2000) <=> "Invalid as Time"
+ end
+
def test_to_f
assert_equal 946684800.0, DateTime.civil(2000).to_f
assert_equal 946684800.0, DateTime.civil(1999,12,31,19,0,0,Rational(-5,24)).to_f
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index c283b546e6..9e97acaffb 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -140,28 +140,30 @@ class DurationTest < ActiveSupport::TestCase
def test_since_and_ago_anchored_to_time_now_when_time_zone_is_not_set
Time.zone = nil
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # since
- assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
- assert_equal Time.local(2000,1,1,0,0,5), 5.seconds.since
- # ago
- assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
- assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago
+ Time.stub(:now, Time.local(2000)) do
+ # since
+ assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
+ assert_equal Time.local(2000,1,1,0,0,5), 5.seconds.since
+ # ago
+ assert_not_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
+ assert_equal Time.local(1999,12,31,23,59,55), 5.seconds.ago
+ end
end
end
def test_since_and_ago_anchored_to_time_zone_now_when_time_zone_is_set
Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- # since
- assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
- assert_equal Time.utc(2000,1,1,0,0,5), 5.seconds.since.time
- assert_equal 'Eastern Time (US & Canada)', 5.seconds.since.time_zone.name
- # ago
- assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
- assert_equal Time.utc(1999,12,31,23,59,55), 5.seconds.ago.time
- assert_equal 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name
+ Time.stub(:now, Time.local(2000)) do
+ # since
+ assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.since
+ assert_equal Time.utc(2000,1,1,0,0,5), 5.seconds.since.time
+ assert_equal 'Eastern Time (US & Canada)', 5.seconds.since.time_zone.name
+ # ago
+ assert_instance_of ActiveSupport::TimeWithZone, 5.seconds.ago
+ assert_equal Time.utc(1999,12,31,23,59,55), 5.seconds.ago.time
+ assert_equal 'Eastern Time (US & Canada)', 5.seconds.ago.time_zone.name
+ end
end
ensure
Time.zone = nil
diff --git a/activesupport/test/core_ext/enumerable_test.rb b/activesupport/test/core_ext/enumerable_test.rb
index e5d8ae7882..f09b7d8850 100644
--- a/activesupport/test/core_ext/enumerable_test.rb
+++ b/activesupport/test/core_ext/enumerable_test.rb
@@ -3,6 +3,8 @@ require 'active_support/core_ext/array'
require 'active_support/core_ext/enumerable'
Payment = Struct.new(:price)
+ExpandedPayment = Struct.new(:dollars, :cents)
+
class SummablePayment < Payment
def +(p) self.class.new(price + p.price) end
end
@@ -110,4 +112,16 @@ class EnumerableTests < ActiveSupport::TestCase
assert_equal [1, 2, 4], (1..5).to_set.without(3, 5)
assert_equal({foo: 1, baz: 3}, {foo: 1, bar: 2, baz: 3}.without(:bar))
end
+
+ def test_pluck
+ payments = GenericEnumerable.new([ Payment.new(5), Payment.new(15), Payment.new(10) ])
+ assert_equal [5, 15, 10], payments.pluck(:price)
+
+ payments = GenericEnumerable.new([
+ ExpandedPayment.new(5, 99),
+ ExpandedPayment.new(15, 0),
+ ExpandedPayment.new(10, 50)
+ ])
+ assert_equal [[5, 99], [15, 0], [10, 50]], payments.pluck(:dollars, :cents)
+ end
end
diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb
index a7e12117f3..99af274614 100644
--- a/activesupport/test/core_ext/hash/transform_keys_test.rb
+++ b/activesupport/test/core_ext/hash/transform_keys_test.rb
@@ -18,9 +18,17 @@ class TransformKeysTest < ActiveSupport::TestCase
assert_same original, mapped
end
- test "transform_keys returns an Enumerator if no block is given" do
+ test "transform_keys returns a sized Enumerator if no block is given" do
original = { a: 'a', b: 'b' }
enumerator = original.transform_keys
+ assert_equal original.size, enumerator.size
+ assert_equal Enumerator, enumerator.class
+ end
+
+ test "transform_keys! returns a sized Enumerator if no block is given" do
+ original = { a: 'a', b: 'b' }
+ enumerator = original.transform_keys!
+ assert_equal original.size, enumerator.size
assert_equal Enumerator, enumerator.class
end
@@ -29,4 +37,10 @@ class TransformKeysTest < ActiveSupport::TestCase
mapped = original.transform_keys.with_index { |k, i| [k, i].join.to_sym }
assert_equal({ a0: 'a', b1: 'b' }, mapped)
end
+
+ test "transform_keys! is chainable with Enumerable methods" do
+ original = { a: 'a', b: 'b' }
+ original.transform_keys!.with_index { |k, i| [k, i].join.to_sym }
+ assert_equal({ a0: 'a', b1: 'b' }, original)
+ end
end
diff --git a/activesupport/test/core_ext/hash/transform_values_test.rb b/activesupport/test/core_ext/hash/transform_values_test.rb
index 45ed11fef7..114022fbaf 100644
--- a/activesupport/test/core_ext/hash/transform_values_test.rb
+++ b/activesupport/test/core_ext/hash/transform_values_test.rb
@@ -47,9 +47,17 @@ class TransformValuesTest < ActiveSupport::TestCase
assert_nil mapped[:b]
end
- test "transform_values returns an Enumerator if no block is given" do
+ test "transform_values returns a sized Enumerator if no block is given" do
original = { a: 'a', b: 'b' }
enumerator = original.transform_values
+ assert_equal original.size, enumerator.size
+ assert_equal Enumerator, enumerator.class
+ end
+
+ test "transform_values! returns a sized Enumerator if no block is given" do
+ original = { a: 'a', b: 'b' }
+ enumerator = original.transform_values!
+ assert_equal original.size, enumerator.size
assert_equal Enumerator, enumerator.class
end
@@ -58,4 +66,10 @@ class TransformValuesTest < ActiveSupport::TestCase
mapped = original.transform_values.with_index { |v, i| [v, i].join }
assert_equal({ a: 'a0', b: 'b1' }, mapped)
end
+
+ test "transform_values! is chainable with Enumerable methods" do
+ original = { a: 'a', b: 'b' }
+ original.transform_values!.with_index { |v, i| [v, i].join }
+ assert_equal({ a: 'a0', b: 'b1' }, original)
+ end
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index e10bee5e00..1b66f784e4 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -524,6 +524,10 @@ class HashExtTest < ActiveSupport::TestCase
end
def test_indifferent_reverse_merging
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.reverse_merge! key: :new_value
+ assert_equal :old_value, hash[:key]
+
hash = HashWithIndifferentAccess.new('some' => 'value', 'other' => 'value')
hash.reverse_merge!(:some => 'noclobber', :another => 'clobber')
assert_equal 'value', hash[:some]
@@ -547,6 +551,11 @@ class HashExtTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
end
+ def test_indifferent_select_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).select
+ assert_instance_of Enumerator, enum
+ end
+
def test_indifferent_select_returns_a_hash_when_unchanged
hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select {|k,v| true}
@@ -568,6 +577,11 @@ class HashExtTest < ActiveSupport::TestCase
assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
end
+ def test_indifferent_reject_returns_enumerator
+ enum = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject
+ assert_instance_of Enumerator, enum
+ end
+
def test_indifferent_reject_bang
indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
indifferent_strings.reject! {|k,v| v != 1}
@@ -961,10 +975,11 @@ class HashExtTest < ActiveSupport::TestCase
assert_raise(RuntimeError) { original.except!(:a) }
end
- def test_except_with_mocha_expectation_on_original
+ def test_except_does_not_delete_values_in_original
original = { :a => 'x', :b => 'y' }
- original.expects(:delete).never
- original.except(:a)
+ assert_not_called(original, :delete) do
+ original.except(:a)
+ end
end
def test_compact
@@ -998,6 +1013,55 @@ class HashExtTest < ActiveSupport::TestCase
assert hash.key?('a')
assert_equal 1, hash[:a]
end
+
+ def test_dup_with_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, v| raise "walrus" }
+ assert_nothing_raised { hash.dup }
+ end
+
+ def test_dup_with_default_proc_sets_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| k + 1 }
+ new_hash = hash.dup
+
+ assert_equal 3, new_hash[2]
+
+ new_hash.default = 2
+ assert_equal 2, new_hash[:non_existant]
+ end
+
+ def test_to_hash_with_raising_default_proc
+ hash = HashWithIndifferentAccess.new
+ hash.default_proc = proc { |h, k| raise "walrus" }
+
+ assert_nothing_raised { hash.to_hash }
+ end
+
+ def test_new_from_hash_copying_default_should_not_raise_when_default_proc_does
+ hash = Hash.new
+ hash.default_proc = proc { |h, k| raise "walrus" }
+
+ assert_deprecated { HashWithIndifferentAccess.new_from_hash_copying_default(hash) }
+ end
+
+ def test_new_with_to_hash_conversion_copies_default
+ normal_hash = Hash.new(3)
+ normal_hash[:a] = 1
+
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
+ assert_equal 1, hash[:a]
+ assert_equal 3, hash[:b]
+ end
+
+ def test_new_with_to_hash_conversion_copies_default_proc
+ normal_hash = Hash.new { 1 + 2 }
+ normal_hash[:a] = 1
+
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(normal_hash))
+ assert_equal 1, hash[:a]
+ assert_equal 3, hash[:b]
+ end
end
class IWriteMyOwnXML
@@ -1523,9 +1587,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
@@ -1549,7 +1613,6 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_not_same hash_wia, hash_wia.with_indifferent_access
end
-
def test_allows_setting_frozen_array_values_with_indifferent_access
value = [1, 2, 3].freeze
hash = HashWithIndifferentAccess.new
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index e49330128b..825df439a5 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -8,7 +8,7 @@ class MarshalTest < ActiveSupport::TestCase
def teardown
ActiveSupport::Dependencies.clear
- remove_constants(:E, :ClassFolder)
+ remove_constants(:EM, :ClassFolder)
end
test "that Marshal#load still works" do
@@ -22,14 +22,14 @@ class MarshalTest < ActiveSupport::TestCase
test "that a missing class is autoloaded from string" do
dumped = nil
with_autoloading_fixtures do
- dumped = Marshal.dump(E.new)
+ dumped = Marshal.dump(EM.new)
end
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of E, Marshal.load(dumped)
+ assert_kind_of EM, Marshal.load(dumped)
end
end
@@ -50,16 +50,16 @@ class MarshalTest < ActiveSupport::TestCase
test "that more than one missing class is autoloaded" do
dumped = nil
with_autoloading_fixtures do
- dumped = Marshal.dump([E.new, ClassFolder.new])
+ dumped = Marshal.dump([EM.new, ClassFolder.new])
end
- remove_constants(:E, :ClassFolder)
+ remove_constants(:EM, :ClassFolder)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
loaded = Marshal.load(dumped)
assert_equal 2, loaded.size
- assert_kind_of E, loaded[0]
+ assert_kind_of EM, loaded[0]
assert_kind_of ClassFolder, loaded[1]
end
end
@@ -67,10 +67,10 @@ class MarshalTest < ActiveSupport::TestCase
test "that a real missing class is causing an exception" do
dumped = nil
with_autoloading_fixtures do
- dumped = Marshal.dump(E.new)
+ dumped = Marshal.dump(EM.new)
end
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
assert_raise(NameError) do
@@ -84,10 +84,10 @@ class MarshalTest < ActiveSupport::TestCase
end
with_autoloading_fixtures do
- dumped = Marshal.dump([E.new, SomeClass.new])
+ dumped = Marshal.dump([EM.new, SomeClass.new])
end
- remove_constants(:E)
+ remove_constants(:EM)
self.class.send(:remove_const, :SomeClass)
ActiveSupport::Dependencies.clear
@@ -96,8 +96,8 @@ class MarshalTest < ActiveSupport::TestCase
Marshal.load(dumped)
end
- assert_nothing_raised("E failed to load while we expect only SomeClass to fail loading") do
- E.new
+ assert_nothing_raised("EM failed to load while we expect only SomeClass to fail loading") do
+ EM.new
end
assert_raise(NameError, "We expected SomeClass to not be loaded but it is!") do
@@ -109,15 +109,15 @@ class MarshalTest < ActiveSupport::TestCase
test "loading classes from files trigger autoloading" do
Tempfile.open("object_serializer_test") do |f|
with_autoloading_fixtures do
- Marshal.dump(E.new, f)
+ Marshal.dump(EM.new, f)
end
f.rewind
- remove_constants(:E)
+ remove_constants(:EM)
ActiveSupport::Dependencies.clear
with_autoloading_fixtures do
- assert_kind_of E, Marshal.load(f)
+ assert_kind_of EM, Marshal.load(f)
end
end
end
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..65fadc5c20
--- /dev/null
+++ b/activesupport/test/core_ext/module/attribute_accessor_per_thread_test.rb
@@ -0,0 +1,109 @@
+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
+end
diff --git a/activesupport/test/core_ext/module/attribute_accessor_test.rb b/activesupport/test/core_ext/module/attribute_accessor_test.rb
index 48f3cc579f..0b0f3a2808 100644
--- a/activesupport/test/core_ext/module/attribute_accessor_test.rb
+++ b/activesupport/test/core_ext/module/attribute_accessor_test.rb
@@ -69,6 +69,20 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
end
end
assert_equal "invalid attribute name: 1nvalid", exception.message
+
+ exception = assert_raises NameError do
+ Class.new do
+ mattr_reader "valid_part\ninvalid_part"
+ end
+ end
+ assert_equal "invalid attribute name: valid_part\ninvalid_part", exception.message
+
+ exception = assert_raises NameError do
+ Class.new do
+ mattr_writer "valid_part\ninvalid_part"
+ end
+ end
+ assert_equal "invalid attribute name: valid_part\ninvalid_part", exception.message
end
def test_should_use_default_value_if_block_passed
@@ -76,4 +90,10 @@ class ModuleAttributeAccessorTest < ActiveSupport::TestCase
assert_equal 'default_reader_value', @module.defr
assert_equal 'default_writer_value', @module.class_variable_get('@@defw')
end
+
+ def test_should_not_invoke_default_value_block_multiple_times
+ count = 0
+ @module.cattr_accessor(:defcount){ count += 1 }
+ assert_equal 1, count
+ end
end
diff --git a/activesupport/test/core_ext/module/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/remove_method_test.rb b/activesupport/test/core_ext/module/remove_method_test.rb
index 4657f0c175..0d684dc70e 100644
--- a/activesupport/test/core_ext/module/remove_method_test.rb
+++ b/activesupport/test/core_ext/module/remove_method_test.rb
@@ -6,24 +6,54 @@ module RemoveMethodTests
def do_something
return 1
end
-
+
+ def do_something_protected
+ return 1
+ end
+ protected :do_something_protected
+
+ def do_something_private
+ return 1
+ end
+ private :do_something_private
+
+ class << self
+ def do_something_else
+ return 2
+ end
+ end
end
end
class RemoveMethodTest < ActiveSupport::TestCase
-
+
def test_remove_method_from_an_object
RemoveMethodTests::A.class_eval{
self.remove_possible_method(:do_something)
}
assert !RemoveMethodTests::A.new.respond_to?(:do_something)
end
-
+
+ def test_remove_singleton_method_from_an_object
+ RemoveMethodTests::A.class_eval{
+ self.remove_possible_singleton_method(:do_something_else)
+ }
+ assert !RemoveMethodTests::A.respond_to?(:do_something_else)
+ end
+
def test_redefine_method_in_an_object
RemoveMethodTests::A.class_eval{
self.redefine_method(:do_something) { return 100 }
+ self.redefine_method(:do_something_protected) { return 100 }
+ self.redefine_method(:do_something_private) { return 100 }
}
assert_equal 100, RemoveMethodTests::A.new.do_something
+ assert_equal 100, RemoveMethodTests::A.new.send(:do_something_protected)
+ assert_equal 100, RemoveMethodTests::A.new.send(:do_something_private)
+
+ assert RemoveMethodTests::A.public_method_defined? :do_something
+ assert RemoveMethodTests::A.protected_method_defined? :do_something_protected
+ assert RemoveMethodTests::A.private_method_defined? :do_something_private
end
-end \ No newline at end of file
+end
diff --git a/activesupport/test/core_ext/module_test.rb b/activesupport/test/core_ext/module_test.rb
index bdfbadcf1d..0ed66f8c37 100644
--- a/activesupport/test/core_ext/module_test.rb
+++ b/activesupport/test/core_ext/module_test.rb
@@ -83,6 +83,16 @@ Product = Struct.new(:name) do
end
end
+class Block
+ def hello?
+ true
+ end
+end
+
+HasBlock = Struct.new(:block) do
+ delegate :hello?, to: :block
+end
+
class ParameterSet
delegate :[], :[]=, :to => :@params
@@ -301,6 +311,11 @@ class ModuleTest < ActiveSupport::TestCase
assert_raise(NoMethodError) { product.type_name }
end
+ def test_delegation_with_method_arguments
+ has_block = HasBlock.new(Block.new)
+ assert has_block.hello?
+ end
+
def test_parent
assert_equal Yz::Zy, Yz::Zy::Cd.parent
assert_equal Yz, Yz::Zy.parent
diff --git a/activesupport/test/core_ext/numeric_ext_test.rb b/activesupport/test/core_ext/numeric_ext_test.rb
index 2d8796179e..5654aeb4f8 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)
@@ -280,14 +290,18 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
end
def test_to_s__human_size_with_si_prefix
- assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si)
- assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si)
- assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si)
- assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si)
- assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si)
- assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
- assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
- assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ assert_deprecated do
+ assert_equal '3 Bytes', 3.14159265.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.0.to_s(:human_size, :prefix => :si)
+ assert_equal '123 Bytes', 123.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 KB', 1234.to_s(:human_size, :prefix => :si)
+ assert_equal '12.3 KB', 12345.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 MB', 1234567.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 GB', 1234567890.to_s(:human_size, :prefix => :si)
+ assert_equal '1.23 TB', 1234567890123.to_s(:human_size, :prefix => :si)
+ 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
def test_to_s__human_size_with_options_hash
@@ -386,6 +400,32 @@ class NumericExtFormattingTest < ActiveSupport::TestCase
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/blank_test.rb b/activesupport/test/core_ext/object/blank_test.rb
index 8a5e385dd7..a142096993 100644
--- a/activesupport/test/core_ext/object/blank_test.rb
+++ b/activesupport/test/core_ext/object/blank_test.rb
@@ -1,4 +1,3 @@
-
require 'abstract_unit'
require 'active_support/core_ext/object/blank'
diff --git a/activesupport/test/core_ext/object/json_gem_encoding_test.rb b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
new file mode 100644
index 0000000000..02ab17fb64
--- /dev/null
+++ b/activesupport/test/core_ext/object/json_gem_encoding_test.rb
@@ -0,0 +1,66 @@
+require 'abstract_unit'
+require 'json'
+require 'json/encoding_test_cases'
+
+# These test cases were added to test that we do not interfere with json gem's
+# output when the AS encoder is loaded, primarily for problems reported in
+# #20775. They need to be executed in isolation to reproduce the scenario
+# correctly, because other test cases might have already loaded additional
+# dependencies.
+
+# The AS::JSON encoder requires the BigDecimal core_ext, which, unfortunately,
+# changes the BigDecimal#to_s output, and consequently the JSON gem output. So
+# we need to require this unfront to ensure we don't get a false failure, but
+# ideally we should just fix the BigDecimal core_ext to not change to_s without
+# arguments.
+require 'active_support/core_ext/big_decimal'
+
+class JsonGemEncodingTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ JSONTest::EncodingTestCases.constants.each_with_index do |name|
+ JSONTest::EncodingTestCases.const_get(name).each_with_index do |(subject, _), i|
+ test("#{name[0..-6].underscore} #{i}") do
+ assert_same_with_or_without_active_support(subject)
+ end
+ end
+ end
+
+ class CustomToJson
+ def to_json(*)
+ '"custom"'
+ end
+ end
+
+ test "custom to_json" do
+ assert_same_with_or_without_active_support(CustomToJson.new)
+ end
+
+ private
+ def require_or_skip(file)
+ require(file) || skip("'#{file}' was already loaded")
+ end
+
+ def assert_same_with_or_without_active_support(subject)
+ begin
+ expected = JSON.generate(subject, quirks_mode: true)
+ rescue JSON::GeneratorError => e
+ exception = e
+ end
+
+ require_or_skip 'active_support/core_ext/object/json'
+
+ if exception
+ assert_raises_with_message JSON::GeneratorError, e.message do
+ JSON.generate(subject, quirks_mode: true)
+ end
+ else
+ assert_equal expected, JSON.generate(subject, quirks_mode: true)
+ end
+ end
+
+ def assert_raises_with_message(exception_class, message, &block)
+ err = assert_raises(exception_class) { block.call }
+ assert_match message, err.message
+ end
+end
diff --git a/activesupport/test/core_ext/object/try_test.rb b/activesupport/test/core_ext/object/try_test.rb
index 5ea0f0eca6..25bf0207b8 100644
--- a/activesupport/test/core_ext/object/try_test.rb
+++ b/activesupport/test/core_ext/object/try_test.rb
@@ -121,7 +121,7 @@ class ObjectTryTest < ActiveSupport::TestCase
assert_equal 5, Decorator.new(@string).size
end
- def test_try_with_overriden_method_on_delegator
+ def test_try_with_overridden_method_on_delegator
assert_equal 'overridden reverse', Decorator.new(@string).reverse
end
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 cb24147ab3..2e69816364 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -143,15 +143,49 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_string_parameterized_normal_preserve_case
+ StringToParameterizedPreserveCase.each do |normal, slugged|
+ assert_equal(normal.parameterize(preserve_case: true), slugged)
+ end
+ end
+
def test_string_parameterized_no_separator
StringToParameterizeWithNoSeparator.each do |normal, slugged|
- assert_equal(normal.parameterize(''), slugged)
+ assert_equal(normal.parameterize(separator: ''), slugged)
+ end
+ end
+
+ def test_string_parameterized_no_separator_deprecated
+ StringToParameterizeWithNoSeparator.each do |normal, slugged|
+ assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: ''` instead./i) do
+ assert_equal(normal.parameterize(''), slugged)
+ end
+ end
+ end
+
+ def test_string_parameterized_no_separator_preserve_case
+ StringToParameterizePreserveCaseWithNoSeparator.each do |normal, slugged|
+ assert_equal(normal.parameterize(separator: '', preserve_case: true), slugged)
end
end
def test_string_parameterized_underscore
StringToParameterizeWithUnderscore.each do |normal, slugged|
- assert_equal(normal.parameterize('_'), slugged)
+ assert_equal(normal.parameterize(separator: '_'), slugged)
+ end
+ end
+
+ def test_string_parameterized_underscore_deprecated
+ StringToParameterizeWithUnderscore.each do |normal, slugged|
+ assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do
+ assert_equal(normal.parameterize('_'), slugged)
+ end
+ end
+ end
+
+ def test_string_parameterized_underscore_preserve_case
+ StringToParameterizePreserceCaseWithUnderscore.each do |normal, slugged|
+ assert_equal(normal.parameterize(separator: '_', preserve_case: true), slugged)
end
end
@@ -435,117 +469,123 @@ class StringConversionsTest < ActiveSupport::TestCase
def test_standard_time_string_to_time_when_current_time_is_standard_time
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 1, 1))
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 1, 1)) do
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ end
end
end
def test_standard_time_string_to_time_when_current_time_is_daylight_savings
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 7, 1))
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 7, 1)) do
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 -0800".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 -0800".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 -0500".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 5, 0), "2012-01-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "2012-01-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "2012-01-01 10:00 PST".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "2012-01-01 10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "2012-01-01 10:00 EST".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "2012-01-01 10:00 EST".to_time(:utc)
+ end
end
end
def test_daylight_savings_string_to_time_when_current_time_is_standard_time
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 1, 1))
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 1, 1)) do
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ end
end
end
def test_daylight_savings_string_to_time_when_current_time_is_daylight_savings
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 7, 1))
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 7, 1)) do
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 -0700".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 -0700".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 -0400".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 -0400".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 6, 0), "2012-07-01 10:00 UTC".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "2012-07-01 10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "2012-07-01 10:00 PDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "2012-07-01 10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "2012-07-01 10:00 EDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "2012-07-01 10:00 EDT".to_time(:utc)
+ end
end
end
def test_partial_string_to_time_when_current_time_is_standard_time
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 1, 1))
- assert_equal Time.local(2012, 1, 1, 10, 0), "10:00".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 6, 0), "10:00 -0100".to_time
- assert_equal Time.utc(2012, 1, 1, 11, 0), "10:00 -0100".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 -0500".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 -0500".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 5, 0), "10:00 UTC".to_time
- assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 13, 0), "10:00 PST".to_time
- assert_equal Time.utc(2012, 1, 1, 18, 0), "10:00 PST".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 12, 0), "10:00 PDT".to_time
- assert_equal Time.utc(2012, 1, 1, 17, 0), "10:00 PDT".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 EST".to_time
- assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 EST".to_time(:utc)
- assert_equal Time.local(2012, 1, 1, 9, 0), "10:00 EDT".to_time
- assert_equal Time.utc(2012, 1, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 1, 1)) do
+ assert_equal Time.local(2012, 1, 1, 10, 0), "10:00".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 6, 0), "10:00 -0100".to_time
+ assert_equal Time.utc(2012, 1, 1, 11, 0), "10:00 -0100".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 -0500".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 5, 0), "10:00 UTC".to_time
+ assert_equal Time.utc(2012, 1, 1, 10, 0), "10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 13, 0), "10:00 PST".to_time
+ assert_equal Time.utc(2012, 1, 1, 18, 0), "10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 12, 0), "10:00 PDT".to_time
+ assert_equal Time.utc(2012, 1, 1, 17, 0), "10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 10, 0), "10:00 EST".to_time
+ assert_equal Time.utc(2012, 1, 1, 15, 0), "10:00 EST".to_time(:utc)
+ assert_equal Time.local(2012, 1, 1, 9, 0), "10:00 EDT".to_time
+ assert_equal Time.utc(2012, 1, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ end
end
end
def test_partial_string_to_time_when_current_time_is_daylight_savings
with_env_tz "US/Eastern" do
- Time.stubs(:now).returns(Time.local(2012, 7, 1))
- assert_equal Time.local(2012, 7, 1, 10, 0), "10:00".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 7, 0), "10:00 -0100".to_time
- assert_equal Time.utc(2012, 7, 1, 11, 0), "10:00 -0100".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 -0500".to_time
- assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 -0500".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 6, 0), "10:00 UTC".to_time
- assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00 UTC".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 14, 0), "10:00 PST".to_time
- assert_equal Time.utc(2012, 7, 1, 18, 0), "10:00 PST".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 13, 0), "10:00 PDT".to_time
- assert_equal Time.utc(2012, 7, 1, 17, 0), "10:00 PDT".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 EST".to_time
- assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 EST".to_time(:utc)
- assert_equal Time.local(2012, 7, 1, 10, 0), "10:00 EDT".to_time
- assert_equal Time.utc(2012, 7, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ Time.stub(:now, Time.local(2012, 7, 1)) do
+ assert_equal Time.local(2012, 7, 1, 10, 0), "10:00".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 7, 0), "10:00 -0100".to_time
+ assert_equal Time.utc(2012, 7, 1, 11, 0), "10:00 -0100".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 -0500".to_time
+ assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 -0500".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 6, 0), "10:00 UTC".to_time
+ assert_equal Time.utc(2012, 7, 1, 10, 0), "10:00 UTC".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 14, 0), "10:00 PST".to_time
+ assert_equal Time.utc(2012, 7, 1, 18, 0), "10:00 PST".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 13, 0), "10:00 PDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 17, 0), "10:00 PDT".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 11, 0), "10:00 EST".to_time
+ assert_equal Time.utc(2012, 7, 1, 15, 0), "10:00 EST".to_time(:utc)
+ assert_equal Time.local(2012, 7, 1, 10, 0), "10:00 EDT".to_time
+ assert_equal Time.utc(2012, 7, 1, 14, 0), "10:00 EDT".to_time(:utc)
+ end
end
end
@@ -776,8 +816,8 @@ class OutputSafetyTest < ActiveSupport::TestCase
end
test "ERB::Util.html_escape should correctly handle invalid UTF-8 strings" do
- string = [192, 60].pack('CC')
- expected = 192.chr + "&lt;"
+ string = "\251 <"
+ expected = "© &lt;"
assert_equal expected, ERB::Util.html_escape(string)
end
@@ -793,6 +833,12 @@ class OutputSafetyTest < ActiveSupport::TestCase
assert_equal escaped_string, ERB::Util.html_escape_once(string)
assert_equal escaped_string, ERB::Util.html_escape_once(escaped_string)
end
+
+ test "ERB::Util.html_escape_once should correctly handle invalid UTF-8 strings" do
+ string = "\251 <"
+ expected = "© &lt;"
+ assert_equal expected, ERB::Util.html_escape_once(string)
+ end
end
class StringExcludeTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index d59775001b..e45df63fd5 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -149,6 +149,9 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2006,3,19,23,59,59,Rational(999999999, 1000)), Time.local(2006,3,19,10,10,10).end_of_day, 'ends DST'
assert_equal Time.local(2006,10,1,23,59,59,Rational(999999999, 1000)), Time.local(2006,10,1,10,10,10).end_of_day, 'start DST'
end
+ with_env_tz 'Asia/Yekaterinburg' do
+ assert_equal Time.local(2015, 2, 8, 23, 59, 59, Rational(999999999, 1000)), Time.new(2015, 2, 8, 8, 0, 0, '+05:00').end_of_day
+ end
end
def test_end_of_hour
@@ -389,6 +392,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,44).change(:usec => 8)
assert_equal Time.local(2005,1,2,11,22,33, 8), Time.local(2005,1,2,11,22,33,2).change(:nsec => 8000)
assert_raise(ArgumentError) { Time.local(2005,1,2,11,22,33, 8).change(:usec => 1, :nsec => 1) }
+ assert_nothing_raised(ArgumentError) { Time.new(2015, 5, 9, 10, 00, 00, '+03:00').change(nsec: 999999999) }
end
def test_utc_change
@@ -530,6 +534,7 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal "17:44", time.to_s(:time)
assert_equal "20050221174430", time.to_s(:number)
assert_equal "20050221174430123456789", time.to_s(:nsec)
+ assert_equal "20050221174430123456", time.to_s(:usec)
assert_equal "February 21, 2005 17:44", time.to_s(:long)
assert_equal "February 21st, 2005 17:44", time.to_s(:long_ordinal)
with_env_tz "UTC" do
@@ -601,13 +606,34 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_days_in_month_feb_in_common_year_without_year_arg
- Time.stubs(:now).returns(Time.utc(2007))
- assert_equal 28, Time.days_in_month(2)
+ Time.stub(:now, Time.utc(2007)) do
+ assert_equal 28, Time.days_in_month(2)
+ end
end
def test_days_in_month_feb_in_leap_year_without_year_arg
- Time.stubs(:now).returns(Time.utc(2008))
- assert_equal 29, Time.days_in_month(2)
+ Time.stub(:now, Time.utc(2008)) do
+ assert_equal 29, Time.days_in_month(2)
+ end
+ end
+
+ def test_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
@@ -619,68 +645,74 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_today_with_time_local
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- assert_equal false, Time.local(1999,12,31,23,59,59).today?
- assert_equal true, Time.local(2000,1,1,0).today?
- assert_equal true, Time.local(2000,1,1,23,59,59).today?
- assert_equal false, Time.local(2000,1,2,0).today?
+ Date.stub(:current, Date.new(2000, 1, 1)) do
+ assert_equal false, Time.local(1999,12,31,23,59,59).today?
+ assert_equal true, Time.local(2000,1,1,0).today?
+ assert_equal true, Time.local(2000,1,1,23,59,59).today?
+ assert_equal false, Time.local(2000,1,2,0).today?
+ end
end
def test_today_with_time_utc
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- assert_equal false, Time.utc(1999,12,31,23,59,59).today?
- assert_equal true, Time.utc(2000,1,1,0).today?
- assert_equal true, Time.utc(2000,1,1,23,59,59).today?
- assert_equal false, Time.utc(2000,1,2,0).today?
+ Date.stub(:current, Date.new(2000, 1, 1)) do
+ assert_equal false, Time.utc(1999,12,31,23,59,59).today?
+ assert_equal true, Time.utc(2000,1,1,0).today?
+ assert_equal true, Time.utc(2000,1,1,23,59,59).today?
+ assert_equal false, Time.utc(2000,1,2,0).today?
+ end
end
def test_past_with_time_current_as_time_local
with_env_tz 'US/Eastern' do
- Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
- assert_equal true, Time.local(2005,2,10,15,30,44).past?
- assert_equal false, Time.local(2005,2,10,15,30,45).past?
- assert_equal false, Time.local(2005,2,10,15,30,46).past?
- assert_equal true, Time.utc(2005,2,10,20,30,44).past?
- assert_equal false, Time.utc(2005,2,10,20,30,45).past?
- assert_equal false, Time.utc(2005,2,10,20,30,46).past?
+ Time.stub(:current, Time.local(2005,2,10,15,30,45)) do
+ assert_equal true, Time.local(2005,2,10,15,30,44).past?
+ assert_equal false, Time.local(2005,2,10,15,30,45).past?
+ assert_equal false, Time.local(2005,2,10,15,30,46).past?
+ assert_equal true, Time.utc(2005,2,10,20,30,44).past?
+ assert_equal false, Time.utc(2005,2,10,20,30,45).past?
+ assert_equal false, Time.utc(2005,2,10,20,30,46).past?
+ end
end
end
def test_past_with_time_current_as_time_with_zone
with_env_tz 'US/Eastern' do
twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)')
- Time.stubs(:current).returns(twz)
- assert_equal true, Time.local(2005,2,10,10,30,44).past?
- assert_equal false, Time.local(2005,2,10,10,30,45).past?
- assert_equal false, Time.local(2005,2,10,10,30,46).past?
- assert_equal true, Time.utc(2005,2,10,15,30,44).past?
- assert_equal false, Time.utc(2005,2,10,15,30,45).past?
- assert_equal false, Time.utc(2005,2,10,15,30,46).past?
+ Time.stub(:current, twz) do
+ assert_equal true, Time.local(2005,2,10,10,30,44).past?
+ assert_equal false, Time.local(2005,2,10,10,30,45).past?
+ assert_equal false, Time.local(2005,2,10,10,30,46).past?
+ assert_equal true, Time.utc(2005,2,10,15,30,44).past?
+ assert_equal false, Time.utc(2005,2,10,15,30,45).past?
+ assert_equal false, Time.utc(2005,2,10,15,30,46).past?
+ end
end
end
def test_future_with_time_current_as_time_local
with_env_tz 'US/Eastern' do
- Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
- assert_equal false, Time.local(2005,2,10,15,30,44).future?
- assert_equal false, Time.local(2005,2,10,15,30,45).future?
- assert_equal true, Time.local(2005,2,10,15,30,46).future?
- assert_equal false, Time.utc(2005,2,10,20,30,44).future?
- assert_equal false, Time.utc(2005,2,10,20,30,45).future?
- assert_equal true, Time.utc(2005,2,10,20,30,46).future?
+ Time.stub(:current, Time.local(2005,2,10,15,30,45)) do
+ assert_equal false, Time.local(2005,2,10,15,30,44).future?
+ assert_equal false, Time.local(2005,2,10,15,30,45).future?
+ assert_equal true, Time.local(2005,2,10,15,30,46).future?
+ assert_equal false, Time.utc(2005,2,10,20,30,44).future?
+ assert_equal false, Time.utc(2005,2,10,20,30,45).future?
+ assert_equal true, Time.utc(2005,2,10,20,30,46).future?
+ end
end
end
def test_future_with_time_current_as_time_with_zone
with_env_tz 'US/Eastern' do
twz = Time.utc(2005,2,10,15,30,45).in_time_zone('Central Time (US & Canada)')
- Time.stubs(:current).returns(twz)
- assert_equal false, Time.local(2005,2,10,10,30,44).future?
- assert_equal false, Time.local(2005,2,10,10,30,45).future?
- assert_equal true, Time.local(2005,2,10,10,30,46).future?
- assert_equal false, Time.utc(2005,2,10,15,30,44).future?
- assert_equal false, Time.utc(2005,2,10,15,30,45).future?
- assert_equal true, Time.utc(2005,2,10,15,30,46).future?
+ Time.stub(:current, twz) do
+ assert_equal false, Time.local(2005,2,10,10,30,44).future?
+ assert_equal false, Time.local(2005,2,10,10,30,45).future?
+ assert_equal true, Time.local(2005,2,10,10,30,46).future?
+ assert_equal false, Time.utc(2005,2,10,15,30,44).future?
+ assert_equal false, Time.utc(2005,2,10,15,30,45).future?
+ assert_equal true, Time.utc(2005,2,10,15,30,46).future?
+ end
end
end
@@ -721,6 +753,13 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal(-1, Time.utc(2000) <=> ActiveSupport::TimeWithZone.new( Time.utc(2000, 1, 1, 0, 0, 1), ActiveSupport::TimeZone['UTC'] ))
end
+ def test_compare_with_string
+ assert_equal 1, Time.utc(2000) <=> Time.utc(1999, 12, 31, 23, 59, 59, 999).to_s
+ assert_equal 0, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 0).to_s
+ assert_equal( -1, Time.utc(2000) <=> Time.utc(2000, 1, 1, 0, 0, 1, 0).to_s)
+ assert_equal nil, Time.utc(2000) <=> 'Invalid as Time'
+ end
+
def test_at_with_datetime
assert_equal Time.utc(2000, 1, 1, 0, 0, 0), Time.at(DateTime.civil(2000, 1, 1, 0, 0, 0))
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index 79d78c02cd..7acada011d 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -2,6 +2,7 @@ require 'abstract_unit'
require 'active_support/time'
require 'time_zone_test_helpers'
require 'active_support/core_ext/string/strip'
+require 'yaml'
class TimeWithZoneTest < ActiveSupport::TestCase
include TimeZoneTestHelpers
@@ -50,7 +51,22 @@ class TimeWithZoneTest < ActiveSupport::TestCase
def test_utc?
assert_equal false, @twz.utc?
+
assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UTC']).utc?
+ assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Etc/UTC']).utc?
+ assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Universal']).utc?
+ assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['UCT']).utc?
+ assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Etc/UCT']).utc?
+ assert_equal true, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Etc/Universal']).utc?
+
+ assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Abidjan']).utc?
+ assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Banjul']).utc?
+ assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Freetown']).utc?
+ assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['GMT']).utc?
+ assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['GMT0']).utc?
+ assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Greenwich']).utc?
+ assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Iceland']).utc?
+ assert_equal false, ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone['Africa/Monrovia']).utc?
end
def test_formatted_offset
@@ -109,14 +125,14 @@ class TimeWithZoneTest < ActiveSupport::TestCase
@twz += 0.1234560001 # advance the time by a fraction of a second
assert_equal "1999-12-31T19:00:00.123-05:00", @twz.xmlschema(3)
assert_equal "1999-12-31T19:00:00.123456-05:00", @twz.xmlschema(6)
- assert_equal "1999-12-31T19:00:00.123456-05:00", @twz.xmlschema(12)
+ assert_equal "1999-12-31T19:00:00.123456000100-05:00", @twz.xmlschema(12)
end
def test_xmlschema_with_fractional_seconds_lower_than_hundred_thousand
@twz += 0.001234 # advance the time by a fraction
assert_equal "1999-12-31T19:00:00.001-05:00", @twz.xmlschema(3)
assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(6)
- assert_equal "1999-12-31T19:00:00.001234-05:00", @twz.xmlschema(12)
+ assert_equal "1999-12-31T19:00:00.001234000000-05:00", @twz.xmlschema(12)
end
def test_xmlschema_with_nil_fractional_seconds
@@ -205,52 +221,61 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_today
- Date.stubs(:current).returns(Date.new(2000, 1, 1))
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(1999,12,31,23,59,59) ).today?
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,0) ).today?
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,23,59,59) ).today?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,2,0) ).today?
+ Date.stub(:current, Date.new(2000, 1, 1)) do
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(1999,12,31,23,59,59) ).today?
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,0) ).today?
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,1,23,59,59) ).today?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.utc(2000,1,2,0) ).today?
+ end
end
def test_past_with_time_current_as_time_local
with_env_tz 'US/Eastern' do
- Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past?
+ Time.stub(:current, Time.local(2005,2,10,15,30,45)) do
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past?
+ end
end
end
def test_past_with_time_current_as_time_with_zone
twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
- Time.stubs(:current).returns(twz)
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past?
+ Time.stub(:current, twz) do
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).past?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).past?
+ end
end
def test_future_with_time_current_as_time_local
with_env_tz 'US/Eastern' do
- Time.stubs(:current).returns(Time.local(2005,2,10,15,30,45))
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future?
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future?
+ Time.stub(:current, Time.local(2005,2,10,15,30,45)) do
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future?
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future?
+ end
end
end
def test_future_with_time_current_as_time_with_zone
twz = ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45) )
- Time.stubs(:current).returns(twz)
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
- assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future?
- assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future?
+ Time.stub(:current, twz) do
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,44)).future?
+ assert_equal false, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,45)).future?
+ assert_equal true, ActiveSupport::TimeWithZone.new( nil, @time_zone, Time.local(2005,2,10,15,30,46)).future?
+ end
end
def test_eql?
+ assert_equal true, @twz.eql?(@twz.dup)
assert_equal true, @twz.eql?(Time.utc(2000))
assert_equal true, @twz.eql?( ActiveSupport::TimeWithZone.new(Time.utc(2000), ActiveSupport::TimeZone["Hawaii"]) )
assert_equal false, @twz.eql?( Time.utc(2000, 1, 1, 0, 0, 1) )
assert_equal false, @twz.eql?( DateTime.civil(1999, 12, 31, 23, 59, 59) )
+
+ other_twz = ActiveSupport::TimeWithZone.new(DateTime.now.utc, @time_zone)
+ assert_equal true, other_twz.eql?(other_twz.dup)
end
def test_hash
@@ -477,22 +502,24 @@ class TimeWithZoneTest < ActiveSupport::TestCase
end
def test_method_missing_with_non_time_return_value
- @twz.time.expects(:foo).returns('bar')
+ time = @twz.time
+ def time.foo; 'bar'; end
assert_equal 'bar', @twz.foo
end
def test_date_part_value_methods
twz = ActiveSupport::TimeWithZone.new(Time.utc(1999,12,31,19,18,17,500), @time_zone)
- twz.expects(:method_missing).never
- assert_equal 1999, twz.year
- assert_equal 12, twz.month
- assert_equal 31, twz.day
- assert_equal 14, twz.hour
- assert_equal 18, twz.min
- assert_equal 17, twz.sec
- assert_equal 500, twz.usec
- assert_equal 5, twz.wday
- assert_equal 365, twz.yday
+ assert_not_called(twz, :method_missing) do
+ assert_equal 1999, twz.year
+ assert_equal 12, twz.month
+ assert_equal 31, twz.day
+ assert_equal 14, twz.hour
+ assert_equal 18, twz.min
+ assert_equal 17, twz.sec
+ assert_equal 500, twz.usec
+ assert_equal 5, twz.wday
+ assert_equal 365, twz.yday
+ end
end
def test_usec_returns_0_when_datetime_is_wrapped
@@ -1033,19 +1060,21 @@ class TimeWithZoneMethodsForTimeAndDateTimeTest < ActiveSupport::TestCase
def test_current_returns_time_now_when_zone_not_set
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- assert_equal false, Time.current.is_a?(ActiveSupport::TimeWithZone)
- assert_equal Time.local(2000), Time.current
+ Time.stub(:now, Time.local(2000)) do
+ assert_equal false, Time.current.is_a?(ActiveSupport::TimeWithZone)
+ assert_equal Time.local(2000), Time.current
+ end
end
end
def test_current_returns_time_zone_now_when_zone_set
Time.zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
with_env_tz 'US/Eastern' do
- Time.stubs(:now).returns Time.local(2000)
- assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone)
- assert_equal 'Eastern Time (US & Canada)', Time.current.time_zone.name
- assert_equal Time.utc(2000), Time.current.time
+ Time.stub(:now, Time.local(2000)) do
+ assert_equal true, Time.current.is_a?(ActiveSupport::TimeWithZone)
+ assert_equal 'Eastern Time (US & Canada)', Time.current.time_zone.name
+ assert_equal Time.utc(2000), Time.current.time
+ end
end
end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 4a1d90bfd6..757e600646 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -187,7 +187,7 @@ class DependenciesTest < ActiveSupport::TestCase
assert_kind_of Module, A
assert_kind_of Class, A::B
assert_kind_of Class, A::C::D
- assert_kind_of Class, A::C::E::F
+ assert_kind_of Class, A::C::EM::F
end
end
@@ -552,24 +552,24 @@ class DependenciesTest < ActiveSupport::TestCase
def test_const_missing_in_anonymous_modules_loads_top_level_constants
with_autoloading_fixtures do
# class_eval STRING pushes the class to the nesting of the eval'ed code.
- klass = Class.new.class_eval "E"
- assert_equal E, klass
+ klass = Class.new.class_eval "EM"
+ assert_equal EM, klass
end
ensure
- remove_constants(:E)
+ remove_constants(:EM)
end
def test_const_missing_in_anonymous_modules_raises_if_the_constant_belongs_to_Object
with_autoloading_fixtures do
- require_dependency 'e'
+ require_dependency 'em'
mod = Module.new
- e = assert_raise(NameError) { mod::E }
- assert_equal 'E cannot be autoloaded from an anonymous class or module', e.message
- assert_equal :E, e.name
+ e = assert_raise(NameError) { mod::EM }
+ assert_equal 'EM cannot be autoloaded from an anonymous class or module', e.message
+ assert_equal :EM, e.name
end
ensure
- remove_constants(:E)
+ remove_constants(:EM)
end
def test_removal_from_tree_should_be_detected
@@ -664,19 +664,19 @@ class DependenciesTest < ActiveSupport::TestCase
def test_preexisting_constants_are_not_marked_as_autoloaded
with_autoloading_fixtures do
- require_dependency 'e'
- assert ActiveSupport::Dependencies.autoloaded?(:E)
+ require_dependency 'em'
+ assert ActiveSupport::Dependencies.autoloaded?(:EM)
ActiveSupport::Dependencies.clear
end
- Object.const_set :E, Class.new
+ Object.const_set :EM, Class.new
with_autoloading_fixtures do
- require_dependency 'e'
- assert ! ActiveSupport::Dependencies.autoloaded?(:E), "E shouldn't be marked autoloaded!"
+ require_dependency 'em'
+ assert ! ActiveSupport::Dependencies.autoloaded?(:EM), "EM shouldn't be marked autoloaded!"
ActiveSupport::Dependencies.clear
end
ensure
- remove_constants(:E)
+ remove_constants(:EM)
end
def test_constants_in_capitalized_nesting_marked_as_autoloaded
@@ -721,12 +721,13 @@ class DependenciesTest < ActiveSupport::TestCase
end
def test_unloadable_constants_should_receive_callback
- Object.const_set :C, Class.new
+ Object.const_set :C, Class.new { def self.before_remove_const; end }
C.unloadable
- C.expects(:before_remove_const).once
- assert C.respond_to?(:before_remove_const)
- ActiveSupport::Dependencies.clear
- assert !defined?(C)
+ assert_called(C, :before_remove_const, times: 1) do
+ assert C.respond_to?(:before_remove_const)
+ ActiveSupport::Dependencies.clear
+ assert !defined?(C)
+ end
ensure
remove_constants(:C)
end
diff --git a/activesupport/test/deprecation/method_wrappers_test.rb b/activesupport/test/deprecation/method_wrappers_test.rb
new file mode 100644
index 0000000000..9a4ca2b217
--- /dev/null
+++ b/activesupport/test/deprecation/method_wrappers_test.rb
@@ -0,0 +1,34 @@
+require 'abstract_unit'
+require 'active_support/deprecation'
+
+class MethodWrappersTest < ActiveSupport::TestCase
+ def setup
+ @klass = Class.new do
+ def new_method; "abc" end
+ alias_method :old_method, :new_method
+ end
+ end
+
+ def test_deprecate_methods_warning_default
+ warning = /old_method is deprecated and will be removed from Rails \d.\d \(use new_method instead\)/
+ ActiveSupport::Deprecation.deprecate_methods(@klass, :old_method => :new_method)
+
+ assert_deprecated(warning) { assert_equal "abc", @klass.new.old_method }
+ end
+
+ def test_deprecate_methods_warning_with_optional_deprecator
+ warning = /old_method is deprecated and will be removed from MyGem next-release \(use new_method instead\)/
+ deprecator = ActiveSupport::Deprecation.new("next-release", "MyGem")
+ ActiveSupport::Deprecation.deprecate_methods(@klass, :old_method => :new_method, :deprecator => deprecator)
+
+ assert_deprecated(warning, deprecator) { assert_equal "abc", @klass.new.old_method }
+ end
+
+ def test_deprecate_methods_warning_when_deprecated_with_custom_deprecator
+ warning = /old_method is deprecated and will be removed from MyGem next-release \(use new_method instead\)/
+ deprecator = ActiveSupport::Deprecation.new("next-release", "MyGem")
+ deprecator.deprecate_methods(@klass, :old_method => :new_method)
+
+ assert_deprecated(warning, deprecator) { assert_equal "abc", @klass.new.old_method }
+ end
+end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 20bd8ee5dd..58a0a3964d 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -163,6 +163,14 @@ class DeprecationTest < ActiveSupport::TestCase
assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class }
end
+ def test_assert_deprecated_raises_when_method_not_deprecated
+ assert_raises(Minitest::Assertion) { assert_deprecated { @dtc.not } }
+ end
+
+ def test_assert_not_deprecated
+ assert_raises(Minitest::Assertion) { assert_not_deprecated { @dtc.partially } }
+ end
+
def test_assert_deprecation_without_match
assert_deprecated do
@dtc.partially
@@ -191,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
@@ -256,17 +264,17 @@ class DeprecationTest < ActiveSupport::TestCase
end
def test_deprecate_with_custom_deprecator
- custom_deprecator = mock('Deprecator') do
- expects(:deprecation_warning)
- end
+ custom_deprecator = Struct.new(:deprecation_warning).new
- klass = Class.new do
- def method
+ assert_called_with(custom_deprecator, :deprecation_warning, [:method, nil]) do
+ klass = Class.new do
+ def method
+ end
+ deprecate :method, deprecator: custom_deprecator
end
- deprecate :method, deprecator: custom_deprecator
- end
- klass.new.method
+ klass.new.method
+ end
end
def test_deprecated_constant_with_deprecator_given
@@ -332,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 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/file_update_checker_shared_tests.rb b/activesupport/test/file_update_checker_shared_tests.rb
new file mode 100644
index 0000000000..9c07e38fe5
--- /dev/null
+++ b/activesupport/test/file_update_checker_shared_tests.rb
@@ -0,0 +1,242 @@
+require 'fileutils'
+
+module FileUpdateCheckerSharedTests
+ extend ActiveSupport::Testing::Declarative
+ include FileUtils
+
+ def tmpdir
+ @tmpdir ||= Dir.mktmpdir(nil, __dir__)
+ end
+
+ def tmpfile(name)
+ "#{tmpdir}/#{name}"
+ end
+
+ def tmpfiles
+ @tmpfiles ||= %w(foo.rb bar.rb baz.rb).map { |f| tmpfile(f) }
+ end
+
+ def teardown
+ FileUtils.rm_rf(@tmpdir) if defined? @tmpdir
+ 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 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("#{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("#{subdir}/nested.rb")
+
+ assert checker.execute_if_updated
+ assert_equal 1, i
+
+ touch("#{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/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
deleted file mode 100644
index 1facd691fa..0000000000
--- a/activesupport/test/hash_with_indifferent_access_test.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-require 'abstract_unit'
-require 'active_support/hash_with_indifferent_access'
-
-class HashWithIndifferentAccessTest < ActiveSupport::TestCase
- def test_reverse_merge
- hash = HashWithIndifferentAccess.new key: :old_value
- hash.reverse_merge! key: :new_value
- assert_equal :old_value, hash[:key]
- end
-
-end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index be68bb2e2e..06cd41c86d 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -8,6 +8,20 @@ class InflectorTest < ActiveSupport::TestCase
include InflectorTestCases
include ConstantizeTestCases
+ def setup
+ # Dups the singleton before each test, restoring the original inflections later.
+ #
+ # This helper is implemented by setting @__instance__ because in some tests
+ # there are module functions that access ActiveSupport::Inflector.inflections,
+ # so we need to replace the singleton itself.
+ @original_inflections = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: @original_inflections.dup)
+ end
+
+ def teardown
+ ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: @original_inflections)
+ end
+
def test_pluralize_plurals
assert_equal "plurals", ActiveSupport::Inflector.pluralize("plurals")
assert_equal "Plurals", ActiveSupport::Inflector.pluralize("Plurals")
@@ -26,20 +40,18 @@ class InflectorTest < ActiveSupport::TestCase
end
def test_uncountable_word_is_not_greedy
- with_dup do
- uncountable_word = "ors"
- countable_word = "sponsor"
+ uncountable_word = "ors"
+ countable_word = "sponsor"
- ActiveSupport::Inflector.inflections.uncountable << uncountable_word
+ ActiveSupport::Inflector.inflections.uncountable << uncountable_word
- assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word)
- assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word)
- assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word)
+ assert_equal uncountable_word, ActiveSupport::Inflector.singularize(uncountable_word)
+ assert_equal uncountable_word, ActiveSupport::Inflector.pluralize(uncountable_word)
+ assert_equal ActiveSupport::Inflector.pluralize(uncountable_word), ActiveSupport::Inflector.singularize(uncountable_word)
- assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word)
- assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word)
- assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word))
- end
+ assert_equal "sponsor", ActiveSupport::Inflector.singularize(countable_word)
+ assert_equal "sponsors", ActiveSupport::Inflector.pluralize(countable_word)
+ assert_equal "sponsor", ActiveSupport::Inflector.singularize(ActiveSupport::Inflector.pluralize(countable_word))
end
SingularToPlural.each do |singular, plural|
@@ -70,11 +82,9 @@ class InflectorTest < ActiveSupport::TestCase
def test_overwrite_previous_inflectors
- with_dup do
- assert_equal("series", ActiveSupport::Inflector.singularize("series"))
- ActiveSupport::Inflector.inflections.singular "series", "serie"
- assert_equal("serie", ActiveSupport::Inflector.singularize("series"))
- end
+ assert_equal("series", ActiveSupport::Inflector.singularize("series"))
+ ActiveSupport::Inflector.inflections.singular "series", "serie"
+ assert_equal("serie", ActiveSupport::Inflector.singularize("series"))
end
MixtureToTitleCase.each_with_index do |(before, titleized), index|
@@ -259,14 +269,32 @@ class InflectorTest < ActiveSupport::TestCase
def test_parameterize_with_custom_separator
jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterizeWithUnderscore.each do |some_string, parameterized_string|
- assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_'))
+ assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, separator: '_'))
+ end
+ end
+
+ def test_parameterize_with_custom_separator_deprecated
+ jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
+ StringToParameterizeWithUnderscore.each do |some_string, parameterized_string|
+ assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '_'` instead./i) do
+ assert_equal(parameterized_string, ActiveSupport::Inflector.parameterize(some_string, '_'))
+ end
end
end
def test_parameterize_with_multi_character_separator
jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
StringToParameterized.each do |some_string, parameterized_string|
- assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__'))
+ assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, separator: '__sep__'))
+ end
+ end
+
+ def test_parameterize_with_multi_character_separator_deprecated
+ jruby_skip "UTF-8 to UTF8-MAC Converter is unavailable"
+ StringToParameterized.each do |some_string, parameterized_string|
+ assert_deprecated(/Passing the separator argument as a positional parameter is deprecated and will soon be removed. Use `separator: '__sep__'` instead./i) do
+ assert_equal(parameterized_string.gsub('-', '__sep__'), ActiveSupport::Inflector.parameterize(some_string, '__sep__'))
+ end
end
end
@@ -367,10 +395,8 @@ class InflectorTest < ActiveSupport::TestCase
%w{plurals singulars uncountables humans}.each do |inflection_type|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def test_clear_#{inflection_type}
- with_dup do
- ActiveSupport::Inflector.inflections.clear :#{inflection_type}
- assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\"
- end
+ ActiveSupport::Inflector.inflections.clear :#{inflection_type}
+ assert ActiveSupport::Inflector.inflections.#{inflection_type}.empty?, \"#{inflection_type} inflections should be empty after clear :#{inflection_type}\"
end
RUBY
end
@@ -405,73 +431,63 @@ class InflectorTest < ActiveSupport::TestCase
end
def test_clear_all
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- # ensure any data is present
- inflect.plural(/(quiz)$/i, '\1zes')
- inflect.singular(/(database)s$/i, '\1')
- inflect.uncountable('series')
- inflect.human("col_rpted_bugs", "Reported bugs")
-
- inflect.clear :all
-
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear :all
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
end
end
def test_clear_with_default
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- # ensure any data is present
- inflect.plural(/(quiz)$/i, '\1zes')
- inflect.singular(/(database)s$/i, '\1')
- inflect.uncountable('series')
- inflect.human("col_rpted_bugs", "Reported bugs")
-
- inflect.clear
-
- assert inflect.plurals.empty?
- assert inflect.singulars.empty?
- assert inflect.uncountables.empty?
- assert inflect.humans.empty?
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ # ensure any data is present
+ inflect.plural(/(quiz)$/i, '\1zes')
+ inflect.singular(/(database)s$/i, '\1')
+ inflect.uncountable('series')
+ inflect.human("col_rpted_bugs", "Reported bugs")
+
+ inflect.clear
+
+ assert inflect.plurals.empty?
+ assert inflect.singulars.empty?
+ assert inflect.uncountables.empty?
+ assert inflect.humans.empty?
end
end
Irregularities.each do |singular, plural|
define_method("test_irregularity_between_#{singular}_and_#{plural}") do
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.irregular(singular, plural)
- assert_equal singular, ActiveSupport::Inflector.singularize(plural)
- assert_equal plural, ActiveSupport::Inflector.pluralize(singular)
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular(singular, plural)
+ assert_equal singular, ActiveSupport::Inflector.singularize(plural)
+ assert_equal plural, ActiveSupport::Inflector.pluralize(singular)
end
end
end
Irregularities.each do |singular, plural|
define_method("test_pluralize_of_irregularity_#{plural}_should_be_the_same") do
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.irregular(singular, plural)
- assert_equal plural, ActiveSupport::Inflector.pluralize(plural)
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular(singular, plural)
+ assert_equal plural, ActiveSupport::Inflector.pluralize(plural)
end
end
end
Irregularities.each do |singular, plural|
define_method("test_singularize_of_irregularity_#{singular}_should_be_the_same") do
- with_dup do
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.irregular(singular, plural)
- assert_equal singular, ActiveSupport::Inflector.singularize(singular)
- end
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.irregular(singular, plural)
+ assert_equal singular, ActiveSupport::Inflector.singularize(singular)
end
end
end
@@ -503,12 +519,10 @@ class InflectorTest < ActiveSupport::TestCase
%w(plurals singulars uncountables humans acronyms).each do |scope|
define_method("test_clear_inflections_with_#{scope}") do
- with_dup do
- # clear the inflections
- ActiveSupport::Inflector.inflections do |inflect|
- inflect.clear(scope)
- assert_equal [], inflect.send(scope)
- end
+ # clear the inflections
+ ActiveSupport::Inflector.inflections do |inflect|
+ inflect.clear(scope)
+ assert_equal [], inflect.send(scope)
end
end
end
@@ -520,18 +534,4 @@ class InflectorTest < ActiveSupport::TestCase
assert_equal "HTTP", ActiveSupport::Inflector.pluralize("HTTP")
end
-
- # Dups the singleton and yields, restoring the original inflections later.
- # Use this in tests what modify the state of the singleton.
- #
- # This helper is implemented by setting @__instance__ because in some tests
- # there are module functions that access ActiveSupport::Inflector.inflections,
- # so we need to replace the singleton itself.
- def with_dup
- original = ActiveSupport::Inflector::Inflections.instance_variable_get(:@__instance__)[:en]
- ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original.dup)
- yield
- ensure
- ActiveSupport::Inflector::Inflections.instance_variable_set(:@__instance__, en: original)
- end
end
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index 18a8b92eb9..14fe97a986 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -1,4 +1,3 @@
-
module InflectorTestCases
SingularToPlural = {
"search" => "searches",
@@ -175,6 +174,17 @@ module InflectorTestCases
"Test with malformed utf8 \251" => "test-with-malformed-utf8"
}
+ StringToParameterizedPreserveCase = {
+ "Donald E. Knuth" => "Donald-E-Knuth",
+ "Random text with *(bad)* characters" => "Random-text-with-bad-characters",
+ "Allow_Under_Scores" => "Allow_Under_Scores",
+ "Trailing bad characters!@#" => "Trailing-bad-characters",
+ "!@#Leading bad characters" => "Leading-bad-characters",
+ "Squeeze separators" => "Squeeze-separators",
+ "Test with + sign" => "Test-with-sign",
+ "Test with malformed utf8 \xA9" => "Test-with-malformed-utf8"
+ }
+
StringToParameterizeWithNoSeparator = {
"Donald E. Knuth" => "donaldeknuth",
"With-some-dashes" => "with-some-dashes",
@@ -186,6 +196,17 @@ module InflectorTestCases
"Test with malformed utf8 \251" => "testwithmalformedutf8"
}
+ StringToParameterizePreserveCaseWithNoSeparator = {
+ "Donald E. Knuth" => "DonaldEKnuth",
+ "With-some-dashes" => "With-some-dashes",
+ "Random text with *(bad)* characters" => "Randomtextwithbadcharacters",
+ "Trailing bad characters!@#" => "Trailingbadcharacters",
+ "!@#Leading bad characters" => "Leadingbadcharacters",
+ "Squeeze separators" => "Squeezeseparators",
+ "Test with + sign" => "Testwithsign",
+ "Test with malformed utf8 \xA9" => "Testwithmalformedutf8"
+ }
+
StringToParameterizeWithUnderscore = {
"Donald E. Knuth" => "donald_e_knuth",
"Random text with *(bad)* characters" => "random_text_with_bad_characters",
@@ -198,6 +219,18 @@ module InflectorTestCases
"Test with malformed utf8 \251" => "test_with_malformed_utf8"
}
+ StringToParameterizePreserceCaseWithUnderscore = {
+ "Donald E. Knuth" => "Donald_E_Knuth",
+ "Random text with *(bad)* characters" => "Random_text_with_bad_characters",
+ "With-some-dashes" => "With-some-dashes",
+ "Allow_Under_Scores" => "Allow_Under_Scores",
+ "Trailing bad characters!@#" => "Trailing_bad_characters",
+ "!@#Leading bad characters" => "Leading_bad_characters",
+ "Squeeze separators" => "Squeeze_separators",
+ "Test with + sign" => "Test_with_sign",
+ "Test with malformed utf8 \xA9" => "Test_with_malformed_utf8"
+ }
+
StringToParameterizedAndNormalized = {
"Malmö" => "malmo",
"Garçons" => "garcons",
diff --git a/activesupport/test/json/encoding_test.rb b/activesupport/test/json/encoding_test.rb
index 2f269a66f0..9f4b62fd8b 100644
--- a/activesupport/test/json/encoding_test.rb
+++ b/activesupport/test/json/encoding_test.rb
@@ -4,122 +4,24 @@ require 'active_support/core_ext/string/inflections'
require 'active_support/json'
require 'active_support/time'
require 'time_zone_test_helpers'
+require 'json/encoding_test_cases'
class TestJSONEncoding < ActiveSupport::TestCase
include TimeZoneTestHelpers
- class Foo
- def initialize(a, b)
- @a, @b = a, b
- end
- end
-
- class Hashlike
- def to_hash
- { :foo => "hello", :bar => "world" }
- end
- end
-
- class Custom
- def initialize(serialized)
- @serialized = serialized
- end
-
- def as_json(options = nil)
- @serialized
- end
- end
-
- class CustomWithOptions
- attr_accessor :foo, :bar
-
- def as_json(options={})
- options[:only] = %w(foo bar)
- super(options)
- end
- end
-
- class OptionsTest
- def as_json(options = :default)
- options
- end
- end
-
- class HashWithAsJson < Hash
- attr_accessor :as_json_called
-
- def initialize(*)
- super
- end
-
- def as_json(options={})
- @as_json_called = true
- super
- end
- end
-
- TrueTests = [[ true, %(true) ]]
- FalseTests = [[ false, %(false) ]]
- NilTests = [[ nil, %(null) ]]
- NumericTests = [[ 1, %(1) ],
- [ 2.5, %(2.5) ],
- [ 0.0/0.0, %(null) ],
- [ 1.0/0.0, %(null) ],
- [ -1.0/0.0, %(null) ],
- [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
- [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
-
- StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
- [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
- [ 'http://test.host/posts/1', %("http://test.host/posts/1")],
- [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
- %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
-
- ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
- [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
-
- RangeTests = [[ 1..2, %("1..2")],
- [ 1...2, %("1...2")],
- [ 1.5..2.5, %("1.5..2.5")]]
-
- SymbolTests = [[ :a, %("a") ],
- [ :this, %("this") ],
- [ :"a b", %("a b") ]]
-
- ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
- HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
- CustomTests = [[ Custom.new("custom"), '"custom"' ],
- [ Custom.new(nil), 'null' ],
- [ Custom.new(:a), '"a"' ],
- [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
- [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
- [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
- [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
-
- RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
-
- DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
- TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
- DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
-
- StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
- StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
- StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
- StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
-
def sorted_json(json)
return json unless json =~ /^\{.*\}$/
'{' + json[1..-2].split(',').sort.join(',') + '}'
end
- constants.grep(/Tests$/).each do |class_tests|
+ JSONTest::EncodingTestCases.constants.each do |class_tests|
define_method("test_#{class_tests[0..-6].underscore}") do
begin
prev = ActiveSupport.use_standard_json_time_format
ActiveSupport.escape_html_entities_in_json = class_tests !~ /^Standard/
ActiveSupport.use_standard_json_time_format = class_tests =~ /^Standard/
- self.class.const_get(class_tests).each do |pair|
+ JSONTest::EncodingTestCases.const_get(class_tests).each do |pair|
assert_equal pair.last, sorted_json(ActiveSupport::JSON.encode(pair.first))
end
ensure
@@ -147,6 +49,13 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal %({\"a\":\"b\",\"c\":\"d\"}), sorted_json(ActiveSupport::JSON.encode(:a => :b, :c => :d))
end
+ def test_hash_keys_encoding
+ ActiveSupport.escape_html_entities_in_json = true
+ assert_equal "{\"\\u003c\\u003e\":\"\\u003c\\u003e\"}", ActiveSupport::JSON.encode("<>" => "<>")
+ ensure
+ ActiveSupport.escape_html_entities_in_json = false
+ end
+
def test_utf8_string_encoded_properly
result = ActiveSupport::JSON.encode('€2.99')
assert_equal '"€2.99"', result
@@ -217,7 +126,7 @@ class TestJSONEncoding < ActiveSupport::TestCase
end
def test_hash_like_with_options
- h = Hashlike.new
+ h = JSONTest::Hashlike.new
json = h.to_json :only => [:foo]
assert_equal({"foo"=>"hello"}, JSON.parse(json))
@@ -338,6 +247,15 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal(%([{"address":{"city":"London"}},{"address":{"city":"Paris"}}]), json)
end
+ class CustomWithOptions
+ attr_accessor :foo, :bar
+
+ def as_json(options={})
+ options[:only] = %w(foo bar)
+ super(options)
+ end
+ end
+
def test_hash_to_json_should_not_keep_options_around
f = CustomWithOptions.new
f.foo = "hello"
@@ -358,6 +276,12 @@ class TestJSONEncoding < ActiveSupport::TestCase
{"foo"=>"other_foo","test"=>"other_test"}], ActiveSupport::JSON.decode(array.to_json))
end
+ class OptionsTest
+ def as_json(options = :default)
+ options
+ end
+ end
+
def test_hash_as_json_without_options
json = { foo: OptionsTest.new }.as_json
assert_equal({"foo" => :default}, json)
@@ -405,6 +329,19 @@ class TestJSONEncoding < ActiveSupport::TestCase
assert_equal false, false.as_json
end
+ class HashWithAsJson < Hash
+ attr_accessor :as_json_called
+
+ def initialize(*)
+ super
+ end
+
+ def as_json(options={})
+ @as_json_called = true
+ super
+ end
+ end
+
def test_json_gem_dump_by_passing_active_support_encoder
h = HashWithAsJson.new
h[:foo] = "hello"
diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb
new file mode 100644
index 0000000000..0159ba8606
--- /dev/null
+++ b/activesupport/test/json/encoding_test_cases.rb
@@ -0,0 +1,88 @@
+require 'bigdecimal'
+
+module JSONTest
+ class Foo
+ def initialize(a, b)
+ @a, @b = a, b
+ end
+ end
+
+ class Hashlike
+ def to_hash
+ { :foo => "hello", :bar => "world" }
+ end
+ end
+
+ class Custom
+ def initialize(serialized)
+ @serialized = serialized
+ end
+
+ def as_json(options = nil)
+ @serialized
+ end
+ end
+
+ class MyStruct < Struct.new(:name, :value)
+ def initialize(*)
+ @unused = "unused instance variable"
+ super
+ end
+ end
+
+ module EncodingTestCases
+ TrueTests = [[ true, %(true) ]]
+ FalseTests = [[ false, %(false) ]]
+ NilTests = [[ nil, %(null) ]]
+ NumericTests = [[ 1, %(1) ],
+ [ 2.5, %(2.5) ],
+ [ 0.0/0.0, %(null) ],
+ [ 1.0/0.0, %(null) ],
+ [ -1.0/0.0, %(null) ],
+ [ BigDecimal('0.0')/BigDecimal('0.0'), %(null) ],
+ [ BigDecimal('2.5'), %("#{BigDecimal('2.5')}") ]]
+
+ StringTests = [[ 'this is the <string>', %("this is the \\u003cstring\\u003e")],
+ [ 'a "string" with quotes & an ampersand', %("a \\"string\\" with quotes \\u0026 an ampersand") ],
+ [ 'http://test.host/posts/1', %("http://test.host/posts/1")],
+ [ "Control characters: \x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\u2028\u2029",
+ %("Control characters: \\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f\\u2028\\u2029") ]]
+
+ ArrayTests = [[ ['a', 'b', 'c'], %([\"a\",\"b\",\"c\"]) ],
+ [ [1, 'a', :b, nil, false], %([1,\"a\",\"b\",null,false]) ]]
+
+ HashTests = [[ {foo: "bar"}, %({\"foo\":\"bar\"}) ],
+ [ {1 => 1, 2 => 'a', 3 => :b, 4 => nil, 5 => false}, %({\"1\":1,\"2\":\"a\",\"3\":\"b\",\"4\":null,\"5\":false}) ]]
+
+ RangeTests = [[ 1..2, %("1..2")],
+ [ 1...2, %("1...2")],
+ [ 1.5..2.5, %("1.5..2.5")]]
+
+ SymbolTests = [[ :a, %("a") ],
+ [ :this, %("this") ],
+ [ :"a b", %("a b") ]]
+
+ ObjectTests = [[ Foo.new(1, 2), %({\"a\":1,\"b\":2}) ]]
+ HashlikeTests = [[ Hashlike.new, %({\"bar\":\"world\",\"foo\":\"hello\"}) ]]
+ StructTests = [[ MyStruct.new(:foo, "bar"), %({\"name\":\"foo\",\"value\":\"bar\"}) ],
+ [ MyStruct.new(nil, nil), %({\"name\":null,\"value\":null}) ]]
+ CustomTests = [[ Custom.new("custom"), '"custom"' ],
+ [ Custom.new(nil), 'null' ],
+ [ Custom.new(:a), '"a"' ],
+ [ Custom.new([ :foo, "bar" ]), '["foo","bar"]' ],
+ [ Custom.new({ :foo => "hello", :bar => "world" }), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Hashlike.new), '{"bar":"world","foo":"hello"}' ],
+ [ Custom.new(Custom.new(Custom.new(:a))), '"a"' ]]
+
+ RegexpTests = [[ /^a/, '"(?-mix:^a)"' ], [/^\w{1,2}[a-z]+/ix, '"(?ix-m:^\\\\w{1,2}[a-z]+)"']]
+
+ DateTests = [[ Date.new(2005,2,1), %("2005/02/01") ]]
+ TimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
+ DateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005/02/01 15:15:10 +0000") ]]
+
+ StandardDateTests = [[ Date.new(2005,2,1), %("2005-02-01") ]]
+ StandardTimeTests = [[ Time.utc(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000Z") ]]
+ StandardDateTimeTests = [[ DateTime.civil(2005,2,1,15,15,10), %("2005-02-01T15:15:10.000+00:00") ]]
+ StandardStringTests = [[ 'this is the <string>', %("this is the <string>")]]
+ end
+end
diff --git a/activesupport/test/log_subscriber_test.rb b/activesupport/test/log_subscriber_test.rb
index 2a0e8d20ed..998a6887c5 100644
--- a/activesupport/test/log_subscriber_test.rb
+++ b/activesupport/test/log_subscriber_test.rb
@@ -82,10 +82,11 @@ class SyncLogSubscriberTest < ActiveSupport::TestCase
def test_does_not_send_the_event_if_logger_is_nil
ActiveSupport::LogSubscriber.logger = nil
- @log_subscriber.expects(:some_event).never
- ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
- instrument "some_event.my_log_subscriber"
- wait
+ assert_not_called(@log_subscriber, :some_event) do
+ ActiveSupport::LogSubscriber.attach_to :my_log_subscriber, @log_subscriber
+ instrument "some_event.my_log_subscriber"
+ wait
+ end
end
def test_does_not_fail_with_non_namespaced_events
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
index d2801849ca..317e09b7f2 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,93 @@ 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_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/message_verifier_test.rb b/activesupport/test/message_verifier_test.rb
index 6c3519df9a..668d78492e 100644
--- a/activesupport/test/message_verifier_test.rb
+++ b/activesupport/test/message_verifier_test.rb
@@ -24,6 +24,7 @@ class MessageVerifierTest < ActiveSupport::TestCase
data, hash = @verifier.generate(@data).split("--")
assert !@verifier.valid_message?(nil)
assert !@verifier.valid_message?("")
+ assert !@verifier.valid_message?("\xff") # invalid encoding
assert !@verifier.valid_message?("#{data.reverse}--#{hash}")
assert !@verifier.valid_message?("#{data}--#{hash.reverse}")
assert !@verifier.valid_message?("purejunk")
diff --git a/activesupport/test/multibyte_chars_test.rb b/activesupport/test/multibyte_chars_test.rb
index e1c4b705f8..c1e0b19248 100644
--- a/activesupport/test/multibyte_chars_test.rb
+++ b/activesupport/test/multibyte_chars_test.rb
@@ -413,12 +413,24 @@ class MultibyteCharsUTF8BehaviourTest < ActiveSupport::TestCase
assert_equal 'にち', @chars.slice!(1..2)
end
+ def test_slice_bang_returns_nil_on_out_of_bound_arguments
+ assert_equal nil, @chars.mb_chars.slice!(9..10)
+ end
+
def test_slice_bang_removes_the_slice_from_the_receiver
chars = 'úüù'.mb_chars
chars.slice!(0,2)
assert_equal 'ù', chars
end
+ def test_slice_bang_returns_nil_and_does_not_modify_receiver_if_out_of_bounds
+ string = 'úüù'
+ chars = string.mb_chars
+ assert_nil chars.slice!(4, 5)
+ assert_equal 'úüù', chars
+ assert_equal 'úüù', string
+ end
+
def test_slice_should_throw_exceptions_on_invalid_arguments
assert_raise(TypeError) { @chars.slice(2..3, 1) }
assert_raise(TypeError) { @chars.slice(1, 2..3) }
@@ -600,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
@@ -686,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 d8704716e7..d493a48fe4 100644
--- a/activesupport/test/multibyte_conformance_test.rb
+++ b/activesupport/test/multibyte_conformance_test.rb
@@ -1,4 +1,3 @@
-
require 'abstract_unit'
require 'multibyte_test_helpers'
@@ -105,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.rb b/activesupport/test/multibyte_grapheme_break_conformance.rb
new file mode 100644
index 0000000000..7d185e2cae
--- /dev/null
+++ b/activesupport/test/multibyte_grapheme_break_conformance.rb
@@ -0,0 +1,76 @@
+# encoding: utf-8
+
+require 'abstract_unit'
+
+require 'fileutils'
+require 'open-uri'
+require 'tmpdir'
+
+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
+
+class MultibyteGraphemeBreakConformanceTest < ActiveSupport::TestCase
+ TEST_DATA_URL = "http://www.unicode.org/Public/#{ActiveSupport::Multibyte::Unicode::UNICODE_VERSION}/ucd/auxiliary"
+ TEST_DATA_FILE = '/GraphemeBreakTest.txt'
+ CACHE_DIR = File.join(Dir.tmpdir, 'cache')
+
+ 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.rb b/activesupport/test/multibyte_normalization_conformance.rb
new file mode 100644
index 0000000000..839aec7fa8
--- /dev/null
+++ b/activesupport/test/multibyte_normalization_conformance.rb
@@ -0,0 +1,129 @@
+# encoding: utf-8
+
+require 'abstract_unit'
+require 'multibyte_test_helpers'
+
+require 'fileutils'
+require 'open-uri'
+require 'tmpdir'
+
+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
+
+class MultibyteNormalizationConformanceTest < 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')
+
+ 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/multibyte_proxy_test.rb b/activesupport/test/multibyte_proxy_test.rb
index 11f5374017..360cf57302 100644
--- a/activesupport/test/multibyte_proxy_test.rb
+++ b/activesupport/test/multibyte_proxy_test.rb
@@ -1,4 +1,3 @@
-
require 'abstract_unit'
class MultibyteProxyText < ActiveSupport::TestCase
diff --git a/activesupport/test/multibyte_test_helpers.rb b/activesupport/test/multibyte_test_helpers.rb
index 2e4b5cc873..58cf5488cd 100644
--- a/activesupport/test/multibyte_test_helpers.rb
+++ b/activesupport/test/multibyte_test_helpers.rb
@@ -1,4 +1,3 @@
-
module MultibyteTestHelpers
UNICODE_STRING = 'こにちわ'.freeze
ASCII_STRING = 'ohayo'.freeze
diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb
index 52ae266f01..dd33641ec2 100644
--- a/activesupport/test/multibyte_unicode_database_test.rb
+++ b/activesupport/test/multibyte_unicode_database_test.rb
@@ -11,8 +11,9 @@ class MultibyteUnicodeDatabaseTest < ActiveSupport::TestCase
UnicodeDatabase::ATTRIBUTES.each do |attribute|
define_method "test_lazy_loading_on_attribute_access_of_#{attribute}" do
- @ucd.expects(:load)
- @ucd.send(attribute)
+ assert_called(@ucd, :load) do
+ @ucd.send(attribute)
+ end
end
end
diff --git a/activesupport/test/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 83efbffdfb..b3464462c8 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))
@@ -106,6 +114,7 @@ module ActiveSupport
assert_equal("123,456,789.78901", number_helper.number_to_delimited(123456789.78901))
assert_equal("0.78901", number_helper.number_to_delimited(0.78901))
assert_equal("123,456.78", number_helper.number_to_delimited("123456.78"))
+ assert_equal("1,23,456.78", number_helper.number_to_delimited("123456.78", delimiter_pattern: /(\d+?)(?=(\d\d)+(\d)(?!\d))/))
assert_equal("123,456.78", number_helper.number_to_delimited("123456.78".html_safe))
end
end
@@ -218,7 +227,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))
@@ -234,15 +245,19 @@ module ActiveSupport
end
def test_number_to_human_size_with_si_prefix
- [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
- assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si)
- assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si)
- assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si)
- assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si)
- assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si)
- assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
- assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
- assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ assert_deprecated do
+ [@instance_with_helpers, TestClassWithClassNumberHelpers, ActiveSupport::NumberHelper].each do |number_helper|
+ assert_equal '3 Bytes', number_helper.number_to_human_size(3.14159265, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123.0, :prefix => :si)
+ assert_equal '123 Bytes', number_helper.number_to_human_size(123, :prefix => :si)
+ assert_equal '1.23 KB', number_helper.number_to_human_size(1234, :prefix => :si)
+ assert_equal '12.3 KB', number_helper.number_to_human_size(12345, :prefix => :si)
+ assert_equal '1.23 MB', number_helper.number_to_human_size(1234567, :prefix => :si)
+ assert_equal '1.23 GB', number_helper.number_to_human_size(1234567890, :prefix => :si)
+ assert_equal '1.23 TB', number_helper.number_to_human_size(1234567890123, :prefix => :si)
+ 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
@@ -293,6 +308,8 @@ module ActiveSupport
assert_equal '1.2346 Million', number_helper.number_to_human(1234567, :precision => 4, :significant => false)
assert_equal '1,2 Million', number_helper.number_to_human(1234567, :precision => 1, :significant => false, :separator => ',')
assert_equal '1 Million', number_helper.number_to_human(1234567, :precision => 0, :significant => true, :separator => ',') #significant forced to false
+ assert_equal '1 Million', number_helper.number_to_human(999999)
+ assert_equal '1 Billion', number_helper.number_to_human(999999999)
end
end
diff --git a/activesupport/test/ordered_options_test.rb b/activesupport/test/ordered_options_test.rb
index fdc745b23b..18767a3536 100644
--- a/activesupport/test/ordered_options_test.rb
+++ b/activesupport/test/ordered_options_test.rb
@@ -85,4 +85,19 @@ class OrderedOptionsTest < ActiveSupport::TestCase
assert_equal 42, a.method(:blah=).call(42)
assert_equal 42, a.method(:blah).call
end
+
+ def test_raises_with_bang
+ a = ActiveSupport::OrderedOptions.new
+ a[:foo] = :bar
+ assert a.respond_to?(:foo!)
+
+ assert_nothing_raised { a.foo! }
+ assert_equal a.foo, a.foo!
+
+ assert_raises(KeyError) do
+ a.foo = nil
+ a.foo!
+ end
+ assert_raises(KeyError) { a.non_existing_key! }
+ end
end
diff --git a/activesupport/test/share_lock_test.rb b/activesupport/test/share_lock_test.rb
new file mode 100644
index 0000000000..465a657308
--- /dev/null
+++ b/activesupport/test/share_lock_test.rb
@@ -0,0 +1,333 @@
+require 'abstract_unit'
+require 'concurrent/atomic/count_down_latch'
+require 'active_support/concurrency/share_lock'
+
+class ShareLockTest < ActiveSupport::TestCase
+ def setup
+ @lock = ActiveSupport::Concurrency::ShareLock.new
+ end
+
+ def test_reentrancy
+ thread = Thread.new do
+ @lock.sharing { @lock.sharing {} }
+ @lock.exclusive { @lock.exclusive {} }
+ end
+ assert_threads_not_stuck thread
+ end
+
+ def test_sharing_doesnt_block
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_latch|
+ assert_threads_not_stuck(Thread.new {@lock.sharing {} })
+ end
+ end
+
+ def test_sharing_blocks_exclusive
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ @lock.exclusive(no_wait: true) { flunk } # polling should fail
+ exclusive_thread = Thread.new { @lock.exclusive {} }
+ assert_threads_stuck_but_releasable_by_latch exclusive_thread, sharing_thread_release_latch
+ end
+ end
+
+ def test_exclusive_blocks_sharing
+ with_thread_waiting_in_lock_section(:exclusive) do |exclusive_thread_release_latch|
+ sharing_thread = Thread.new { @lock.sharing {} }
+ assert_threads_stuck_but_releasable_by_latch sharing_thread, exclusive_thread_release_latch
+ end
+ end
+
+ def test_multiple_exlusives_are_able_to_progress
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ exclusive_threads = (1..2).map do
+ Thread.new do
+ @lock.exclusive {}
+ end
+ end
+
+ assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch
+ end
+ end
+
+ def test_sharing_is_upgradeable_to_exclusive
+ upgrading_thread = Thread.new do
+ @lock.sharing do
+ @lock.exclusive {}
+ end
+ end
+ assert_threads_not_stuck upgrading_thread
+ end
+
+ def test_exclusive_upgrade_waits_for_other_sharers_to_leave
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ in_sharing = Concurrent::CountDownLatch.new
+
+ upgrading_thread = Thread.new do
+ @lock.sharing do
+ in_sharing.count_down
+ @lock.exclusive {}
+ end
+ end
+
+ in_sharing.wait
+ assert_threads_stuck_but_releasable_by_latch upgrading_thread, sharing_thread_release_latch
+ end
+ end
+
+ def test_exclusive_matching_purpose
+ [true, false].each do |use_upgrading|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ exclusive_threads = (1..2).map do
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: :load, compatible: [:load, :unload]) {}
+ end
+ end
+ end
+
+ assert_threads_stuck_but_releasable_by_latch exclusive_threads, sharing_thread_release_latch
+ end
+ end
+ end
+
+ def test_killed_thread_loses_lock
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ thread = Thread.new do
+ @lock.sharing do
+ @lock.exclusive {}
+ end
+ end
+
+ assert_threads_stuck thread
+ thread.kill
+
+ sharing_thread_release_latch.count_down
+
+ thread = Thread.new do
+ @lock.exclusive {}
+ end
+
+ assert_threads_not_stuck thread
+ end
+ end
+
+ def test_exclusive_conflicting_purpose
+ [true, false].each do |use_upgrading|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ begin
+ conflicting_exclusive_threads = [
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: :red, compatible: [:green, :purple]) {}
+ end
+ end,
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: :blue, compatible: [:green]) {}
+ end
+ end
+ ]
+
+ assert_threads_stuck conflicting_exclusive_threads # wait for threads to get into their respective `exclusive {}` blocks
+
+ # This thread will be stuck as long as any other thread is in
+ # a sharing block. While it's blocked, it holds no lock, so it
+ # doesn't interfere with any other attempts.
+ no_purpose_thread = Thread.new do
+ @lock.exclusive {}
+ end
+ assert_threads_stuck no_purpose_thread
+
+ # This thread is compatible with both of the "primary"
+ # attempts above. It's initially stuck on the outer share
+ # lock, but as soon as that's released, it can run --
+ # regardless of whether those threads hold share locks.
+ compatible_thread = Thread.new do
+ @lock.exclusive(purpose: :green, compatible: []) {}
+ end
+ assert_threads_stuck compatible_thread
+
+ assert_threads_stuck conflicting_exclusive_threads
+
+ sharing_thread_release_latch.count_down
+
+ assert_threads_not_stuck compatible_thread # compatible thread is now able to squeak through
+
+ if use_upgrading
+ # The "primary" threads both each hold a share lock, and are
+ # mutually incompatible; they're still stuck.
+ assert_threads_stuck conflicting_exclusive_threads
+
+ # The thread without a specified purpose is also stuck; it's
+ # not compatible with anything.
+ assert_threads_stuck no_purpose_thread
+ else
+ # As the primaries didn't hold a share lock, as soon as the
+ # outer one was released, all the exclusive locks are free
+ # to be acquired in turn.
+
+ assert_threads_not_stuck conflicting_exclusive_threads
+ assert_threads_not_stuck no_purpose_thread
+ end
+ ensure
+ conflicting_exclusive_threads.each(&:kill)
+ no_purpose_thread.kill
+ end
+ end
+ end
+ end
+
+ def test_exclusive_ordering
+ scratch_pad = []
+ scratch_pad_mutex = Mutex.new
+
+ load_params = [:load, [:load]]
+ unload_params = [:unload, [:unload, :load]]
+
+ [load_params, load_params, unload_params, unload_params].permutation do |thread_params|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ threads = thread_params.map do |purpose, compatible|
+ Thread.new do
+ @lock.sharing do
+ @lock.exclusive(purpose: purpose, compatible: compatible) do
+ scratch_pad_mutex.synchronize { scratch_pad << purpose }
+ end
+ end
+ end
+ end
+
+ sleep(0.01)
+ scratch_pad_mutex.synchronize { assert_empty scratch_pad }
+
+ sharing_thread_release_latch.count_down
+
+ assert_threads_not_stuck threads
+ scratch_pad_mutex.synchronize do
+ assert_equal [:load, :load, :unload, :unload], scratch_pad
+ scratch_pad.clear
+ end
+ end
+ end
+ end
+
+ def test_in_shared_section_incompatible_non_upgrading_threads_cannot_preempt_upgrading_threads
+ scratch_pad = []
+ scratch_pad_mutex = Mutex.new
+
+ upgrading_load_params = [:load, [:load], true]
+ non_upgrading_unload_params = [:unload, [:load, :unload], false]
+
+ [upgrading_load_params, non_upgrading_unload_params].permutation do |thread_params|
+ with_thread_waiting_in_lock_section(:sharing) do |sharing_thread_release_latch|
+ threads = thread_params.map do |purpose, compatible, use_upgrading|
+ Thread.new do
+ @lock.send(use_upgrading ? :sharing : :tap) do
+ @lock.exclusive(purpose: purpose, compatible: compatible) do
+ scratch_pad_mutex.synchronize { scratch_pad << purpose }
+ end
+ end
+ end
+ end
+
+ assert_threads_stuck threads
+ scratch_pad_mutex.synchronize { assert_empty scratch_pad }
+
+ sharing_thread_release_latch.count_down
+
+ assert_threads_not_stuck threads
+ scratch_pad_mutex.synchronize do
+ assert_equal [:load, :unload], scratch_pad
+ scratch_pad.clear
+ end
+ end
+ end
+ end
+
+ private
+
+ module CustomAssertions
+ SUFFICIENT_TIMEOUT = 0.2
+
+ private
+
+ def assert_threads_stuck_but_releasable_by_latch(threads, latch)
+ assert_threads_stuck threads
+ latch.count_down
+ assert_threads_not_stuck threads
+ end
+
+ def assert_threads_stuck(threads)
+ sleep(SUFFICIENT_TIMEOUT) # give threads time to do their business
+ assert(Array(threads).all? { |t| t.join(0.001).nil? })
+ end
+
+ def assert_threads_not_stuck(threads)
+ assert(Array(threads).all? { |t| t.join(SUFFICIENT_TIMEOUT) })
+ end
+ end
+
+ class CustomAssertionsTest < ActiveSupport::TestCase
+ include CustomAssertions
+
+ def setup
+ @latch = Concurrent::CountDownLatch.new
+ @thread = Thread.new { @latch.wait }
+ end
+
+ def teardown
+ @latch.count_down
+ @thread.join
+ end
+
+ def test_happy_path
+ assert_threads_stuck_but_releasable_by_latch @thread, @latch
+ end
+
+ def test_detects_stuck_thread
+ assert_raises(Minitest::Assertion) do
+ assert_threads_not_stuck @thread
+ end
+ end
+
+ def test_detects_free_thread
+ @latch.count_down
+ assert_raises(Minitest::Assertion) do
+ assert_threads_stuck @thread
+ end
+ end
+
+ def test_detects_already_released
+ @latch.count_down
+ assert_raises(Minitest::Assertion) do
+ assert_threads_stuck_but_releasable_by_latch @thread, @latch
+ end
+ end
+
+ def test_detects_remains_latched
+ another_latch = Concurrent::CountDownLatch.new
+ assert_raises(Minitest::Assertion) do
+ assert_threads_stuck_but_releasable_by_latch @thread, another_latch
+ end
+ end
+ end
+
+ include CustomAssertions
+
+ def with_thread_waiting_in_lock_section(lock_section)
+ in_section = Concurrent::CountDownLatch.new
+ section_release = Concurrent::CountDownLatch.new
+
+ stuck_thread = Thread.new do
+ @lock.send(lock_section) do
+ in_section.count_down
+ section_release.wait
+ end
+ end
+
+ in_section.wait
+
+ yield section_release
+ ensure
+ section_release.count_down
+ stuck_thread.join # clean up
+ end
+end
diff --git a/activesupport/test/tagged_logging_test.rb b/activesupport/test/tagged_logging_test.rb
index 03a63e94e8..917fa46c96 100644
--- a/activesupport/test/tagged_logging_test.rb
+++ b/activesupport/test/tagged_logging_test.rb
@@ -72,11 +72,11 @@ class TaggedLoggingTest < ActiveSupport::TestCase
test "keeps each tag in their own thread" do
@logger.tagged("BCX") do
Thread.new do
- @logger.tagged("OMG") { @logger.info "Cool story bro" }
+ @logger.tagged("OMG") { @logger.info "Cool story" }
end.join
@logger.info "Funky time"
end
- assert_equal "[OMG] Cool story bro\n[BCX] Funky time\n", @output.string
+ assert_equal "[OMG] Cool story\n[BCX] Funky time\n", @output.string
end
test "keeps each tag in their own instance" do
@@ -84,11 +84,11 @@ class TaggedLoggingTest < ActiveSupport::TestCase
@other_logger = ActiveSupport::TaggedLogging.new(MyLogger.new(@other_output))
@logger.tagged("OMG") do
@other_logger.tagged("BCX") do
- @logger.info "Cool story bro"
+ @logger.info "Cool story"
@other_logger.info "Funky time"
end
end
- assert_equal "[OMG] Cool story bro\n", @output.string
+ assert_equal "[OMG] Cool story\n", @output.string
assert_equal "[BCX] Funky time\n", @other_output.string
end
@@ -97,11 +97,11 @@ class TaggedLoggingTest < ActiveSupport::TestCase
Thread.new do
@logger.tagged("OMG") do
@logger.flush
- @logger.info "Cool story bro"
+ @logger.info "Cool story"
end
end.join
end
- assert_equal "[FLUSHED]\nCool story bro\n", @output.string
+ assert_equal "[FLUSHED]\nCool story\n", @output.string
end
test "mixed levels of tagging" do
diff --git a/activesupport/test/test_case_test.rb b/activesupport/test/test_case_test.rb
index 9e6d1a91d0..18228a2ac5 100644
--- a/activesupport/test/test_case_test.rb
+++ b/activesupport/test/test_case_test.rb
@@ -56,6 +56,14 @@ class AssertDifferenceTest < ActiveSupport::TestCase
end
end
+ def test_assert_difference_retval
+ incremented = assert_difference '@object.num', +1 do
+ @object.increment
+ end
+
+ assert_equal incremented, 1
+ end
+
def test_assert_difference_with_implicit_difference
assert_difference '@object.num' do
@object.increment
diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb
new file mode 100644
index 0000000000..3e5ba7c079
--- /dev/null
+++ b/activesupport/test/testing/method_call_assertions_test.rb
@@ -0,0 +1,123 @@
+require 'abstract_unit'
+require 'active_support/testing/method_call_assertions'
+
+class MethodCallAssertionsTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::MethodCallAssertions
+
+ class Level
+ def increment; 1; end
+ def decrement; end
+ def <<(arg); end
+ end
+
+ setup do
+ @object = Level.new
+ end
+
+ def test_assert_called_with_defaults_to_expect_once
+ assert_called @object, :increment do
+ @object.increment
+ end
+ end
+
+ def test_assert_called_more_than_once
+ assert_called(@object, :increment, times: 2) do
+ @object.increment
+ @object.increment
+ end
+ end
+
+ def test_assert_called_method_with_arguments
+ assert_called(@object, :<<) do
+ @object << 2
+ end
+ end
+
+ def test_assert_called_returns
+ assert_called(@object, :increment, returns: 10) do
+ assert_equal 10, @object.increment
+ end
+ end
+
+ def test_assert_called_failure
+ error = assert_raises(Minitest::Assertion) do
+ assert_called(@object, :increment) do
+ # Call nothing...
+ end
+ end
+
+ assert_equal "Expected increment to be called 1 times, but was called 0 times.\nExpected: 1\n Actual: 0", error.message
+ end
+
+ def test_assert_called_with_message
+ error = assert_raises(Minitest::Assertion) do
+ assert_called(@object, :increment, 'dang it') do
+ # Call nothing...
+ end
+ end
+
+ assert_match(/dang it.\nExpected increment/, error.message)
+ end
+
+ def test_assert_called_with
+ assert_called_with(@object, :increment) do
+ @object.increment
+ end
+ end
+
+ def test_assert_called_with_arguments
+ assert_called_with(@object, :<<, [ 2 ]) do
+ @object << 2
+ end
+ end
+
+ def test_assert_called_with_failure
+ assert_raises(MockExpectationError) do
+ assert_called_with(@object, :<<, [ 4567 ]) do
+ @object << 2
+ end
+ end
+ end
+
+ def test_assert_called_with_returns
+ assert_called_with(@object, :increment, returns: 1) do
+ @object.increment
+ end
+ end
+
+ def test_assert_called_with_multiple_expected_arguments
+ assert_called_with(@object, :<<, [ [ 1 ], [ 2 ] ]) do
+ @object << 1
+ @object << 2
+ end
+ end
+
+ def test_assert_not_called
+ assert_not_called(@object, :decrement) do
+ @object.increment
+ end
+ end
+
+ def test_assert_not_called_failure
+ error = assert_raises(Minitest::Assertion) do
+ assert_not_called(@object, :increment) do
+ @object.increment
+ end
+ end
+
+ assert_equal "Expected increment to be called 0 times, but was called 1 times.\nExpected: 0\n Actual: 1", error.message
+ end
+
+ def test_stub_any_instance
+ stub_any_instance(Level) do |instance|
+ assert_equal instance, Level.new
+ end
+ end
+
+ def test_stub_any_instance_with_instance
+ stub_any_instance(Level, instance: @object) do |instance|
+ assert_equal @object, instance
+ assert_equal instance, Level.new
+ end
+ end
+end
diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb
index 869bc09991..59c3e52c2f 100644
--- a/activesupport/test/time_travel_test.rb
+++ b/activesupport/test/time_travel_test.rb
@@ -3,80 +3,88 @@ require 'active_support/core_ext/date_time'
require 'active_support/core_ext/numeric/time'
class TimeTravelTest < ActiveSupport::TestCase
- setup do
- Time.stubs now: Time.now
- end
-
teardown do
travel_back
end
def test_time_helper_travel
- expected_time = Time.now + 1.day
- travel 1.day
-
- assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
- assert_equal expected_time.to_date, Date.today
- assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
- end
-
- def test_time_helper_travel_with_block
- expected_time = Time.now + 1.day
+ Time.stub(:now, Time.now) do
+ expected_time = Time.now + 1.day
+ travel 1.day
- travel 1.day do
assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
assert_equal expected_time.to_date, Date.today
assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
end
-
- assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db)
- assert_not_equal expected_time.to_date, Date.today
- assert_not_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
end
- def test_time_helper_travel_to
- expected_time = Time.new(2004, 11, 24, 01, 04, 44)
- travel_to expected_time
-
- assert_equal expected_time, Time.now
- assert_equal Date.new(2004, 11, 24), Date.today
- assert_equal expected_time.to_datetime, DateTime.now
+ def test_time_helper_travel_with_block
+ Time.stub(:now, Time.now) do
+ expected_time = Time.now + 1.day
+
+ travel 1.day do
+ assert_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ assert_equal expected_time.to_date, Date.today
+ assert_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
+ end
+
+ assert_not_equal expected_time.to_s(:db), Time.now.to_s(:db)
+ assert_not_equal expected_time.to_date, Date.today
+ assert_not_equal expected_time.to_datetime.to_s(:db), DateTime.now.to_s(:db)
+ end
end
- def test_time_helper_travel_to_with_block
- expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ def test_time_helper_travel_to
+ Time.stub(:now, Time.now) do
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ travel_to expected_time
- travel_to expected_time do
assert_equal expected_time, Time.now
assert_equal Date.new(2004, 11, 24), Date.today
assert_equal expected_time.to_datetime, DateTime.now
end
+ end
- assert_not_equal expected_time, Time.now
- assert_not_equal Date.new(2004, 11, 24), Date.today
- assert_not_equal expected_time.to_datetime, DateTime.now
+ def test_time_helper_travel_to_with_block
+ Time.stub(:now, Time.now) do
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+
+ travel_to expected_time do
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
+ end
+
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ assert_not_equal expected_time.to_datetime, DateTime.now
+ end
end
def test_time_helper_travel_back
- expected_time = Time.new(2004, 11, 24, 01, 04, 44)
+ Time.stub(:now, Time.now) do
+ expected_time = Time.new(2004, 11, 24, 01, 04, 44)
- travel_to expected_time
- assert_equal expected_time, Time.now
- assert_equal Date.new(2004, 11, 24), Date.today
- assert_equal expected_time.to_datetime, DateTime.now
- travel_back
+ travel_to expected_time
+ assert_equal expected_time, Time.now
+ assert_equal Date.new(2004, 11, 24), Date.today
+ assert_equal expected_time.to_datetime, DateTime.now
+ travel_back
- assert_not_equal expected_time, Time.now
- assert_not_equal Date.new(2004, 11, 24), Date.today
- assert_not_equal expected_time.to_datetime, DateTime.now
+ assert_not_equal expected_time, Time.now
+ assert_not_equal Date.new(2004, 11, 24), Date.today
+ assert_not_equal expected_time.to_datetime, DateTime.now
+ end
end
def test_travel_to_will_reset_the_usec_to_avoid_mysql_rouding
- travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do
- assert_equal 50, Time.now.sec
- assert_equal 0, Time.now.usec
- assert_equal 50, DateTime.now.sec
- assert_equal 0, DateTime.now.usec
+ Time.stub(:now, Time.now) do
+ travel_to Time.utc(2014, 10, 10, 10, 10, 50, 999999) do
+ assert_equal 50, Time.now.sec
+ assert_equal 0, Time.now.usec
+ assert_equal 50, DateTime.now.sec
+ assert_equal 0, DateTime.now.usec
+ end
end
end
end
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 5e0474f449..00d40c4497 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'active_support/time'
require 'time_zone_test_helpers'
+require 'yaml'
class TimeZoneTest < ActiveSupport::TestCase
include TimeZoneTestHelpers
@@ -252,9 +253,10 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_parse_with_incomplete_date
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- zone.stubs(:now).returns zone.local(1999,12,31)
- twz = zone.parse('19:00:00')
- assert_equal Time.utc(1999,12,31,19), twz.time
+ zone.stub(:now, zone.local(1999,12,31)) do
+ twz = zone.parse('19:00:00')
+ assert_equal Time.utc(1999,12,31,19), twz.time
+ end
end
def test_parse_with_day_omitted
@@ -284,9 +286,10 @@ class TimeZoneTest < ActiveSupport::TestCase
def test_parse_with_missing_time_components
zone = ActiveSupport::TimeZone['Eastern Time (US & Canada)']
- zone.stubs(:now).returns zone.local(1999, 12, 31, 12, 59, 59)
- twz = zone.parse('2012-12-01')
- assert_equal Time.utc(2012, 12, 1), twz.time
+ zone.stub(:now, zone.local(1999, 12, 31, 12, 59, 59)) do
+ twz = zone.parse('2012-12-01')
+ assert_equal Time.utc(2012, 12, 1), twz.time
+ end
end
def test_parse_with_javascript_date
diff --git a/activesupport/test/xml_mini_test.rb b/activesupport/test/xml_mini_test.rb
index 0e4e7427d2..55e8181b54 100644
--- a/activesupport/test/xml_mini_test.rb
+++ b/activesupport/test/xml_mini_test.rb
@@ -3,6 +3,7 @@ require 'active_support/xml_mini'
require 'active_support/builder'
require 'active_support/core_ext/hash'
require 'active_support/core_ext/big_decimal'
+require 'yaml'
module XmlMiniTest
class RenameKeyTest < ActiveSupport::TestCase