Skip to content

Commit d529dab

Browse files
committed
Change condition configuration to be flat
Prior to this commit, we had three configuration options for conditions: `SolidusPromotions.config.order_conditions`, `SolidusPromotions.config.line_item_conditions`, and `SolidusPromotions.config.shipment_conditions`. Conditions can, however, also target shipping rates, and future iterations of the promotion system will need conditions that can target arbitary objects like `Spree::Product` instances or `Spree::Price` instances. This commit changes the configuration to simply be `SolidusPromotions.config.conditions` and uses a small bit of metaprogramming to see whether a condition is applicable to an object, and thus paves the way for promotions on prices, products, and taxons.
1 parent 3896980 commit d529dab

File tree

7 files changed

+132
-30
lines changed

7 files changed

+132
-30
lines changed

promotions/app/models/solidus_promotions/benefit.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,19 @@ class Benefit < Spree::Base
6464
# @return [ActiveRecord::Relation<SolidusPromotions::Benefit>]
6565
scope :of_type, ->(type) { where(type: Array.wrap(type).map(&:to_s)) }
6666

67+
# Base set of order-level condition classes available to all benefits.
68+
#
69+
# These generic order conditions apply regardless of the concrete benefit
70+
# type, as every benefit ultimately operates within the context of an order.
71+
# Concrete benefit subclasses may extend or override this to include
72+
# additional applicable conditions that are specific to their discount
73+
# target (e.g., line-item or shipment conditions).
74+
#
75+
# @return [Enumerable<Class<SolidusPromotions::Condition>>]
76+
def self.possible_conditions
77+
SolidusPromotions::Condition.applicable_to([Spree::Order])
78+
end
79+
6780
# Returns relations that should be preloaded for this condition.
6881
#
6982
# Override this method in subclasses to specify associations that should be eager loaded
@@ -237,7 +250,7 @@ def level
237250
#
238251
# @return [Set<Class<SolidusPromotions::Condition>>]
239252
def available_conditions
240-
possible_conditions - conditions.select(&:persisted?)
253+
self.class.possible_conditions - conditions.select(&:persisted?)
241254
end
242255

243256
# Returns the calculators allowed for this benefit type.
@@ -302,7 +315,8 @@ def applicable_line_items(order)
302315
#
303316
# @return [Set<Class<SolidusPromotions::Condition>>]
304317
def possible_conditions
305-
Set.new(SolidusPromotions.config.order_conditions)
318+
Spree.deprecator.warn("Use #{self.class.name}.possible_conditions instead.")
319+
self.class.possible_conditions
306320
end
307321

308322
private

promotions/app/models/solidus_promotions/benefits/adjust_line_item.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@
33
module SolidusPromotions
44
module Benefits
55
class AdjustLineItem < Benefit
6+
def self.possible_conditions
7+
SolidusPromotions::Condition.applicable_to([Spree::Order, Spree::LineItem])
8+
end
9+
610
def discount_line_item(line_item, ...)
711
adjustment = find_adjustment(line_item) || build_adjustment(line_item)
812
adjustment.amount = compute_amount(line_item, ...)
913
adjustment.label = adjustment_label(line_item)
1014
adjustment
1115
end
1216

13-
def possible_conditions
14-
super + SolidusPromotions.config.line_item_conditions
15-
end
16-
1717
def level
1818
:line_item
1919
end

promotions/app/models/solidus_promotions/benefits/adjust_shipment.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
module SolidusPromotions
44
module Benefits
55
class AdjustShipment < Benefit
6+
def self.possible_conditions
7+
SolidusPromotions::Condition.applicable_to([Spree::Order, Spree::Shipment])
8+
end
9+
610
def discount_shipment(shipment, ...)
711
adjustment = find_adjustment(shipment) || build_adjustment(shipment)
812
adjustment.amount = compute_amount(shipment, ...)
@@ -17,10 +21,6 @@ def discount_shipping_rate(shipping_rate, ...)
1721
discount
1822
end
1923

20-
def possible_conditions
21-
super + SolidusPromotions.config.shipment_conditions
22-
end
23-
2424
def level
2525
:shipment
2626
end

