spree_core 5.2.1 → 5.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/app/finders/spree/line_items/find_by_variant.rb +1 -1
  3. data/app/helpers/spree/images_helper.rb +34 -18
  4. data/app/helpers/spree/mail_helper.rb +1 -1
  5. data/app/jobs/spree/imports/create_rows_job.rb +1 -1
  6. data/app/jobs/spree/variants/remove_line_item_job.rb +1 -1
  7. data/app/models/spree/asset.rb +26 -2
  8. data/app/models/spree/export.rb +1 -1
  9. data/app/models/spree/image/configuration/active_storage.rb +6 -15
  10. data/app/models/spree/image.rb +2 -1
  11. data/app/models/spree/import.rb +1 -1
  12. data/app/models/spree/order/gift_card.rb +3 -3
  13. data/app/models/spree/order/store_credit.rb +2 -2
  14. data/app/models/spree/order.rb +4 -4
  15. data/app/models/spree/order_contents.rb +9 -9
  16. data/app/models/spree/order_merger.rb +3 -3
  17. data/app/models/spree/page_sections/featured_taxon.rb +1 -2
  18. data/app/models/spree/promotion/actions/create_line_items.rb +6 -6
  19. data/app/models/spree/promotion_handler/coupon.rb +1 -1
  20. data/app/models/spree/shipping_method.rb +1 -1
  21. data/app/models/spree/store.rb +1 -1
  22. data/app/models/spree/themes/default.rb +7 -4
  23. data/app/presenters/spree/csv/product_variant_presenter.rb +1 -4
  24. data/app/services/spree/cart/add_item.rb +2 -2
  25. data/app/services/spree/cart/remove_item.rb +4 -4
  26. data/app/services/spree/cart/remove_line_item.rb +3 -3
  27. data/app/services/spree/cart/remove_out_of_stock_items.rb +1 -1
  28. data/app/services/spree/cart/set_quantity.rb +1 -1
  29. data/app/services/spree/checkout/advance.rb +1 -1
  30. data/app/services/spree/checkout/complete.rb +1 -1
  31. data/app/services/spree/data_feeds/google/rss.rb +4 -4
  32. data/app/services/spree/line_items/helper.rb +1 -1
  33. data/app/services/spree/shipments/helper.rb +2 -2
  34. data/lib/spree/core/configuration.rb +30 -0
  35. data/lib/spree/core/controller_helpers/auth.rb +1 -1
  36. data/lib/spree/core/controller_helpers/store.rb +1 -1
  37. data/lib/spree/core/dependencies.rb +2 -2
  38. data/lib/spree/core/dependencies_helper.rb +100 -5
  39. data/lib/spree/core/search/base.rb +1 -1
  40. data/lib/spree/core/version.rb +1 -1
  41. data/lib/spree/core.rb +34 -0
  42. data/lib/spree/testing_support/authorization_helpers.rb +2 -2
  43. data/lib/tasks/dependencies.rake +76 -0
  44. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77f4a7f3643f4b9263ab029e06d7870f5f45397e295f4116a485bc82a280d15f
4
- data.tar.gz: 482208c5fd5725818a539a4daa4d566bd4c57c1c82595166e6232c91c56b876f
3
+ metadata.gz: 40e658898150457b9405d6d68e58d78c1b31b23518e2ffdb6829d3bfb0d68a90
4
+ data.tar.gz: e36b7e5d2e39426e2ea9cb4078625f72c36ed58e626959b277174f7f033bdd1f
5
5
  SHA512:
6
- metadata.gz: 6424ad89c3f5a35cade4d6fc2dc67a74bbf1e0fab459727630c49582f5f94581924b16fba3ce54844d9fddac8cf2f2c2e442c4937f00db98d4276f1e5fb45649
7
- data.tar.gz: 5dbaa89fd0648e3b35d5f4d7d48ee1066d3bf2da71cae97ee2c9769600380c95054f0aee146f845d4c92910f9b1944bc52155b98508345482ba3bb47224c689f
6
+ metadata.gz: f01a4d3e5245986e4a851dc51fb18103405cf2fab2669a43d504c922a5e7284739a23f6cd17da01276a6ab9a71b4c9589667a4bd7e3373d2c83a9508ea9b87e4
7
+ data.tar.gz: 1be2f451d4c370d7c72eee740dfce1536308d61ad7f16c114a7db2eb2462401150ca874b56cda455645abccdc65e9bc97e5810ef82dac16a263ed64ea584c7f7
@@ -5,7 +5,7 @@ module Spree
5
5
  line_item = order.line_items.loaded? ? order.line_items.detect { |li| li.variant_id == variant.id } : order.line_items.find_by(variant_id: variant.id)
6
6
 
7
7
  if line_item
8
- Spree::Dependencies.cart_compare_line_items_service.constantize.call(order: order, line_item: line_item, options: options).value
8
+ Spree.cart_compare_line_items_service.call(order: order, line_item: line_item, options: options).value
9
9
  end
10
10
 
11
11
  line_item
@@ -7,10 +7,17 @@ module Spree
7
7
  # @param options [Hash] options for the image tag
8
8
  # @option options [Integer] :width the width of the image
9
9
  # @option options [Integer] :height the height of the image
10
+ # @option options [Symbol] :variant use a preprocessed named variant (e.g., :mini, :small, :medium, :large, :xlarge)
10
11
  def spree_image_tag(image, options = {})
12
+ url_options = if options[:variant].present?
13
+ { variant: options[:variant] }
14
+ else
15
+ { width: options[:width], height: options[:height], format: options[:format] }
16
+ end
17
+
11
18
  image_tag(
12
- spree_image_url(image, { width: options[:width], height: options[:height], format: options[:format] }),
13
- options
19
+ spree_image_url(image, url_options),
20
+ options.except(:variant, :format)
14
21
  )
