-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Simplify Variant#default_price logic
#4076
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,12 +5,13 @@ module DefaultPrice | |
| extend ActiveSupport::Concern | ||
|
|
||
| included do | ||
| has_one :default_price, | ||
| -> { with_discarded.currently_valid.with_default_attributes }, | ||
| class_name: 'Spree::Price', | ||
| inverse_of: :variant, | ||
| dependent: :destroy, | ||
| autosave: true | ||
| delegate :display_price, :display_amount, :price, to: :default_price, allow_nil: true | ||
| delegate :price=, to: :default_price_or_build | ||
|
|
||
| # @see Spree::Variant::PricingOptions.default_price_attributes | ||
| def self.default_price_attributes | ||
| Spree::Config.default_pricing_options.desired_attributes | ||
| end | ||
| end | ||
|
|
||
| # Returns `#prices` prioritized for being considered as default price | ||
|
|
@@ -20,15 +21,60 @@ def currently_valid_prices | |
| prices.currently_valid | ||
| end | ||
|
|
||
| def find_or_build_default_price | ||
| default_price || build_default_price(Spree::Config.default_pricing_options.desired_attributes) | ||
| # Returns {#default_price} or builds it from {Spree::Variant.default_price_attributes} | ||
| # | ||
| # @return [Spree::Price, nil] | ||
| # @see Spree::Variant.default_price_attributes | ||
| def default_price_or_build | ||
| default_price || | ||
| prices.build(self.class.default_price_attributes) | ||
| end | ||
|
|
||
| delegate :display_price, :display_amount, :price, to: :find_or_build_default_price | ||
| delegate :price=, to: :find_or_build_default_price | ||
| # 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. | ||
| # | ||
| # 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 | ||
| # found, non-persisted records take preference. When more than one persisted | ||
| # candidate exists, the one most recently updated is taken or, in case of | ||
| # race condition, the one with higher id. | ||
| # | ||
| # @return [Spree::Price, nil] | ||
| # @see Spree::Variant.default_price_attributes | ||
| def default_price | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method is a bit complex and hard to read to understand what it does. Any thoughts about moving it to a separate class that is responsible to select the default price only?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I completely agree. I have some reserves because of:
WDYT? We can do it now if you think it's better to leave it clean at this point.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As we discussed IRL yesterday. Maybe a good tradeoff could be trying to use more semantic variable names and/or extracting something to a method in this same class to help reading the code.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to make everything clear. I think that the result is much more readable. Thanks for pointing it out. Please, take a look when you have a second.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is perfect, thanks Marc! |
||
| prioritized_default( | ||
| prices_meeting_criteria_to_be_default( | ||
| (prices + prices.with_discarded).uniq | ||
| ) | ||
| ) | ||
| 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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -60,6 +60,17 @@ class Product < Spree::Base | |
| has_many :line_items, through: :variants_including_master | ||
| has_many :orders, through: :line_items | ||
|
|
||
| scope :sort_by_master_default_price_amount_asc, -> { | ||
| with_default_price.order('spree_prices.amount ASC') | ||
| } | ||
| scope :sort_by_master_default_price_amount_desc, -> { | ||
| with_default_price.order('spree_prices.amount DESC') | ||
| } | ||
| scope :with_default_price, -> { | ||
| left_joins(master: :prices) | ||
| .where(master: { spree_prices: Spree::Config.default_pricing_options.desired_attributes }) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This didn't work with some database adapters in the past. Can you confirm it does with pg, mysql and sqlite? Because if this works, the cursed
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems it works. That's the SQL generated for each adapter: sqlite: => "SELECT \"spree_products\".* FROM \"spree_products\" LEFT OUTER JOIN \"spree_variants\" \"master\" ON \"master\".\"is_master\" = 1 AND \"master\".\"product_id\" = \"spree_products\".\"id\" LEFT OUTER JOIN \"spree_prices\" ON \"spree_prices\".\"deleted_at\" IS NULL AND \"spree_prices\".\"variant_id\" = \"master\".\"id\" WHERE \"spree_products\".\"deleted_at\" IS NULL AND \"spree_prices\".\"currency\" = 'USD' AND \"spree_prices\".\"country_iso\" IS NULL"Postgres: "SELECT \"spree_products\".* FROM \"spree_products\" LEFT OUTER JOIN \"spree_variants\" \"master\" ON \"master\".\"is_master\" = TRUE AND \"master\".\"product_id\" = \"spree_products\".\"id\" LEFT OUTER JOIN \"spree_prices\" ON \"spree_prices\".\"deleted_at\" IS NULL AND \"spree_prices\".\"variant_id\" = \"master\".\"id\" WHERE \"spree_products\".\"deleted_at\" IS NULL AND \"spree_prices\".\"currency\" = 'USD' AND \"spree_prices\".\"country_iso\" IS NULL"MySQL: "SELECT `spree_products`.* FROM `spree_products` LEFT OUTER JOIN `spree_variants` `master` ON `master`.`is_master` = TRUE AND `master`.`product_id` = `spree_products`.`id` LEFT OUTER JOIN `spree_prices` ON `spree_prices`.`deleted_at` IS NULL AND `spree_prices`.`variant_id` = `master`.`id` WHERE `spree_products`.`deleted_at` IS NULL AND `spree_prices`.`currency` = 'USD' AND `spree_prices`.`country_iso` IS NULL"While the tests using that method work in the three adapters:
We could tackle that scope in a separate PR. |
||
| } | ||
|
|
||
| def find_or_build_master | ||
| master || build_master | ||
| end | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.