mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
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.
This commit is contained in:
parent
a815c7c721
commit
75924c4517
6 changed files with 149 additions and 8 deletions
|
@ -76,7 +76,7 @@ class MessageDeliveryTest < ActiveSupport::TestCase
|
||||||
|
|
||||||
test "should enqueue a delivery with a delay" do
|
test "should enqueue a delivery with a delay" do
|
||||||
travel_to Time.new(2004, 11, 24, 01, 04, 44) do
|
travel_to Time.new(2004, 11, 24, 01, 04, 44) do
|
||||||
assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f + 600.seconds, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
|
assert_performed_with(job: ActionMailer::DeliveryJob, at: Time.current.to_f + 600, args: ["DelayedMailer", "test_message", "deliver_now", 1, 2, 3]) do
|
||||||
@mail.deliver_later wait: 600.seconds
|
@mail.deliver_later wait: 600.seconds
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -832,7 +832,7 @@ class DateHelperTest < ActionView::TestCase
|
||||||
|
|
||||||
def test_select_date_with_too_big_range_between_start_year_and_end_year
|
def test_select_date_with_too_big_range_between_start_year_and_end_year
|
||||||
assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: 2000, end_year: 20000, prefix: "date[first]", order: [:month, :day, :year]) }
|
assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: 2000, end_year: 20000, prefix: "date[first]", order: [:month, :day, :year]) }
|
||||||
assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: Date.today.year - 100.years, end_year: 2000, prefix: "date[first]", order: [:month, :day, :year]) }
|
assert_raise(ArgumentError) { select_date(Time.mktime(2003, 8, 16), start_year: 100, end_year: 2000, prefix: "date[first]", order: [:month, :day, :year]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def test_select_date_can_have_more_then_1000_years_interval_if_forced_via_parameter
|
def test_select_date_can_have_more_then_1000_years_interval_if_forced_via_parameter
|
||||||
|
|
|
@ -1,3 +1,55 @@
|
||||||
|
* 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.
|
* Update `DateTime#change` to support `:usec` and `:nsec` options.
|
||||||
|
|
||||||
Adding support for these options now allows us to update the `DateTime#end_of`
|
Adding support for these options now allows us to update the `DateTime#end_of`
|
||||||
|
|
|
@ -156,7 +156,7 @@ module ActiveSupport
|
||||||
expires_in = options[:expires_in].to_i
|
expires_in = options[:expires_in].to_i
|
||||||
if expires_in > 0 && !options[:raw]
|
if expires_in > 0 && !options[:raw]
|
||||||
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
# Set the memcache expire a few minutes in the future to support race condition ttls on read
|
||||||
expires_in += 5.minutes
|
expires_in += 300
|
||||||
end
|
end
|
||||||
rescue_error_with false do
|
rescue_error_with false do
|
||||||
@data.send(method, key, value, expires_in, options)
|
@data.send(method, key, value, expires_in, options)
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
require "active_support/core_ext/array/conversions"
|
require "active_support/core_ext/array/conversions"
|
||||||
require "active_support/core_ext/object/acts_like"
|
require "active_support/core_ext/object/acts_like"
|
||||||
|
require "active_support/core_ext/string/filters"
|
||||||
|
require "active_support/deprecation"
|
||||||
|
|
||||||
module ActiveSupport
|
module ActiveSupport
|
||||||
# Provides accurate date and time measurements using Date#advance and
|
# Provides accurate date and time measurements using Date#advance and
|
||||||
|
@ -88,6 +90,25 @@ module ActiveSupport
|
||||||
@parts.default = 0
|
@parts.default = 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def coerce(other) #:nodoc:
|
||||||
|
ActiveSupport::Deprecation.warn(<<-MSG.squish)
|
||||||
|
Implicit coercion of ActiveSupport::Duration to a Numeric
|
||||||
|
is deprecated and will raise a TypeError in Rails 5.2.
|
||||||
|
MSG
|
||||||
|
|
||||||
|
[other, value]
|
||||||
|
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
|
# Adds another Duration or a Numeric to this Duration. Numeric values
|
||||||
# are treated as seconds.
|
# are treated as seconds.
|
||||||
def +(other)
|
def +(other)
|
||||||
|
@ -109,6 +130,24 @@ module ActiveSupport
|
||||||
self + (-other)
|
self + (-other)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Multiplies this Duration by a Numeric and returns a new Duration.
|
||||||
|
def *(other)
|
||||||
|
if Numeric === other
|
||||||
|
Duration.new(value * other, parts.map { |type, number| [type, number * other] })
|
||||||
|
else
|
||||||
|
value * other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Devides this Duration by a Numeric and returns a new Duration.
|
||||||
|
def /(other)
|
||||||
|
if Numeric === other
|
||||||
|
Duration.new(value / other, parts.map { |type, number| [type, number / other] })
|
||||||
|
else
|
||||||
|
value / other
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def -@ #:nodoc:
|
def -@ #:nodoc:
|
||||||
Duration.new(-value, parts.map { |type, number| [type, -number] })
|
Duration.new(-value, parts.map { |type, number| [type, -number] })
|
||||||
end
|
end
|
||||||
|
@ -212,8 +251,6 @@ module ActiveSupport
|
||||||
ISO8601Serializer.new(self, precision: precision).serialize
|
ISO8601Serializer.new(self, precision: precision).serialize
|
||||||
end
|
end
|
||||||
|
|
||||||
delegate :<=>, to: :value
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def sum(sign, time = ::Time.current)
|
def sum(sign, time = ::Time.current)
|
||||||
|
|
|
@ -84,9 +84,47 @@ class DurationTest < ActiveSupport::TestCase
|
||||||
assert_nothing_raised { Date.today - Date.today }
|
assert_nothing_raised { Date.today - Date.today }
|
||||||
end
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_multiply
|
||||||
|
assert_equal 7.days, 1.day * 7
|
||||||
|
assert_instance_of ActiveSupport::Duration, 1.day * 7
|
||||||
|
|
||||||
|
assert_deprecated do
|
||||||
|
assert_equal 86400, 1.day * 1.second
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_divide
|
||||||
|
assert_equal 1.day, 7.days / 7
|
||||||
|
assert_instance_of ActiveSupport::Duration, 7.days / 7
|
||||||
|
|
||||||
|
assert_deprecated do
|
||||||
|
assert_equal 1, 1.day / 1.day
|
||||||
|
end
|
||||||
|
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
|
def test_plus_with_time
|
||||||
|
assert_deprecated do
|
||||||
assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration"
|
assert_equal 1 + 1.second, 1.second + 1, "Duration + Numeric should == Numeric + Duration"
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_time_plus_duration_returns_same_time_datatype
|
def test_time_plus_duration_returns_same_time_datatype
|
||||||
twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"] , Time.utc(2016, 4, 28, 00, 45))
|
twz = ActiveSupport::TimeWithZone.new(nil, ActiveSupport::TimeZone["Moscow"] , Time.utc(2016, 4, 28, 00, 45))
|
||||||
|
@ -104,6 +142,13 @@ class DurationTest < ActiveSupport::TestCase
|
||||||
assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb"
|
assert_equal 'expected a time or date, got ""', e.message, "ensure ArgumentError is not being raised by dependencies.rb"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_implicit_coercion_is_deprecated
|
||||||
|
assert_deprecated { 1 + 1.second }
|
||||||
|
assert_deprecated { 1 - 1.second }
|
||||||
|
assert_deprecated { 1 * 1.second }
|
||||||
|
assert_deprecated { 1 / 1.second }
|
||||||
|
end
|
||||||
|
|
||||||
def test_fractional_weeks
|
def test_fractional_weeks
|
||||||
assert_equal((86400 * 7) * 1.5, 1.5.weeks)
|
assert_equal((86400 * 7) * 1.5, 1.5.weeks)
|
||||||
assert_equal((86400 * 7) * 1.7, 1.7.weeks)
|
assert_equal((86400 * 7) * 1.7, 1.7.weeks)
|
||||||
|
@ -241,14 +286,21 @@ class DurationTest < ActiveSupport::TestCase
|
||||||
def test_comparable
|
def test_comparable
|
||||||
assert_equal(-1, (0.seconds <=> 1.second))
|
assert_equal(-1, (0.seconds <=> 1.second))
|
||||||
assert_equal(-1, (1.second <=> 1.minute))
|
assert_equal(-1, (1.second <=> 1.minute))
|
||||||
|
|
||||||
|
assert_deprecated do
|
||||||
assert_equal(-1, (1 <=> 1.minute))
|
assert_equal(-1, (1 <=> 1.minute))
|
||||||
|
end
|
||||||
|
|
||||||
assert_equal(0, (0.seconds <=> 0.seconds))
|
assert_equal(0, (0.seconds <=> 0.seconds))
|
||||||
assert_equal(0, (0.seconds <=> 0.minutes))
|
assert_equal(0, (0.seconds <=> 0.minutes))
|
||||||
assert_equal(0, (1.second <=> 1.second))
|
assert_equal(0, (1.second <=> 1.second))
|
||||||
assert_equal(1, (1.second <=> 0.second))
|
assert_equal(1, (1.second <=> 0.second))
|
||||||
assert_equal(1, (1.minute <=> 1.second))
|
assert_equal(1, (1.minute <=> 1.second))
|
||||||
|
|
||||||
|
assert_deprecated do
|
||||||
assert_equal(1, (61 <=> 1.minute))
|
assert_equal(1, (61 <=> 1.minute))
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def test_twelve_months_equals_one_year
|
def test_twelve_months_equals_one_year
|
||||||
assert_equal 12.months, 1.year
|
assert_equal 12.months, 1.year
|
||||||
|
|
Loading…
Reference in a new issue