15
22
  end
16
23
 
@@ -19,9 +26,17 @@ module Spree
19
26
  return unless image.variable?
20
27
  return if image.respond_to?(:attached?) && !image.attached?
21
28
  url_helpers = respond_to?(:main_app) ? main_app : Rails.application.routes.url_helpers
29
+
30
+ # Use preprocessed named variant if specified (e.g., :mini, :small, :medium, :large, :xlarge)
31
+ if options[:variant].present?
32
+ return url_helpers.cdn_image_url(image.variant(options[:variant]))
33
+ end
34
+
35
+ # Dynamic variant generation for width/height options
22
36
  width = options[:width]
23
37
  height = options[:height]
24
38
 
39
+ # Double dimensions for retina displays
25
40
  width *= 2 if width.present?
26
41
  height *= 2 if height.present?
27
42
 
@@ -74,27 +89,28 @@ module Spree
74
89
  # @param options [Hash] options for the image variant
75
90
  # @option options [Integer] :width the width of the image
76
91
  # @option options [Integer] :height the height of the image
92
+ #
93
+ # @note The key order matters for variation digest matching with preprocessed variants.
94
+ # Active Storage reorders keys alphabetically, so use: format, resize_to_fill/limit, saver
95
+ # @note Use string values (not symbols) for format because variation keys are JSON-encoded
96
+ # in URLs and JSON converts symbols to strings, causing digest mismatches.
77
97
  def spree_image_variant_options(options = {})
78
- {
79
- saver: options[:format] == :png ? png_variant_options : webp_variant_options,
80
- format: options[:format] || :webp
81
- }.merge(options.except(:format))
98
+ format_opt = options[:format]&.to_s
99
+ saver_options = format_opt == "png" ? png_saver_options : Spree::Asset::WEBP_SAVER_OPTIONS
100
+ format = format_opt || "webp"
101
+
102
+ # Build hash in alphabetical order to match Active Storage's key ordering
103
+ result = {}
104
+ result[:format] = format
105
+ result[:resize_to_fill] = options[:resize_to_fill] if options[:resize_to_fill]
106
+ result[:resize_to_limit] = options[:resize_to_limit] if options[:resize_to_limit]
107
+ result[:saver] = saver_options
108
+ result
82
109
  end
83
110
 
84
111
  private
85
112
 
