aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG.md629
-rw-r--r--activesupport/lib/active_support/core_ext/object/duplicable.rb38
-rw-r--r--activesupport/lib/active_support/core_ext/string/inflections.rb32
-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/evented_file_update_checker.rb5
-rw-r--r--activesupport/lib/active_support/gem_version.rb4
-rw-r--r--activesupport/lib/active_support/inflector/methods.rb40
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb17
-rw-r--r--activesupport/test/core_ext/object/duplicable_test.rb5
-rw-r--r--activesupport/test/core_ext/string_ext_test.rb12
-rw-r--r--activesupport/test/deprecation_test.rb23
-rw-r--r--activesupport/test/inflector_test.rb13
-rw-r--r--activesupport/test/inflector_test_cases.rb15
-rw-r--r--activesupport/test/time_zone_test.rb4
15 files changed, 214 insertions, 676 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index d12cbc34dd..6f99155199 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,628 +1 @@
-* Fixed bug in `DateAndTime::Compatibility#to_time` that caused it to
- raise `RuntimeError: can't modify frozen Time` when called on any frozen `Time`.
- Properly pass through the frozen `Time` or `ActiveSupport::TimeWithZone` object
- when calling `#to_time`.
-
- *Kevin McPhillips* & *Andrew White*
-
-* Remove implicit coercion deprecation of durations
-
- In #28204 we deprecated implicit conversion of durations to a numeric which
- represented the number of seconds in the duration because of unwanted side
- effects with calculations on durations and dates. This unfortunately had
- the side effect of forcing a explicit cast when configuring third-party
- libraries like expiration in Redis, e.g:
-
- redis.expire("foo", 5.minutes)
-
- To work around this we've removed the deprecation and added a private class
- that wraps the numeric and can perform calculation involving durations and
- ensure that they remain a duration irrespective of the order of operations.
-
- *Andrew White*
-
-* Update `titleize` regex to allow apostrophes
-
- In 4b685aa the regex in `titleize` was updated to not match apostrophes to
- better reflect the nature of the transformation. Unfortunately, this had the
- side effect of breaking capitalization on the first word of a sub-string, e.g:
-
- >> "This was 'fake news'".titleize
- => "This Was 'fake News'"
-
- This is fixed by extending the look-behind to also check for a word
- character on the other side of the apostrophe.
-
- Fixes #28312.
-
- *Andrew White*
-
-* Add `rfc3339` aliases to `xmlschema` for `Time` and `ActiveSupport::TimeWithZone`
-
- For naming consistency when using the RFC 3339 profile of ISO 8601 in applications.
-
- *Andrew White*
-
-* Add `Time.rfc3339` parsing method
-
- `Time.xmlschema` and consequently its alias `iso8601` accepts timestamps
- without a offset in contravention of the RFC 3339 standard. This method
- enforces that constraint and raises an `ArgumentError` if it doesn't.
-
- *Andrew White*
-
-* Add `ActiveSupport::TimeZone.rfc3339` parsing method
-
- Previously, there was no way to get a RFC 3339 timestamp into a specific
- timezone without either using `parse` or chaining methods. The new method
- allows parsing directly into the timezone, e.g:
-
- >> Time.zone = "Hawaii"
- => "Hawaii"
- >> Time.zone.rfc3339("1999-12-31T14:00:00Z")
- => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
- This new method has stricter semantics than the current `parse` method,
- and will raise an `ArgumentError` instead of returning nil, e.g:
-
- >> Time.zone = "Hawaii"
- => "Hawaii"
- >> Time.zone.rfc3339("foobar")
- ArgumentError: invalid date
- >> Time.zone.parse("foobar")
- => nil
-
- It will also raise an `ArgumentError` when either the time or offset
- components are missing, e.g:
-
- >> Time.zone = "Hawaii"
- => "Hawaii"
- >> Time.zone.rfc3339("1999-12-31")
- ArgumentError: invalid date
- >> Time.zone.rfc3339("1999-12-31T14:00:00")
- ArgumentError: invalid date
-
- *Andrew White*
-
-* Add `ActiveSupport::TimeZone.iso8601` parsing method
-
- Previously, there was no way to get a ISO 8601 timestamp into a specific
- timezone without either using `parse` or chaining methods. The new method
- allows parsing directly into the timezone, e.g:
-
- >> Time.zone = "Hawaii"
- => "Hawaii"
- >> Time.zone.iso8601("1999-12-31T14:00:00Z")
- => Fri, 31 Dec 1999 14:00:00 HST -10:00
-
- If the timestamp is a ISO 8601 date (YYYY-MM-DD), then the time is set
- to midnight, e.g:
-
- >> Time.zone = "Hawaii"
- => "Hawaii"
- >> Time.zone.iso8601("1999-12-31")
- => Fri, 31 Dec 1999 00:00:00 HST -10:00
-
- This new method has stricter semantics than the current `parse` method,
- and will raise an `ArgumentError` instead of returning nil, e.g:
-
- >> Time.zone = "Hawaii"
- => "Hawaii"
- >> Time.zone.iso8601("foobar")
- ArgumentError: invalid date
- >> Time.zone.parse("foobar")
- => nil
-
- *Andrew White*
-
-* Deprecate implicit coercion of `ActiveSupport::Duration`
-
- Currently `ActiveSupport::Duration` implicitly converts to a seconds
- value when used in a calculation except for the explicit examples of
- addition and subtraction where the duration is the receiver, e.g:
-
- >> 2 * 1.day
- => 172800
-
- This results in lots of confusion especially when using durations
- with dates because adding/subtracting a value from a date treats
- integers as a day and not a second, e.g:
-
- >> Date.today
- => Wed, 01 Mar 2017
- >> Date.today + 2 * 1.day
- => Mon, 10 Apr 2490
-
- To fix this we're implementing `coerce` so that we can provide a
- deprecation warning with the intent of removing the implicit coercion
- in Rails 5.2, e.g:
-
- >> 2 * 1.day
- DEPRECATION WARNING: Implicit coercion of ActiveSupport::Duration
- to a Numeric is deprecated and will raise a TypeError in Rails 5.2.
- => 172800
-
- In Rails 5.2 it will raise `TypeError`, e.g:
-
- >> 2 * 1.day
- TypeError: ActiveSupport::Duration can't be coerced into Integer
-
- This is the same behavior as with other types in Ruby, e.g:
-
- >> 2 * "foo"
- TypeError: String can't be coerced into Integer
- >> "foo" * 2
- => "foofoo"
-
- As part of this deprecation add `*` and `/` methods to `AS::Duration`
- so that calculations that keep the duration as the receiver work
- correctly whether the final receiver is a `Date` or `Time`, e.g:
-
- >> Date.today
- => Wed, 01 Mar 2017
- >> Date.today + 1.day * 2
- => Fri, 03 Mar 2017
-
- Fixes #27457.
-
- *Andrew White*
-
-* Update `DateTime#change` to support `:usec` and `:nsec` options.
-
- Adding support for these options now allows us to update the `DateTime#end_of`
- methods to match the equivalent `Time#end_of` methods, e.g:
-
- datetime = DateTime.now.end_of_day
- datetime.nsec == 999999999 # => true
-
- Fixes #21424.
-
- *Dan Moore*, *Andrew White*
-
-* Add `ActiveSupport::Duration#before` and `#after` as aliases for `#until` and `#since`
-
- These read more like English and require less mental gymnastics to read and write.
-
- Before:
-
- 2.weeks.since(customer_start_date)
- 5.days.until(today)
-
- After:
-
- 2.weeks.after(customer_start_date)
- 5.days.before(today)
-
- *Nick Johnstone*
-
-* Soft-deprecated the top-level `HashWithIndifferentAccess` constant.
- `ActiveSupport::HashWithIndifferentAccess` should be used instead.
-
- Fixes #28157.
-
- *Robin Dupret*
-
-* In Core Extensions, make `MarshalWithAutoloading#load` pass through the second, optional
- argument for `Marshal#load( source [, proc] )`. This way we don't have to do
- `Marshal.method(:load).super_method.call(source, proc)` just to be able to pass a proc.
-
- *Jeff Latz*
-
-* `ActiveSupport::Gzip.decompress` now checks checksum and length in footer.
-
- *Dylan Thacker-Smith*
-
-
-## Rails 5.1.0.beta1 (February 23, 2017) ##
-
-* Cache `ActiveSupport::TimeWithZone#to_datetime` before freezing.
-
- *Adam Rice*
-
-* Deprecate `ActiveSupport.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 duplication 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/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/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/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/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 a641b96c57..371a39a5e6 100644
--- a/activesupport/lib/active_support/gem_version.rb
+++ b/activesupport/lib/active_support/gem_version.rb
@@ -6,9 +6,9 @@ module ActiveSupport
module VERSION
MAJOR = 5
- MINOR = 1
+ MINOR = 2
TINY = 0
- PRE = "beta1"
+ PRE = "alpha"
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
end
diff --git a/activesupport/lib/active_support/inflector/methods.rb b/activesupport/lib/active_support/inflector/methods.rb
index 51c221ac0e..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(?<!\w['’`])[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/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index 18477b9f6b..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
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 41cc9888c6..a98951e889 100644
--- a/activesupport/test/core_ext/string_ext_test.rb
+++ b/activesupport/test/core_ext/string_ext_test.rb
@@ -78,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
@@ -199,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/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/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 f3352e3301..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",
diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb
index 1615d8fdb2..de111cc40e 100644
--- a/activesupport/test/time_zone_test.rb
+++ b/activesupport/test/time_zone_test.rb
@@ -714,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)