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 receiver subsection for Duration Arithmetic #345

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

aeroastro
Copy link

We prefer an instance of ActiveSupport::Duration
as a receiver to calculate relative time like 1.minute.after(created_at)

README.adoc Outdated Show resolved Hide resolved
Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

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

Thank you!

I don't suggest gathering examples of real-world usages, as the "bad" section examples look terrifyingly bad.
Still, if you have time, here is a good source https://github.com/eliotsykes/real-world-rails.

README.adoc Outdated Show resolved Hide resolved
@aeroastro aeroastro force-pushed the feature/active-support-duration branch from 14935f2 to c4ca239 Compare September 6, 2023 14:59
We prefer an instance of `ActiveSupport::Duration` as a receiver
to calculate relative time like `1.minute.since(created_at)`
@aeroastro aeroastro force-pushed the feature/active-support-duration branch from c4ca239 to 71d5465 Compare November 21, 2023 06:43
@aeroastro
Copy link
Author

Thank you all for the reviews ❤️
I have squashed the commits into 1 commit to be merged.

@pirj pirj requested review from koic and andyw8 November 21, 2023 19:01
@andyw8
Copy link
Contributor

andyw8 commented Nov 22, 2023

I find to hard to say whether either is 'good' or 'bad' without seeing the examples in a wider context.

@aeroastro
Copy link
Author

@andyw8

Thank you for the review.
I can show you some example code for this section.

Manage Date of Graduate Programs

https://github.com/gems-uff/sapos/blob/c80f4b586bc1027c6ba19b8d2a6a064ac5cc1b2b/app/models/date_utils.rb#L7-L18

Good Pattern (Original Code)

  def self.add_to_date(date, total_semesters, total_months, total_days)
    if total_semesters != 0
      semester_months = (
        12 * (total_semesters / 2)
      ) + (
        (date.month == 3 ? 5 : 7) * (total_semesters % 2)
      ) - 1
      date = semester_months.months.since(date).end_of_month
    end

    total_days.days.since(total_months.months.since(date).end_of_month)
  end

Diff from Bad to Good

  def self.add_to_date(date, total_semesters, total_months, total_days)
    if total_semesters != 0
      semester_months = (
        12 * (total_semesters / 2)
      ) + (
        (date.month == 3 ? 5 : 7) * (total_semesters % 2)
      ) - 1
-     date = date.since(semester_months.months).end_of_month
+     date = semester_months.months.since(date).end_of_month
    end

-   (date.since(total_months.months).end_of_month).since(total_days.days)
+   total_days.days.since(total_months.months.since(date).end_of_month)
  end

Redmine Milestone Calculation

https://github.com/k41n/redmine_milestones/blob/e5e6eac3de08d8ee0399346bfe58da68bea69690/app/models/milestone.rb#L145-L157

Good Pattern (Original Code)

  def planned_end_date
    if fixed_planned_end_date || planned_end_date_offset.nil?
      read_attribute(:planned_end_date)
    else
      return nil if planned_end_date_offset.nil?
      return nil if previous_planned_end_date_milestone.nil?
      if previous_planned_end_date_milestone.planned_end_date.nil?
        return planned_end_date_offset.days.since(self.previous_planned_end_date_milestone.actual_date) if self.previous_planned_end_date_milestone.actual_date
      else
        return planned_end_date_offset.days.since(self.previous_planned_end_date_milestone.planned_end_date)
      end
    end
  end

Diff from Bad to Good

  def planned_end_date
    if fixed_planned_end_date || planned_end_date_offset.nil?
      read_attribute(:planned_end_date)
    else
      return nil if planned_end_date_offset.nil?
      return nil if previous_planned_end_date_milestone.nil?
      if previous_planned_end_date_milestone.planned_end_date.nil?
-       return self.previous_planned_end_date_milestone.actual_date.since(planned_end_date_offset.days) if self.previous_planned_end_date_milestone.actual_date
+       return planned_end_date_offset.days.since(self.previous_planned_end_date_milestone.actual_date) if self.previous_planned_end_date_milestone.actual_date
      else
-       return self.previous_planned_end_date_milestone.planned_end_date.since(planned_end_date_offset.days)
+       return planned_end_date_offset.days.since(self.previous_planned_end_date_milestone.planned_end_date)
      end
    end
  end

Optional Argument

I have just created an example.

Good Pattern (Original Code)

def calculate_day_after_tomorrow(since: nil)
  if since
    2.days.since(since)
  else
    2.days.since
  end
end

Diff from Bad to Good

def calculate_day_after_tomorrow(since: nil)
  if since
-   since.since(2.days)
+   2.days.since(since)
  else
-   Time.current.since(2.days)
+   2.days.since
  end
end

Expiring Ticket

I have just created an example code

Good Pattern (Original Code)

def purchase_ticket(user, ticket)
  purchased_ticket = PurchasedTicket.new(user_id: user.id, ticket_id: ticket.id, purchased_at: Time.current)
  purchased_ticket.expires_at = ticket.expires_in.seconds.since(purchased_ticket.purchased_at)
  purchased_ticket.save!

  purchased_ticket
end

def extend_expired_at(purchased_ticket, duration)
  purchased_ticket.expires_at = duration.since(purchased_ticket.expires_at)
  purchased_ticket.save!

  purchased_ticket
end

Diff from Bad to Good

def purchase_ticket(user, ticket)
  purchased_ticket = PurchasedTicket.new(user_id: user.id, ticket_id: ticket.id, purchased_at: Time.current)
- purchased_ticket.expires_at = purchased_ticket.purchased_at.since(ticket.expires_in.seconds)
+ purchased_ticket.expires_at = ticket.expires_in.seconds.since(purchased_ticket.purchased_at)
  purchased_ticket.save!

  purchased_ticket
end

def extend_expired_at(purchased_ticket, duration)
- purchased_ticket.expires_at = purchased_ticket.expires_at.since(duration)
+ purchased_ticket.expires_at = duration.since(purchased_ticket.expires_at)
  purchased_ticket.save!

  purchased_ticket
end

@pirj
Copy link
Member

pirj commented Dec 4, 2023

For the optional argument example:

def calculate_day_after_tomorrow(since: Time.current)
  2.days.since(since)
end

and 2.days.from_now instead of the no-arg 2.days.since call.

Otherwise, looks good.

Should we put this into a separate guideline?

@aeroastro
Copy link
Author

@pirj

Thank you for the review and mentioning the 2.days.from_now.

In my opinion, I would like to focus on 1.minute.since(created_at) for style guide, and also for rubocop-rails

@pirj
Copy link
Member

pirj commented Dec 6, 2023

Afair, since is an alias of from_now.
We gave an agreement not to break other guidelines in example code, even in bad if the bad part can be presented differently.

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

Successfully merging this pull request may close these issues.

3 participants