86
- def webp_variant_options
87
- {
88
- strip: true,
89
- quality: 75,
90
- lossless: false,
91
- alpha_q: 85,
92
- reduction_effort: 6,
93
- smart_subsample: true
94
- }
95
- end
96
-
97
- def png_variant_options
113
+ def png_saver_options
98
114
  {
99
115
  strip: true,
100
116
  compression_level: 8,
@@ -5,7 +5,7 @@ module Spree
5
5
 
6
6
  def variant_image_url(variant)
7
7
  image = variant.default_image
8
- image.present? && image.attached? ? spree_image_url(image, width: 100, height: 100, format: :png) : image_url('noimage/small.png')
8
+ image.present? && image.attached? ? spree_image_url(image, variant: :mini) : image_url('noimage/small.png')
9
9
  end
10
10
 
11
11
  def name_for(order)
@@ -31,7 +31,7 @@ module Spree
31
31
 
32
32
  upsert_options = {}
33
33
  if %w[PostgreSQL SQLite].include?(ActiveRecord::Base.connection.adapter_name)
34
- upsert_options[:unique_by] = %i[import_id row_number]
34
+ upsert_options[:unique_by] = :index_spree_import_rows_on_import_id_and_row_number
35
35
  end
36
36
  # NOTE: Ensure a DB-level unique index on [:import_id, :row_number] for all adapters.
37
37
 
@@ -4,7 +4,7 @@ module Spree
4
4
  queue_as Spree.queues.variants
5
5
 
6
6
  def perform(line_item:)
7
- Spree::Dependencies.cart_remove_line_item_service.constantize.call(order: line_item.order, line_item: line_item)
7
+ Spree.cart_remove_line_item_service.call(order: line_item.order, line_item: line_item)
8
8
  end
9
9
  end
10
10
  end
@@ -12,9 +12,33 @@ module Spree
12
12
  belongs_to :viewable, polymorphic: true, touch: true
13
13
  acts_as_list scope: [:viewable_id, :viewable_type]
14
14
 
15
- delegate :key, :attached?, :variant, :variable?, :blob, :filename, to: :attachment
15
+ delegate :key, :attached?, :variant, :variable?, :blob, :filename, :variation, to: :attachment
16
16
 
17
- has_one_attached :attachment, service: Spree.public_storage_service_name
17
+ WEBP_SAVER_OPTIONS = {
18
+ strip: true,
19
+ quality: 75,
20
+ lossless: false,
21
+ alpha_q: 85,
22
+ reduction_effort: 6,
23
+ smart_subsample: true
24
+ }.freeze
25
+
26
+ has_one_attached :attachment, service: Spree.public_storage_service_name do |attachable|
27
+ # Note: Key order matters for variation digest matching.
28
+ # Active Storage reorders keys alphabetically when calling variant(:name),
29
+ # so we must define them in alphabetical order: format, resize_to_fill, saver
30
+ #
31
+ # IMPORTANT: Use string values (not symbols) for format because the variation key
32
+ # is JSON-encoded in URLs. JSON converts symbols to strings, so "webp" != :webp
33
+ # after round-tripping, which causes digest mismatches.
34
+ Spree::Config.product_image_variant_sizes.each do |name, (width, height)|
35
+ attachable.variant name,
36
+ format: "webp",
37
+ resize_to_fill: [width, height],
38
+ saver: WEBP_SAVER_OPTIONS,
39
+ preprocessed: true
40
+ end
41
+ end
18
42
 
19
43
  default_scope { includes(attachment_attachment: :blob) }
20
44
 
@@ -157,7 +157,7 @@ module Spree
157
157
  end
158
158
 
159
159
  def current_ability
160
- @current_ability ||= Spree::Dependencies.ability_class.constantize.new(user, { store: store })
160
+ @current_ability ||= Spree.ability_class.new(user, { store: store })
161
161
  end
162
162
 
163
163
  # eg. Spree::Exports::Products => products-store-my-store-code-20241030133348.csv
@@ -7,25 +7,16 @@ module Spree
7
7
  included do
8
8
  validates :attachment, attached: true, content_type: Rails.application.config.active_storage.web_image_content_types
9
9
 
10
+ # Returns image styles derived from Spree::Config.product_image_variant_sizes
11
+ # Format: { variant_name: 'WxH>' } for API compatibility
10
12
  def self.styles
11
- @styles ||= {
12
- mini: '48x48>',
13
- small: '100x100>',
14
- product: '240x240>',
15
- pdp_thumbnail: '160x200>',
16
- plp_and_carousel: '448x600>',
17
- plp_and_carousel_xs: '254x340>',
18
- plp_and_carousel_sm: '350x468>',
19
- plp_and_carousel_md: '222x297>',
20
- plp_and_carousel_lg: '278x371>',
21
- large: '600x600>',
22
- plp: '278x371>',
23
- zoomed: '650x870>'
24
- }
13
+ @styles ||= Spree::Config.product_image_variant_sizes.transform_values do |dimensions|
14
+ "#{dimensions[0]}x#{dimensions[1]}>"
15
+ end
25
16
  end
26
17
 
27
18
  def default_style
28
- :product
19
+ :small
29
20
  end
30
21
  end
31
22
  end
@@ -51,7 +51,8 @@ module Spree
51
51
  end
52
52
 
53
53
  def plp_url
54
- generate_url(size: self.class.styles[:plp_and_carousel])
54
+ Spree::Deprecation.warn "Image#plp_url is deprecated. Use variant(:large) instead."
55
+ generate_url(size: self.class.styles[:large])
55
56
  end
56
57
 
57
58
  private
@@ -217,7 +217,7 @@ module Spree
217
217
  # Returns the current ability for the import
218
218
  # @return [Spree::Ability]
219
219
  def current_ability
220
- @current_ability ||= Spree::Dependencies.ability_class.constantize.new(user, { store: store })
220
+ @current_ability ||= Spree.ability_class.new(user, { store: store })
221
221
  end
222
222
 
223
223
  class << self
@@ -25,13 +25,13 @@ module Spree
25
25
  # @param gift_card [Spree::GiftCard] the gift card to apply
26
26
  # @return [Spree::Order] the order with the gift card applied
27
27
  def apply_gift_card(gift_card)
28
- Spree::Dependencies.gift_card_apply_service.constantize.call(gift_card: gift_card, order: self)
28
+ Spree.gift_card_apply_service.call(gift_card: gift_card, order: self)
29
29
  end
30
30
 
31
31
  # Removes a gift card from the order
32
32
  # @return [Spree::Order] the order with the gift card removed
33
33
  def remove_gift_card
34
- Spree::Dependencies.gift_card_remove_service.constantize.call(order: self)
34
+ Spree.gift_card_remove_service.call(order: self)
35
35
  end
36
36
 
37
37
  def recalculate_gift_card
@@ -44,7 +44,7 @@ module Spree
44
44
  def redeem_gift_card
45
45
  return unless gift_card.present?
46
46
 
47
- Spree::Dependencies.gift_card_redeem_service.constantize.call(gift_card: gift_card)
47
+ Spree.gift_card_redeem_service.call(gift_card: gift_card)
48
48
  end
49
49
  end
50
50
  end
@@ -2,11 +2,11 @@ module Spree
2
2
  class Order < Spree.base_class
3
3
  module StoreCredit
4
4
  def add_store_credit_payments(amount = nil)
5
- Spree::Dependencies.checkout_add_store_credit_service.constantize.call(order: self, amount: amount)
5
+ Spree.checkout_add_store_credit_service.call(order: self, amount: amount)
6
6
  end
7
7
 
8
8
  def remove_store_credit_payments
9
- Spree::Dependencies.checkout_remove_store_credit_service.constantize.call(order: self)
9
+ Spree.checkout_remove_store_credit_service.call(order: self)
10
10
  end
11
11
 
12
12
  def covered_by_store_credit?
@@ -349,7 +349,7 @@ module Spree
349
349
  end
350
350
 
351
351
  def updater
352
- @updater ||= Spree::Dependencies.order_updater.constantize.new(self)
352
+ @updater ||= Spree.order_updater.new(self)
353
353
  end
354
354
 
355
355
  def update_with_updater!
@@ -407,7 +407,7 @@ module Spree
407
407
  def find_line_item_by_variant(variant, options = {})
408
408
  line_items.detect do |line_item|
409
409
  line_item.variant_id == variant.id &&
410
- Spree::Dependencies.cart_compare_line_items_service.constantize.new.call(order: self, line_item: line_item, options: options).value
410
+ Spree.cart_compare_line_items_service.new.call(order: self, line_item: line_item, options: options).value
411
411
  end
412
412
  end
413
413
 
@@ -578,7 +578,7 @@ module Spree
578
578
  def empty!
579
579
  raise Spree.t(:cannot_empty_completed_order) if completed?
580
580
 
581
- result = Spree::Dependencies.cart_empty_service.constantize.call(order: self)
581
+ result = Spree.cart_empty_service.call(order: self)
582
582
  result.value
583
583
  end
584
584
 
@@ -957,7 +957,7 @@ module Spree
957
957
  if gift_card.present?
958
958
  recalculate_gift_card
959
959
  elsif using_store_credit?
960
- Spree::Dependencies.checkout_add_store_credit_service.constantize.call(order: self)
960
+ Spree.checkout_add_store_credit_service.call(order: self)
961
961
  end
962
962
  end
963
963
  end
@@ -7,17 +7,17 @@ module Spree
7
7
  end
8
8
 
9
9
  def add(variant, quantity = 1, options = {})
10
- Spree::Dependencies.cart_add_item_service.constantize.call(order: order,
11
- variant: variant,
12
- quantity: quantity,
13
- options: options).value
10
+ Spree.cart_add_item_service.call(order: order,
11
+ variant: variant,
12
+ quantity: quantity,
13
+ options: options).value
14
14
  end
15
15
 
16
16
  def remove(variant, quantity = 1, options = {})
17
- Spree::Dependencies.cart_remove_item_service.constantize.call(order: order,
18
- variant: variant,
19
- quantity: quantity,
20
- options: options).value
17
+ Spree.cart_remove_item_service.call(order: order,
18
+ variant: variant,
19
+ quantity: quantity,
20
+ options: options).value
21
21
  end
22
22
 
23
23
  def remove_line_item(line_item, options = {})
@@ -25,7 +25,7 @@ module Spree
25
25
  end
26
26
 
27
27
  def update_cart(params)
28
- Spree::Dependencies.cart_update_service.constantize.call(order: order, params: params).value
28
+ Spree.cart_update_service.call(order: order, params: params).value
29
29
  end
30
30
  end
31
31
  end
@@ -35,9 +35,9 @@ module Spree
35
35
  def find_matching_line_item(other_order_line_item)
36
36
  order.line_items.detect do |my_li|
37
37
  my_li.variant == other_order_line_item.variant &&
38
- Spree::Dependencies.cart_compare_line_items_service.constantize.new.call(order: order,
39
- line_item: my_li,
40
- options: other_order_line_item.serializable_hash).value
38
+ Spree.cart_compare_line_items_service.new.call(order: order,
39
+ line_item: my_li,
40
+ options: other_order_line_item.serializable_hash).value
41
41
  end
42
42
  end
43
43
 
@@ -61,8 +61,7 @@ module Spree
61
61
  sort_by: 'default'
62
62
  }
63
63
 
64
- products_finder = Spree::Dependencies.products_finder.constantize
65
- products_finder.new(scope: store.products, params: finder_params).execute.limit(preferred_max_products_to_show)
64
+ Spree.products_finder.new(scope: store.products, params: finder_params).execute.limit(preferred_max_products_to_show)
66
65
  end
67
66
  end
68
67
 
@@ -47,9 +47,9 @@ module Spree
47
47
  current_quantity = order.quantity_of(item.variant)
48
48
  next unless current_quantity < item.quantity && item_available?(item)
49
49
 
50
- line_item = Spree::Dependencies.cart_add_item_service.constantize.call(order: order,
51
- variant: item.variant,
52
- quantity: item.quantity - current_quantity).value
50
+ line_item = Spree.cart_add_item_service.call(order: order,
51
+ variant: item.variant,
52
+ quantity: item.quantity - current_quantity).value
53
53
  action_taken = true if line_item.try(:valid?)
54
54
  end
55
55
  action_taken
@@ -68,9 +68,9 @@ module Spree
68
68
  line_item = order.find_line_item_by_variant(item.variant)
69
69
  next unless line_item.present?
70
70
 
71
- Spree::Dependencies.cart_remove_item_service.constantize.call(order: order,
72
- variant: item.variant,
73
- quantity: (item.quantity || 1))
71
+ Spree.cart_remove_item_service.call(order: order,
72
+ variant: item.variant,
73
+ quantity: (item.quantity || 1))
74
74
  action_taken = true
75
75
  end
76
76
 
@@ -133,7 +133,7 @@ module Spree
133
133
  line_item = order.find_line_item_by_variant(item.variant)
134
134
  next if line_item.blank?
135
135
 
136
- Spree::Dependencies.cart_remove_item_service.constantize.call(order: order, variant: item.variant, quantity: item.quantity)
136
+ Spree.cart_remove_item_service.call(order: order, variant: item.variant, quantity: item.quantity)
137
137
  end
138
138
  end
139
139
 
@@ -77,7 +77,7 @@ module Spree
77
77
 
78
78
  # your shipping method subclasses can override this method to provide a custom tracking number service
79
79
  def tracking_number_service(tracking)
80
- @tracking_number_service ||= Spree::Dependencies.tracking_number_service.constantize.new(tracking)
80
+ @tracking_number_service ||= Spree.tracking_number_service.new(tracking)
81
81
  end
82
82
 
83
83
  def self.calculators
@@ -210,7 +210,7 @@ module Spree
210
210
 
211
211
  def self.current(url = nil)
212
212
  if url.present?
213
- Spree::Dependencies.current_store_finder.constantize.new(url: url).execute
213
+ Spree.current_store_finder.new(url: url).execute
214
214
  else
215
215
  Spree::Current.store
216
216
  end
@@ -85,10 +85,13 @@ module Spree
85
85
  preference :border_shadow_blur, :integer, default: 5
86
86
 
87
87
  # PRODUCT IMAGES
88
- preference :product_listing_image_height, :integer, default: 300
89
- preference :product_listing_image_width, :integer, default: 300
90
- preference :product_listing_image_height_mobile, :integer, default: 190
91
- preference :product_listing_image_width_mobile, :integer, default: 190
88
+ # These defaults match preprocessed variant sizes for optimal performance:
89
+ # Desktop (360x360) large variant (720x720 at 2x)
90
+ # Mobile (200x200) medium variant (400x400 at 2x)
91
+ preference :product_listing_image_height, :integer, default: 360
92
+ preference :product_listing_image_width, :integer, default: 360
93
+ preference :product_listing_image_height_mobile, :integer, default: 200
94
+ preference :product_listing_image_width_mobile, :integer, default: 200
92
95
  end
93
96
  end
94
97
  end
@@ -135,10 +135,7 @@ module Spree
135
135
  private
136
136
 
137
137
  def image_url_options
138
- {
139
- width: 1000,
140
- height: 1000
141
- }
138
+ { variant: :xlarge }
142
139
  end
143
140
  end
144
141
  end
@@ -6,7 +6,7 @@ module Spree
6
6
  def call(order:, variant:, quantity: nil, public_metadata: {}, private_metadata: {}, options: {})
7
7
  ApplicationRecord.transaction do
8
8
  run :add_to_line_item
9
- run Spree::Dependencies.cart_recalculate_service.constantize
9
+ run Spree.cart_recalculate_service
10
10
  end
11
11
  end
12
12
 
@@ -18,7 +18,7 @@ module Spree
18
18
 
19
19
  return failure(variant, "#{variant.name} is not available in #{order.currency}") if variant.amount_in(order.currency).nil?
20
20
 
21
- line_item = Spree::Dependencies.line_item_by_variant_finder.constantize.new.execute(order: order, variant: variant, options: options)
21
+ line_item = Spree.line_item_by_variant_finder.new.execute(order: order, variant: variant, options: options)
22
22
 
23
23
  line_item_created = line_item.nil?
24
24
  if line_item.nil?
@@ -9,9 +9,9 @@ module Spree
9
9
 
10
10
  ActiveRecord::Base.transaction do
11
11
  line_item = remove_from_line_item(order: order, variant: variant, quantity: quantity, options: options)
12
- Spree::Dependencies.cart_recalculate_service.constantize.call(line_item: line_item,
13
- order: order,
14
- options: options)
12
+ Spree.cart_recalculate_service.call(line_item: line_item,
13
+ order: order,
14
+ options: options)
15
15
  success(line_item)
16
16
  end
17
17
  end
@@ -19,7 +19,7 @@ module Spree
19
19
  private
20
20
 
21
21
  def remove_from_line_item(order:, variant:, quantity:, options:)
22
- line_item = Spree::Dependencies.line_item_by_variant_finder.constantize.new.execute(order: order, variant: variant, options: options)
22
+ line_item = Spree.line_item_by_variant_finder.new.execute(order: order, variant: variant, options: options)
23
23
 
24
24
  raise ActiveRecord::RecordNotFound if line_item.nil?
25
25
 
@@ -7,9 +7,9 @@ module Spree
7
7
  options ||= {}
8
8
  ActiveRecord::Base.transaction do
9
9
  order.line_items.destroy(line_item)
10
- Spree::Dependencies.cart_recalculate_service.constantize.new.call(order: order,
11
- line_item: line_item,
12
- options: options)
10
+ Spree.cart_recalculate_service.new.call(order: order,
11
+ line_item: line_item,
12
+ options: options)
13
13
  end
14
14
  success(line_item)
15
15
  end
@@ -49,7 +49,7 @@ module Spree
49
49
  end
50
50
 
51
51
  def cart_remove_line_item_service
52
- Spree::Dependencies.cart_remove_line_item_service.constantize
52
+ Spree.cart_remove_line_item_service
53
53
  end
54
54
  end
55
55
  end
@@ -6,7 +6,7 @@ module Spree
6
6
  def call(order:, line_item:, quantity: nil)
7
7
  ActiveRecord::Base.transaction do
8
8
  run :change_item_quantity
9
- run Spree::Dependencies.cart_recalculate_service.constantize
9
+ run Spree.cart_recalculate_service
10
10
  end
11
11
  end
12
12
 
@@ -15,7 +15,7 @@ module Spree
15
15
  transitions_count = 0
16
16
 
17
17
  until cannot_make_transition?(order, state)
18
- next_result = Spree::Dependencies.checkout_next_service.constantize.call(order: order)
18
+ next_result = Spree.checkout_next_service.call(order: order)
19
19
  return failure(order, order.errors) if next_result.failure? && (transitions_count.zero? || state.present?)
20
20
 
21
21
  transitions_count +=1
@@ -4,7 +4,7 @@ module Spree
4
4
  prepend Spree::ServiceModule::Base
5
5
 
6
6
  def call(order:)
7
- Spree::Dependencies.checkout_next_service.constantize.call(order: order) until cannot_make_transition?(order)
7
+ Spree.checkout_next_service.call(order: order) until cannot_make_transition?(order)
8
8
 
9
9
  if order.reload.complete?
10
10
  success(order)
@@ -87,19 +87,19 @@ module Spree
87
87
  end
88
88
 
89
89
  def optional_attributes
90
- Spree::Dependencies.data_feeds_google_optional_attributes_service.constantize.new
90
+ Spree.data_feeds_google_optional_attributes_service.new
91
91
  end
92
92
 
93
93
  def required_attributes
94
- Spree::Dependencies.data_feeds_google_required_attributes_service.constantize.new
94
+ Spree.data_feeds_google_required_attributes_service.new
95
95
  end
96
96
 
97
97
  def optional_sub_attributes
98
- Spree::Dependencies.data_feeds_google_optional_sub_attributes_service.constantize.new
98
+ Spree.data_feeds_google_optional_sub_attributes_service.new
99
99
  end
100
100
 
101
101
  def products_list
102
- Spree::Dependencies.data_feeds_google_products_list.constantize.new
102
+ Spree.data_feeds_google_products_list.new
103
103
  end
104
104
  end
105
105
  end
@@ -4,7 +4,7 @@ module Spree
4
4
  protected
5
5
 
6
6
  def recalculate_service
7
- Spree::Dependencies.cart_recalculate_service.constantize
7
+ Spree.cart_recalculate_service
8
8
  end
9
9
  end
10
10
  end
@@ -12,11 +12,11 @@ module Spree
12
12
  end
13
13
 
14
14
  def add_item_service
15
- Spree::Dependencies.cart_add_item_service.constantize
15
+ Spree.cart_add_item_service
16
16
  end
17
17
 
18
18
  def remove_item_service
19
- Spree::Dependencies.cart_remove_item_service.constantize
19
+ Spree.cart_remove_item_service
20
20
  end
21
21
  end
22
22
  end
@@ -46,6 +46,36 @@ module Spree
46
46
  preference :expedited_exchanges_days_window, :integer, default: 14 # the amount of days the customer has to return their item after the expedited exchange is shipped in order to avoid being charged
47
47
  preference :geocode_addresses, :boolean, default: true
48
48
  preference :images_save_from_url_job_attempts, :integer, default: 5
49
+
50
+ # Preprocessed product image variant sizes at 2x retina resolution.
51
+ # These variants are generated on upload to reduce runtime processing.
52
+ # When using spree_image_tag, pass variant option instead of width and height.
53
+ #
54
+ # Default sizes:
55
+ # mini (128x128) - admin thumbnails, checkout line items
56
+ # small (256x256) - cart/order items, gallery thumbnails
57
+ # medium (400x400) - mobile listing, admin media
58
+ # large (720x720) - product listing, mobile gallery
59
+ # xlarge (2000x2000) - gallery main, lightbox
60
+ #
61
+ # To customize, override in your initializer:
62
+ # Spree::Config.product_image_variant_sizes = {
63
+ # mini: [128, 128],
64
+ # small: [256, 256],
65
+ # # ... your custom sizes
66
+ # }
67
+ attr_writer :product_image_variant_sizes
68
+
69
+ def product_image_variant_sizes
70
+ @product_image_variant_sizes ||= {
71
+ mini: [128, 128],
72
+ small: [256, 256],
73
+ medium: [400, 400],
74
+ large: [720, 720],
75
+ xlarge: [2000, 2000],
76
+ og_image: [1200, 630]
77
+ }
78
+ end
49
79
  preference :layout, :string, deprecated: 'Please use Spree::Frontend::Config[:layout] instead'
50
80
  preference :logo, :string, deprecated: true
51
81
  preference :mailer_logo, :string, deprecated: true
@@ -17,7 +17,7 @@ module Spree
17
17
 
18
18
  # Needs to be overridden so that we use Spree's Ability rather than anyone else's.
19
19
  def current_ability
20
- @current_ability ||= Spree::Dependencies.ability_class.constantize.new(try_spree_current_user, { store: current_store })
20
+ @current_ability ||= Spree.ability_class.new(try_spree_current_user, { store: current_store })
21
21
  end
22
22
 
23
23
  def redirect_back_or_default(default)
@@ -64,7 +64,7 @@ module Spree
64
64
  end
65
65
 
66
66
  def current_store_finder
67
- Spree::Dependencies.current_store_finder.constantize
67
+ Spree.current_store_finder
68
68
  end
69
69
 
70
70
  def raise_record_not_found_if_store_is_not_found
@@ -97,8 +97,8 @@ module Spree
97
97
  # finders
98
98
  address_finder: 'Spree::Addresses::Find',
99
99
  country_finder: 'Spree::Countries::Find',
100
- cms_page_finder: 'Spree::CmsPages::Find', # LEGACY
101
- menu_finder: 'Spree::Menus::Find', # LEGACY
100
+ cms_page_finder: nil, # LEGACY
101
+ menu_finder: nil, # LEGACY
102
102
  current_order_finder: 'Spree::Orders::FindCurrent',
103
103
  current_store_finder: 'Spree::Stores::FindCurrent',
104
104
  completed_order_finder: 'Spree::Orders::FindComplete',
@@ -1,21 +1,103 @@
1
1
  module Spree
2
+ class DependencyError < StandardError; end
3
+
2
4
  module DependenciesHelper
5
+ # Patterns to skip when finding the actual caller (internal routing code)
6
+ INTERNAL_CALLER_PATTERNS = [
7
+ %r{lib/spree/core\.rb.*method_missing},
8
+ %r{lib/spree/api\.rb.*method_missing}
9
+ ].freeze
10
+
3
11
  def self.included(base)
4
12
  injection_points = base::INJECTION_POINTS_WITH_DEFAULTS.keys.freeze
5
13
  base.const_set(:INJECTION_POINTS, injection_points)