promotions/app/models/solidus_promotions/condition.rb

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,27 @@ class Condition < Spree::Base
5454
validate :unique_per_benefit, on: :create
5555
validate :possible_condition_for_benefit, if: -> { benefit.present? }
5656

57+
class << self
58+
# Returns the subset of conditions that can compute eligibility instances of the provided array of classes.
59+
#
60+
# @example SolidusPromotions::Condition.applicable_to([Spree::Order, Spree::LineItem])
61+
#
62+
# @param [Array<Class>] An array of classes to compute eligibility for
63+
# @return [Array<SolidusPromotions::Condition>] Conditions that can compute eligibility for the provided classes.
64+
def applicable_to(classes)
65+
SolidusPromotions.config.conditions.select do |condition_class|
66+
(condition_class.instance_methods & classes.map { |k| eligible_method_for(k) }).any?
67+
end
68+
end
69+
70+
# Generates the eligibility method name for a promotable
71+
#
72+
# @return [Symbol] the method name
73+
def eligible_method_for(promotable_class)
74+
:"#{promotable_class.name.demodulize.underscore}_eligible?"
75+
end
76+
end
77+
5778
# Returns relations that should be preloaded for this condition.
5879
#
5980
# Override this method in subclasses to specify associations that should be eager loaded
@@ -178,11 +199,8 @@ def updateable?
178199

179200
private
180201

181-
# Generates the eligibility method name for a promotable
182-
#
183-
# @return [Symbol] the method name
184202
def eligible_method_for(promotable)
185-
:"#{promotable.class.name.demodulize.underscore}_eligible?"
203+
self.class.eligible_method_for(promotable.class)
186204
end
187205

188206
# Validates that only one instance of this condition type exists per benefit.
@@ -199,7 +217,7 @@ def unique_per_benefit
199217
# Checks the benefit's {Benefit#possible_conditions} to ensure this condition
200218
# type is compatible.
201219
def possible_condition_for_benefit
202-
benefit.possible_conditions.include?(self.class) || errors.add(:type, :invalid_condition_type)
220+
benefit.class.possible_conditions.include?(self.class) || errors.add(:type, :invalid_condition_type)
203221
end
204222

205223
# Generates a translated eligibility error message.

promotions/lib/solidus_promotions/configuration.rb

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,37 @@ class Configuration < Spree::Preferences::Configuration
3232
# In case solidus_legacy_promotions is loaded, we need to define this.
3333
class_name_attribute :shipping_promotion_handler_class, default: "Spree::NullPromotionHandler"
3434

35-
add_class_set :order_conditions, default: [
35+
def order_conditions
36+
SolidusPromotions::Condition.applicable_to([Spree::Order])
37+
end
38+
deprecate order_conditions: :conditions, deprecator: Spree.deprecator
39+
40+
def order_conditions=(conditions)
41+
conditions.each { self.conditions << _1 }
42+
end
43+
deprecate :order_conditions= => :conditions=, :deprecator => Spree.deprecator
44+
45+
def line_item_conditions
46+
SolidusPromotions::Condition.applicable_to([Spree::LineItem])
47+
end
48+
deprecate line_item_conditions: :conditions, deprecator: Spree.deprecator
49+
50+
def line_item_conditions=(conditions)
51+
conditions.each { self.conditions << _1 }
52+
end
53+
deprecate :line_item_conditions= => :conditions=, :deprecator => Spree.deprecator
54+
55+
def shipment_conditions
56+
SolidusPromotions::Condition.applicable_to([Spree::Shipment])
57+
end
58+
deprecate shipment_conditions: :conditions, deprecator: Spree.deprecator
59+
60+
def shipment_conditions=(conditions)
61+
conditions.each { self.conditions << _1 }
62+
end
63+
deprecate :shipment_conditions= => :conditions=, :deprecator => Spree.deprecator
64+
65+
add_class_set :conditions, default: [
3666
"SolidusPromotions::Conditions::FirstOrder",
3767
"SolidusPromotions::Conditions::FirstRepeatPurchaseSince",
3868
"SolidusPromotions::Conditions::ItemTotal",
@@ -49,15 +79,10 @@ class Configuration < Spree::Preferences::Configuration
4979
"SolidusPromotions::Conditions::OrderTaxon",
5080
"SolidusPromotions::Conditions::UserLoggedIn",
5181
"SolidusPromotions::Conditions::UserRole",
52-
"SolidusPromotions::Conditions::User"
53-
]
54-
55-
add_class_set :line_item_conditions, default: [
82+
"SolidusPromotions::Conditions::User",
5683
"SolidusPromotions::Conditions::LineItemOptionValue",
5784
"SolidusPromotions::Conditions::LineItemProduct",
58-
"SolidusPromotions::Conditions::LineItemTaxon"
59-
]
60-
add_class_set :shipment_conditions, default: [
85+
"SolidusPromotions::Conditions::LineItemTaxon",
6186
"SolidusPromotions::Conditions::ShippingMethod"
6287
]
6388

promotions/spec/lib/solidus_promotions/configuration_spec.rb

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require "rails_helper"
44

55
RSpec.describe SolidusPromotions::Configuration do
6-
subject(:config) { SolidusPromotions.config }
6+
subject(:config) { described_class.new }
77

88
it "has a nice accessor" do
99
expect(subject).to be_a(described_class)
@@ -31,20 +31,53 @@
3131
it { is_expected.to be_a(Spree::Core::NestedClassSet) }
3232
end
3333

34-
describe ".order_conditions" do
34+
describe ".order_conditions", :silence_deprecations do
3535
subject { config.order_conditions }
3636

37-
it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) }
37+
it { is_expected.to include(SolidusPromotions::Conditions::User) }
3838
end
3939

40-
describe ".line_item_conditions" do
40+
describe ".line_item_conditions", :silence_deprecations do
4141
subject { config.line_item_conditions }
4242

43-
it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) }
43+
it { is_expected.to include(SolidusPromotions::Conditions::LineItemProduct) }
4444
end
4545

46-
describe ".shipment_conditions" do
47-
subject { config.line_item_conditions }
46+
describe ".shipment_conditions", :silence_deprecations do
47+
subject { config.shipment_conditions }
48+
49+
it { is_expected.to include(SolidusPromotions::Conditions::ShippingMethod) }
50+
end
51+
52+
describe "deprecated condition setters", :silence_deprecations do
53+
before do
54+
stub_const("TestCondition", Class.new(SolidusPromotions::Condition))
55+
end
56+
57+
describe "order_conditions=" do
58+
it "adds conditions to the #conditions set" do
59+
expect { config.order_conditions = [TestCondition] }.to change { config.conditions.count }.by(1)
60+
expect(config.conditions).to include(TestCondition)
61+
end
62+
end
63+
64+
describe "line_item_conditions=" do
65+
it "adds conditions to the #conditions set" do
66+
expect { config.line_item_conditions = [TestCondition] }.to change { config.conditions.count }.by(1)
67+
expect(config.conditions).to include(TestCondition)
68+
end
69+
end
70+
71+
describe "shipment_conditions=" do
72+
it "adds conditions to the #conditions set" do
73+
expect { config.shipment_conditions = [TestCondition] }.to change { config.conditions.count }.by(1)
74+
expect(config.conditions).to include(TestCondition)
75+
end
76+
end
77+
end
78+
79+
describe ".conditions" do
80+
subject { config.conditions }
4881

4982
it { is_expected.to be_a(Spree::Core::ClassConstantizer::Set) }
5083
end

promotions/spec/models/solidus_promotions/benefit_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,4 +309,16 @@ def line_item_adjustment_label(_line_item, _options = {})
309309
it { is_expected.to eq("Something entirely different") }
310310
end
311311
end
312+
313+
describe ".possible_conditions" do
314+
subject { described_class.possible_conditions }
315+
316+
it { is_expected.to include(SolidusPromotions::Conditions::User) }
317+
end
318+
319+
describe "#possible_conditions", :silence_deprecations do
320+
subject { described_class.new.possible_conditions }
321+
322+
it { is_expected.to include(SolidusPromotions::Conditions::User) }
323+
end
312324
end

0 commit comments

Comments
 (0)