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

[v3.2] Fix variant price performance regressions #4657

Closed
wants to merge 10 commits into from
2 changes: 1 addition & 1 deletion backend/app/views/spree/admin/products/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<%= f.field_container :price do %>
<%= f.label :price, class: Spree::Config.require_master_price ? 'required' : '' %>

<% if f.object.new_record? || f.object.has_default_price? %>
<% if (f.object.new_record? || f.object.has_default_price?) && !f.object.discarded? %>
<%= render "spree/admin/shared/number_with_currency",
f: f,
amount_attr: :price,
Expand Down
32 changes: 4 additions & 28 deletions core/app/models/concerns/spree/default_price.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ def self.default_price_attributes

# Returns `#prices` prioritized for being considered as default price
#
# @deprecated
# @return [ActiveRecord::Relation<Spree::Price>]
def currently_valid_prices
prices.currently_valid
end
deprecate :currently_valid_prices, deprecator: Spree::Deprecation

# Returns {#default_price} or builds it from {Spree::Variant.default_price_attributes}
#
Expand All @@ -33,7 +35,7 @@ def default_price_or_build
# Select from {#prices} the one to be considered as the default
#
# This method works with the in-memory association, so non-persisted prices
# are taken into account. Discarded prices are also considered.
# are taken into account.
#
# A price is a candidate to be considered as the default when it meets
# {Spree::Variant.default_price_attributes} criteria. When more than one candidate is
Expand All @@ -44,37 +46,11 @@ def default_price_or_build
# @return [Spree::Price, nil]
# @see Spree::Variant.default_price_attributes
def default_price
prioritized_default(
prices_meeting_criteria_to_be_default(
(prices + prices.with_discarded).uniq
)
)
price_selector.price_for_options(Spree::Config.default_pricing_options)
end

def has_default_price?
default_price.present? && !default_price.discarded?
end

private

def prices_meeting_criteria_to_be_default(prices)
criteria = self.class.default_price_attributes.transform_keys(&:to_s)
prices.select do |price|
contender = price.attributes.slice(*criteria.keys)
criteria == contender
end
end

def prioritized_default(prices)
prices.min do |prev, succ|
contender_one, contender_two = [succ, prev].map do |item|
[
item.updated_at || Time.zone.now,
item.id || Float::INFINITY
]
end
contender_one <=> contender_two
end
end
end
end
2 changes: 1 addition & 1 deletion core/app/models/spree/variant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class Variant < Spree::Base
after_discard do
stock_items.discard_all
images.destroy_all
prices.discard_all
end

attr_writer :rebuild_vat_prices
Expand Down Expand Up @@ -52,6 +51,7 @@ class Variant < Spree::Base
has_many :images, -> { order(:position) }, as: :viewable, dependent: :destroy, class_name: "Spree::Image"

has_many :prices,
-> { with_discarded },
class_name: 'Spree::Price',
dependent: :destroy,
inverse_of: :variant,
Expand Down
19 changes: 18 additions & 1 deletion core/app/models/spree/variant/price_selector.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,29 @@ def price_for(price_options)
# @param [Spree::Variant::PricingOptions] price_options Pricing Options to abide by
# @return [Spree::Price, nil] The most specific price for this set of pricing options.
def price_for_options(price_options)
variant.currently_valid_prices.detect do |price|
sorted_prices_for(variant).detect do |price|
(price.country_iso == price_options.desired_attributes[:country_iso] ||
price.country_iso.nil?
) && price.currency == price_options.desired_attributes[:currency]
end
end

private

# Returns `#prices` prioritized for being considered as default price
#
# @return [Array<Spree::Price>]
def sorted_prices_for(variant)
variant.prices.select do |price|
variant.discarded? || price.kept?
end.sort_by do |price|
[
price.country_iso.nil? ? 0 : 1,
price.updated_at || Time.zone.now,
price.id || Float::INFINITY,
]
end.reverse
end
end
end
end
8 changes: 4 additions & 4 deletions core/spec/helpers/products_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ module Spree
let(:currency) { 'JPY' }

before do
variant
product.prices.update_all(currency: currency)
variant.prices.each { |p| p.update(currency: currency) }
product.master.prices.each { |p| p.update(currency: currency) }
end

context "when variant is more than master" do
Expand Down Expand Up @@ -92,8 +92,8 @@ module Spree
let(:variant_price) { 150 }

before do
variant
product.prices.update_all(currency: currency)
variant.prices.each { |p| p.update(currency: currency) }
product.master.prices.each { |p| p.update(currency: currency) }
end

it "should return the variant price if the price is different than master" do
Expand Down
32 changes: 23 additions & 9 deletions core/spec/models/spree/variant_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,20 @@
expect(variant.default_price.attributes).to eq(price.attributes)
end

it 'includes discarded prices' do
variant = create(:variant)
price = create(:price, variant: variant, currency: 'USD')
price.discard
context "when the variant and the price are both soft-deleted" do
it "will use a deleted price as the default price" do
variant = create(:variant, deleted_at: 1.day.ago)
variant.prices.each { |price| price.discard }
expect(variant.reload.price).to be_present
end
end

expect(variant.default_price).to eq(price)
context "when the variant is not soft-deleted, but its price is" do
it "will not use a deleted price as the default price" do
variant = create(:variant)
variant.prices.each { |price| price.discard }
expect(variant.reload.price).not_to be_present
end
end
end

Expand Down Expand Up @@ -313,6 +321,12 @@
end

describe '#currently_valid_prices' do
around do |example|
Spree::Deprecation.silence do
example.run
end
end

it 'returns prioritized prices' do
price_1 = create(:price, country: create(:country))
price_2 = create(:price, country: nil)
Expand Down Expand Up @@ -759,7 +773,7 @@
end

describe "#discard" do
it "discards related associations" do
it "discards related stock items and images, but not prices" do
variant.images = [create(:image)]

expect(variant.stock_items).not_to be_empty
Expand All @@ -770,16 +784,16 @@

expect(variant.images).to be_empty
expect(variant.stock_items.reload).to be_empty
expect(variant.prices).to be_empty
expect(variant.prices).not_to be_empty
end

describe 'default_price' do
let!(:previous_variant_price) { variant.default_price }

it "should discard default_price" do
it "should not discard default_price" do
variant.discard
variant.reload
expect(previous_variant_price.reload).to be_discarded
expect(previous_variant_price.reload).not_to be_discarded
end

it "should keep its price if deleted" do
Expand Down
58 changes: 0 additions & 58 deletions core/spec/support/concerns/default_price.rb

This file was deleted.