-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Update usage count in Promotion eligibility check #3297
Conversation
core/app/models/spree/promotion.rb
Outdated
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]) |
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.
Style/SafeNavigation: Use safe navigation (&.) instead of checking if an object exists before calling the method.
@filippoliverani Hey there! Can you please rebase against master now that #3278 has been merged? It should fix the flaky spec, thanks! |
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.
@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.
core/app/models/spree/adjustment.rb
Outdated
@@ -56,6 +56,14 @@ class Adjustment < Spree::Base | |||
extend DisplayMoney | |||
money_methods :amount | |||
|
|||
def self.adjusted_orders(excluded_orders: []) |
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.
I have a couple of questions about this class method/scope:
- Maybe a different name could help understand what this method does: what about a simpler
in_completed_orders()
? I think that maybe withadjusted_orders()
could not be immediately clear that it's just searching into completed orders. - 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?
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.
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.
@kennyadsl Thank you for your suggestions, I'll rebase and resolve the problems 👍 . |
f63676c
to
bef44c2
Compare
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.
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!
core/app/models/spree/promotion.rb
Outdated
@@ -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 = []) |
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.
We need to also update the YARD documentation here.
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.
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])
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.
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.
core/app/models/spree/promotion.rb
Outdated
joins(:order). | ||
merge(Spree::Order.complete). | ||
distinct. | ||
def usage_count(excluded_orders = []) |
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.
Same for YARD documentation and keyword argument
Thank you @kennyadsl, I'm still very new to Solidus and your help is much appreciated! |
bef44c2
to
382f4ad
Compare
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.
@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.
core/app/models/spree/promotion.rb
Outdated
@@ -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]) |
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.
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..
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.
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?
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.
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 |
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.
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.
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 could be the right time to speed up the query, I'll profile both versions with some test data, thanks.
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.
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?
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.
🤔 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.
@cedum Thank you for the feedback, in the meantime I'll check the existing specs too 👍! |
382f4ad
to
a63e792
Compare
@cedum now |
💯LGTM
agree 🤝 |
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.
👍 Thanks @filippoliverani, and @cedum for the in-depth review!
a63e792
to
8fdd33d
Compare
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.
Prefer 1.day syntax
8fdd33d
to
df5a01e
Compare
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: