Skip to content

Commit 5a885dc

Browse files
committed
Allow optional passing of options to calculators
For the upcoming "strikethrough prices"/"promotion previews" feature, we need to allow passing options to calculator's `compute` methods. For strikethrough prices, we will need the current order and the desired quantity to be passed into the calculator, and this commit allows passing any desired options. Since calculators are polymorphic in all directions and can be used in all kinds of context, we don't specify which options are possible. This also does the groundwork for allowing the SolidusPromotions::Benefit to take options and pass them on to its calculator. Because these methods are used a lot in Solidus, adds YARD-style documentation to the modified methods.
1 parent 2d1fcff commit 5a885dc

File tree

4 files changed

+78
-10
lines changed

4 files changed

+78
-10
lines changed

core/app/models/spree/calculator.rb

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,41 @@ class Calculator < Spree::Base
66

77
belongs_to :calculable, polymorphic: true, optional: true
88

9-
# This method calls a compute_<computable> method. must be overriden in concrete calculator.
9+
# Computes an amount based on the calculable and the computable parameter.
1010
#
11-
# It should return amount computed based on #calculable and the computable parameter
12-
def compute(computable)
11+
# This method dynamically calls a compute_<computable> method based on the class
12+
# of the computable parameter. Concrete calculator classes must implement the
13+
# appropriate compute method for each computable type they support.
14+
#
15+
# For example, if the computable is a Spree::LineItem, this will call
16+
# compute_line_item(computable, ...). If the computable is a Spree::Order,
17+
# it will call compute_order(computable, ...).
18+
#
19+
# @param computable [Object] The object to compute the amount for (e.g.,
20+
# Spree::LineItem, Spree::Order, Spree::Shipment,
21+
# Spree::ShippingRate)
22+
# @param ... [args, kwargs] Additional arguments passed to the specific compute method
23+
#
24+
# @return [BigDecimal, Numeric] The computed amount
25+
#
26+
# @raise [NotImplementedError] If the calculator doesn't implement the required compute method
27+
#
28+
# @example Implementing a calculator for line items
29+
# class MyCalculator < Spree::Calculator
30+
# def compute_line_item(line_item)
31+
# line_item.amount * 0.1 # 10% of line item amount
32+
# end
33+
# end
34+
#
35+
# @see Spree::CalculatedAdjustments for how calculators connect to calculables, such as
36+
# Spree::TaxRate, Spree::ShippingRate, SolidusPromotions::Benefit, or Spree::PromotionAction
37+
def compute(computable, ...)
1338
# Spree::LineItem -> :compute_line_item
1439
computable_name = computable.class.name.demodulize.underscore
15-
method_name = "compute_#{computable_name}".to_sym
40+
method_name = :"compute_#{computable_name}"
1641
calculator_class = self.class
1742
if respond_to?(method_name)
18-
send(method_name, computable)
43+
send(method_name, computable, ...)
1944
else
2045
raise NotImplementedError, "Please implement '#{method_name}(#{computable_name})' in your calculator: #{calculator_class.name}"
2146
end

core/spec/models/spree/calculator_spec.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ def self.name
99
"SimpleCalculator"
1010
end
1111

12-
def compute_simple_computable(_)
12+
def compute_simple_computable(_, _options = {})
1313
"computed"
1414
end
1515
end
@@ -53,5 +53,15 @@ def self.name
5353
expect { subject }.to raise_error NotImplementedError, /Please implement \'compute_line_item\(line_item\)\' in your calculator/
5454
end
5555
end
56+
57+
context "with options" do
58+
let(:order) { double(Spree::Order) }
59+
subject { calculator.compute(computable, order: order) }
60+
61+
it "passes the options to compute_simple_computable" do
62+
expect(calculator).to receive(:compute_simple_computable).with(computable, order: order)
63+
subject
64+
end
65+
end
5666
end
5767
end

promotions/app/models/solidus_promotions/benefit.rb

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,25 @@ def can_discount?(object)
2929
"`SolidusPromotions::Benefits::LineItemBenefit` or `SolidusPromotions::Benefits::ShipmentBenefit` modules"
3030
end
3131

32-
def discount(adjustable)
33-
amount = compute_amount(adjustable)
32+
# Calculates and returns a discount for the given adjustable object.
33+
#
34+
# This method computes the discount amount using the benefit's calculator and returns
35+
# an ItemDiscount object representing the discount to be applied. If the computed
36+
# amount is zero, no discount is returned.
37+
#
38+
# @param adjustable [Object] The object to calculate the discount for (e.g., LineItem, Order, Shipment)
39+
# @param ... [args, kwargs] Additional arguments passed to the calculator's compute method
40+
#
41+
# @return [SolidusPromotions::ItemDiscount, nil] An ItemDiscount object if a discount applies, nil if the amount is zero
42+
#
43+
# @example Calculating a discount for a line item
44+
# benefit.discount(line_item)
45+
# # => #<SolidusPromotions::ItemDiscount item: #<Spree::LineItem>, amount: -10.00, ...>
46+
#
47+
# @see #compute_amount
48+
# @see #adjustment_label
49+
def discount(adjustable, ...)
50+
amount = compute_amount(adjustable, ...)
3451
return if amount.zero?
3552
ItemDiscount.new(
3653
item: adjustable,
@@ -41,8 +58,8 @@ def discount(adjustable)
4158
end
4259

4360
# Ensure a negative amount which does not exceed the object's amount
44-
def compute_amount(adjustable)
45-
promotion_amount = calculator.compute(adjustable) || Spree::ZERO
61+
def compute_amount(adjustable, ...)
62+
promotion_amount = calculator.compute(adjustable, ...) || Spree::ZERO
4663
[adjustable.discountable_amount, promotion_amount.abs].min * -1
4764
end
4865

promotions/spec/models/solidus_promotions/benefit_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,22 @@
104104
expect(subject).to be nil
105105
end
106106
end
107+
108+
context "if passing in extra options" do
109+
let(:calculator_class) do
110+
Class.new(Spree::Calculator) do
111+
def compute_line_item(_line_item, _options) = 1
112+
end
113+
end
114+
let(:calculator) { calculator_class.new }
115+
let(:discountable) { build(:line_item) }
116+
117+
subject { benefit.discount(discountable, extra_data: "foo") }
118+
it "passes the option on to the calculator" do
119+
expect(calculator).to receive(:compute_line_item).with(discountable, extra_data: "foo").and_return(1)
120+
subject
121+
end
122+
end
107123
end
108124

109125
describe ".original_promotion_action" do

0 commit comments

Comments
 (0)