New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Deprecate implicit coercion of ActiveSupport::Duration
#28204
Conversation
@@ -832,7 +832,7 @@ def test_select_date | |||
|
|||
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: 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]) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test exhibits the exact problem I'm trying to fix - Date.today.year - 100.years
gives -3155757983.0
and not the probable intent of 1917
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhh this is the problem I'm trying to solve with the deprecation - complete misunderstanding of how durations work.
@@ -1,3 +1,21 @@ | |||
* Deprecate implicit coercion of `ActiveSupport::Duration` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This changelog is confusion. What are the cases where an implicit coercion is made?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better now?
6719f0b
to
d8430a3
Compare
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.
d8430a3
to
75924c4
Compare
@@ -76,7 +76,7 @@ def test_should_enqueue_and_run_correctly_in_activejob | |||
|
|||
test "should enqueue a delivery with a delay" 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dhh this has now been changed to Time.current + 600.seconds
which it what it should've been in the first place. Also changed another test to Time.current + 1.hour
instead of Time.now.to_f + 3600
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cools. I would even go with Time.current + 10.minutes. That's the wonder of these aliases. You get to pick the scale that makes the most sense.
@@ -156,7 +156,7 @@ def write_entry(key, entry, options) | |||
expires_in = options[:expires_in].to_i | |||
if expires_in > 0 && !options[:raw] | |||
# Set the memcache expire a few minutes in the future to support race condition ttls on read | |||
expires_in += 5.minutes | |||
expires_in += 300 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change stems from the to_i
- the memcache client expects an integer for expires_in
. It could be rewritten to use 5.minutes
as the receiver but I thought creating an object was wasteful this deep inside the caching code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless we pin this as a performance bottleneck, I would always go for the clearer code. IMO, 5.minutes is much clearer than 300. This is where I really don't like to manually apply .to_i. But I could live with an alias like expires_in += 5.minutes.in_seconds
.
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.
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.
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: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:
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:In Rails 5.2 it will raise
TypeError
, e.g:This is the same behavior as with other types in Ruby, e.g:
As part of this deprecation add
*
and/
methods toAS::Duration
so that calculations that keep the duration as the receiver workcorrectly whether the final receiver is a
Date
orTime
, e.g:Fixes #27457.