6
- base.attr_accessor(*injection_points)
14
+
15
+ injection_points.each do |point|
16
+ # Original getter - returns raw value (string, class, or proc result)
17
+ # BACKWARDS COMPATIBLE: Spree::Dependencies.foo.constantize still works
18
+ base.attr_reader(point)
19
+
20
+ # Setter with override tracking
21
+ # BACKWARDS COMPATIBLE: Spree::Dependencies.foo = "MyClass" still works
22
+ base.define_method("#{point}=") do |value|
23
+ @overrides ||= {}
24
+ @overrides[point] = {
25
+ value: value,
26
+ source: find_caller_source,
27
+ set_at: Time.current
28
+ }
29
+ # Clear memoized class when value changes
30
+ remove_instance_variable("@#{point}_resolved") if instance_variable_defined?("@#{point}_resolved")
31
+ instance_variable_set("@#{point}", value)
32
+ end
33
+
34
+ # Returns resolved class with memoization
35
+ # Usage: Spree::Dependencies.foo_class or Spree.foo
36
+ base.define_method("#{point}_class") do
37
+ return instance_variable_get("@#{point}_resolved") if instance_variable_defined?("@#{point}_resolved")
38
+
39
+ value = send(point)
40
+ resolved = case value
41
+ when String then value.constantize
42
+ when Proc then value.call.then { |v| v.is_a?(String) ? v.constantize : v }
43
+ else value
44
+ end
45
+ instance_variable_set("@#{point}_resolved", resolved)
46
+ resolved
47
+ end
48
+ end
7
49
  end
8
50
 
9
51
  def initialize
10
52
  set_default_values
11
53
  end
12
54
 
55
+ # Returns hash of all overridden dependencies with metadata
56
+ def overrides
57
+ @overrides || {}
58
+ end
59
+
60
+ # Check if a specific dependency has been overridden
61
+ def overridden?(name)
62
+ overrides.key?(name.to_sym)
63
+ end
64
+
65
+ # Get override info for a specific dependency
66
+ def override_info(name)
67
+ overrides[name.to_sym]
68
+ end
69
+
70
+ # Returns array of all dependencies with current values and metadata
13
71
  def current_values
14
- values = []
15
- self.class::INJECTION_POINTS.each do |injection_point|
16
- values << { injection_point.to_s => instance_variable_get("@#{injection_point}") }
72
+ self.class::INJECTION_POINTS.map do |point|
73
+ default = self.class::INJECTION_POINTS_WITH_DEFAULTS[point]
74
+ default_val = default.respond_to?(:call) ? default.call : default
75
+ current = send(point)
76
+
77
+ {
78
+ name: point,
79
+ current: current,
80
+ default: default_val,
81
+ # Use overridden? to check if setter was called, not value comparison
82
+ # Value comparison fails for proc-based defaults that reference other dependencies
83
+ overridden: overridden?(point),
84
+ override_info: overrides[point]
85
+ }
17
86
  end
18
- values
87
+ end
88
+
89
+ # Validate all dependencies can be resolved to classes
90
+ # Raises Spree::DependencyError if any dependency is invalid
91
+ def validate!
92
+ errors = []
93
+ self.class::INJECTION_POINTS.each do |point|
94
+ send("#{point}_class")
95
+ rescue NameError => e
96
+ errors << { name: point, value: send(point), error: e.message }
97
+ end
98
+ raise Spree::DependencyError, "Invalid dependencies: #{errors.map { |e| e[:name] }.join(', ')}" if errors.any?
99
+
100
+ true
19
101
  end
20
102
 
21
103
  private
@@ -26,5 +108,18 @@ module Spree
26
108
  instance_variable_set("@#{injection_point}", value)
27
109
  end
28
110
  end
111
+
112
+ # Find the actual caller source, skipping internal DSL routing code
113
+ # This ensures Spree.foo = X reports the user's code location, not method_missing
114
+ def find_caller_source
115
+ caller_locations(2, 10).each do |location|
116
+ location_str = location.to_s
117
+ next if INTERNAL_CALLER_PATTERNS.any? { |pattern| location_str.match?(pattern) }
118
+
119
+ return location_str
120
+ end
121
+ # Fallback to immediate caller if no external caller found
122
+ caller_locations(2, 1).first.to_s
123
+ end
29
124
  end
30
125
  end
@@ -32,7 +32,7 @@ module Spree
32
32
  def extended_base_scope
33
33
  base_scope = current_store.products.spree_base_scopes
34
34
  base_scope = get_products_conditions_for(base_scope, keywords)
35
- base_scope = Spree::Dependencies.products_finder.constantize.new(**product_finder_params(base_scope)).execute
35
+ base_scope = Spree.products_finder.new(**product_finder_params(base_scope)).execute
36
36
  base_scope = add_search_scopes(base_scope)
37
37
  add_eagerload_scopes(base_scope)
38
38
  end
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.2.1'.freeze
2
+ VERSION = '5.2.2'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
data/lib/spree/core.rb CHANGED
@@ -374,6 +374,40 @@ module Spree
374
374
  @permissions ||= PermissionConfiguration.new
375
375
  end
376
376
 
377
+ class << self
378
+ # Dynamic methods for core dependencies
379
+ #
380
+ # @example Getting a dependency (returns resolved class)
381
+ # Spree.cart_add_item_service.call(order: order, variant: variant)
382
+ #
383
+ # @example Setting a dependency
384
+ # Spree.cart_add_item_service = MyApp::CartAddItem
385
+ def method_missing(method_name, *args, &block)
386
+ base_name = method_name.to_s.chomp('=').to_sym
387
+
388
+ return super unless core_dependency?(base_name)
389
+
390
+ if method_name.to_s.end_with?('=')
391
+ Spree::Dependencies.send(method_name, args.first)
392
+ else
393
+ # Returns resolved class (not string)
394
+ Spree::Dependencies.send("#{method_name}_class")
395
+ end
396
+ end
397
+
398
+ def respond_to_missing?(method_name, include_private = false)
399
+ base_name = method_name.to_s.chomp('=').to_sym
400
+ core_dependency?(base_name) || super
401
+ end
402
+
403
+ private
404
+
405
+ def core_dependency?(name)
406
+ defined?(Spree::Dependencies) &&
407
+ Spree::Dependencies.class::INJECTION_POINTS.include?(name)
408
+ end
409
+ end
410
+
377
411
  module Core
