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

Update usage count in Promotion eligibility check #3297

Merged

Conversation

filippoliverani
Copy link
Contributor

Description

This PR addresses issue #3205.

Eligibility check on an Adjustment bound to a Promotion with a usage limit should not include in usage count the current Adjustment itself.

With this PR the eligibility check does not consider the Order of the Adjustment it is applied to.
I updated the check adding an optional array of Orders to exclude from the computation (in the same vein as Promotion #used_by?) to avoid any breaking change to Promotion and PromotionCode puplic APIs.

Checklist:

  • I have followed Pull Request guidelines
  • I have added a detailed description into each commit message
  • I have added tests to cover this change

return false if usage_limit_exceeded?
return false if promotion_code && promotion_code.usage_limit_exceeded?
return false if usage_limit_exceeded?([promotable])
return false if promotion_code && promotion_code.usage_limit_exceeded?([promotable])

Choose a reason for hiding this comment

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

Style/SafeNavigation: Use safe navigation (&.) instead of checking if an object exists before calling the method.

@kennyadsl
Copy link
Member

@filippoliverani Hey there! Can you please rebase against master now that #3278 has been merged? It should fix the flaky spec, thanks!

Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

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

@filippoliverani thanks for fixing this issue! I just left some comments, please let me know what do you think. 🙂

Also, we should address the Hound complain. I know it was already there but we recently bumped the Rubocop target Ruby version and we are still in a phase where we have some warnings.

@@ -56,6 +56,14 @@ class Adjustment < Spree::Base
extend DisplayMoney
money_methods :amount

def self.adjusted_orders(excluded_orders: [])
Copy link
Member

Choose a reason for hiding this comment

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

I have a couple of questions about this class method/scope:

  1. Maybe a different name could help understand what this method does: what about a simpler in_completed_orders()? I think that maybe with adjusted_orders() could not be immediately clear that it's just searching into completed orders.
  2. I'm not sure that eligible should be part of this method. Chaining it from outside adds a bit of duplication but makes the code more flexible IMO, what do you think?

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 agree on both points.
At first I had used a much longer name but it was quite hard to read, in_completed_orders strikes a good balance.

@filippoliverani
Copy link
Contributor Author

@kennyadsl Thank you for your suggestions, I'll rebase and resolve the problems 👍 .

@filippoliverani filippoliverani force-pushed the promotion-eligibility-usage-count branch from f63676c to bef44c2 Compare August 9, 2019 11:31
Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

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

Thanks @filippoliverani, this works for me! I left some additional comments (sorry for missing them in the first review!) on updating the YARD documentation and using keyword arguments as much as possible. WDYT?

@cedum I'd like to have your opinion here, since you opened the issue. Let us know if you think this solution makes sense for you as well!

@@ -166,22 +168,20 @@ def products
# Whether the promotion has exceeded it's usage restrictions.
#
# @return true or false
def usage_limit_exceeded?
def usage_limit_exceeded?(excluded_orders = [])
Copy link
Member

Choose a reason for hiding this comment

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

We need to also update the YARD documentation here.

Copy link
Member

Choose a reason for hiding this comment

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

Also, can we use a keyword argument here so it's clear, even when this method is called, that the array contains orders to exclude? I feel like this

usage_limit_exceeded?([promotable])

could be better for the reader in this form:

usage_limit_exceeded?(excluded_orders: [promotable])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We need to also update the YARD documentation here.

Missed that 😱

can we use a keyword argument here so it's clear, even when this method is called, that the array contains orders to exclude?

Absolutely agree, I used a positional argument just to be consistent with #used_by? but here I'd prefer a keyword one too.

joins(:order).
merge(Spree::Order.complete).
distinct.
def usage_count(excluded_orders = [])
Copy link
Member

Choose a reason for hiding this comment

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

Same for YARD documentation and keyword argument

@filippoliverani
Copy link
Contributor Author

Thank you @kennyadsl, I'm still very new to Solidus and your help is much appreciated!

@filippoliverani filippoliverani force-pushed the promotion-eligibility-usage-count branch from bef44c2 to 382f4ad Compare August 17, 2019 16:33
Copy link
Contributor

@cedum cedum left a comment

Choose a reason for hiding this comment

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

@filippoliverani thanks for working on this issue! I did a quick review and left a few concerns.

I'd take a look also at the existing specs related to promos eligibility. When I was investigating this issue there were different spec examples testing nothing. I'll also look into it by the end of this week and let you know.

@@ -126,9 +126,10 @@ def activate(order:, line_item: nil, user: nil, path: nil, promotion_code: nil)
# called anytime order.recalculate happens
def eligible?(promotable, promotion_code: nil)
return false if inactive?
return false if usage_limit_exceeded?
return false if promotion_code && promotion_code.usage_limit_exceeded?
return false if usage_limit_exceeded?(excluded_orders: [promotable])
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we must handle here the different cases a promotable object can be:

  • Spree::Order
  • Spree::LineItem
  • Spree::Shipment

You can simulate these cases by creating different promos with different Action' adjustment types..

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 had this doubt but I thought that I could ignore LineItems and Shipments when computing eligibility check on them because usage count was not directly affected.

Do you think that updating#eligible?to handle non Order promotables excluding Orders to which LineItems and Shipments belong to would be enough?

Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think that updating #eligible? to handle non Order promotables excluding Orders to which LineItems and Shipments belong to would be enough?

👍 yes. AFAIK, a promotion's usage count is always the number of distinct orders that promo is applied. I cannot think of cases where it's counted by a different object.

joins(:order).
merge(Spree::Order.complete).
where.not(spree_orders: { id: excluded_orders }).
distinct
Copy link
Contributor

Choose a reason for hiding this comment

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

Not strictly related to this PR, since the distinct comes from the previous implementation. Just a heads-up.

Last time I checked it, the generated query was very slow because of DISTINCT(*) on a relatively big table (~400k rows). It was something like ~1ms query response with group by vs ~500ms using distinct(*) on my local machine.
It's worth double checking it and, if confirmed, opening a separate issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This could be the right time to speed up the query, I'll profile both versions with some test data, thanks.

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 tested it on about 500k rows of random genereated rows but I couldn't find any big difference. If you have a more realistic dump without sensitive data could you please share it with me?

Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 thanks for looking into this. I'll try to create a reproducible case with dummy data then eventually will open a separate issue with steps to reproduce it.

@filippoliverani
Copy link
Contributor Author

@cedum Thank you for the feedback, in the meantime I'll check the existing specs too 👍!

@filippoliverani filippoliverani force-pushed the promotion-eligibility-usage-count branch from 382f4ad to a63e792 Compare August 27, 2019 14:14
@filippoliverani
Copy link
Contributor Author

@cedum now #eligible? should handle all promotable types. I updated and refactored a bit its specs to increase coverage.
I saw some other duplicated and maybe obsolete specs in promotion_spec.rb but maybe it would be better to clean them up in an other PR, WDYT?

@cedum
Copy link
Contributor

cedum commented Aug 29, 2019

@cedum now #eligible? should handle all promotable types. I updated and refactored a bit its specs to increase coverage.

💯LGTM

I saw some other duplicated and maybe obsolete specs in promotion_spec.rb but maybe it would be better to clean them up in an other PR, WDYT?

agree 🤝

Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

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

👍 Thanks @filippoliverani, and @cedum for the in-depth review!

@filippoliverani filippoliverani force-pushed the promotion-eligibility-usage-count branch from a63e792 to 8fdd33d Compare November 20, 2019 09:51
Filippo Liverani and others added 8 commits November 20, 2019 12:00
Eligibility check on an Adjustment bound to a Promotion with an usage
limit should not include in usage count the current Adjustment itself.
Move Promotion and PromotionCode common code in #usage_count query to a
dedicated Adjustment class method to remove duplication.
Rename and make more flexible Adjustment class method to query
completed orders.
Use safe navigation and empty lines after guard clauses
Prefer a keyword argument when passing optional excluded orders to
Promotion and PromotionCode usage limit count methods.
Add YARD documentation to cover new Adjustment scope and new Promotion
and PromotionCode usage limit arguments.
Exclude from Promotion eligiblity check Orders to which promotable
LineItems and Shipments belong to.
@filippoliverani filippoliverani force-pushed the promotion-eligibility-usage-count branch from 8fdd33d to df5a01e Compare November 20, 2019 11:01
@kennyadsl kennyadsl merged commit 3f2223b into solidusio:master Nov 27, 2019
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.

5 participants