Skip to content
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

Add Duration#before and #after as aliases for #ago and #since #27721

Closed
wants to merge 2 commits into from

Conversation

Widdershin
Copy link
Contributor

It's common in test cases at my job to have code like this:

let(:today) { customer_start_date + 2.weeks }
let(:earlier_date) { today - 5.days }

With this change, we can instead write:

let(:today) { 2.weeks.after(customer_start_date) }
let(:earlier_date) { 5.days.before(today) }

I find this to be much more readable, and it makes me happy 😄

A small downside I see is that before and after don't make very much sense when used without an argument.

I would be happy to pull these into a new set of methods that always take an argument, but I figured that the lowest impact change was an alias.

I also did not add a test case, as I saw there was no additional test coverage for since and ago. If desired I will happily add a test case.

Please let me know if there's anything else I can address. Additionally, if this change isn't desired, please feel free to close it.

@maclover7
Copy link
Contributor

cc @pixeltrix

@jeremy
Copy link
Member

jeremy commented Jan 20, 2017

For comparison, the existing API:

let(:today) { 2.weeks.since(customer_start_date) }
let(:earlier_date) { 5.days.until(today) }

@pixeltrix
Copy link
Contributor

I think the consensus is that existing methods are fine and adding new aliases isn't required.

@Widdershin sorry, but thanks for your suggestion.

@pixeltrix pixeltrix closed this Jan 20, 2017
@dhh
Copy link
Member

dhh commented Jan 20, 2017

I'm quite sympathetic to this. If you gave me the choice between:

let(:today) { 2.weeks.since(customer_start_date) }
let(:earlier_date) { 5.days.until(today) }

And

let(:today) { 2.weeks.after(customer_start_date) }
let(:earlier_date) { 5.days.before(today) }

I'd say the latter requires far less mental gymnastics to read. And I think that's a good example of the strength of alias. It allows us to bend the same instructions to read the most natural in more instances.

@dhh dhh reopened this Jan 20, 2017
@dhh
Copy link
Member

dhh commented Jan 20, 2017

Actually, I think the after is compelling enough that I'd like to see this make it in. I'd want to write code like the latter.

@dhh
Copy link
Member

dhh commented Jan 20, 2017

Need a CHANGELOG entry explaining its value. Do use the before/after example.

@Widdershin
Copy link
Contributor Author

@dhh Thanks for your consideration.

I've altered my commit to include a CHANGELOG entry. Let me know if you want anything else added.

@@ -1,3 +1,28 @@
* Add `ActiveSupport::Duration#before` and `#after` as aliases for `#until` and `#since`

It is common in specs to declare dates relative to one another as part of setup.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good example, but it suggests that the API is somehow related to specs or that its value is derived from an edge case usage.

Think a simple before/after with just the code is clear & convincing, e.g.

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)


# Calculates a new Time or Date that is as far in the past
# as this Duration represents.
def ago(time = ::Time.current)
sum(-1, time)
end
alias :until :ago
alias :before :ago
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still feels weird to alias these—and leave ago's required argument as optional—rather than define new methods. Not necessary for this PR, but worth considering

# Calculates this duration in the past relative to +date_or_time+.
def before(date_or_time)
  sum(-1, date_or_time)
end
alias :until :before

def ago
  before ::Time.current
end

That'd break people using the odd 2.days.ago customer_start_date and 5.weeks.since formulations, though, so it'd need deprecation 😐

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm happy to make this change if it's desired, but as you say, perhaps in a different PR. What is the process for deprecating something like that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The process is to allow both old and new method signatures and give a deprecation warning if we see the old one.

# Calculates this duration in the past relative to +date_or_time+.
def before(date_or_time)
  sum(-1, date_or_time)
end
alias :until :before

def ago(deprecated_date_or_time = nil)
  if deprecated_date_or_time
    ActiveSupport::Deprecation.warn "Passing a date_or_time argument to #ago is deprecated and will be removed in Rails 5.2. Use #before(date_or_time) instead."
  end
  before deprecated_date_or_time || ::Time.current
end

@Widdershin
Copy link
Contributor Author

I have resolved the conflicts and updated the CHANGELOG entry as per @jeremy's feedback.

Please let me know if there's anything else you would like done before this is merged.

@@ -1,3 +1,19 @@
* 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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can chop this comma.

Copy link
Member

@jeremy jeremy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs test coverage for the new aliases. (If these were removed, no tests would fail, but apps would break.)

@jeremy jeremy added this to the 5.1.0 milestone Feb 9, 2017
@Widdershin
Copy link
Contributor Author

Hi @jeremy, thanks for your review.

I have added test coverage. I included a test for calling before and after with no arguments, following the rationale that since it's part of the public API it should be tested to avoid regressions.

I did not duplicate the more intricate test coverage for since and ago, but I can if desired.

I also removed the comma from my changelog entry.

Please let me know if there are any other changes desired. 😸

It's common in test cases at my job to have code like this:

let(:today) { customer_start_date + 2.weeks }
let(:earlier_date) { today - 5.days }

With this change, we can instead write

let(:today) { 2.weeks.after(customer_start_date) }
let(:earlier_date) { 5.days.before(today) }
@Widdershin
Copy link
Contributor Author

Looks like I have a failure to look into. Strange, as I've adapted that test directly from another one.

@rafaelfranca rafaelfranca removed this from the 5.1.0 milestone Feb 23, 2017
@jeremy jeremy added this to the 5.1.0 milestone Feb 26, 2017
@jeremy jeremy closed this in 2d84a6b Feb 26, 2017
@Widdershin
Copy link
Contributor Author

Thanks @jeremy for your review and merge, and also for sorting out that test case for me. 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants