aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
authorLeonel Galán <leonel@getstealz.com>2017-04-03 22:59:43 -0400
committerGitHub <noreply@github.com>2017-04-03 22:59:43 -0400
commit8648f289f01656b2258687126108c389127108e0 (patch)
tree1ccb15f72f00d8c30497ad4e48f203545dd3383b /activesupport
parent28b3582aa2cd6007c708134b911b3f360ad5d659 (diff)
parente8c33349bfabca28996ac74d344d69c7aaffec50 (diff)
downloadrails-8648f289f01656b2258687126108c389127108e0.tar.gz
rails-8648f289f01656b2258687126108c389127108e0.tar.bz2
rails-8648f289f01656b2258687126108c389127108e0.zip
Merge branch 'master' into bug/filtered_parameters_class
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md408
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb2
-rw-r--r--activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb8
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/calculations.rb18
-rw-r--r--activesupport/lib/active_support/core_ext/date_time/compatibility.rb12
-rw-r--r--activesupport/lib/active_support/core_ext/hash/reverse_merge.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb4
-rw-r--r--activesupport/lib/active_support/core_ext/module/delegation.rb2
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/object/with_options.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb32
-rw-r--r--activesupport/lib/active_support/core_ext/time/calculations.rb23
-rw-r--r--activesupport/lib/active_support/core_ext/time/compatibility.rb11
-rw-r--r--activesupport/lib/active_support/core_ext/time/conversions.rb3
-rw-r--r--activesupport/lib/active_support/deprecation.rb3
-rw-r--r--activesupport/lib/active_support/deprecation/constant_accessor.rb50
-rw-r--r--activesupport/lib/active_support/deprecation/method_wrappers.rb2
-rw-r--r--activesupport/lib/active_support/deprecation/reporting.rb6
-rw-r--r--activesupport/lib/active_support/duration.rb111
-rw-r--r--activesupport/lib/active_support/duration/iso8601_serializer.rb2
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb5
-rw-r--r--activesupport/lib/active_support/gem_version.rb2
-rw-r--r--activesupport/lib/active_support/gzip.rb2
-rw-r--r--activesupport/lib/active_support/hash_with_indifferent_access.rb6
-rw-r--r--activesupport/lib/active_support/i18n_railtie.rb2
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb40
-rw-r--r--activesupport/lib/active_support/message_encryptor.rb7
-rw-r--r--activesupport/lib/active_support/multibyte/chars.rb9
-rw-r--r--activesupport/lib/active_support/testing/autorun.rb4
-rw-r--r--activesupport/lib/active_support/time_with_zone.rb15
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb82
-rw-r--r--activesupport/lib/active_support/xml_mini/libxml.rb2
-rw-r--r--activesupport/test/caching_test.rb11
-rw-r--r--activesupport/test/core_ext/date_and_time_compatibility_test.rb166
-rw-r--r--activesupport/test/core_ext/date_time_ext_test.rb29
-rw-r--r--activesupport/test/core_ext/duration_test.rb157
-rw-r--r--activesupport/test/core_ext/hash_ext_test.rb679
-rw-r--r--activesupport/test/core_ext/marshal_test.rb13
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb5
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb13
-rw-r--r--activesupport/test/core_ext/time_ext_test.rb36
-rw-r--r--activesupport/test/core_ext/time_with_zone_test.rb40
-rw-r--r--activesupport/test/deprecation_test.rb23
-rw-r--r--activesupport/test/gzip_test.rb10
-rw-r--r--activesupport/test/hash_with_indifferent_access_test.rb734
-rw-r--r--activesupport/test/inflector_test.rb13
-rw-r--r--activesupport/test/inflector_test_cases.rb16
-rw-r--r--activesupport/test/json/encoding_test_cases.rb4
-rw-r--r--activesupport/test/time_zone_test.rb186
50 files changed, 1885 insertions, 1182 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 2fb7f29d73..6f99155199 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,407 +1 @@
-* Deprecate `.halt_callback_chains_on_return_false`.
-
- *Rafael Mendonça França*
-
-* Remove deprecated behavior that halts callbacks when the return is false.
-
- *Rafael Mendonça França*
-
-* Deprecate passing string to `:if` and `:unless` conditional options
- on `set_callback` and `skip_callback`.
-
- *Ryuta Kamizono*
-
-* Raise `ArgumentError` when passing string to define callback.
-
- *Ryuta Kamizono*
-
-* Updated Unicode version to 9.0.0
-
- Now we can handle new emojis such like "👩‍👩‍👧‍👦" ("\u{1F469}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466}").
-
- version 8.0.0
-
- "👩‍👩‍👧‍👦".mb_chars.grapheme_length # => 4
- "👩‍👩‍👧‍👦".mb_chars.reverse # => "👦👧‍👩‍👩‍"
-
- version 9.0.0
-
- "👩‍👩‍👧‍👦".mb_chars.grapheme_length # => 1
- "👩‍👩‍👧‍👦".mb_chars.reverse # => "👩‍👩‍👧‍👦"
-
- *Fumiaki MATSUSHIMA*
-
-* Changed `ActiveSupport::Inflector#transliterate` to raise `ArgumentError` when it receives
- anything except a string.
-
- *Kevin McPhillips*
-
-* Fixed bugs that `StringInquirer#respond_to_missing?` and
- `ArrayInquirer#respond_to_missing?` do not fallback to `super`.
-
- *Akira Matsuda*
-
-* Fix inconsistent results when parsing large durations and constructing durations from code
-
- ActiveSupport::Duration.parse('P3Y') == 3.years # It should be true
-
- Duration parsing made independent from any moment of time:
- Fixed length in seconds is assigned to each duration part during parsing.
-
- Changed duration of months and years in seconds to more accurate and logical:
-
- 1. The value of 365.2425 days in Gregorian year is more accurate
- as it accounts for every 400th non-leap year.
-
- 2. Month's length is bound to year's duration, which makes
- sensible comparisons like `12.months == 1.year` to be `true`
- and nonsensical ones like `30.days == 1.month` to be `false`.
-
- Calculations on times and dates with durations shouldn't be affected as
- duration's numeric value isn't used in calculations, only parts are used.
-
- Methods on `Numeric` like `2.days` now use these predefined durations
- to avoid duplicating of duration constants through the codebase and
- eliminate creation of intermediate durations.
-
- *Andrey Novikov, Andrew White*
-
-* Change return value of `Rational#duplicable?`, `ComplexClass#duplicable?`
- to false.
-
- *utilum*
-
-* Change return value of `NilClass#duplicable?`, `FalseClass#duplicable?`,
- `TrueClass#duplicable?`, `Symbol#duplicable?` and `Numeric#duplicable?`
- to true with Ruby 2.4+. These classes can dup with Ruby 2.4+.
-
- *Yuji Yaginuma*
-
-* Remove deprecated class `ActiveSupport::Concurrency::Latch`.
-
- *Andrew White*
-
-* Remove deprecated separator argument from `parameterize`.
-
- *Andrew White*
-
-* Remove deprecated method `Numeric#to_formatted_s`.
-
- *Andrew White*
-
-* Remove deprecated method `alias_method_chain`.
-
- *Andrew White*
-
-* Remove deprecated constant `MissingSourceFile`.
-
- *Andrew White*
-
-* Remove deprecated methods `Module.qualified_const_defined?`,
- `Module.qualified_const_get` and `Module.qualified_const_set`.
-
- *Andrew White*
-
-* Remove deprecated `:prefix` option from `number_to_human_size`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::HashWithIndifferentAccess.new_from_hash_copying_default`.
-
- *Andrew White*
-
-* Remove deprecated file `active_support/core_ext/time/marshal.rb`.
-
- *Andrew White*
-
-* Remove deprecated file `active_support/core_ext/struct.rb`.
-
- *Andrew White*
-
-* Remove deprecated file `active_support/core_ext/module/method_transplanting.rb`.
-
- *Andrew White*
-
-* Remove deprecated method `Module.local_constants`.
-
- *Andrew White*
-
-* Remove deprecated file `active_support/core_ext/kernel/debugger.rb`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::Cache::Store#namespaced_key`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::Cache::Strategy::LocalCache::LocalStore#set_cache_value`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::Cache::MemCacheStore#escape_key`.
-
- *Andrew White*
-
-* Remove deprecated method `ActiveSupport::Cache::FileStore#key_file_path`.
-
- *Andrew White*
-
-* Ensure duration parsing is consistent across DST changes.
-
- Previously `ActiveSupport::Duration.parse` used `Time.current` and
- `Time#advance` to calculate the number of seconds in the duration
- from an arbitrary collection of parts. However as `advance` tries to
- be consistent across DST boundaries this meant that either the
- duration was shorter or longer depending on the time of year.
-
- This was fixed by using an absolute reference point in UTC which
- isn't subject to DST transitions. An arbitrary date of Jan 1st, 2000
- was chosen for no other reason that it seemed appropriate.
-
- Additionally, duration parsing should now be marginally faster as we
- are no longer creating instances of `ActiveSupport::TimeWithZone`
- every time we parse a duration string.
-
- Fixes #26941.
-
- *Andrew White*
-
-* Use `Hash#compact` and `Hash#compact!` from Ruby 2.4. Old Ruby versions
- will continue to get these methods from Active Support as before.
-
- *Prathamesh Sonpatki*
-
-* Fix `ActiveSupport::TimeZone#strptime`.
- Support for timestamps in format of seconds (%s) and milliseconds (%Q).
-
- Fixes #26840.
-
- *Lev Denisov*
-
-* Fix `DateAndTime::Calculations#copy_time_to`. Copy `nsec` instead of `usec`.
-
- Jumping forward or backward between weeks now preserves nanosecond digits.
-
- *Josua Schmid*
-
-* Fix `ActiveSupport::TimeWithZone#in` across DST boundaries.
-
- Previously calls to `in` were being sent to the non-DST aware
- method `Time#since` via `method_missing`. It is now aliased to
- the DST aware `ActiveSupport::TimeWithZone#+` which handles
- transitions across DST boundaries, e.g:
-
- Time.zone = "US/Eastern"
-
- t = Time.zone.local(2016,11,6,1)
- # => Sun, 06 Nov 2016 01:00:00 EDT -05:00
-
- t.in(1.hour)
- # => Sun, 06 Nov 2016 01:00:00 EST -05:00
-
- Fixes #26580.
-
- *Thomas Balthazar*
-
-* Remove unused parameter `options = nil` for `#clear` of
- `ActiveSupport::Cache::Strategy::LocalCache::LocalStore` and
- `ActiveSupport::Cache::Strategy::LocalCache`.
-
- *Yosuke Kabuto*
-
-* Fix `thread_mattr_accessor` subclass no longer overwrites parent.
-
- Assigning a value to a subclass using `thread_mattr_accessor` no
- longer changes the value of the parent class. This brings the
- behavior inline with the documentation.
-
- Given:
-
- class Account
- thread_mattr_accessor :user
- end
-
- class Customer < Account
- end
-
- Account.user = "DHH"
- Customer.user = "Rafael"
-
- Before:
-
- Account.user # => "Rafael"
-
- After:
-
- Account.user # => "DHH"
-
- *Shinichi Maeshima*
-
-* Since weeks are no longer converted to days, add `:weeks` to the list of
- parts that `ActiveSupport::TimeWithZone` will recognize as possibly being
- of variable duration to take account of DST transitions.
-
- Fixes #26039.
-
- *Andrew White*
-
-* Defines `Regexp.match?` for Ruby versions prior to 2.4. The predicate
- has the same interface, but it does not have the performance boost. Its
- purpose is to be able to write 2.4 compatible code.
-
- *Xavier Noria*
-
-* Allow `MessageEncryptor` to take advantage of authenticated encryption modes.
-
- AEAD modes like `aes-256-gcm` provide both confidentiality and data
- authenticity, eliminating the need to use `MessageVerifier` to check if the
- encrypted data has been tampered with. This speeds up encryption/decryption
- and results in shorter cipher text.
-
- *Bart de Water*
-
-* Introduce `assert_changes` and `assert_no_changes`.
-
- `assert_changes` is a more general `assert_difference` that works with any
- value.
-
- assert_changes 'Error.current', from: nil, to: 'ERR' do
- expected_bad_operation
- end
-
- Can be called with strings, to be evaluated in the binding (context) of
- the block given to the assertion, or a lambda.
-
- assert_changes -> { Error.current }, from: nil, to: 'ERR' do
- expected_bad_operation
- end
-
- The `from` and `to` arguments are compared with the case operator (`===`).
-
- assert_changes 'Error.current', from: nil, to: Error do
- expected_bad_operation
- end
-
- This is pretty useful, if you need to loosely compare a value. For example,
- you need to test a token has been generated and it has that many random
- characters.
-
- user = User.start_registration
- assert_changes 'user.token', to: /\w{32}/ do
- user.finish_registration
- end
-
- *Genadi Samokovarov*
-
-* Fix `ActiveSupport::TimeZone#strptime`. Now raises `ArgumentError` when the
- given time doesn't match the format. The error is the same as the one given
- by Ruby's `Date.strptime`. Previously it raised
- `NoMethodError: undefined method empty? for nil:NilClass.` due to a bug.
-
- Fixes #25701.
-
- *John Gesimondo*
-
-* `travel/travel_to` travel time helpers, now raise on nested calls,
- as this can lead to confusing time stubbing.
-
- Instead of:
-
- travel_to 2.days.from_now do
- # 2 days from today
- travel_to 3.days.from_now do
- # 5 days from today
- end
- end
-
- preferred way to achieve above is:
-
- travel 2.days do
- # 2 days from today
- end
-
- travel 5.days do
- # 5 days from today
- end
-
- *Vipul A M*
-
-* Support parsing JSON time in ISO8601 local time strings in
- `ActiveSupport::JSON.decode` when `parse_json_times` is enabled.
- Strings in the format of `YYYY-MM-DD hh:mm:ss` (without a `Z` at
- the end) will be parsed in the local timezone (`Time.zone`). In
- addition, date strings (`YYYY-MM-DD`) are now parsed into `Date`
- objects.
-
- *Grzegorz Witek*
-
-* Fixed `ActiveSupport::Logger.broadcast` so that calls to `#silence` now
- properly delegate to all loggers. Silencing now properly suppresses logging
- to both the log and the console.
-
- *Kevin McPhillips*
-
-* Remove deprecated arguments in `assert_nothing_raised`.
-
- *Rafel Mendonça França*
-
-* `Date.to_s` doesn't produce too many spaces. For example, `to_s(:short)`
- will now produce `01 Feb` instead of ` 1 Feb`.
-
- Fixes #25251.
-
- *Sean Griffin*
-
-* Introduce `Module#delegate_missing_to`.
-
- When building a decorator, a common pattern emerges:
-
- class Partition
- def initialize(first_event)
- @events = [ first_event ]
- end
-
- def people
- if @events.first.detail.people.any?
- @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- else
- @events.collect(&:creator).uniq
- end
- end
-
- private
- def respond_to_missing?(name, include_private = false)
- @events.respond_to?(name, include_private)
- end
-
- def method_missing(method, *args, &block)
- @events.send(method, *args, &block)
- end
- end
-
- With `Module#delegate_missing_to`, the above is condensed to:
-
- class Partition
- delegate_missing_to :@events
-
- def initialize(first_event)
- @events = [ first_event ]
- end
-
- def people
- if @events.first.detail.people.any?
- @events.collect { |e| Array(e.detail.people) }.flatten.uniq
- else
- @events.collect(&:creator).uniq
- end
- end
- end
-
- *Genadi Samokovarov*, *DHH*
-
-* Rescuable: If a handler doesn't match the exception, check for handlers
- matching the exception's cause.
-
- *Jeremy Daer*
-
-Please check [5-0-stable](https://github.com/rails/rails/blob/5-0-stable/activesupport/CHANGELOG.md) for previous changes.
+Please check [5-1-stable](https://github.com/rails/rails/blob/5-1-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index fea072d91c..56fe1457d0 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -28,6 +28,7 @@ module ActiveSupport
@pruning = false
end
+ # Delete all data stored in a given cache store.
def clear(options = nil)
synchronize do
@data.clear
@@ -83,6 +84,7 @@ module ActiveSupport
modify_value(name, -amount, options)
end
+ # Deletes cache entries if the cache key matches a given pattern.
def delete_matched(matcher, options = nil)
options = merged_options(options)
instrument(:delete_matched, matcher.inspect) do
diff --git a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
index 174cb72b1e..4c3679e4bf 100644
--- a/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
+++ b/activesupport/lib/active_support/cache/strategy/local_cache_middleware.rb
@@ -28,13 +28,13 @@ module ActiveSupport
response[2] = ::Rack::BodyProxy.new(response[2]) do
LocalCacheRegistry.set_cache_for(local_cache_key, nil)
end
+ cleanup_on_body_close = true
response
rescue Rack::Utils::InvalidParameterError
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
[400, {}, []]
- rescue Exception
- LocalCacheRegistry.set_cache_for(local_cache_key, nil)
- raise
+ ensure
+ LocalCacheRegistry.set_cache_for(local_cache_key, nil) unless
+ cleanup_on_body_close
end
end
end
diff --git a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
index db95ae0db5..ab80392460 100644
--- a/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_and_time/compatibility.rb
@@ -10,13 +10,5 @@ module DateAndTime
# this behavior, but new apps will have an initializer that sets
# this to true, because the new behavior is preferred.
mattr_accessor(:preserve_timezone, instance_writer: false) { false }
-
- def to_time
- if preserve_timezone
- @_to_time_with_instance_offset ||= getlocal(utc_offset)
- else
- @_to_time_with_system_offset ||= getlocal
- end
- end
end
end
diff --git a/activesupport/lib/active_support/core_ext/date_time/calculations.rb b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
index 70d5c9af8e..7a9eb8c266 100644
--- a/activesupport/lib/active_support/core_ext/date_time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/calculations.rb
@@ -47,13 +47,23 @@ class DateTime
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, day: 1) # => DateTime.new(1981, 8, 1, 22, 35, 0)
# DateTime.new(2012, 8, 29, 22, 35, 0).change(year: 1981, hour: 0) # => DateTime.new(1981, 8, 29, 0, 0, 0)
def change(options)
+ if new_nsec = options[:nsec]
+ raise ArgumentError, "Can't change both :nsec and :usec at the same time: #{options.inspect}" if options[:usec]
+ new_fraction = Rational(new_nsec, 1000000000)
+ else
+ new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
+ new_fraction = Rational(new_usec, 1000000)
+ end
+
+ raise ArgumentError, "argument out of range" if new_fraction >= 1
+
::DateTime.civil(
options.fetch(:year, year),
options.fetch(:month, month),
options.fetch(:day, day),
options.fetch(:hour, hour),
options.fetch(:min, options[:hour] ? 0 : min),
- options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec + sec_fraction),
+ options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec) + new_fraction,
options.fetch(:offset, offset),
options.fetch(:start, start)
)
@@ -122,7 +132,7 @@ class DateTime
# Returns a new DateTime representing the end of the day (23:59:59).
def end_of_day
- change(hour: 23, min: 59, sec: 59)
+ change(hour: 23, min: 59, sec: 59, usec: Rational(999999999, 1000))
end
alias :at_end_of_day :end_of_day
@@ -134,7 +144,7 @@ class DateTime
# Returns a new DateTime representing the end of the hour (hh:59:59).
def end_of_hour
- change(min: 59, sec: 59)
+ change(min: 59, sec: 59, usec: Rational(999999999, 1000))
end
alias :at_end_of_hour :end_of_hour
@@ -146,7 +156,7 @@ class DateTime
# Returns a new DateTime representing the end of the minute (hh:mm:59).
def end_of_minute
- change(sec: 59)
+ change(sec: 59, usec: Rational(999999999, 1000))
end
alias :at_end_of_minute :end_of_minute
diff --git a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
index 30bb7f4a60..eb8b8b2c65 100644
--- a/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/date_time/compatibility.rb
@@ -1,5 +1,15 @@
require "active_support/core_ext/date_and_time/compatibility"
class DateTime
- prepend DateAndTime::Compatibility
+ include DateAndTime::Compatibility
+
+ remove_possible_method :to_time
+
+ # Either return an instance of `Time` with the same UTC offset
+ # as +self+ or an instance of `Time` representing the same time
+ # in the the local system timezone depending on the setting of
+ # on the setting of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ preserve_timezone ? getlocal(utc_offset) : getlocal
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
index efb9d1b8a0..061c959442 100644
--- a/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
+++ b/activesupport/lib/active_support/core_ext/hash/reverse_merge.rb
@@ -12,6 +12,7 @@ class Hash
def reverse_merge(other_hash)
other_hash.merge(self)
end
+ alias_method :with_defaults, :reverse_merge
# Destructive +reverse_merge+.
def reverse_merge!(other_hash)
@@ -19,4 +20,5 @@ class Hash
merge!(other_hash) { |key, left, right| left }
end
alias_method :reverse_update, :reverse_merge!
+ alias_method :with_defaults!, :reverse_merge!
end
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
index edfc8296fe..bba2b3be2e 100644
--- a/activesupport/lib/active_support/core_ext/marshal.rb
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -1,7 +1,7 @@
module ActiveSupport
module MarshalWithAutoloading # :nodoc:
- def load(source)
- super(source)
+ def load(source, proc = nil)
+ super(source, proc)
rescue ArgumentError, NameError => exc
if exc.message.match(%r|undefined class/module (.+?)(?:::)?\z|)
# try loading the class/module
diff --git a/activesupport/lib/active_support/core_ext/module/delegation.rb b/activesupport/lib/active_support/core_ext/module/delegation.rb
index d82758e40d..cdf27f49ad 100644
--- a/activesupport/lib/active_support/core_ext/module/delegation.rb
+++ b/activesupport/lib/active_support/core_ext/module/delegation.rb
@@ -260,7 +260,7 @@ class Module
# end
#
# The target can be anything callable within the object. E.g. instance
- # variables, methods, constants ant the likes.
+ # variables, methods, constants and the likes.
def delegate_missing_to(target)
target = target.to_s
target = "self.#{target}" if DELEGATION_RESERVED_METHOD_NAMES.include?(target)
diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb
index ea81df2bd8..b028df97ee 100644
--- a/activesupport/lib/active_support/core_ext/object/duplicable.rb
+++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb
@@ -106,8 +106,8 @@ require "bigdecimal"
class BigDecimal
# BigDecimals are duplicable:
#
- # BigDecimal.new("1.2").duplicable? # => true
- # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
+ # BigDecimal.new("1.2").duplicable? # => true
+ # BigDecimal.new("1.2").dup # => #<BigDecimal:...,'0.12E1',18(18)>
def duplicable?
true
end
@@ -124,21 +124,31 @@ class Method
end
class Complex
- # Complexes are not duplicable:
- #
- # Complex(1).duplicable? # => false
- # Complex(1).dup # => TypeError: can't copy Complex
- def duplicable?
- false
+ begin
+ Complex(1).dup
+ rescue TypeError
+
+ # Complexes are not duplicable:
+ #
+ # Complex(1).duplicable? # => false
+ # Complex(1).dup # => TypeError: can't copy Complex
+ def duplicable?
+ false
+ end
end
end
class Rational
- # Rationals are not duplicable:
- #
- # Rational(1).duplicable? # => false
- # Rational(1).dup # => TypeError: can't copy Rational
- def duplicable?
- false
+ begin
+ Rational(1).dup
+ rescue TypeError
+
+ # Rationals are not duplicable:
+ #
+ # Rational(1).duplicable? # => false
+ # Rational(1).dup # => TypeError: can't copy Rational
+ def duplicable?
+ false
+ end
end
end
diff --git a/activesupport/lib/active_support/core_ext/object/with_options.rb b/activesupport/lib/active_support/core_ext/object/with_options.rb
index cf39b1d312..3a44e08630 100644
--- a/activesupport/lib/active_support/core_ext/object/with_options.rb
+++ b/activesupport/lib/active_support/core_ext/object/with_options.rb
@@ -62,6 +62,17 @@ class Object
#
# Hence the inherited default for `if` key is ignored.
#
+ # NOTE: You cannot call class methods implicitly inside of with_options.
+ # You can access these methods using the class name instead:
+ #
+ # class Phone < ActiveRecord::Base
+ # enum phone_number_type: [home: 0, office: 1, mobile: 2]
+ #
+ # with_options presence: true do
+ # validates :phone_number_type, inclusion: { in: Phone.phone_number_types.keys }
+ # end
+ # end
+ #
def with_options(options, &block)
option_merger = ActiveSupport::OptionMerger.new(self, options)
block.arity.zero? ? option_merger.instance_eval(&block) : block.call(option_merger)
diff --git a/activesupport/lib/active_support/core_ext/string/inflections.rb b/activesupport/lib/active_support/core_ext/string/inflections.rb
index 7a2fc5c1b5..b27cfe53f4 100644
--- a/activesupport/lib/active_support/core_ext/string/inflections.rb
+++ b/activesupport/lib/active_support/core_ext/string/inflections.rb
@@ -100,12 +100,17 @@ class String
# a nicer looking title. +titleize+ is meant for creating pretty output. It is not
# used in the Rails internals.
#
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
# +titleize+ is also aliased as +titlecase+.
#
- # 'man from the boondocks'.titleize # => "Man From The Boondocks"
- # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
- def titleize
- ActiveSupport::Inflector.titleize(self)
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
+ # 'string_ending_with_id'.titleize(keep_id_suffix: true) # => "String Ending With Id"
+ def titleize(keep_id_suffix: false)
+ ActiveSupport::Inflector.titleize(self, keep_id_suffix: keep_id_suffix)
end
alias_method :titlecase, :titleize
@@ -202,7 +207,7 @@ class String
ActiveSupport::Inflector.classify(self)
end
- # Capitalizes the first word, turns underscores into spaces, and strips a
+ # Capitalizes the first word, turns underscores into spaces, and (by default)strips a
# trailing '_id' if present.
# Like +titleize+, this is meant for creating pretty output.
#
@@ -210,12 +215,17 @@ class String
# optional parameter +capitalize+ to false.
# By default, this parameter is true.
#
- # 'employee_salary'.humanize # => "Employee salary"
- # 'author_id'.humanize # => "Author"
- # 'author_id'.humanize(capitalize: false) # => "author"
- # '_id'.humanize # => "Id"
- def humanize(options = {})
- ActiveSupport::Inflector.humanize(self, options)
+ # The trailing '_id' can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
+ # 'employee_salary'.humanize # => "Employee salary"
+ # 'author_id'.humanize # => "Author"
+ # 'author_id'.humanize(capitalize: false) # => "author"
+ # '_id'.humanize # => "Id"
+ # 'author_id'.humanize(keep_id_suffix: true) # => "Author Id"
+ def humanize(capitalize: true, keep_id_suffix: false)
+ ActiveSupport::Inflector.humanize(self, capitalize: capitalize, keep_id_suffix: keep_id_suffix)
end
# Converts just the first character to uppercase.
diff --git a/activesupport/lib/active_support/core_ext/time/calculations.rb b/activesupport/lib/active_support/core_ext/time/calculations.rb
index cbdcb86d6d..7b7aeef25a 100644
--- a/activesupport/lib/active_support/core_ext/time/calculations.rb
+++ b/activesupport/lib/active_support/core_ext/time/calculations.rb
@@ -53,6 +53,29 @@ class Time
end
alias_method :at_without_coercion, :at
alias_method :at, :at_with_coercion
+
+ # Creates a +Time+ instance from an RFC 3339 string.
+ #
+ # Time.rfc3339('1999-12-31T14:00:00-10:00') # => 2000-01-01 00:00:00 -1000
+ #
+ # If the time or offset components are missing then an +ArgumentError+ will be raised.
+ #
+ # Time.rfc3339('1999-12-31') # => ArgumentError: invalid date
+ def rfc3339(str)
+ parts = Date._rfc3339(str)
+
+ raise ArgumentError, "invalid date" if parts.empty?
+
+ Time.new(
+ parts.fetch(:year),
+ parts.fetch(:mon),
+ parts.fetch(:mday),
+ parts.fetch(:hour),
+ parts.fetch(:min),
+ parts.fetch(:sec) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset)
+ )
+ end
end
# Returns the number of seconds since 00:00:00.
diff --git a/activesupport/lib/active_support/core_ext/time/compatibility.rb b/activesupport/lib/active_support/core_ext/time/compatibility.rb
index ca4b9574d5..45e86b77ce 100644
--- a/activesupport/lib/active_support/core_ext/time/compatibility.rb
+++ b/activesupport/lib/active_support/core_ext/time/compatibility.rb
@@ -1,5 +1,14 @@
require "active_support/core_ext/date_and_time/compatibility"
+require "active_support/core_ext/module/remove_method"
class Time
- prepend DateAndTime::Compatibility
+ include DateAndTime::Compatibility
+
+ remove_possible_method :to_time
+
+ # Either return +self+ or the time in the local system timezone depending
+ # on the setting of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ preserve_timezone ? self : getlocal
+ end
end
diff --git a/activesupport/lib/active_support/core_ext/time/conversions.rb b/activesupport/lib/active_support/core_ext/time/conversions.rb
index f2bbe55aa6..595bda6b4f 100644
--- a/activesupport/lib/active_support/core_ext/time/conversions.rb
+++ b/activesupport/lib/active_support/core_ext/time/conversions.rb
@@ -64,4 +64,7 @@ class Time
def formatted_offset(colon = true, alternate_utc_string = nil)
utc? && alternate_utc_string || ActiveSupport::TimeZone.seconds_to_utc_offset(utc_offset, colon)
end
+
+ # Aliased to +xmlschema+ for compatibility with +DateTime+
+ alias_method :rfc3339, :xmlschema
end
diff --git a/activesupport/lib/active_support/deprecation.rb b/activesupport/lib/active_support/deprecation.rb
index 191e582de8..72c74e966a 100644
--- a/activesupport/lib/active_support/deprecation.rb
+++ b/activesupport/lib/active_support/deprecation.rb
@@ -15,6 +15,7 @@ module ActiveSupport
require "active_support/deprecation/instance_delegator"
require "active_support/deprecation/behaviors"
require "active_support/deprecation/reporting"
+ require "active_support/deprecation/constant_accessor"
require "active_support/deprecation/method_wrappers"
require "active_support/deprecation/proxy_wrappers"
require "active_support/core_ext/module/deprecation"
@@ -32,7 +33,7 @@ module ActiveSupport
# and the second is a library name
#
# ActiveSupport::Deprecation.new('2.0', 'MyLibrary')
- def initialize(deprecation_horizon = "5.2", gem_name = "Rails")
+ def initialize(deprecation_horizon = "5.3", 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/constant_accessor.rb b/activesupport/lib/active_support/deprecation/constant_accessor.rb
new file mode 100644
index 0000000000..2b19de365f
--- /dev/null
+++ b/activesupport/lib/active_support/deprecation/constant_accessor.rb
@@ -0,0 +1,50 @@
+require "active_support/inflector/methods"
+
+module ActiveSupport
+ class Deprecation
+ # DeprecatedConstantAccessor transforms a constant into a deprecated one by
+ # hooking +const_missing+.
+ #
+ # It takes the names of an old (deprecated) constant and of a new constant
+ # (both in string form) and optionally a deprecator. The deprecator defaults
+ # to +ActiveSupport::Deprecator+ if none is specified.
+ #
+ # The deprecated constant now returns the same object as the new one rather
+ # than a proxy object, so it can be used transparently in +rescue+ blocks
+ # etc.
+ #
+ # PLANETS = %w(mercury venus earth mars jupiter saturn uranus neptune pluto)
+ #
+ # (In a later update, the original implementation of `PLANETS` has been removed.)
+ #
+ # PLANETS_POST_2006 = %w(mercury venus earth mars jupiter saturn uranus neptune)
+ # include ActiveSupport::Deprecation::DeprecatedConstantAccessor
+ # deprecate_constant 'PLANETS', 'PLANETS_POST_2006'
+ #
+ # PLANETS.map { |planet| planet.capitalize }
+ # # => DEPRECATION WARNING: PLANETS is deprecated! Use PLANETS_POST_2006 instead.
+ # (Backtrace information…)
+ # ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
+ module DeprecatedConstantAccessor
+ def self.included(base)
+ extension = Module.new do
+ def const_missing(missing_const_name)
+ if class_variable_defined?(:@@_deprecated_constants)
+ if (replacement = class_variable_get(:@@_deprecated_constants)[missing_const_name.to_s])
+ replacement[:deprecator].warn(replacement[:message] || "#{name}::#{missing_const_name} is deprecated! Use #{replacement[:new]} instead.", caller_locations)
+ return ActiveSupport::Inflector.constantize(replacement[:new].to_s)
+ end
+ end
+ super
+ end
+
+ def deprecate_constant(const_name, new_constant, message: nil, deprecator: ActiveSupport::Deprecation.instance)
+ class_variable_set(:@@_deprecated_constants, {}) unless class_variable_defined?(:@@_deprecated_constants)
+ class_variable_get(:@@_deprecated_constants)[const_name.to_s] = { new: new_constant, message: message, deprecator: deprecator }
+ end
+ end
+ base.singleton_class.prepend extension
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb
index 7655fa4f99..930d71e8d2 100644
--- a/activesupport/lib/active_support/deprecation/method_wrappers.rb
+++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb
@@ -18,7 +18,7 @@ module ActiveSupport
#
# Using the default deprecator:
# ActiveSupport::Deprecation.deprecate_methods(Fred, :aaa, bbb: :zzz, ccc: 'use Bar#ccc instead')
- # # => [:aaa, :bbb, :ccc]
+ # # => Fred
#
# Fred.aaa
# # DEPRECATION WARNING: aaa is deprecated and will be removed from Rails 5.1. (called from irb_binding at (irb):10)
diff --git a/activesupport/lib/active_support/deprecation/reporting.rb b/activesupport/lib/active_support/deprecation/reporting.rb
index b8d200ba94..58c5c50e30 100644
--- a/activesupport/lib/active_support/deprecation/reporting.rb
+++ b/activesupport/lib/active_support/deprecation/reporting.rb
@@ -48,11 +48,11 @@ module ActiveSupport
private
# Outputs a deprecation warning message
#
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name)
+ # deprecated_method_warning(:method_name)
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon}"
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, :another_method)
+ # deprecated_method_warning(:method_name, :another_method)
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (use another_method instead)"
- # ActiveSupport::Deprecation.deprecated_method_warning(:method_name, "Optional message")
+ # deprecated_method_warning(:method_name, "Optional message")
# # => "method_name is deprecated and will be removed from Rails #{deprecation_horizon} (Optional message)"
def deprecated_method_warning(method_name, message = nil)
warning = "#{method_name} is deprecated and will be removed from #{gem_name} #{deprecation_horizon}"
diff --git a/activesupport/lib/active_support/duration.rb b/activesupport/lib/active_support/duration.rb
index 003f6203ef..99080e34a1 100644
--- a/activesupport/lib/active_support/duration.rb
+++ b/activesupport/lib/active_support/duration.rb
@@ -1,5 +1,8 @@
require "active_support/core_ext/array/conversions"
+require "active_support/core_ext/module/delegation"
require "active_support/core_ext/object/acts_like"
+require "active_support/core_ext/string/filters"
+require "active_support/deprecation"
module ActiveSupport
# Provides accurate date and time measurements using Date#advance and
@@ -7,6 +10,66 @@ module ActiveSupport
#
# 1.month.ago # equivalent to Time.now.advance(months: -1)
class Duration
+ class Scalar < Numeric #:nodoc:
+ attr_reader :value
+ delegate :to_i, :to_f, :to_s, to: :value
+
+ def initialize(value)
+ @value = value
+ end
+
+ def coerce(other)
+ [Scalar.new(other), self]
+ end
+
+ def -@
+ Scalar.new(-value)
+ end
+
+ def <=>(other)
+ if Scalar === other || Duration === other
+ value <=> other.value
+ elsif Numeric === other
+ value <=> other
+ else
+ nil
+ end
+ end
+
+ def +(other)
+ calculate(:+, other)
+ end
+
+ def -(other)
+ calculate(:-, other)
+ end
+
+ def *(other)
+ calculate(:*, other)
+ end
+
+ def /(other)
+ calculate(:/, other)
+ end
+
+ private
+ def calculate(op, other)
+ if Scalar === other
+ Scalar.new(value.public_send(op, other.value))
+ elsif Duration === other
+ Duration.seconds(value).public_send(op, other)
+ elsif Numeric === other
+ Scalar.new(value.public_send(op, other))
+ else
+ raise_type_error(other)
+ end
+ end
+
+ def raise_type_error(other)
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
+ end
+ end
+
SECONDS_PER_MINUTE = 60
SECONDS_PER_HOUR = 3600
SECONDS_PER_DAY = 86400
@@ -88,6 +151,24 @@ module ActiveSupport
@parts.default = 0
end
+ def coerce(other) #:nodoc:
+ if Scalar === other
+ [other, self]
+ else
+ [Scalar.new(other), self]
+ end
+ end
+
+ # Compares one Duration with another or a Numeric to this Duration.
+ # Numeric values are treated as seconds.
+ def <=>(other)
+ if Duration === other
+ value <=> other.value
+ elsif Numeric === other
+ value <=> other
+ end
+ end
+
# Adds another Duration or a Numeric to this Duration. Numeric values
# are treated as seconds.
def +(other)
@@ -109,6 +190,28 @@ module ActiveSupport
self + (-other)
end
+ # Multiplies this Duration by a Numeric and returns a new Duration.
+ def *(other)
+ if Scalar === other || Duration === other
+ Duration.new(value * other.value, parts.map { |type, number| [type, number * other.value] })
+ elsif Numeric === other
+ Duration.new(value * other, parts.map { |type, number| [type, number * other] })
+ else
+ raise_type_error(other)
+ end
+ end
+
+ # Divides this Duration by a Numeric and returns a new Duration.
+ def /(other)
+ if Scalar === other || Duration === other
+ Duration.new(value / other.value, parts.map { |type, number| [type, number / other.value] })
+ elsif Numeric === other
+ Duration.new(value / other, parts.map { |type, number| [type, number / other] })
+ else
+ raise_type_error(other)
+ end
+ end
+
def -@ #:nodoc:
Duration.new(-value, parts.map { |type, number| [type, -number] })
end
@@ -180,6 +283,7 @@ module ActiveSupport
sum(1, time)
end
alias :from_now :since
+ alias :after :since
# Calculates a new Time or Date that is as far in the past
# as this Duration represents.
@@ -187,6 +291,7 @@ module ActiveSupport
sum(-1, time)
end
alias :until :ago
+ alias :before :ago
def inspect #:nodoc:
parts.
@@ -210,8 +315,6 @@ module ActiveSupport
ISO8601Serializer.new(self, precision: precision).serialize
end
- delegate :<=>, to: :value
-
private
def sum(sign, time = ::Time.current)
@@ -235,5 +338,9 @@ module ActiveSupport
def method_missing(method, *args, &block)
value.send(method, *args, &block)
end
+
+ def raise_type_error(other)
+ raise TypeError, "no implicit conversion of #{other.class} into #{self.class}"
+ end
end
end
diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb
index 51d53e2f8d..e5d458b3ab 100644
--- a/activesupport/lib/active_support/duration/iso8601_serializer.rb
+++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb
@@ -4,7 +4,7 @@ require "active_support/core_ext/hash/transform_values"
module ActiveSupport
class Duration
# Serializes duration to string according to ISO 8601 Duration format.
- class ISO8601Serializer
+ class ISO8601Serializer # :nodoc:
def initialize(duration, precision: nil)
@duration = duration
@precision = precision
diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index 8e0dc71dca..f59f5d17dc 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -125,6 +125,11 @@ module ActiveSupport
dtw.compact!
dtw.uniq!
+ normalized_gem_paths = Gem.path.map { |path| File.join path, "" }
+ dtw = dtw.reject do |path|
+ normalized_gem_paths.any? { |gem_path| path.to_s.start_with?(gem_path) }
+ end
+
@ph.filter_out_descendants(dtw)
end
diff --git a/activesupport/lib/active_support/gem_version.rb b/activesupport/lib/active_support/gem_version.rb
index 74f2d8dd4b..371a39a5e6 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -6,7 +6,7 @@ module ActiveSupport
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
PRE = "alpha"
diff --git a/activesupport/lib/active_support/gzip.rb b/activesupport/lib/active_support/gzip.rb
index 84eef6a623..95a86889ec 100644
--- a/activesupport/lib/active_support/gzip.rb
+++ b/activesupport/lib/active_support/gzip.rb
@@ -21,7 +21,7 @@ module ActiveSupport
# Decompresses a gzipped string.
def self.decompress(source)
- Zlib::GzipReader.new(StringIO.new(source)).read
+ Zlib::GzipReader.wrap(StringIO.new(source), &:read)
end
# Compresses a string using gzip.
diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb
index 79e7feaf47..1e1d6d3aac 100644
--- a/activesupport/lib/active_support/hash_with_indifferent_access.rb
+++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb
@@ -225,11 +225,13 @@ module ActiveSupport
def reverse_merge(other_hash)
super(self.class.new(other_hash))
end
+ alias_method :with_defaults, :reverse_merge
# Same semantics as +reverse_merge+ but modifies the receiver in-place.
def reverse_merge!(other_hash)
replace(reverse_merge(other_hash))
end
+ alias_method :with_defaults!, :reverse_merge!
# Replaces the contents of this hash with other_hash.
#
@@ -270,7 +272,7 @@ module ActiveSupport
end
def compact
- dup.compact!
+ dup.tap(&:compact!)
end
# Convert to a regular hash with string keys.
@@ -316,4 +318,6 @@ module ActiveSupport
end
end
+# :stopdoc:
+
HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
diff --git a/activesupport/lib/active_support/i18n_railtie.rb b/activesupport/lib/active_support/i18n_railtie.rb
index b94368df14..b749913ee9 100644
--- a/activesupport/lib/active_support/i18n_railtie.rb
+++ b/activesupport/lib/active_support/i18n_railtie.rb
@@ -2,6 +2,8 @@ require "active_support"
require "active_support/file_update_checker"
require "active_support/core_ext/array/wrap"
+# :enddoc:
+
module I18n
class Railtie < Rails::Railtie
config.i18n = ActiveSupport::OrderedOptions.new
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 8ccb735c6d..1b089a7538 100644
--- a/activesupport/lib/active_support/inflector/methods.rb
+++ b/activesupport/lib/active_support/inflector/methods.rb
@@ -108,33 +108,38 @@ module ActiveSupport
# * Replaces underscores with spaces, if any.
# * Downcases all words except acronyms.
# * Capitalizes the first word.
- #
# The capitalization of the first word can be turned off by setting the
# +:capitalize+ option to false (default is true).
#
- # humanize('employee_salary') # => "Employee salary"
- # humanize('author_id') # => "Author"
- # humanize('author_id', capitalize: false) # => "author"
- # humanize('_id') # => "Id"
+ # The trailing '_id' can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true (default is false).
+ #
+ # humanize('employee_salary') # => "Employee salary"
+ # humanize('author_id') # => "Author"
+ # humanize('author_id', capitalize: false) # => "author"
+ # humanize('_id') # => "Id"
+ # humanize('author_id', keep_id_suffix: true) # => "Author Id"
#
# If "SSL" was defined to be an acronym:
#
# humanize('ssl_error') # => "SSL error"
#
- def humanize(lower_case_and_underscored_word, options = {})
+ def humanize(lower_case_and_underscored_word, capitalize: true, keep_id_suffix: false)
result = lower_case_and_underscored_word.to_s.dup
inflections.humans.each { |(rule, replacement)| break if result.sub!(rule, replacement) }
result.sub!(/\A_+/, "".freeze)
- result.sub!(/_id\z/, "".freeze)
+ unless keep_id_suffix
+ result.sub!(/_id\z/, "".freeze)
+ end
result.tr!("_".freeze, " ".freeze)
result.gsub!(/([a-z\d]*)/i) do |match|
"#{inflections.acronyms[match] || match.downcase}"
end
- if options.fetch(:capitalize, true)
+ if capitalize
result.sub!(/\A\w/) { |match| match.upcase }
end
@@ -154,14 +159,21 @@ module ActiveSupport
# create a nicer looking title. +titleize+ is meant for creating pretty
# output. It is not used in the Rails internals.
#
+ # The trailing '_id','Id'.. can be kept and capitalized by setting the
+ # optional parameter +keep_id_suffix+ to true.
+ # By default, this parameter is false.
+ #
# +titleize+ is also aliased as +titlecase+.
#
- # titleize('man from the boondocks') # => "Man From The Boondocks"
- # titleize('x-men: the last stand') # => "X Men: The Last Stand"
- # titleize('TheManWithoutAPast') # => "The Man Without A Past"
- # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
- def titleize(word)
- humanize(underscore(word)).gsub(/\b(?<!['’`])[a-z]/) { |match| match.capitalize }
+ # titleize('man from the boondocks') # => "Man From The Boondocks"
+ # titleize('x-men: the last stand') # => "X Men: The Last Stand"
+ # titleize('TheManWithoutAPast') # => "The Man Without A Past"
+ # titleize('raiders_of_the_lost_ark') # => "Raiders Of The Lost Ark"
+ # titleize('string_ending_with_id', keep_id_suffix: true) # => "String Ending With Id"
+ def titleize(word, keep_id_suffix: false)
+ humanize(underscore(word), keep_id_suffix: keep_id_suffix).gsub(/\b(?<!\w['’`])[a-z]/) do |match|
+ match.capitalize
+ end
end
# Creates the name of a table like Rails does for models to table names.
diff --git a/activesupport/lib/active_support/message_encryptor.rb b/activesupport/lib/active_support/message_encryptor.rb
index 0671469788..24053b4fe5 100644
--- a/activesupport/lib/active_support/message_encryptor.rb
+++ b/activesupport/lib/active_support/message_encryptor.rb
@@ -50,6 +50,11 @@ module ActiveSupport
# key by using <tt>ActiveSupport::KeyGenerator</tt> or a similar key
# derivation function.
#
+ # First additional parameter is used as the signature key for +MessageVerifier+.
+ # This allows you to specify keys to encrypt and sign data.
+ #
+ # ActiveSupport::MessageEncryptor.new('secret', 'signature_secret')
+ #
# Options:
# * <tt>:cipher</tt> - Cipher to use. Can be any cipher returned by
# <tt>OpenSSL::Cipher.ciphers</tt>. Default is 'aes-256-cbc'.
@@ -61,7 +66,7 @@ module ActiveSupport
sign_secret = signature_key_or_options.first
@secret = secret
@sign_secret = sign_secret
- @cipher = options[:cipher] || "aes-256-cbc"
+ @cipher = options[:cipher] || DEFAULT_CIPHER
@digest = options[:digest] || "SHA1" unless aead_mode?
@verifier = resolve_verifier
@serializer = options[:serializer] || Marshal
diff --git a/activesupport/lib/active_support/multibyte/chars.rb b/activesupport/lib/active_support/multibyte/chars.rb
index 65d6259a06..8c58466556 100644
--- a/activesupport/lib/active_support/multibyte/chars.rb
+++ b/activesupport/lib/active_support/multibyte/chars.rb
@@ -16,7 +16,8 @@ module ActiveSupport #:nodoc:
# through the +mb_chars+ method. Methods which would normally return a
# String object now return a Chars object so methods can be chained.
#
- # 'The Perfect String '.mb_chars.downcase.strip.normalize # => "the perfect string"
+ # 'The Perfect String '.mb_chars.downcase.strip.normalize
+ # # => #<ActiveSupport::Multibyte::Chars:0x007fdc434ccc10 @wrapped_string="the perfect string">
#
# Chars objects are perfectly interchangeable with String objects as long as
# no explicit class checks are made. If certain methods do explicitly check
@@ -134,7 +135,7 @@ module ActiveSupport #:nodoc:
# Converts characters in the string to the opposite case.
#
- # 'El Cañón".mb_chars.swapcase.to_s # => "eL cAÑÓN"
+ # 'El Cañón'.mb_chars.swapcase.to_s # => "eL cAÑÓN"
def swapcase
chars Unicode.swapcase(@wrapped_string)
end
@@ -148,8 +149,8 @@ module ActiveSupport #:nodoc:
# Capitalizes the first letter of every word, when possible.
#
- # "ÉL QUE SE ENTERÓ".mb_chars.titleize # => "Él Que Se Enteró"
- # "日本語".mb_chars.titleize # => "日本語"
+ # "ÉL QUE SE ENTERÓ".mb_chars.titleize.to_s # => "Él Que Se Enteró"
+ # "日本語".mb_chars.titleize.to_s # => "日本語"
def titleize
chars(downcase.to_s.gsub(/\b('?\S)/u) { Unicode.upcase($1) })
end
diff --git a/activesupport/lib/active_support/testing/autorun.rb b/activesupport/lib/active_support/testing/autorun.rb
index 3108e3e549..a18788f38e 100644
--- a/activesupport/lib/active_support/testing/autorun.rb
+++ b/activesupport/lib/active_support/testing/autorun.rb
@@ -2,8 +2,8 @@ gem "minitest"
require "minitest"
-if Minitest.respond_to?(:run_via) && !Minitest.run_via[:rails]
- Minitest.run_via[:ruby] = true
+if Minitest.respond_to?(:run_via) && !Minitest.run_via.set?
+ Minitest.run_via = :ruby
end
Minitest.autorun
diff --git a/activesupport/lib/active_support/time_with_zone.rb b/activesupport/lib/active_support/time_with_zone.rb
index 889f71c4f3..b0dd6b7e8c 100644
--- a/activesupport/lib/active_support/time_with_zone.rb
+++ b/activesupport/lib/active_support/time_with_zone.rb
@@ -148,6 +148,7 @@ module ActiveSupport
"#{time.strftime(PRECISIONS[fraction_digits.to_i])}#{formatted_offset(true, 'Z'.freeze)}"
end
alias_method :iso8601, :xmlschema
+ alias_method :rfc3339, :xmlschema
# Coerces time to a string for JSON encoding. The default format is ISO 8601.
# You can get %Y/%m/%d %H:%M:%S +offset style by setting
@@ -410,6 +411,17 @@ module ActiveSupport
@to_datetime ||= utc.to_datetime.new_offset(Rational(utc_offset, 86_400))
end
+ # Returns an instance of +Time+, either with the same UTC offset
+ # as +self+ or in the local system timezone depending on the setting
+ # of +ActiveSupport.to_time_preserves_timezone+.
+ def to_time
+ if preserve_timezone
+ @to_time_with_instance_offset ||= getlocal(utc_offset)
+ else
+ @to_time_with_system_offset ||= getlocal
+ end
+ end
+
# So that +self+ <tt>acts_like?(:time)</tt>.
def acts_like_time?
true
@@ -427,7 +439,8 @@ module ActiveSupport
end
def freeze
- period; utc; time # preload instance variables before freezing
+ # preload instance variables before freezing
+ period; utc; time; to_datetime; to_time
super
end
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 09cb9cbbe1..ce5207546d 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -250,14 +250,21 @@ module ActiveSupport
# for time zones in the country specified by its ISO 3166-1 Alpha2 code.
def country_zones(country_code)
code = country_code.to_s.upcase
- @country_zones[code] ||=
- TZInfo::Country.get(code).zone_identifiers.map do |tz_id|
- name = MAPPING.key(tz_id)
- name && self[name]
- end.compact.sort!
+ @country_zones[code] ||= load_country_zones(code)
end
private
+ def load_country_zones(code)
+ country = TZInfo::Country.get(code)
+ country.zone_identifiers.map do |tz_id|
+ if MAPPING.value?(tz_id)
+ self[MAPPING.key(tz_id)]
+ else
+ create(tz_id, nil, TZInfo::Timezone.new(tz_id))
+ end
+ end.sort!
+ end
+
def zones_map
@zones_map ||= begin
MAPPING.each_key { |place| self[place] } # load all the zones
@@ -340,6 +347,41 @@ module ActiveSupport
end
# Method for creating new ActiveSupport::TimeWithZone instance in time zone
+ # of +self+ from an ISO 8601 string.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.iso8601('1999-12-31T14:00:00') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ #
+ # If the time components are missing then they will be set to zero.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.iso8601('1999-12-31') # => Fri, 31 Dec 1999 00:00:00 HST -10:00
+ #
+ # If the string is invalid then an +ArgumentError+ will be raised unlike +parse+
+ # which returns +nil+ when given an invalid date string.
+ def iso8601(str)
+ parts = Date._iso8601(str)
+
+ raise ArgumentError, "invalid date" if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year),
+ parts.fetch(:mon),
+ parts.fetch(:mday),
+ parts.fetch(:hour, 0),
+ parts.fetch(:min, 0),
+ parts.fetch(:sec, 0) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset, 0)
+ )
+
+ if parts[:offset]
+ TimeWithZone.new(time.utc, self)
+ else
+ TimeWithZone.new(nil, self, time)
+ end
+ end
+
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone
# of +self+ from parsed string.
#
# Time.zone = 'Hawaii' # => "Hawaii"
@@ -359,6 +401,36 @@ module ActiveSupport
parts_to_time(Date._parse(str, false), now)
end
+ # Method for creating new ActiveSupport::TimeWithZone instance in time zone
+ # of +self+ from an RFC 3339 string.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.rfc3339('2000-01-01T00:00:00Z') # => Fri, 31 Dec 1999 14:00:00 HST -10:00
+ #
+ # If the time or zone components are missing then an +ArgumentError+ will
+ # be raised. This is much stricter than either +parse+ or +iso8601+ which
+ # allow for missing components.
+ #
+ # Time.zone = 'Hawaii' # => "Hawaii"
+ # Time.zone.rfc3339('1999-12-31') # => ArgumentError: invalid date
+ def rfc3339(str)
+ parts = Date._rfc3339(str)
+
+ raise ArgumentError, "invalid date" if parts.empty?
+
+ time = Time.new(
+ parts.fetch(:year),
+ parts.fetch(:mon),
+ parts.fetch(:mday),
+ parts.fetch(:hour),
+ parts.fetch(:min),
+ parts.fetch(:sec) + parts.fetch(:sec_fraction, 0),
+ parts.fetch(:offset)
+ )
+
+ TimeWithZone.new(time.utc, self)
+ end
+
# Parses +str+ according to +format+ and returns an ActiveSupport::TimeWithZone.
#
# Assumes that +str+ is a time in the time zone +self+,
diff --git a/activesupport/lib/active_support/xml_mini/libxml.rb b/activesupport/lib/active_support/xml_mini/libxml.rb
index 44b0bdb7dc..cde2967132 100644
--- a/activesupport/lib/active_support/xml_mini/libxml.rb
+++ b/activesupport/lib/active_support/xml_mini/libxml.rb
@@ -74,5 +74,7 @@ module LibXML #:nodoc:
end
end
+# :enddoc:
+
LibXML::XML::Document.include(LibXML::Conversions::Document)
LibXML::XML::Node.include(LibXML::Conversions::Node)
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index c543122d91..c67ffe69b8 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -47,6 +47,17 @@ module ActiveSupport
assert_raises(RuntimeError) { middleware.call({}) }
assert_nil LocalCacheRegistry.cache_for(key)
end
+
+ def test_local_cache_cleared_on_throw
+ key = "super awesome key"
+ assert_nil LocalCacheRegistry.cache_for key
+ middleware = Middleware.new("<3", key).new(->(env) {
+ assert LocalCacheRegistry.cache_for(key), "should have a cache"
+ throw :warden
+ })
+ assert_throws(:warden) { middleware.call({}) }
+ assert_nil LocalCacheRegistry.cache_for(key)
+ end
end
end
end
diff --git a/activesupport/test/core_ext/date_and_time_compatibility_test.rb b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
index 4c90460032..6c6205a4d2 100644
--- a/activesupport/test/core_ext/date_and_time_compatibility_test.rb
+++ b/activesupport/test/core_ext/date_and_time_compatibility_test.rb
@@ -16,11 +16,13 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_time_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @utc_offset, time.utc_offset
+ assert_equal source.object_id, time.object_id
end
end
end
@@ -28,11 +30,43 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_time_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = Time.new(2016, 4, 23, 15, 11, 12, 3600).to_time
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_equal source.object_id, time.object_id
+ end
+ end
+ end
+
+ def test_time_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_equal source.object_id, time.object_id
+ assert_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_time_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = Time.new(2016, 4, 23, 15, 11, 12, 3600).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ assert_not_equal source.object_id, time.object_id
+ assert_not_predicate time, :frozen?
end
end
end
@@ -40,7 +74,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -52,7 +87,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_datetime_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).to_time
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24))
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -61,17 +97,47 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
end
end
+ def test_datetime_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_datetime_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = DateTime.new(2016, 4, 23, 15, 11, 12, Rational(1, 24)).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
def test_twz_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @utc_offset, time.utc_offset
- time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
+ time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
@@ -84,19 +150,69 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_twz_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = ActiveSupport::TimeWithZone.new(@utc_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone)
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone)
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_twz_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @date_time, time.getutc
+ assert_instance_of Time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_twz_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = ActiveSupport::TimeWithZone.new(@utc_time, @zone).freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
- time = ActiveSupport::TimeWithZone.new(@date_time, @zone).to_time
+ source = ActiveSupport::TimeWithZone.new(@date_time, @zone).freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @date_time, time.getutc
assert_instance_of Time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
end
end
end
@@ -104,7 +220,8 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_string_to_time_preserves_timezone
with_preserve_timezone(true) do
with_env_tz "US/Eastern" do
- time = "2016-04-23T15:11:12+01:00".to_time
+ source = "2016-04-23T15:11:12+01:00"
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
@@ -116,11 +233,40 @@ class DateAndTimeCompatibilityTest < ActiveSupport::TestCase
def test_string_to_time_does_not_preserve_time_zone
with_preserve_timezone(false) do
with_env_tz "US/Eastern" do
- time = "2016-04-23T15:11:12+01:00".to_time
+ source = "2016-04-23T15:11:12+01:00"
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @system_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_string_to_time_frozen_preserves_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ source = "2016-04-23T15:11:12+01:00".freeze
+ time = source.to_time
+
+ assert_instance_of Time, time
+ assert_equal @utc_time, time.getutc
+ assert_equal @utc_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
+ end
+ end
+ end
+
+ def test_string_to_time_frozen_does_not_preserve_time_zone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ source = "2016-04-23T15:11:12+01:00".freeze
+ time = source.to_time
assert_instance_of Time, time
assert_equal @utc_time, time.getutc
assert_equal @system_offset, time.utc_offset
+ assert_not_predicate time, :frozen?
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 e3b31c10f5..36f0ee22b8 100644
--- a/activesupport/test/core_ext/date_time_ext_test.rb
+++ b/activesupport/test/core_ext/date_time_ext_test.rb
@@ -4,8 +4,8 @@ require "core_ext/date_and_time_behavior"
require "time_zone_test_helpers"
class DateTimeExtCalculationsTest < ActiveSupport::TestCase
- def date_time_init(year, month, day, hour, minute, second, *args)
- DateTime.civil(year, month, day, hour, minute, second)
+ def date_time_init(year, month, day, hour, minute, second, usec = 0)
+ DateTime.civil(year, month, day, hour, minute, second + (usec / 1000000))
end
include DateAndTimeBehavior
@@ -113,7 +113,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_day
- assert_equal DateTime.civil(2005, 2, 4, 23, 59, 59), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day
+ assert_equal DateTime.civil(2005, 2, 4, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 10, 10, 10).end_of_day
end
def test_beginning_of_hour
@@ -121,7 +121,7 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_hour
- assert_equal DateTime.civil(2005, 2, 4, 19, 59, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour
+ assert_equal DateTime.civil(2005, 2, 4, 19, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_hour
end
def test_beginning_of_minute
@@ -129,13 +129,13 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
end
def test_end_of_minute
- assert_equal DateTime.civil(2005, 2, 4, 19, 30, 59), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute
+ assert_equal DateTime.civil(2005, 2, 4, 19, 30, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 4, 19, 30, 10).end_of_minute
end
def test_end_of_month
- assert_equal DateTime.civil(2005, 3, 31, 23, 59, 59), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month
- assert_equal DateTime.civil(2005, 2, 28, 23, 59, 59), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month
- assert_equal DateTime.civil(2005, 4, 30, 23, 59, 59), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 3, 31, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 3, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 2, 28, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 2, 20, 10, 10, 10).end_of_month
+ assert_equal DateTime.civil(2005, 4, 30, 23, 59, Rational(59999999999, 1000000000)), DateTime.civil(2005, 4, 20, 10, 10, 10).end_of_month
end
def test_last_year
@@ -162,12 +162,19 @@ class DateTimeExtCalculationsTest < ActiveSupport::TestCase
assert_equal DateTime.civil(2006, 2, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2006)
assert_equal DateTime.civil(2005, 6, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(month: 6)
assert_equal DateTime.civil(2012, 9, 22, 15, 15, 10), DateTime.civil(2005, 2, 22, 15, 15, 10).change(year: 2012, month: 9)
- assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16)
- assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45)
- assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45)
+ assert_equal DateTime.civil(2005, 2, 22, 16), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16)
+ assert_equal DateTime.civil(2005, 2, 22, 16, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(hour: 16, min: 45)
+ assert_equal DateTime.civil(2005, 2, 22, 15, 45), DateTime.civil(2005, 2, 22, 15, 15, 10).change(min: 45)
# datetime with fractions of a second
assert_equal DateTime.civil(2005, 2, 1, 15, 15, 10.7), DateTime.civil(2005, 2, 22, 15, 15, 10.7).change(day: 1)
+ assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(usec: 8)
+ assert_equal DateTime.civil(2005, 1, 2, 11, 22, Rational(33000008, 1000000)), DateTime.civil(2005, 1, 2, 11, 22, 33).change(nsec: 8000)
+ assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1, nsec: 1) }
+ assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 1000000) }
+ assert_raise(ArgumentError) { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 1000000000) }
+ assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(usec: 999999) }
+ assert_nothing_raised { DateTime.civil(2005, 1, 2, 11, 22, 0).change(nsec: 999999999) }
end
def test_advance
diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb
index 6a275d1d5b..1648a9b270 100644
--- a/activesupport/test/core_ext/duration_test.rb
+++ b/activesupport/test/core_ext/duration_test.rb
@@ -84,6 +84,38 @@ class DurationTest < ActiveSupport::TestCase
assert_nothing_raised { Date.today - Date.today }
end
+ def test_plus
+ assert_equal 2.seconds, 1.second + 1.second
+ assert_instance_of ActiveSupport::Duration, 1.second + 1.second
+ assert_equal 2.seconds, 1.second + 1
+ assert_instance_of ActiveSupport::Duration, 1.second + 1
+ end
+
+ def test_minus
+ assert_equal 1.second, 2.seconds - 1.second
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1.second
+ assert_equal 1.second, 2.seconds - 1
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1
+ assert_equal 1.second, 2 - 1.second
+ assert_instance_of ActiveSupport::Duration, 2.seconds - 1
+ end
+
+ def test_multiply
+ assert_equal 7.days, 1.day * 7
+ assert_instance_of ActiveSupport::Duration, 1.day * 7
+ assert_equal 86400, 1.day * 1.second
+ end
+
+ def test_divide
+ assert_equal 1.day, 7.days / 7
+ assert_instance_of ActiveSupport::Duration, 7.days / 7
+ assert_equal 1, 1.day / 1.day
+ end
+
+ def test_date_added_with_multiplied_duration
+ assert_equal Date.civil(2017, 1, 3), Date.civil(2017, 1, 1) + 1.day * 2
+ end
+
def test_plus_with_time
assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration"
end
@@ -179,6 +211,19 @@ class DurationTest < ActiveSupport::TestCase
Time.zone = nil
end
+ def test_before_and_afer
+ t = Time.local(2000)
+ assert_equal t + 1, 1.second.after(t)
+ assert_equal t - 1, 1.second.before(t)
+ end
+
+ def test_before_and_after_without_argument
+ Time.stub(:now, Time.local(2000)) do
+ assert_equal Time.now - 1.second, 1.second.before
+ assert_equal Time.now + 1.second, 1.second.after
+ end
+ end
+
def test_adding_hours_across_dst_boundary
with_env_tz "CET" do
assert_equal Time.local(2009, 3, 29, 0, 0, 0) + 24.hours, Time.local(2009, 3, 30, 1, 0, 0)
@@ -237,6 +282,118 @@ class DurationTest < ActiveSupport::TestCase
assert_equal(1, (61 <=> 1.minute))
end
+ def test_implicit_coercion
+ assert_equal 2.days, 2 * 1.day
+ assert_instance_of ActiveSupport::Duration, 2 * 1.day
+ assert_equal Time.utc(2017, 1, 3), Time.utc(2017, 1, 1) + 2 * 1.day
+ assert_equal Date.civil(2017, 1, 3), Date.civil(2017, 1, 1) + 2 * 1.day
+ end
+
+ def test_scalar_coerce
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+ assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar
+ assert_instance_of ActiveSupport::Duration, 10.seconds + scalar
+ end
+
+ def test_scalar_delegations
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+ assert_kind_of Float, scalar.to_f
+ assert_kind_of Integer, scalar.to_i
+ assert_kind_of String, scalar.to_s
+ end
+
+ def test_scalar_unary_minus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal(-10, -scalar)
+ assert_instance_of ActiveSupport::Duration::Scalar, -scalar
+ end
+
+ def test_scalar_compare
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal(1, scalar <=> 5)
+ assert_equal(0, scalar <=> 10)
+ assert_equal(-1, scalar <=> 15)
+ assert_equal(nil, scalar <=> "foo")
+ end
+
+ def test_scalar_plus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 20, 10 + scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 10 + scalar
+ assert_equal 20, scalar + 10
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar + 10
+ assert_equal 20, 10.seconds + scalar
+ assert_instance_of ActiveSupport::Duration, 10.seconds + scalar
+ assert_equal 20, scalar + 10.seconds
+ assert_instance_of ActiveSupport::Duration, scalar + 10.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar + "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_minus
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 10, 20 - scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 20 - scalar
+ assert_equal 5, scalar - 5
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar - 5
+ assert_equal 10, 20.seconds - scalar
+ assert_instance_of ActiveSupport::Duration, 20.seconds - scalar
+ assert_equal 5, scalar - 5.seconds
+ assert_instance_of ActiveSupport::Duration, scalar - 5.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar - "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_multiply
+ scalar = ActiveSupport::Duration::Scalar.new(5)
+
+ assert_equal 10, 2 * scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 2 * scalar
+ assert_equal 10, scalar * 2
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar * 2
+ assert_equal 10, 2.seconds * scalar
+ assert_instance_of ActiveSupport::Duration, 2.seconds * scalar
+ assert_equal 10, scalar * 2.seconds
+ assert_instance_of ActiveSupport::Duration, scalar * 2.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar * "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
+ def test_scalar_divide
+ scalar = ActiveSupport::Duration::Scalar.new(10)
+
+ assert_equal 10, 100 / scalar
+ assert_instance_of ActiveSupport::Duration::Scalar, 100 / scalar
+ assert_equal 5, scalar / 2
+ assert_instance_of ActiveSupport::Duration::Scalar, scalar / 2
+ assert_equal 10, 100.seconds / scalar
+ assert_instance_of ActiveSupport::Duration, 2.seconds * scalar
+ assert_equal 5, scalar / 2.seconds
+ assert_instance_of ActiveSupport::Duration, scalar / 2.seconds
+
+ exception = assert_raises(TypeError) do
+ scalar / "foo"
+ end
+
+ assert_equal "no implicit conversion of String into ActiveSupport::Duration::Scalar", exception.message
+ end
+
def test_twelve_months_equals_one_year
assert_equal 12.months, 1.year
end
diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb
index 05813ad388..18da5fcf5f 100644
--- a/activesupport/test/core_ext/hash_ext_test.rb
+++ b/activesupport/test/core_ext/hash_ext_test.rb
@@ -8,31 +8,6 @@ require "active_support/core_ext/object/deep_dup"
require "active_support/inflections"
class HashExtTest < ActiveSupport::TestCase
- class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
- end
-
- class SubclassingArray < Array
- end
-
- class SubclassingHash < Hash
- end
-
- class NonIndifferentHash < Hash
- def nested_under_indifferent_access
- self
- end
- end
-
- class HashByConversion
- def initialize(hash)
- @hash = hash
- end
-
- def to_hash
- @hash
- end
- end
-
def setup
@strings = { "a" => 1, "b" => 2 }
@nested_strings = { "a" => { "b" => { "c" => 3 } } }
@@ -262,461 +237,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal({ "a" => { b: { "c" => 3 } } }, @nested_mixed)
end
- def test_symbolize_keys_for_hash_with_indifferent_access
- assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
- assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
- end
-
- def test_deep_symbolize_keys_for_hash_with_indifferent_access
- assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
- assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
- end
-
- def test_symbolize_keys_bang_for_hash_with_indifferent_access
- assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
- assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
- assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
- assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
- assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
- assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
- assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
- assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
- end
-
- def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
- assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
- assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
- end
-
- def test_stringify_keys_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
- assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
- assert_equal @strings, @strings.with_indifferent_access.stringify_keys
- assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
- end
-
- def test_deep_stringify_keys_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
- assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
- end
-
- def test_stringify_keys_bang_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
- assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
- end
-
- def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
- assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
- end
-
- def test_nested_under_indifferent_access
- foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
-
- foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of NonIndifferentHash, foo["foo"]
-
- foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
- assert_kind_of IndifferentHash, foo["foo"]
- end
-
- def test_indifferent_assorted
- @strings = @strings.with_indifferent_access
- @symbols = @symbols.with_indifferent_access
- @mixed = @mixed.with_indifferent_access
-
- assert_equal "a", @strings.__send__(:convert_key, :a)
-
- assert_equal 1, @strings.fetch("a")
- assert_equal 1, @strings.fetch(:a.to_s)
- assert_equal 1, @strings.fetch(:a)
-
- hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
- method_map = { '[]': 1, fetch: 1, values_at: [1],
- has_key?: true, include?: true, key?: true,
- member?: true }
-
- hashes.each do |name, hash|
- method_map.sort_by(&:to_s).each do |meth, expected|
- assert_equal(expected, hash.__send__(meth, "a"),
- "Calling #{name}.#{meth} 'a'")
- assert_equal(expected, hash.__send__(meth, :a),
- "Calling #{name}.#{meth} :a")
- end
- end
-
- assert_equal [1, 2], @strings.values_at("a", "b")
- assert_equal [1, 2], @strings.values_at(:a, :b)
- assert_equal [1, 2], @symbols.values_at("a", "b")
- assert_equal [1, 2], @symbols.values_at(:a, :b)
- assert_equal [1, 2], @mixed.values_at("a", "b")
- assert_equal [1, 2], @mixed.values_at(:a, :b)
- end
-
- def test_indifferent_reading
- hash = HashWithIndifferentAccess.new
- hash["a"] = 1
- hash["b"] = true
- hash["c"] = false
- hash["d"] = nil
-
- assert_equal 1, hash[:a]
- assert_equal true, hash[:b]
- assert_equal false, hash[:c]
- assert_nil hash[:d]
- assert_nil hash[:e]
- end
-
- def test_indifferent_reading_with_nonnil_default
- hash = HashWithIndifferentAccess.new(1)
- hash["a"] = 1
- hash["b"] = true
- hash["c"] = false
- hash["d"] = nil
-
- assert_equal 1, hash[:a]
- assert_equal true, hash[:b]
- assert_equal false, hash[:c]
- assert_nil hash[:d]
- assert_equal 1, hash[:e]
- end
-
- def test_indifferent_writing
- hash = HashWithIndifferentAccess.new
- hash[:a] = 1
- hash["b"] = 2
- hash[3] = 3
-
- assert_equal 1, hash["a"]
- assert_equal 2, hash["b"]
- assert_equal 1, hash[:a]
- assert_equal 2, hash[:b]
- assert_equal 3, hash[3]
- end
-
- def test_indifferent_update
- hash = HashWithIndifferentAccess.new
- hash[:a] = "a"
- hash["b"] = "b"
-
- updated_with_strings = hash.update(@strings)
- updated_with_symbols = hash.update(@symbols)
- updated_with_mixed = hash.update(@mixed)
-
- assert_equal 1, updated_with_strings[:a]
- assert_equal 1, updated_with_strings["a"]
- assert_equal 2, updated_with_strings["b"]
-
- assert_equal 1, updated_with_symbols[:a]
- assert_equal 2, updated_with_symbols["b"]
- assert_equal 2, updated_with_symbols[:b]
-
- assert_equal 1, updated_with_mixed[:a]
- assert_equal 2, updated_with_mixed["b"]
-
- assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
- end
-
- def test_update_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- hash.update HashByConversion.new(a: 1)
- assert_equal 1, hash["a"]
- end
-
- def test_indifferent_merging
- hash = HashWithIndifferentAccess.new
- hash[:a] = "failure"
- hash["b"] = "failure"
-
- other = { "a" => 1, :b => 2 }
-
- merged = hash.merge(other)
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 1, merged[:a]
- assert_equal 2, merged["b"]
-
- hash.update(other)
-
- assert_equal 1, hash[:a]
- assert_equal 2, hash["b"]
- end
-
- def test_merge_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- merged = hash.merge HashByConversion.new(a: 1)
- assert_equal 1, merged["a"]
- end
-
- def test_indifferent_replace
- hash = HashWithIndifferentAccess.new
- hash[:a] = 42
-
- replaced = hash.replace(b: 12)
-
- assert hash.key?("b")
- assert !hash.key?(:a)
- assert_equal 12, hash[:b]
- assert_same hash, replaced
- end
-
- def test_replace_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new
- hash[:a] = 42
-
- replaced = hash.replace(HashByConversion.new(b: 12))
-
- assert hash.key?("b")
- assert !hash.key?(:a)
- assert_equal 12, hash[:b]
- assert_same hash, replaced
- end
-
- def test_indifferent_merging_with_block
- hash = HashWithIndifferentAccess.new
- hash[:a] = 1
- hash["b"] = 3
-
- other = { "a" => 4, :b => 2, "c" => 10 }
-
- merged = hash.merge(other) { |key, old, new| old > new ? old : new }
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 4, merged[:a]
- assert_equal 3, merged["b"]
- assert_equal 10, merged[:c]
-
- other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2)
-
- merged = hash.merge(other_indifferent) { |key, old, new| old + new }
-
- assert_equal HashWithIndifferentAccess, merged.class
- assert_equal 10, merged[:a]
- assert_equal 5, merged[:b]
- 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]
- assert_equal "clobber", hash[:another]
- end
-
- def test_indifferent_deleting
- get_hash = proc { { a: "foo" }.with_indifferent_access }
- hash = get_hash.call
- assert_equal "foo", hash.delete(:a)
- assert_nil hash.delete(:a)
- hash = get_hash.call
- assert_equal "foo", hash.delete("a")
- assert_nil hash.delete("a")
- end
-
- def test_indifferent_select
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 }
-
- assert_equal({ "a" => 1 }, hash)
- 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 }
-
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
- end
-
- def test_indifferent_select_bang
- indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
- indifferent_strings.select! { |k, v| v == 1 }
-
- assert_equal({ "a" => 1 }, indifferent_strings)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
- end
-
- def test_indifferent_reject
- hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 }
-
- assert_equal({ "a" => 1 }, hash)
- 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 }
-
- assert_equal({ "a" => 1 }, indifferent_strings)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
- end
-
- def test_indifferent_compact
- hash_contain_nil_value = @strings.merge("z" => nil)
- hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
- compacted_hash = hash.compact
-
- assert_equal(@strings, compacted_hash)
- assert_equal(hash_contain_nil_value, hash)
- assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash
- end
-
- def test_indifferent_to_hash
- # Should convert to a Hash with String keys.
- assert_equal @strings, @mixed.with_indifferent_access.to_hash
-
- # Should preserve the default value.
- mixed_with_default = @mixed.dup
- mixed_with_default.default = "1234"
- roundtrip = mixed_with_default.with_indifferent_access.to_hash
- assert_equal @strings, roundtrip
- assert_equal "1234", roundtrip.default
-
- # Ensure nested hashes are not HashWithIndiffereneAccess
- new_to_hash = @nested_mixed.with_indifferent_access.to_hash
- assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
- assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
- assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
- end
-
- def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
- hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] }
- hash[:a] << 1
-
- assert_equal [1], hash[:a]
- end
-
- def test_with_indifferent_access_has_no_side_effects_on_existing_hash
- hash = { content: [{ :foo => :bar, "bar" => "baz" }] }
- hash.with_indifferent_access
-
- assert_equal [:foo, "bar"], hash[:content].first.keys
- end
-
- def test_indifferent_hash_with_array_of_hashes
- hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access
- assert_equal "1", hash[:urls][:url].first[:address]
-
- hash = hash.to_hash
- assert_not hash.instance_of?(HashWithIndifferentAccess)
- assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
- assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
- end
-
- def test_should_preserve_array_subclass_when_value_is_array
- array = SubclassingArray.new
- array << { "address" => "1" }
- hash = { "urls" => { "url" => array } }.with_indifferent_access
- assert_equal SubclassingArray, hash[:urls][:url].class
- end
-
- def test_should_preserve_array_class_when_hash_value_is_frozen_array
- array = SubclassingArray.new
- array << { "address" => "1" }
- hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access
- assert_equal SubclassingArray, hash[:urls][:url].class
- end
-
- def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h[:first] = 1
- h = h.stringify_keys
- assert_equal 1, h["first"]
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h = h.symbolize_keys
- assert_equal 1, h[:first]
- end
-
- def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h[:first] = 1
- h = h.deep_stringify_keys
- assert_equal 1, h["first"]
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h = h.deep_symbolize_keys
- assert_equal 1, h[:first]
- end
-
- def test_to_options_on_indifferent_preserves_hash
- h = HashWithIndifferentAccess.new
- h["first"] = 1
- h.to_options!
- assert_equal 1, h["first"]
- end
-
- def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
- h = HashWithIndifferentAccess.new(a: { b: "b" })
- dup = h.dup
-
- dup[:a][:c] = "c"
- assert_equal "c", h[:a][:c]
- end
-
- def test_indifferent_sub_hashes
- h = { "user" => { "id" => 5 } }.with_indifferent_access
- ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
-
- h = { user: { id: 5 } }.with_indifferent_access
- ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
- end
-
- def test_indifferent_duplication
- # Should preserve default value
- h = HashWithIndifferentAccess.new
- h.default = "1234"
- assert_equal h.default, h.dup.default
-
- # Should preserve class for subclasses
- h = IndifferentHash.new
- assert_equal h.class, h.dup.class
- end
-
- def test_nested_dig_indifferent_access
- skip if RUBY_VERSION < "2.3.0"
- data = { "this" => { "views" => 1234 } }.with_indifferent_access
- assert_equal 1234, data.dig(:this, :views)
- end
-
def test_assert_valid_keys
assert_nothing_raised do
{ failure: "stuff", funny: "business" }.assert_valid_keys([ :failure, :funny ])
@@ -749,12 +269,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal "Unknown key: :failore. Valid keys are: :failure", exception.message
end
- def test_assorted_keys_not_stringified
- original = { Object.new => 2, 1 => 2, [] => true }
- indiff = original.with_indifferent_access
- assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!")
- end
-
def test_deep_merge
hash_1 = { a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } } }
hash_2 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
@@ -785,37 +299,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, hash_1
end
- def test_deep_merge_on_indifferent_access
- hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } })
- hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } })
- hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
- expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
- assert_equal expected, hash_1.deep_merge(hash_2)
- assert_equal expected, hash_1.deep_merge(hash_3)
-
- hash_1.deep_merge!(hash_2)
- assert_equal expected, hash_1
- end
-
- def test_store_on_indifferent_access
- hash = HashWithIndifferentAccess.new
- hash.store(:test1, 1)
- hash.store("test1", 11)
- hash[:test2] = 2
- hash["test2"] = 22
- expected = { "test1" => 11, "test2" => 22 }
- assert_equal expected, hash
- end
-
- def test_constructor_on_indifferent_access
- hash = HashWithIndifferentAccess[:foo, 1]
- assert_equal 1, hash[:foo]
- assert_equal 1, hash["foo"]
- hash[:foo] = 3
- assert_equal 3, hash[:foo]
- assert_equal 3, hash["foo"]
- end
-
def test_reverse_merge
defaults = { a: "x", b: "y", c: 10 }.freeze
options = { a: 1, b: 2 }
@@ -836,6 +319,21 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, merged
end
+ def test_with_defaults_aliases_reverse_merge
+ defaults = { a: "x", b: "y", c: 10 }.freeze
+ options = { a: 1, b: 2 }
+ expected = { a: 1, b: 2, c: 10 }
+
+ # Should be an alias for reverse_merge
+ assert_equal expected, options.with_defaults(defaults)
+ assert_not_equal expected, options
+
+ # Should be an alias for reverse_merge!
+ merged = options.dup
+ assert_equal expected, merged.with_defaults!(defaults)
+ assert_equal expected, merged
+ end
+
def test_slice
original = { a: "x", b: "y", c: 10 }
expected = { a: "x", b: "y" }
@@ -878,38 +376,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_equal expected, original.slice(*[:a, :b])
end
- def test_indifferent_slice
- original = { a: "x", b: "y", c: 10 }.with_indifferent_access
- expected = { a: "x", b: "y" }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- # Should return a new hash with only the given keys.
- assert_equal expected, original.slice(*keys), keys.inspect
- assert_not_equal expected, original
- end
- end
-
- def test_indifferent_slice_inplace
- original = { a: "x", b: "y", c: 10 }.with_indifferent_access
- expected = { c: 10 }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- # Should replace the hash with only the given keys.
- copy = original.dup
- assert_equal expected, copy.slice!(*keys)
- end
- end
-
- def test_indifferent_slice_access_with_symbols
- original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" }
- original = original.with_indifferent_access
-
- slice = original.slice(:login, :password)
-
- assert_equal "bender", slice[:login]
- assert_equal "bender", slice["login"]
- end
-
def test_slice_bang_does_not_override_default
hash = Hash.new(0)
hash.update(a: 1, b: 2)
@@ -947,18 +413,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_nil extracted[:x]
end
- def test_indifferent_extract
- original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access
- expected = { a: 1, b: 2 }.with_indifferent_access
- remaining = { c: 3, d: 4 }.with_indifferent_access
-
- [["a", "b"], [:a, :b]].each do |keys|
- copy = original.dup
- assert_equal expected, copy.extract!(*keys)
- assert_equal remaining, copy
- end
- end
-
def test_except
original = { a: "x", b: "y", c: 10 }
expected = { a: "x", b: "y" }
@@ -1030,54 +484,6 @@ class HashExtTest < ActiveSupport::TestCase
assert_nil(h.compact!)
assert_equal(@symbols, h)
end
-
- def test_new_with_to_hash_conversion
- hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
- 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_existent]
- 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_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
@@ -1598,61 +1004,6 @@ class HashToXmlTest < ActiveSupport::TestCase
assert_equal expected, Hash.from_trusted_xml('<product><name type="yaml">:value</name></product>')
end
- def test_should_use_default_proc_for_unknown_key
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_equal 3, hash_wia[:new_key]
- end
-
- def test_should_return_nil_if_no_key_is_supplied
- hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
- assert_nil hash_wia.default
- end
-
- def test_should_use_default_value_for_unknown_key
- hash_wia = HashWithIndifferentAccess.new(3)
- assert_equal 3, hash_wia[:new_key]
- end
-
- def test_should_use_default_value_if_no_key_is_supplied
- hash_wia = HashWithIndifferentAccess.new(3)
- assert_equal 3, hash_wia.default
- end
-
- def test_should_nil_if_no_default_value_is_supplied
- hash_wia = HashWithIndifferentAccess.new
- assert_nil hash_wia.default
- end
-
- def test_should_return_dup_for_with_indifferent_access
- hash_wia = HashWithIndifferentAccess.new
- assert_equal hash_wia, hash_wia.with_indifferent_access
- assert_not_same hash_wia, hash_wia.with_indifferent_access
- end
-
- def test_allows_setting_frozen_array_values_with_indifferent_access
- value = [1, 2, 3].freeze
- hash = HashWithIndifferentAccess.new
- hash[:key] = value
- assert_equal hash[:key], value
- end
-
- def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
- hash = Hash.new(3)
- hash_wia = hash.with_indifferent_access
- assert_equal 3, hash_wia.default
- end
-
- def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
- hash = Hash.new do
- 2 + 1
- end
- assert_equal 3, hash[:foo]
-
- hash_wia = hash.with_indifferent_access
- assert_equal 3, hash_wia[:foo]
- assert_equal 3, hash_wia[:bar]
- end
-
# The XML builder seems to fail miserably when trying to tag something
# with the same name as a Kernel method (throw, test, loop, select ...)
def test_kernel_method_names_to_xml
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
index a899f98705..cabeed2fae 100644
--- a/activesupport/test/core_ext/marshal_test.rb
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -19,6 +19,19 @@ class MarshalTest < ActiveSupport::TestCase
end
end
+ test "that Marshal#load still works when passed a proc" do
+ example_string = "test"
+
+ example_proc = Proc.new do |o|
+ if o.is_a?(String)
+ o.capitalize!
+ end
+ end
+
+ dumped = Marshal.dump(example_string)
+ assert_equal Marshal.load(dumped, example_proc), "Test"
+ end
+
test "that a missing class is autoloaded from string" do
dumped = nil
with_autoloading_fixtures do
diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb
index e6f3c8aef2..68b0129980 100644
--- a/activesupport/test/core_ext/object/duplicable_test.rb
+++ b/activesupport/test/core_ext/object/duplicable_test.rb
@@ -4,7 +4,10 @@ require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/numeric/time"
class DuplicableTest < ActiveSupport::TestCase
- if RUBY_VERSION >= "2.4.1"
+ if RUBY_VERSION >= "2.5.0"
+ RAISE_DUP = [method(:puts)]
+ ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)]
+ elsif RUBY_VERSION >= "2.4.1"
RAISE_DUP = [method(:puts), Complex(1), Rational(1)]
ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal.new("4.56"), nil, false, true, 1, 2.3]
elsif RUBY_VERSION >= "2.4.0" # Due to 2.4.0 bug. This elsif cannot be removed unless we drop 2.4.0 support...
diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb
index 5d90fa2509..a98951e889 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -1,5 +1,6 @@
require "date"
require "abstract_unit"
+require "timeout"
require "inflector_test_cases"
require "constantize_test_cases"
@@ -77,6 +78,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_titleize_with_keep_id_suffix
+ MixtureToTitleCaseWithKeepIdSuffix.each do |before, titleized|
+ assert_equal(titleized, before.titleize(keep_id_suffix: true))
+ end
+ end
+
def test_upcase_first
assert_equal "What a Lovely Day", "what a Lovely Day".upcase_first
end
@@ -198,6 +205,12 @@ class StringInflectionsTest < ActiveSupport::TestCase
end
end
+ def test_humanize_with_keep_id_suffix
+ UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human|
+ assert_equal(human, underscore.humanize(keep_id_suffix: true))
+ end
+ end
+
def test_humanize_with_html_escape
assert_equal "Hello", ERB::Util.html_escape("hello").humanize
end
diff --git a/activesupport/test/core_ext/time_ext_test.rb b/activesupport/test/core_ext/time_ext_test.rb
index a399e36dc9..bd644c8457 100644
--- a/activesupport/test/core_ext/time_ext_test.rb
+++ b/activesupport/test/core_ext/time_ext_test.rb
@@ -569,6 +569,11 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
Time::DATE_FORMATS.delete(:custom)
end
+ def test_rfc3339_with_fractional_seconds
+ time = Time.new(1999, 12, 31, 19, 0, Rational(1, 8), -18000)
+ assert_equal "1999-12-31T19:00:00.125-05:00", time.rfc3339(3)
+ end
+
def test_to_date
assert_equal Date.new(2005, 2, 21), Time.local(2005, 2, 21, 17, 44, 30).to_date
end
@@ -910,6 +915,37 @@ class TimeExtCalculationsTest < ActiveSupport::TestCase
def test_all_year
assert_equal Time.local(2011, 1, 1, 0, 0, 0)..Time.local(2011, 12, 31, 23, 59, 59, Rational(999999999, 1000)), Time.local(2011, 6, 7, 10, 10, 10).all_year
end
+
+ def test_rfc3339_parse
+ time = Time.rfc3339("1999-12-31T19:00:00.125-05:00")
+
+ assert_equal 1999, time.year
+ assert_equal 12, time.month
+ assert_equal 31, time.day
+ assert_equal 19, time.hour
+ assert_equal 0, time.min
+ assert_equal 0, time.sec
+ assert_equal 125000, time.usec
+ assert_equal(-18000, time.utc_offset)
+
+ exception = assert_raises(ArgumentError) do
+ Time.rfc3339("1999-12-31")
+ end
+
+ assert_equal "invalid date", exception.message
+
+ exception = assert_raises(ArgumentError) do
+ Time.rfc3339("1999-12-31T19:00:00")
+ end
+
+ assert_equal "invalid date", exception.message
+
+ exception = assert_raises(ArgumentError) do
+ Time.rfc3339("foobar")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
end
class TimeExtMarshalingTest < ActiveSupport::TestCase
diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb
index ab5ec98642..c3afe68378 100644
--- a/activesupport/test/core_ext/time_with_zone_test.rb
+++ b/activesupport/test/core_ext/time_with_zone_test.rb
@@ -144,6 +144,16 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal "1999-12-31T19:00:00-05:00", @twz.xmlschema(nil)
end
+ def test_iso8601_with_fractional_seconds
+ @twz += Rational(1, 8)
+ assert_equal "1999-12-31T19:00:00.125-05:00", @twz.iso8601(3)
+ end
+
+ def test_rfc3339_with_fractional_seconds
+ @twz += Rational(1, 8)
+ assert_equal "1999-12-31T19:00:00.125-05:00", @twz.rfc3339(3)
+ end
+
def test_to_yaml
yaml = <<-EOF.strip_heredoc
--- !ruby/object:ActiveSupport::TimeWithZone
@@ -421,11 +431,29 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_equal time, Time.at(time)
end
- def test_to_time
- with_env_tz "US/Eastern" do
- assert_equal Time, @twz.to_time.class
- assert_equal Time.local(1999, 12, 31, 19), @twz.to_time
- assert_equal Time.local(1999, 12, 31, 19).utc_offset, @twz.to_time.utc_offset
+ def test_to_time_with_preserve_timezone
+ with_preserve_timezone(true) do
+ with_env_tz "US/Eastern" do
+ time = @twz.to_time
+
+ assert_equal Time, time.class
+ assert_equal time.object_id, @twz.to_time.object_id
+ assert_equal Time.local(1999, 12, 31, 19), time
+ assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
+ end
+ end
+ end
+
+ def test_to_time_without_preserve_timezone
+ with_preserve_timezone(false) do
+ with_env_tz "US/Eastern" do
+ time = @twz.to_time
+
+ assert_equal Time, time.class
+ assert_equal time.object_id, @twz.to_time.object_id
+ assert_equal Time.local(1999, 12, 31, 19), time
+ assert_equal Time.local(1999, 12, 31, 19).utc_offset, time.utc_offset
+ end
end
end
@@ -507,6 +535,8 @@ class TimeWithZoneTest < ActiveSupport::TestCase
assert_nothing_raised do
@twz.period
@twz.time
+ @twz.to_datetime
+ @twz.to_time
end
end
diff --git a/activesupport/test/deprecation_test.rb b/activesupport/test/deprecation_test.rb
index 5f72fbf662..36d1ef0849 100644
--- a/activesupport/test/deprecation_test.rb
+++ b/activesupport/test/deprecation_test.rb
@@ -35,6 +35,18 @@ class Deprecatee
A = ActiveSupport::Deprecation::DeprecatedConstantProxy.new("Deprecatee::A", "Deprecatee::B::C")
end
+class DeprecateeWithAccessor
+ include ActiveSupport::Deprecation::DeprecatedConstantAccessor
+
+ module B
+ C = 1
+ end
+ deprecate_constant "A", "DeprecateeWithAccessor::B::C"
+
+ class NewException < StandardError; end
+ deprecate_constant "OldException", "DeprecateeWithAccessor::NewException"
+end
+
class DeprecationTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Stream
@@ -162,6 +174,17 @@ class DeprecationTest < ActiveSupport::TestCase
assert_not_deprecated { assert_equal Deprecatee::B::C.class, Deprecatee::A.class }
end
+ def test_deprecated_constant_accessor
+ assert_not_deprecated { DeprecateeWithAccessor::B::C }
+ assert_deprecated("DeprecateeWithAccessor::A") { assert_equal DeprecateeWithAccessor::B::C, DeprecateeWithAccessor::A }
+ end
+
+ def test_deprecated_constant_accessor_exception
+ raise DeprecateeWithAccessor::NewException.new("Test")
+ rescue DeprecateeWithAccessor::OldException => e
+ assert_kind_of DeprecateeWithAccessor::NewException, e
+ end
+
def test_assert_deprecated_raises_when_method_not_deprecated
assert_raises(Minitest::Assertion) { assert_deprecated { @dtc.not } }
end
diff --git a/activesupport/test/gzip_test.rb b/activesupport/test/gzip_test.rb
index f51d3cdf65..33e0cd2a04 100644
--- a/activesupport/test/gzip_test.rb
+++ b/activesupport/test/gzip_test.rb
@@ -30,4 +30,14 @@ class GzipTest < ActiveSupport::TestCase
assert_equal true, (gzipped_by_best_compression.bytesize < gzipped_by_speed.bytesize)
end
+
+ def test_decompress_checks_crc
+ compressed = ActiveSupport::Gzip.compress("Hello World")
+ first_crc_byte_index = compressed.bytesize - 8
+ compressed.setbyte(first_crc_byte_index, compressed.getbyte(first_crc_byte_index) ^ 0xff)
+
+ assert_raises(Zlib::GzipFile::CRCError) do
+ ActiveSupport::Gzip.decompress(compressed)
+ end
+ end
end
diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb
new file mode 100644
index 0000000000..ee862320e8
--- /dev/null
+++ b/activesupport/test/hash_with_indifferent_access_test.rb
@@ -0,0 +1,734 @@
+require "abstract_unit"
+require "active_support/core_ext/hash"
+require "bigdecimal"
+require "active_support/core_ext/string/access"
+require "active_support/ordered_hash"
+require "active_support/core_ext/object/conversions"
+require "active_support/core_ext/object/deep_dup"
+require "active_support/inflections"
+
+class HashWithIndifferentAccessTest < ActiveSupport::TestCase
+ HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
+
+ class IndifferentHash < ActiveSupport::HashWithIndifferentAccess
+ end
+
+ class SubclassingArray < Array
+ end
+
+ class SubclassingHash < Hash
+ end
+
+ class NonIndifferentHash < Hash
+ def nested_under_indifferent_access
+ self
+ end
+ end
+
+ class HashByConversion
+ def initialize(hash)
+ @hash = hash
+ end
+
+ def to_hash
+ @hash
+ end
+ end
+
+ def setup
+ @strings = { "a" => 1, "b" => 2 }
+ @nested_strings = { "a" => { "b" => { "c" => 3 } } }
+ @symbols = { a: 1, b: 2 }
+ @nested_symbols = { a: { b: { c: 3 } } }
+ @mixed = { :a => 1, "b" => 2 }
+ @nested_mixed = { "a" => { b: { "c" => 3 } } }
+ @integers = { 0 => 1, 1 => 2 }
+ @nested_integers = { 0 => { 1 => { 2 => 3 } } }
+ @illegal_symbols = { [] => 3 }
+ @nested_illegal_symbols = { [] => { [] => 3 } }
+ end
+
+ def test_symbolize_keys_for_hash_with_indifferent_access
+ assert_instance_of Hash, @symbols.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @symbols.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @strings.with_indifferent_access.symbolize_keys
+ assert_equal @symbols, @mixed.with_indifferent_access.symbolize_keys
+ end
+
+ def test_deep_symbolize_keys_for_hash_with_indifferent_access
+ assert_instance_of Hash, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_strings.with_indifferent_access.deep_symbolize_keys
+ assert_equal @nested_symbols, @nested_mixed.with_indifferent_access.deep_symbolize_keys
+ end
+
+ def test_symbolize_keys_bang_for_hash_with_indifferent_access
+ assert_raise(NoMethodError) { @symbols.with_indifferent_access.dup.symbolize_keys! }
+ assert_raise(NoMethodError) { @strings.with_indifferent_access.dup.symbolize_keys! }
+ assert_raise(NoMethodError) { @mixed.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_bang_for_hash_with_indifferent_access
+ assert_raise(NoMethodError) { @nested_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_strings.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ assert_raise(NoMethodError) { @nested_mixed.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
+ assert_equal @illegal_symbols, @illegal_symbols.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @illegal_symbols.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_preserves_keys_that_cant_be_symbolized_for_hash_with_indifferent_access
+ assert_equal @nested_illegal_symbols, @nested_illegal_symbols.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_illegal_symbols.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @integers, @integers.with_indifferent_access.symbolize_keys
+ assert_raise(NoMethodError) { @integers.with_indifferent_access.dup.symbolize_keys! }
+ end
+
+ def test_deep_symbolize_keys_preserves_integer_keys_for_hash_with_indifferent_access
+ assert_equal @nested_integers, @nested_integers.with_indifferent_access.deep_symbolize_keys
+ assert_raise(NoMethodError) { @nested_integers.with_indifferent_access.deep_dup.deep_symbolize_keys! }
+ end
+
+ def test_stringify_keys_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.stringify_keys
+ assert_equal @strings, @symbols.with_indifferent_access.stringify_keys
+ assert_equal @strings, @strings.with_indifferent_access.stringify_keys
+ assert_equal @strings, @mixed.with_indifferent_access.stringify_keys
+ end
+
+ def test_deep_stringify_keys_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_stringify_keys
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_stringify_keys
+ end
+
+ def test_stringify_keys_bang_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @symbols.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @symbols.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @strings.with_indifferent_access.dup.stringify_keys!
+ assert_equal @strings, @mixed.with_indifferent_access.dup.stringify_keys!
+ end
+
+ def test_deep_stringify_keys_bang_for_hash_with_indifferent_access
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, @nested_symbols.with_indifferent_access.dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_symbols.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_strings.with_indifferent_access.deep_dup.deep_stringify_keys!
+ assert_equal @nested_strings, @nested_mixed.with_indifferent_access.deep_dup.deep_stringify_keys!
+ end
+
+ def test_nested_under_indifferent_access
+ foo = { "foo" => SubclassingHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of ActiveSupport::HashWithIndifferentAccess, foo["foo"]
+
+ foo = { "foo" => NonIndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of NonIndifferentHash, foo["foo"]
+
+ foo = { "foo" => IndifferentHash.new.tap { |h| h["bar"] = "baz" } }.with_indifferent_access
+ assert_kind_of IndifferentHash, foo["foo"]
+ end
+
+ def test_indifferent_assorted
+ @strings = @strings.with_indifferent_access
+ @symbols = @symbols.with_indifferent_access
+ @mixed = @mixed.with_indifferent_access
+
+ assert_equal "a", @strings.__send__(:convert_key, :a)
+
+ assert_equal 1, @strings.fetch("a")
+ assert_equal 1, @strings.fetch(:a.to_s)
+ assert_equal 1, @strings.fetch(:a)
+
+ hashes = { :@strings => @strings, :@symbols => @symbols, :@mixed => @mixed }
+ method_map = { '[]': 1, fetch: 1, values_at: [1],
+ has_key?: true, include?: true, key?: true,
+ member?: true }
+
+ hashes.each do |name, hash|
+ method_map.sort_by(&:to_s).each do |meth, expected|
+ assert_equal(expected, hash.__send__(meth, "a"),
+ "Calling #{name}.#{meth} 'a'")
+ assert_equal(expected, hash.__send__(meth, :a),
+ "Calling #{name}.#{meth} :a")
+ end
+ end
+
+ assert_equal [1, 2], @strings.values_at("a", "b")
+ assert_equal [1, 2], @strings.values_at(:a, :b)
+ assert_equal [1, 2], @symbols.values_at("a", "b")
+ assert_equal [1, 2], @symbols.values_at(:a, :b)
+ assert_equal [1, 2], @mixed.values_at("a", "b")
+ assert_equal [1, 2], @mixed.values_at(:a, :b)
+ end
+
+ def test_indifferent_reading
+ hash = HashWithIndifferentAccess.new
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = nil
+
+ assert_equal 1, hash[:a]
+ assert_equal true, hash[:b]
+ assert_equal false, hash[:c]
+ assert_nil hash[:d]
+ assert_nil hash[:e]
+ end
+
+ def test_indifferent_reading_with_nonnil_default
+ hash = HashWithIndifferentAccess.new(1)
+ hash["a"] = 1
+ hash["b"] = true
+ hash["c"] = false
+ hash["d"] = nil
+
+ assert_equal 1, hash[:a]
+ assert_equal true, hash[:b]
+ assert_equal false, hash[:c]
+ assert_nil hash[:d]
+ assert_equal 1, hash[:e]
+ end
+
+ def test_indifferent_writing
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 1
+ hash["b"] = 2
+ hash[3] = 3
+
+ assert_equal 1, hash["a"]
+ assert_equal 2, hash["b"]
+ assert_equal 1, hash[:a]
+ assert_equal 2, hash[:b]
+ assert_equal 3, hash[3]
+ end
+
+ def test_indifferent_update
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = "a"
+ hash["b"] = "b"
+
+ updated_with_strings = hash.update(@strings)
+ updated_with_symbols = hash.update(@symbols)
+ updated_with_mixed = hash.update(@mixed)
+
+ assert_equal 1, updated_with_strings[:a]
+ assert_equal 1, updated_with_strings["a"]
+ assert_equal 2, updated_with_strings["b"]
+
+ assert_equal 1, updated_with_symbols[:a]
+ assert_equal 2, updated_with_symbols["b"]
+ assert_equal 2, updated_with_symbols[:b]
+
+ assert_equal 1, updated_with_mixed[:a]
+ assert_equal 2, updated_with_mixed["b"]
+
+ assert [updated_with_strings, updated_with_symbols, updated_with_mixed].all? { |h| h.keys.size == 2 }
+ end
+
+ def test_update_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash.update HashByConversion.new(a: 1)
+ assert_equal 1, hash["a"]
+ end
+
+ def test_indifferent_merging
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = "failure"
+ hash["b"] = "failure"
+
+ other = { "a" => 1, :b => 2 }
+
+ merged = hash.merge(other)
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 1, merged[:a]
+ assert_equal 2, merged["b"]
+
+ hash.update(other)
+
+ assert_equal 1, hash[:a]
+ assert_equal 2, hash["b"]
+ end
+
+ def test_merge_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ merged = hash.merge HashByConversion.new(a: 1)
+ assert_equal 1, merged["a"]
+ end
+
+ def test_indifferent_replace
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 42
+
+ replaced = hash.replace(b: 12)
+
+ assert hash.key?("b")
+ assert !hash.key?(:a)
+ assert_equal 12, hash[:b]
+ assert_same hash, replaced
+ end
+
+ def test_replace_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 42
+
+ replaced = hash.replace(HashByConversion.new(b: 12))
+
+ assert hash.key?("b")
+ assert !hash.key?(:a)
+ assert_equal 12, hash[:b]
+ assert_same hash, replaced
+ end
+
+ def test_indifferent_merging_with_block
+ hash = HashWithIndifferentAccess.new
+ hash[:a] = 1
+ hash["b"] = 3
+
+ other = { "a" => 4, :b => 2, "c" => 10 }
+
+ merged = hash.merge(other) { |key, old, new| old > new ? old : new }
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 4, merged[:a]
+ assert_equal 3, merged["b"]
+ assert_equal 10, merged[:c]
+
+ other_indifferent = HashWithIndifferentAccess.new("a" => 9, :b => 2)
+
+ merged = hash.merge(other_indifferent) { |key, old, new| old + new }
+
+ assert_equal HashWithIndifferentAccess, merged.class
+ assert_equal 10, merged[:a]
+ assert_equal 5, merged[:b]
+ 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]
+ assert_equal "clobber", hash[:another]
+ end
+
+ def test_indifferent_with_defaults_aliases_reverse_merge
+ hash = HashWithIndifferentAccess.new key: :old_value
+ actual = hash.with_defaults key: :new_value
+ assert_equal :old_value, actual[:key]
+
+ hash = HashWithIndifferentAccess.new key: :old_value
+ hash.with_defaults! key: :new_value
+ assert_equal :old_value, hash[:key]
+ end
+
+ def test_indifferent_deleting
+ get_hash = proc { { a: "foo" }.with_indifferent_access }
+ hash = get_hash.call
+ assert_equal "foo", hash.delete(:a)
+ assert_nil hash.delete(:a)
+ hash = get_hash.call
+ assert_equal "foo", hash.delete("a")
+ assert_nil hash.delete("a")
+ end
+
+ def test_indifferent_select
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).select { |k, v| v == 1 }
+
+ assert_equal({ "a" => 1 }, hash)
+ 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 }
+
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash
+ end
+
+ def test_indifferent_select_bang
+ indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings)
+ indifferent_strings.select! { |k, v| v == 1 }
+
+ assert_equal({ "a" => 1 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_reject
+ hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).reject { |k, v| v != 1 }
+
+ assert_equal({ "a" => 1 }, hash)
+ 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 }
+
+ assert_equal({ "a" => 1 }, indifferent_strings)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings
+ end
+
+ def test_indifferent_compact
+ hash_contain_nil_value = @strings.merge("z" => nil)
+ hash = ActiveSupport::HashWithIndifferentAccess.new(hash_contain_nil_value)
+ compacted_hash = hash.compact
+
+ assert_equal(@strings, compacted_hash)
+ assert_equal(hash_contain_nil_value, hash)
+ assert_instance_of ActiveSupport::HashWithIndifferentAccess, compacted_hash
+
+ empty_hash = ActiveSupport::HashWithIndifferentAccess.new
+ compacted_hash = empty_hash.compact
+
+ assert_equal compacted_hash, empty_hash
+
+ non_empty_hash = ActiveSupport::HashWithIndifferentAccess.new(foo: :bar)
+ compacted_hash = non_empty_hash.compact
+
+ assert_equal compacted_hash, non_empty_hash
+ end
+
+ def test_indifferent_to_hash
+ # Should convert to a Hash with String keys.
+ assert_equal @strings, @mixed.with_indifferent_access.to_hash
+
+ # Should preserve the default value.
+ mixed_with_default = @mixed.dup
+ mixed_with_default.default = "1234"
+ roundtrip = mixed_with_default.with_indifferent_access.to_hash
+ assert_equal @strings, roundtrip
+ assert_equal "1234", roundtrip.default
+
+ # Ensure nested hashes are not HashWithIndiffereneAccess
+ new_to_hash = @nested_mixed.with_indifferent_access.to_hash
+ assert_not new_to_hash.instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"].instance_of?(HashWithIndifferentAccess)
+ assert_not new_to_hash["a"]["b"].instance_of?(HashWithIndifferentAccess)
+ end
+
+ def test_lookup_returns_the_same_object_that_is_stored_in_hash_indifferent_access
+ hash = HashWithIndifferentAccess.new { |h, k| h[k] = [] }
+ hash[:a] << 1
+
+ assert_equal [1], hash[:a]
+ end
+
+ def test_with_indifferent_access_has_no_side_effects_on_existing_hash
+ hash = { content: [{ :foo => :bar, "bar" => "baz" }] }
+ hash.with_indifferent_access
+
+ assert_equal [:foo, "bar"], hash[:content].first.keys
+ end
+
+ def test_indifferent_hash_with_array_of_hashes
+ hash = { "urls" => { "url" => [ { "address" => "1" }, { "address" => "2" } ] } }.with_indifferent_access
+ assert_equal "1", hash[:urls][:url].first[:address]
+
+ hash = hash.to_hash
+ assert_not hash.instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"].instance_of?(HashWithIndifferentAccess)
+ assert_not hash["urls"]["url"].first.instance_of?(HashWithIndifferentAccess)
+ end
+
+ def test_should_preserve_array_subclass_when_value_is_array
+ array = SubclassingArray.new
+ array << { "address" => "1" }
+ hash = { "urls" => { "url" => array } }.with_indifferent_access
+ assert_equal SubclassingArray, hash[:urls][:url].class
+ end
+
+ def test_should_preserve_array_class_when_hash_value_is_frozen_array
+ array = SubclassingArray.new
+ array << { "address" => "1" }
+ hash = { "urls" => { "url" => array.freeze } }.with_indifferent_access
+ assert_equal SubclassingArray, hash[:urls][:url].class
+ end
+
+ def test_stringify_and_symbolize_keys_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h[:first] = 1
+ h = h.stringify_keys
+ assert_equal 1, h["first"]
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h = h.symbolize_keys
+ assert_equal 1, h[:first]
+ end
+
+ def test_deep_stringify_and_deep_symbolize_keys_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h[:first] = 1
+ h = h.deep_stringify_keys
+ assert_equal 1, h["first"]
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h = h.deep_symbolize_keys
+ assert_equal 1, h[:first]
+ end
+
+ def test_to_options_on_indifferent_preserves_hash
+ h = HashWithIndifferentAccess.new
+ h["first"] = 1
+ h.to_options!
+ assert_equal 1, h["first"]
+ end
+
+ def test_to_options_on_indifferent_preserves_works_as_hash_with_dup
+ h = HashWithIndifferentAccess.new(a: { b: "b" })
+ dup = h.dup
+
+ dup[:a][:c] = "c"
+ assert_equal "c", h[:a][:c]
+ end
+
+ def test_indifferent_sub_hashes
+ h = { "user" => { "id" => 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
+
+ h = { user: { id: 5 } }.with_indifferent_access
+ ["user", :user].each { |user| [:id, "id"].each { |id| assert_equal 5, h[user][id], "h[#{user.inspect}][#{id.inspect}] should be 5" } }
+ end
+
+ def test_indifferent_duplication
+ # Should preserve default value
+ h = HashWithIndifferentAccess.new
+ h.default = "1234"
+ assert_equal h.default, h.dup.default
+
+ # Should preserve class for subclasses
+ h = IndifferentHash.new
+ assert_equal h.class, h.dup.class
+ end
+
+ def test_nested_dig_indifferent_access
+ skip if RUBY_VERSION < "2.3.0"
+ data = { "this" => { "views" => 1234 } }.with_indifferent_access
+ assert_equal 1234, data.dig(:this, :views)
+ end
+
+ def test_assorted_keys_not_stringified
+ original = { Object.new => 2, 1 => 2, [] => true }
+ indiff = original.with_indifferent_access
+ assert(!indiff.keys.any? { |k| k.kind_of? String }, "A key was converted to a string!")
+ end
+
+ def test_deep_merge_on_indifferent_access
+ hash_1 = HashWithIndifferentAccess.new(a: "a", b: "b", c: { c1: "c1", c2: "c2", c3: { d1: "d1" } })
+ hash_2 = HashWithIndifferentAccess.new(a: 1, c: { c1: 2, c3: { d2: "d2" } })
+ hash_3 = { a: 1, c: { c1: 2, c3: { d2: "d2" } } }
+ expected = { "a" => 1, "b" => "b", "c" => { "c1" => 2, "c2" => "c2", "c3" => { "d1" => "d1", "d2" => "d2" } } }
+ assert_equal expected, hash_1.deep_merge(hash_2)
+ assert_equal expected, hash_1.deep_merge(hash_3)
+
+ hash_1.deep_merge!(hash_2)
+ assert_equal expected, hash_1
+ end
+
+ def test_store_on_indifferent_access
+ hash = HashWithIndifferentAccess.new
+ hash.store(:test1, 1)
+ hash.store("test1", 11)
+ hash[:test2] = 2
+ hash["test2"] = 22
+ expected = { "test1" => 11, "test2" => 22 }
+ assert_equal expected, hash
+ end
+
+ def test_constructor_on_indifferent_access
+ hash = HashWithIndifferentAccess[:foo, 1]
+ assert_equal 1, hash[:foo]
+ assert_equal 1, hash["foo"]
+ hash[:foo] = 3
+ assert_equal 3, hash[:foo]
+ assert_equal 3, hash["foo"]
+ end
+
+ def test_indifferent_slice
+ original = { a: "x", b: "y", c: 10 }.with_indifferent_access
+ expected = { a: "x", b: "y" }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ # Should return a new hash with only the given keys.
+ assert_equal expected, original.slice(*keys), keys.inspect
+ assert_not_equal expected, original
+ end
+ end
+
+ def test_indifferent_slice_inplace
+ original = { a: "x", b: "y", c: 10 }.with_indifferent_access
+ expected = { c: 10 }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ # Should replace the hash with only the given keys.
+ copy = original.dup
+ assert_equal expected, copy.slice!(*keys)
+ end
+ end
+
+ def test_indifferent_slice_access_with_symbols
+ original = { "login" => "bender", "password" => "shiny", "stuff" => "foo" }
+ original = original.with_indifferent_access
+
+ slice = original.slice(:login, :password)
+
+ assert_equal "bender", slice[:login]
+ assert_equal "bender", slice["login"]
+ end
+
+ def test_indifferent_extract
+ original = { :a => 1, "b" => 2, :c => 3, "d" => 4 }.with_indifferent_access
+ expected = { a: 1, b: 2 }.with_indifferent_access
+ remaining = { c: 3, d: 4 }.with_indifferent_access
+
+ [["a", "b"], [:a, :b]].each do |keys|
+ copy = original.dup
+ assert_equal expected, copy.extract!(*keys)
+ assert_equal remaining, copy
+ end
+ end
+
+ def test_new_with_to_hash_conversion
+ hash = HashWithIndifferentAccess.new(HashByConversion.new(a: 1))
+ 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_existent]
+ 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_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
+
+ def test_inheriting_from_top_level_hash_with_indifferent_access_preserves_ancestors_chain
+ klass = Class.new(::HashWithIndifferentAccess)
+ assert_equal ActiveSupport::HashWithIndifferentAccess, klass.ancestors[1]
+ end
+
+ def test_inheriting_from_hash_with_indifferent_access_properly_dumps_ivars
+ klass = Class.new(::HashWithIndifferentAccess) do
+ def initialize(*)
+ @foo = "bar"
+ super
+ end
+ end
+
+ yaml_output = klass.new.to_yaml
+
+ # `hash-with-ivars` was introduced in 2.0.9 (https://git.io/vyUQW)
+ if Gem::Version.new(Psych::VERSION) >= Gem::Version.new("2.0.9")
+ assert_includes yaml_output, "hash-with-ivars"
+ assert_includes yaml_output, "@foo: bar"
+ else
+ assert_includes yaml_output, "hash"
+ end
+ end
+ HashWithIndifferentAccess = ActiveSupport::HashWithIndifferentAccess
+
+ def test_should_use_default_proc_for_unknown_key
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_return_nil_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new { 1 + 2 }
+ assert_nil hash_wia.default
+ end
+
+ def test_should_use_default_value_for_unknown_key
+ hash_wia = HashWithIndifferentAccess.new(3)
+ assert_equal 3, hash_wia[:new_key]
+ end
+
+ def test_should_use_default_value_if_no_key_is_supplied
+ hash_wia = HashWithIndifferentAccess.new(3)
+ assert_equal 3, hash_wia.default
+ end
+
+ def test_should_nil_if_no_default_value_is_supplied
+ hash_wia = HashWithIndifferentAccess.new
+ assert_nil hash_wia.default
+ end
+
+ def test_should_return_dup_for_with_indifferent_access
+ hash_wia = HashWithIndifferentAccess.new
+ assert_equal hash_wia, hash_wia.with_indifferent_access
+ assert_not_same hash_wia, hash_wia.with_indifferent_access
+ end
+
+ def test_allows_setting_frozen_array_values_with_indifferent_access
+ value = [1, 2, 3].freeze
+ hash = HashWithIndifferentAccess.new
+ hash[:key] = value
+ assert_equal hash[:key], value
+ end
+
+ def test_should_copy_the_default_value_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new(3)
+ hash_wia = hash.with_indifferent_access
+ assert_equal 3, hash_wia.default
+ end
+
+ def test_should_copy_the_default_proc_when_converting_to_hash_with_indifferent_access
+ hash = Hash.new do
+ 2 + 1
+ end
+ assert_equal 3, hash[:foo]
+
+ hash_wia = hash.with_indifferent_access
+ assert_equal 3, hash_wia[:foo]
+ assert_equal 3, hash_wia[:bar]
+ end
+end
diff --git a/activesupport/test/inflector_test.rb b/activesupport/test/inflector_test.rb
index 03d7b3fe94..14bc10513b 100644
--- a/activesupport/test/inflector_test.rb
+++ b/activesupport/test/inflector_test.rb
@@ -119,6 +119,13 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ MixtureToTitleCaseWithKeepIdSuffix.each_with_index do |(before, titleized), index|
+ define_method "test_titleize_with_keep_id_suffix_mixture_to_title_case_#{index}" do
+ assert_equal(titleized, ActiveSupport::Inflector.titleize(before, keep_id_suffix: true),
+ "mixture to TitleCase with keep_id_suffix failed for #{before}")
+ end
+ end
+
def test_camelize
CamelToUnderscore.each do |camel, underscore|
assert_equal(camel, ActiveSupport::Inflector.camelize(underscore))
@@ -324,6 +331,12 @@ class InflectorTest < ActiveSupport::TestCase
end
end
+ def test_humanize_with_keep_id_suffix
+ UnderscoreToHumanWithKeepIdSuffix.each do |underscore, human|
+ assert_equal(human, ActiveSupport::Inflector.humanize(underscore, keep_id_suffix: true))
+ end
+ end
+
def test_humanize_by_rule
ActiveSupport::Inflector.inflections do |inflect|
inflect.human(/_cnt$/i, '\1_count')
diff --git a/activesupport/test/inflector_test_cases.rb b/activesupport/test/inflector_test_cases.rb
index b660987d92..d61ca3fc18 100644
--- a/activesupport/test/inflector_test_cases.rb
+++ b/activesupport/test/inflector_test_cases.rb
@@ -248,12 +248,27 @@ module InflectorTestCases
"_external_id" => "External"
}
+ UnderscoreToHumanWithKeepIdSuffix = {
+ "this_is_a_string_ending_with_id" => "This is a string ending with id",
+ "employee_id" => "Employee id",
+ "employee_id_something_else" => "Employee id something else",
+ "underground" => "Underground",
+ "_id" => "Id",
+ "_external_id" => "External id"
+ }
+
UnderscoreToHumanWithoutCapitalize = {
"employee_salary" => "employee salary",
"employee_id" => "employee",
"underground" => "underground"
}
+ MixtureToTitleCaseWithKeepIdSuffix = {
+ "this_is_a_string_ending_with_id" => "This Is A String Ending With Id",
+ "EmployeeId" => "Employee Id",
+ "Author Id" => "Author Id"
+ }
+
MixtureToTitleCase = {
"active_record" => "Active Record",
"ActiveRecord" => "Active Record",
@@ -271,6 +286,7 @@ module InflectorTestCases
"¿por qué?" => "¿Por Qué?",
"Fred’s" => "Fred’s",
"Fred`s" => "Fred`s",
+ "this was 'fake news'" => "This Was 'Fake News'",
ActiveSupport::SafeBuffer.new("confirmation num") => "Confirmation Num"
}
diff --git a/activesupport/test/json/encoding_test_cases.rb b/activesupport/test/json/encoding_test_cases.rb
index b2f0cf3048..7e4775cec8 100644
--- a/activesupport/test/json/encoding_test_cases.rb
+++ b/activesupport/test/json/encoding_test_cases.rb
@@ -1,4 +1,8 @@
require "bigdecimal"
+require "date"
+require "time"
+require "pathname"
+require "uri"
module JSONTest
class Foo
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 4794b55742..de111cc40e 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -215,6 +215,95 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_equal secs, twz.to_f
end
+ def test_iso8601
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T19:00:00")
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
+ assert_equal Time.utc(2000), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_fractional_seconds
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T19:00:00.750")
+ assert_equal 750000, twz.time.usec
+ assert_equal Time.utc(1999, 12, 31, 19, 0, 0 + Rational(3, 4)), twz.time
+ assert_equal Time.utc(2000, 1, 1, 0, 0, 0 + Rational(3, 4)), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_zone
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T14:00:00-10:00")
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
+ assert_equal Time.utc(2000), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_invalid_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.iso8601("foobar")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_iso8601_with_missing_time_components
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31")
+ assert_equal Time.utc(1999, 12, 31, 0, 0, 0), twz.time
+ assert_equal Time.utc(1999, 12, 31, 5, 0, 0), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_with_old_date
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1883-12-31T19:00:00")
+ assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_far_future_date_with_time_zone_offset_in_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("2050-12-31T19:00:00-10:00") # i.e., 2050-01-01 05:00:00 UTC
+ assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_iso8601_should_not_black_out_system_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.iso8601("2012-03-25T03:29:00")
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_iso8601_should_black_out_app_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.iso8601("2012-03-11T02:29:00")
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_iso8601_doesnt_use_local_dst
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["UTC"]
+ twz = zone.iso8601("2013-03-10T02:00:00")
+ assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time
+ end
+ end
+
+ def test_iso8601_handles_dst_jump
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("2013-03-10T02:00:00")
+ assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time
+ end
+ end
+
def test_parse
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.parse("1999-12-31 19:00:00")
@@ -314,6 +403,99 @@ class TimeZoneTest < ActiveSupport::TestCase
end
end
+ def test_rfc3339
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.rfc3339("1999-12-31T14:00:00-10:00")
+ assert_equal Time.utc(1999, 12, 31, 19), twz.time
+ assert_equal Time.utc(2000), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_with_fractional_seconds
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("1999-12-31T14:00:00.750-10:00")
+ assert_equal 750000, twz.time.usec
+ assert_equal Time.utc(1999, 12, 31, 19, 0, 0 + Rational(3, 4)), twz.time
+ assert_equal Time.utc(2000, 1, 1, 0, 0, 0 + Rational(3, 4)), twz.utc
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_with_missing_time
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.rfc3339("1999-12-31")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_rfc3339_with_missing_offset
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.rfc3339("1999-12-31T19:00:00")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_rfc3339_with_invalid_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+
+ exception = assert_raises(ArgumentError) do
+ zone.rfc3339("foobar")
+ end
+
+ assert_equal "invalid date", exception.message
+ end
+
+ def test_rfc3339_with_old_date
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.rfc3339("1883-12-31T19:00:00-05:00")
+ assert_equal [0, 0, 19, 31, 12, 1883], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_far_future_date_with_time_zone_offset_in_string
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.rfc3339("2050-12-31T19:00:00-10:00") # i.e., 2050-01-01 05:00:00 UTC
+ assert_equal [0, 0, 0, 1, 1, 2051], twz.to_a[0, 6]
+ assert_equal zone, twz.time_zone
+ end
+
+ def test_rfc3339_should_not_black_out_system_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.rfc3339("2012-03-25T03:29:00-07:00")
+ assert_equal [0, 29, 3, 25, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_rfc3339_should_black_out_app_timezone_dst_jump
+ with_env_tz("EET") do
+ zone = ActiveSupport::TimeZone["Pacific Time (US & Canada)"]
+ twz = zone.rfc3339("2012-03-11T02:29:00-08:00")
+ assert_equal [0, 29, 3, 11, 3, 2012], twz.to_a[0, 6]
+ end
+ end
+
+ def test_rfc3339_doesnt_use_local_dst
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["UTC"]
+ twz = zone.rfc3339("2013-03-10T02:00:00Z")
+ assert_equal Time.utc(2013, 3, 10, 2, 0, 0), twz.time
+ end
+ end
+
+ def test_rfc3339_handles_dst_jump
+ with_env_tz "US/Eastern" do
+ zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
+ twz = zone.iso8601("2013-03-10T02:00:00-05:00")
+ assert_equal Time.utc(2013, 3, 10, 3, 0, 0), twz.time
+ end
+ end
+
def test_strptime
zone = ActiveSupport::TimeZone["Eastern Time (US & Canada)"]
twz = zone.strptime("1999-12-31 12:00:00", "%Y-%m-%d %H:%M:%S")
@@ -532,6 +714,10 @@ class TimeZoneTest < ActiveSupport::TestCase
assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"]
end
+ def test_country_zones_without_mappings
+ assert_includes ActiveSupport::TimeZone.country_zones(:sv), ActiveSupport::TimeZone["America/El_Salvador"]
+ end
+
def test_to_yaml
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Pacific/Honolulu\n", ActiveSupport::TimeZone["Hawaii"].to_yaml)
assert_equal("--- !ruby/object:ActiveSupport::TimeZone\nname: Europe/London\n", ActiveSupport::TimeZone["Europe/London"].to_yaml)