-
-
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
Add support for sorting store credits with different algorithms #4677
Add support for sorting store credits with different algorithms #4677
Conversation
Codecov Report
@@ Coverage Diff @@
## master #4677 +/- ##
=======================================
Coverage 86.24% 86.25%
=======================================
Files 574 575 +1
Lines 14596 14606 +10
=======================================
+ Hits 12588 12598 +10
Misses 2008 2008
📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more |
Hi @tmtrademarked ! I think your PR makes sense! I only have a few suggestions to get it approved faster :)
Cheers! |
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 Tom, this is a good change!
I left a couple of ideas, let me know what you do think!
core/app/models/spree/order.rb
Outdated
@@ -596,8 +596,9 @@ def add_store_credit_payments | |||
|
|||
if matching_store_credits.any? | |||
payment_method = Spree::PaymentMethod::StoreCredit.first | |||
sorter = Spree::Config.order_credit_sorter_class.new(self) |
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.
What about passing store credits to sort in the initializer instead of the order? Having the sorter know about the order is something that only applies in your domain, while the store credits will be needed in any scenario.
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'm definitely fine with passing the credits in the constructor! Will update after we're aligned on the other comment, though.
core/app/models/spree/order.rb
Outdated
|
||
matching_store_credits.order_by_priority.each do |credit| | ||
sorter.in_sorted_order(matching_store_credits).each do |credit| |
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.
What about just using call
here? It is a sorter, and sorting is the only thing it is expected to do, so I guess sorter.call
is enough semantic.
Also, if you think my previous suggestion makes sense, this could be without any argument in the core, you could change the signature in your own implementation, passing the order. WDYT?
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 like the idea of renaming to call
or sort
or something similar with no arguments. I'll apply that in a follow-up.
But I'm slightly confused by your suggestion, which makes me think I don't quite understand something. How could I change the signature of call
in my implementation if the gem itself doesn't call the method with that signature? So my proposal would be to update the constructor to take in both the order and the credits.
Here's why I'd advocate for passing the order as well as the credits. My suspicion is that almost any client that needs to override this logic likely needs context which is external to the credits themselves. I can easily imagine rules that are product dependent, or address dependent, or shipping method dependent. So it seems to me that designing the sorter to give the two primary pieces of context (the available credits and the order) makes the most sense for the broadest number of potential use cases.
What do you think - does that make sense to you? If so, I'll update the constructor to take in order, credits
and update the signature of the primary method to just call
with no args!
c6cd7e0
to
3900ce8
Compare
Thanks, @gsmendoza! I've squashed the commits, and tweaked the commit message. Let me know what 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.
Thanks for the feedback, @kennyadsl ! I've got a few clarifications, but let me know what you think and I can make the changes?
core/app/models/spree/order.rb
Outdated
@@ -596,8 +596,9 @@ def add_store_credit_payments | |||
|
|||
if matching_store_credits.any? | |||
payment_method = Spree::PaymentMethod::StoreCredit.first | |||
sorter = Spree::Config.order_credit_sorter_class.new(self) |
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'm definitely fine with passing the credits in the constructor! Will update after we're aligned on the other comment, though.
core/app/models/spree/order.rb
Outdated
|
||
matching_store_credits.order_by_priority.each do |credit| | ||
sorter.in_sorted_order(matching_store_credits).each do |credit| |
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 like the idea of renaming to call
or sort
or something similar with no arguments. I'll apply that in a follow-up.
But I'm slightly confused by your suggestion, which makes me think I don't quite understand something. How could I change the signature of call
in my implementation if the gem itself doesn't call the method with that signature? So my proposal would be to update the constructor to take in both the order and the credits.
Here's why I'd advocate for passing the order as well as the credits. My suspicion is that almost any client that needs to override this logic likely needs context which is external to the credits themselves. I can easily imagine rules that are product dependent, or address dependent, or shipping method dependent. So it seems to me that designing the sorter to give the two primary pieces of context (the available credits and the order) makes the most sense for the broadest number of potential use cases.
What do you think - does that make sense to you? If so, I'll update the constructor to take in order, credits
and update the signature of the primary method to just call
with no args!
No, you are totally right! The core would continue to pass no arguments to the |
Yeah, that makes sense to me! I can understand the argument that |
@credits = credits | ||
end | ||
|
||
def sort |
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.
@kennyadsl - I went with sort
as the method name which felt like a slightly clearer verb to me (personally). Let me know if you think call
would be more inline with the overall conventions of Solidus, though?
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.
@waiting-for-dev What do you think? I'm still for using call
for consistency, but we still don't have a real convention in Solidus.
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.
Yeah, I'm more in favor of using #call
. That's the convention we want to use for objects that do a single action, where the class name describes that action.
It seems like the spec failures here are flakes unrelated to this change, from all the logs I can see. |
Looks good @tmtrademarked ! Thank you! |
48c4741
to
12fedb6
Compare
It seems like a few different tests have failed, but none appear related to my changes. I don't have the ability to re-run the tests myself, unfortunately. Is anyone from Solidus able to trigger a re-run to see if that makes it pass? |
@kennyadsl @gsmendoza - any further thoughts about this one? Would love to be able to land this in a forthcoming release if at all possible. :-) |
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 @tmtrademarked, I left some suggestions 🙂
@credits = credits | ||
end | ||
|
||
def sort |
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.
Yeah, I'm more in favor of using #call
. That's the convention we want to use for objects that do a single action, where the class name describes that action.
core/lib/spree/app_configuration.rb
Outdated
# @!attribute [rw] order_credit_sorter_class | ||
# @return [Class] a class with the same public interfaces as | ||
# Spree::OrderCreditSorter. | ||
class_name_attribute :order_credit_sorter_class, default: 'Spree::OrderCreditSorter' |
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.
What about :store_credit_sorter_class
& Spree::StoreCreditSorter
as the names? I'm not very familiar with that sub-system, so reading "order credit" made me think that maybe it was something different than store credits.
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'm fine with that name if we prefer - my only possible reservation about it is that there could be multiple contexts in which you'd want to "sort store credits". For example, maybe the UI for rendering the list of a users credits. That could be slightly confusing, potentially.
What do you think, @waiting-for-dev ? I'm more than happy to change the name if you think that makes the purpose more obvious!
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.
That's a good point. What about something along the lines of Spree::StoreCreditUtilizationSorter
? (feel free to find a better name 🙂 )
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.
Renamed to Spree::StoreCreditPrioritizer
- thoughts?
end | ||
|
||
it 'uses the default sorting order' do | ||
expect(credits).to receive(:order_by_priority) |
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.
Maybe a matter of taste, but I'd prefer to see the actual behavior being tested instead of the implementation details. I'd assert on the returned order instead.
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 can do that - will update!
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.
It looks perfect now. Thanks, @tmtrademarked! Last ask, could you please squash the commits so that we don't pollute the git history with the details of the discussion in this PR?
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 second what @waiting-for-dev asked about squashing. Excellent work @tmtrademarked, thanks!
In our store, we have a set of custom eligibility rules for store credits based on the contents of the order. So we need a hook to inject a different way to prioritize store credits, as opposed to simply the type priority. While it might be possible to monkey patch the `in_priority_order` scope, that feels somewhat wrong - and the `add_store_credit_payment` method of `Spree::Order` does a *lot* that we would prefer not to monkey patch. This PR adds a very simple configuration hook in the middle of this method to allow stores to inject some new behavior. By default, this simply does what the existing method did - sort by credit priority.
f385be9
to
3ae11f3
Compare
Thanks guys - commits are squashed! |
We introduced an extension point for promotion adjustments in solidusio#4460 [1]. We change the expected interface from `#adjust!` to `#call` for consistency, as that's the new standard we want to follow (see solidusio#4677 [2] for an example). That's going to ease the adoption of a service layer if we eventually introduce them. [1] - solidusio#4460 [2] - solidusio#4677
We introduced an extension point for promotion adjustments in solidusio#4460 [1]. We change the expected interface from `#adjust!` to `#call` for consistency, as that's the new standard we want to follow (see solidusio#4677 [2] for an example). That's going to ease the adoption of a service layer if we eventually introduce it. [1] - solidusio#4460 [2] - solidusio#4677
We introduced an extension point for promotion adjustments in solidusio#4460 [1]. We change the expected interface from `#adjust!` to `#call` for consistency, as that's the new standard we want to follow (see solidusio#4677 [2] for an example). That's going to ease the adoption of a service layer if we eventually introduce it. [1] - solidusio#4460 [2] - solidusio#4677
Summary
In our store, we have a set of custom eligibility rules for store credits based on the contents of the order. So we need a hook to inject a different way to prioritize store credits, as opposed to simply the type priority. While it might be possible to monkey patch the
in_priority_order
scope, that feels somewhat wrong - and theadd_store_credit_payment
method ofSpree::Order
does a lot that we would prefer not to monkey patch.This PR adds a very simple configuration hook in the middle of this method to allow stores to inject some new behavior. By default, this simply does what the existing method did - sort by credit priority.
Checklist