378
412
  autoload :ProductFilters, 'spree/core/product_filters'
379
413
  autoload :TokenGenerator, 'spree/core/token_generator'
@@ -30,7 +30,7 @@ module Spree
30
30
 
31
31
  def stub_authorization!
32
32
  ability = build_ability
33
- ability_class = Spree::Dependencies.ability_class.constantize
33
+ ability_class = Spree.ability_class
34
34
 
35
35
  after(:all) do
36
36
  ability_class.remove_ability(ability)
@@ -51,7 +51,7 @@ module Spree
51
51
 
52
52
  def custom_authorization!(&block)
53
53
  ability = build_ability(&block)
54
- ability_class = Spree::Dependencies.ability_class.constantize
54
+ ability_class = Spree.ability_class
55
55
 
56
56
  after(:all) do
57
57
  ability_class.remove_ability(ability)
@@ -0,0 +1,76 @@
1
+ namespace :spree do
2
+ namespace :dependencies do
3
+ desc 'List all Spree dependencies with their current values'
4
+ task list: :environment do
5
+ print_dependencies('CORE', Spree::Dependencies)
6
+ print_dependencies('API', Spree::Api::Dependencies) if defined?(Spree::Api::Dependencies)
7
+ end
8
+
9
+ desc 'Show only overridden dependencies'
10
+ task overrides: :environment do
11
+ core_overrides = Spree::Dependencies.current_values.select { |d| d[:overridden] }
12
+ api_overrides = if defined?(Spree::Api::Dependencies)
13
+ Spree::Api::Dependencies.current_values.select { |d| d[:overridden] }
14
+ else
15
+ []
16
+ end
17
+
18
+ if core_overrides.empty? && api_overrides.empty?
19
+ puts 'No dependencies have been overridden.'
20
+ else
21
+ print_overrides('Core', core_overrides) if core_overrides.any?
22
+ print_overrides('API', api_overrides) if api_overrides.any?
23
+ end
24
+ end
25
+
26
+ desc 'Validate all dependencies resolve to valid classes'
27
+ task validate: :environment do
28
+ errors = validate_dependencies('Core', Spree::Dependencies)
29
+ errors += validate_dependencies('API', Spree::Api::Dependencies) if defined?(Spree::Api::Dependencies)
30
+
31
+ puts "\n"
32
+ if errors.any?
33
+ puts "\n\e[31m#{errors.size} invalid dependencies:\e[0m"
34
+ errors.each { |err| puts " [#{err[:source]}] #{err[:name]}: #{err[:error]}" }
35
+ exit 1
36
+ else
37
+ puts "\e[32mAll dependencies valid\e[0m"
38
+ end
39
+ end
40
+
41
+ def print_dependencies(name, deps)
42
+ puts "\n[#{name}]"
43
+
44
+ # Calculate max width for alignment
45
+ max_name_width = deps.class::INJECTION_POINTS.map(&:length).max
46
+
47
+ deps.current_values.each do |dep|
48
+ status = dep[:overridden] ? ' [OVERRIDDEN]' : ''
49
+ puts "#{dep[:name].to_s.ljust(max_name_width)} #{dep[:current]}#{status}"
50
+ end
51
+ end
52
+
53
+ def print_overrides(name, overrides)
54
+ puts "\n[#{name} OVERRIDES]"
55
+
56
+ max_name_width = overrides.map { |d| d[:name].length }.max
57
+
58
+ overrides.each do |dep|
59
+ source = dep[:override_info] ? " (#{dep[:override_info][:source]})" : ''
60
+ puts "#{dep[:name].to_s.ljust(max_name_width)} #{dep[:default]} -> #{dep[:current]}#{source}"
61
+ end
62
+ end
63
+
64
+ def validate_dependencies(name, deps)
65
+ errors = []
66
+ deps.class::INJECTION_POINTS.each do |point|
67
+ deps.send("#{point}_class")
68
+ print "\e[32m.\e[0m"
69
+ rescue NameError => e
70
+ errors << { source: name, name: point, error: e.message }
71
+ print "\e[31mF\e[0m"
72
+ end
73
+ errors
74
+ end
75
+ end
76
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: spree_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.2.1
4
+ version: 5.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sean Schofield
@@ -1665,6 +1665,7 @@ files:
1665
1665
  - lib/spree/translation_migrations.rb
1666
1666
  - lib/spree_core.rb
1667
1667
  - lib/tasks/core.rake
1668
+ - lib/tasks/dependencies.rake
1668
1669
  - lib/tasks/exchanges.rake
1669
1670
  - vendor/javascript/@rails--request.js.js
1670
1671
  - vendor/javascript/@stimulus-components--auto-submit.js
@@ -1677,9 +1678,9 @@ licenses:
1677
1678
  - BSD-3-Clause
1678
1679
  metadata:
1679
1680
  bug_tracker_uri: https://github.com/spree/spree/issues
1680
- changelog_uri: https://github.com/spree/spree/releases/tag/v5.2.1
1681
+ changelog_uri: https://github.com/spree/spree/releases/tag/v5.2.2
1681
1682
  documentation_uri: https://docs.spreecommerce.org/
1682
- source_code_uri: https://github.com/spree/spree/tree/v5.2.1
1683
+ source_code_uri: https://github.com/spree/spree/tree/v5.2.2
1683
1684
  rdoc_options: []
1684
1685
  require_paths:
1685
1686
  - lib