spree_core 5.2.6 → 5.3.0.rc1

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 (294) hide show
  1. checksums.yaml +4 -4
  2. data/app/helpers/spree/addresses_helper.rb +1 -23
  3. data/app/helpers/spree/base_helper.rb +25 -77
  4. data/app/helpers/spree/currency_helper.rb +2 -2
  5. data/app/helpers/spree/images_helper.rb +5 -0
  6. data/app/helpers/spree/mail_helper.rb +1 -1
  7. data/app/helpers/spree/products_helper.rb +1 -1
  8. data/app/jobs/spree/events/subscriber_job.rb +58 -0
  9. data/app/jobs/spree/products/refresh_metrics_job.rb +14 -0
  10. data/app/jobs/spree/products/touch_taxons_job.rb +0 -1
  11. data/app/jobs/spree/variants/touch_job.rb +9 -0
  12. data/app/models/concerns/spree/adjustment_source.rb +8 -0
  13. data/app/models/concerns/spree/parameterizable_name.rb +1 -1
  14. data/app/models/concerns/spree/product_scopes.rb +54 -8
  15. data/app/models/concerns/spree/publishable.rb +253 -0
  16. data/app/models/concerns/spree/unique_name.rb +1 -1
  17. data/app/models/concerns/spree/user_methods.rb +21 -1
  18. data/app/models/spree/ability.rb +3 -3
  19. data/app/models/spree/address.rb +0 -3
  20. data/app/models/spree/adjustable/promotion_accumulator.rb +1 -1
  21. data/app/models/spree/adjustment.rb +32 -6
  22. data/app/models/spree/asset.rb +6 -3
  23. data/app/models/spree/base.rb +3 -1
  24. data/app/models/spree/classification.rb +2 -2
  25. data/app/models/spree/credit_card.rb +0 -3
  26. data/app/models/spree/current.rb +24 -3
  27. data/app/models/spree/custom_domain.rb +1 -1
  28. data/app/models/spree/customer_group.rb +94 -0
  29. data/app/models/spree/customer_group_user.rb +16 -0
  30. data/app/models/spree/customer_return.rb +2 -3
  31. data/app/models/spree/digital.rb +2 -4
  32. data/app/models/spree/digital_link.rb +2 -3
  33. data/app/models/spree/event.rb +104 -0
  34. data/app/models/spree/export.rb +7 -1
  35. data/app/models/spree/gift_card.rb +13 -1
  36. data/app/models/spree/gift_card_batch.rb +3 -1
  37. data/app/models/spree/image.rb +20 -5
  38. data/app/models/spree/import.rb +17 -12
  39. data/app/models/spree/import_row.rb +13 -26
  40. data/app/models/spree/inventory_unit.rb +0 -3
  41. data/app/models/spree/invitation.rb +12 -6
  42. data/app/models/spree/line_item.rb +57 -4
  43. data/app/models/spree/newsletter_subscriber.rb +2 -0
  44. data/app/models/spree/option_type.rb +2 -5
  45. data/app/models/spree/option_value.rb +1 -4
  46. data/app/models/spree/order/checkout.rb +7 -2
  47. data/app/models/spree/order/emails.rb +3 -11
  48. data/app/models/spree/order/store_credit.rb +5 -1
  49. data/app/models/spree/order.rb +80 -31
  50. data/app/models/spree/order_updater.rb +28 -9
  51. data/app/models/spree/payment/custom_events.rb +37 -0
  52. data/app/models/spree/payment.rb +13 -4
  53. data/app/models/spree/payment_capture_event.rb +0 -4
  54. data/app/models/spree/payment_method.rb +1 -1
  55. data/app/models/spree/permission_sets/default_customer.rb +3 -3
  56. data/app/models/spree/policy.rb +1 -8
  57. data/app/models/spree/post.rb +2 -7
  58. data/app/models/spree/post_category.rb +2 -0
  59. data/app/models/spree/price.rb +8 -6
  60. data/app/models/spree/price_list.rb +263 -0
  61. data/app/models/spree/price_rule.rb +26 -0
  62. data/app/models/spree/price_rules/customer_group_rule.rb +21 -0
  63. data/app/models/spree/price_rules/user_rule.rb +19 -0
  64. data/app/models/spree/price_rules/volume_rule.rb +21 -0
  65. data/app/models/spree/price_rules/zone_rule.rb +19 -0
  66. data/app/models/spree/product.rb +117 -76
  67. data/app/models/spree/product_property.rb +2 -2
  68. data/app/models/spree/promotion/rules/customer_group.rb +22 -0
  69. data/app/models/spree/promotion/rules/user.rb +2 -2
  70. data/app/models/spree/promotion.rb +3 -4
  71. data/app/models/spree/promotion_handler/coupon.rb +3 -3
  72. data/app/models/spree/property.rb +3 -6
  73. data/app/models/spree/prototype.rb +0 -3
  74. data/app/models/spree/refund.rb +2 -3
  75. data/app/models/spree/reimbursement.rb +5 -3
  76. data/app/models/spree/report.rb +7 -1
  77. data/app/models/spree/reports/products_performance.rb +1 -1
  78. data/app/models/spree/reports/sales_total.rb +1 -1
  79. data/app/models/spree/return_authorization.rb +7 -3
  80. data/app/models/spree/return_item.rb +15 -4
  81. data/app/models/spree/shipment/custom_events.rb +47 -0
  82. data/app/models/spree/shipment.rb +12 -5
  83. data/app/models/spree/shipping_category.rb +0 -3
  84. data/app/models/spree/shipping_method.rb +0 -3
  85. data/app/models/spree/stock/quantifier.rb +1 -1
  86. data/app/models/spree/stock_item.rb +2 -0
  87. data/app/models/spree/stock_location.rb +0 -3
  88. data/app/models/spree/stock_movement/custom_events.rb +44 -0
  89. data/app/models/spree/stock_movement.rb +3 -0
  90. data/app/models/spree/stock_transfer.rb +2 -3
  91. data/app/models/spree/store.rb +64 -92
  92. data/app/models/spree/store_credit.rb +3 -4
  93. data/app/models/spree/store_product.rb +14 -0
  94. data/app/models/spree/subscriber.rb +186 -0
  95. data/app/models/spree/tax_category.rb +0 -4
  96. data/app/models/spree/tax_rate.rb +0 -3
  97. data/app/models/spree/taxon.rb +3 -36
  98. data/app/models/spree/taxonomy.rb +0 -3
  99. data/app/models/spree/variant.rb +114 -21
  100. data/app/models/spree/webhook_delivery.rb +60 -0
  101. data/app/models/spree/webhook_endpoint.rb +53 -0
  102. data/app/models/spree/wished_item.rb +2 -4
  103. data/app/models/spree/wishlist.rb +2 -3
  104. data/app/models/spree/zone.rb +0 -9
  105. data/app/paginators/spree/shared/paginate.rb +4 -0
  106. data/app/serializers/spree/events/asset_serializer.rb +22 -0
  107. data/app/serializers/spree/events/base_serializer.rb +61 -0
  108. data/app/serializers/spree/events/customer_return_serializer.rb +20 -0
  109. data/app/serializers/spree/events/digital_link_serializer.rb +20 -0
  110. data/app/serializers/spree/events/digital_serializer.rb +18 -0
  111. data/app/serializers/spree/events/export_serializer.rb +22 -0
  112. data/app/serializers/spree/events/gift_card_batch_serializer.rb +24 -0
  113. data/app/serializers/spree/events/gift_card_serializer.rb +29 -0
  114. data/app/serializers/spree/events/image_serializer.rb +9 -0
  115. data/app/serializers/spree/events/import_row_serializer.rb +23 -0
  116. data/app/serializers/spree/events/import_serializer.rb +24 -0
  117. data/app/serializers/spree/events/invitation_serializer.rb +28 -0
  118. data/app/serializers/spree/events/line_item_serializer.rb +31 -0
  119. data/app/serializers/spree/events/newsletter_subscriber_serializer.rb +21 -0
  120. data/app/serializers/spree/events/order_serializer.rb +39 -0
  121. data/app/serializers/spree/events/payment_serializer.rb +24 -0
  122. data/app/serializers/spree/events/post_category_serializer.rb +20 -0
  123. data/app/serializers/spree/events/post_serializer.rb +26 -0
  124. data/app/serializers/spree/events/price_serializer.rb +22 -0
  125. data/app/serializers/spree/events/product_serializer.rb +24 -0
  126. data/app/serializers/spree/events/promotion_serializer.rb +32 -0
  127. data/app/serializers/spree/events/refund_serializer.rb +23 -0
  128. data/app/serializers/spree/events/reimbursement_serializer.rb +22 -0
  129. data/app/serializers/spree/events/report_serializer.rb +23 -0
  130. data/app/serializers/spree/events/return_authorization_serializer.rb +22 -0
  131. data/app/serializers/spree/events/return_item_serializer.rb +27 -0
  132. data/app/serializers/spree/events/shipment_serializer.rb +24 -0
  133. data/app/serializers/spree/events/stock_item_serializer.rb +22 -0
  134. data/app/serializers/spree/events/stock_movement_serializer.rb +22 -0
  135. data/app/serializers/spree/events/stock_transfer_serializer.rb +22 -0
  136. data/app/serializers/spree/events/store_credit_serializer.rb +30 -0
  137. data/app/serializers/spree/events/user_serializer.rb +18 -0
  138. data/app/serializers/spree/events/variant_serializer.rb +34 -0
  139. data/app/serializers/spree/events/wished_item_serializer.rb +20 -0
  140. data/app/serializers/spree/events/wishlist_serializer.rb +22 -0
  141. data/app/services/spree/addresses/update.rb +1 -1
  142. data/app/services/spree/cart/add_item.rb +1 -1
  143. data/app/services/spree/coupon_codes/coupon_codes_handler.rb +2 -1
  144. data/app/services/spree/data_feeds/google/required_attributes.rb +4 -4
  145. data/app/services/spree/newsletter/verify.rb +5 -0
  146. data/app/services/spree/products/auto_match_taxons.rb +1 -1
  147. data/app/services/spree/seeds/all.rb +1 -1
  148. data/app/services/spree/taxons/add_products.rb +8 -4
  149. data/app/services/spree/taxons/regenerate_products.rb +8 -0
  150. data/app/services/spree/taxons/remove_products.rb +12 -7
  151. data/app/subscribers/spree/event_log_subscriber.rb +64 -0
  152. data/app/subscribers/spree/export_subscriber.rb +26 -0
  153. data/app/subscribers/spree/invitation_email_subscriber.rb +40 -0
  154. data/app/subscribers/spree/product_metrics_subscriber.rb +29 -0
  155. data/app/subscribers/spree/report_subscriber.rb +26 -0
  156. data/config/locales/en.yml +126 -0
  157. data/db/migrate/20251110120000_create_spree_price_lists.rb +22 -0
  158. data/db/migrate/20251110120001_create_spree_price_rules.rb +13 -0
  159. data/db/migrate/20251110120002_add_price_list_id_to_spree_prices.rb +6 -0
  160. data/db/migrate/20251110120003_add_price_list_id_to_spree_line_items.rb +5 -0
  161. data/db/migrate/20251201172118_fix_indexes_on_friendly_id_slugs.rb +8 -0
  162. data/db/migrate/20251214000001_create_spree_webhook_endpoints.rb +19 -0
  163. data/db/migrate/20251214000002_create_spree_webhook_deliveries.rb +23 -0
  164. data/db/migrate/20251222000000_add_performance_indexes_to_spree_adjustments.rb +25 -0
  165. data/db/migrate/20260112000000_fix_spree_prices_unique_indexes.rb +33 -0
  166. data/db/migrate/20260115120000_create_spree_customer_groups.rb +14 -0
  167. data/db/migrate/20260115120001_create_spree_customer_group_users.rb +14 -0
  168. data/db/migrate/20260117140831_remove_not_null_constraint_from_policy_name.rb +5 -0
  169. data/db/migrate/20260118120000_add_statistics_to_store_products.rb +11 -0
  170. data/db/migrate/20260119120000_add_counter_caches_to_spree_products.rb +9 -0
  171. data/db/migrate/20260119170000_add_counter_caches_to_spree_taxons.rb +9 -0
  172. data/db/migrate/20260120120000_add_image_count_to_spree_variants.rb +9 -0
  173. data/lib/generators/spree/dummy/dummy_generator.rb +14 -2
  174. data/lib/spree/core/configuration.rb +1 -0
  175. data/lib/spree/core/controller_helpers/auth.rb +0 -15
  176. data/lib/spree/core/controller_helpers/currency.rb +13 -9
  177. data/lib/spree/core/controller_helpers/search.rb +1 -1
  178. data/lib/spree/core/controller_helpers/store.rb +5 -1
  179. data/lib/spree/core/engine.rb +61 -78
  180. data/lib/spree/core/importer/order.rb +1 -1
  181. data/lib/spree/core/importer/product.rb +1 -1
  182. data/lib/spree/core/preferences/preferable.rb +14 -1
  183. data/lib/spree/core/pricing/context.rb +63 -0
  184. data/lib/spree/core/pricing/resolver.rb +129 -0
  185. data/lib/spree/core/search/base.rb +1 -1
  186. data/lib/spree/core/token_generator.rb +1 -1
  187. data/lib/spree/core/version.rb +1 -1
  188. data/lib/spree/core.rb +42 -47
  189. data/lib/spree/events/adapters/active_support_notifications.rb +112 -0
  190. data/lib/spree/events/adapters/base.rb +193 -0
  191. data/lib/spree/events/registry.rb +99 -0
  192. data/lib/spree/events.rb +240 -0
  193. data/lib/spree/permitted_attributes.rb +13 -2
  194. data/lib/spree/testing_support/common_rake.rb +68 -35
  195. data/lib/spree/testing_support/factories/customer_group_factory.rb +6 -0
  196. data/lib/spree/testing_support/factories/customer_group_user_factory.rb +6 -0
  197. data/lib/spree/testing_support/factories/price_factory.rb +4 -0
  198. data/lib/spree/testing_support/factories/price_list_factory.rb +34 -0
  199. data/lib/spree/testing_support/factories/price_rule_factory.rb +49 -0
  200. data/lib/spree/testing_support/factories/promotion_rule_factory.rb +12 -0
  201. data/lib/spree/testing_support/factories/stock_item_factory.rb +6 -4
  202. data/lib/spree/testing_support/factories/store_product_factory.rb +6 -0
  203. data/lib/spree/testing_support/factories/taxon_factory.rb +0 -1
  204. data/lib/spree/testing_support/factories/webhook_delivery_factory.rb +48 -0
  205. data/lib/spree/testing_support/factories/webhook_endpoint_factory.rb +22 -0
  206. data/lib/spree/testing_support/lifecycle_events.rb +38 -0
  207. data/lib/spree/testing_support/store.rb +4 -2
  208. data/lib/spree/webhooks.rb +22 -0
  209. data/lib/tasks/products.rake +40 -0
  210. data/lib/tasks/taxons.rake +19 -0
  211. data/lib/tasks/variants.rake +18 -0
  212. metadata +112 -114
  213. data/app/jobs/spree/themes/duplicate_components_job.rb +0 -59
  214. data/app/jobs/spree/themes/screenshot_job.rb +0 -81
  215. data/app/models/concerns/spree/has_page_links.rb +0 -53
  216. data/app/models/spree/page.rb +0 -188
  217. data/app/models/spree/page_block.rb +0 -73
  218. data/app/models/spree/page_blocks/buttons.rb +0 -29
  219. data/app/models/spree/page_blocks/heading.rb +0 -18
  220. data/app/models/spree/page_blocks/image.rb +0 -20
  221. data/app/models/spree/page_blocks/link.rb +0 -21
  222. data/app/models/spree/page_blocks/mega_nav.rb +0 -33
  223. data/app/models/spree/page_blocks/mega_nav_with_subcategories.rb +0 -32
  224. data/app/models/spree/page_blocks/metafields.rb +0 -18
  225. data/app/models/spree/page_blocks/nav.rb +0 -15
  226. data/app/models/spree/page_blocks/newsletter_form.rb +0 -18
  227. data/app/models/spree/page_blocks/products/brand.rb +0 -15
  228. data/app/models/spree/page_blocks/products/buy_buttons.rb +0 -24
  229. data/app/models/spree/page_blocks/products/description.rb +0 -18
  230. data/app/models/spree/page_blocks/products/price.rb +0 -18
  231. data/app/models/spree/page_blocks/products/quantity_selector.rb +0 -20
  232. data/app/models/spree/page_blocks/products/share.rb +0 -8
  233. data/app/models/spree/page_blocks/products/title.rb +0 -19
  234. data/app/models/spree/page_blocks/products/variant_picker.rb +0 -13
  235. data/app/models/spree/page_blocks/subheading.rb +0 -17
  236. data/app/models/spree/page_blocks/text.rb +0 -16
  237. data/app/models/spree/page_link.rb +0 -60
  238. data/app/models/spree/page_section.rb +0 -222
  239. data/app/models/spree/page_sections/announcement_bar.rb +0 -28
  240. data/app/models/spree/page_sections/breadcrumbs.rb +0 -12
  241. data/app/models/spree/page_sections/collection_banner.rb +0 -18
  242. data/app/models/spree/page_sections/custom_code.rb +0 -11
  243. data/app/models/spree/page_sections/featured_posts.rb +0 -45
  244. data/app/models/spree/page_sections/featured_product.rb +0 -50
  245. data/app/models/spree/page_sections/featured_taxon.rb +0 -90
  246. data/app/models/spree/page_sections/featured_taxons.rb +0 -45
  247. data/app/models/spree/page_sections/footer.rb +0 -101
  248. data/app/models/spree/page_sections/header.rb +0 -62
  249. data/app/models/spree/page_sections/image_banner.rb +0 -55
  250. data/app/models/spree/page_sections/image_with_text.rb +0 -65
  251. data/app/models/spree/page_sections/main_password_footer.rb +0 -18
  252. data/app/models/spree/page_sections/main_password_header.rb +0 -20
  253. data/app/models/spree/page_sections/newsletter.rb +0 -54
  254. data/app/models/spree/page_sections/page_title.rb +0 -19
  255. data/app/models/spree/page_sections/post_details.rb +0 -19
  256. data/app/models/spree/page_sections/post_grid.rb +0 -19
  257. data/app/models/spree/page_sections/product_details.rb +0 -53
  258. data/app/models/spree/page_sections/product_grid.rb +0 -13
  259. data/app/models/spree/page_sections/related_products.rb +0 -58
  260. data/app/models/spree/page_sections/rich_text.rb +0 -31
  261. data/app/models/spree/page_sections/taxon_banner.rb +0 -18
  262. data/app/models/spree/page_sections/taxon_grid.rb +0 -17
  263. data/app/models/spree/page_sections/video.rb +0 -107
  264. data/app/models/spree/pages/account.rb +0 -19
  265. data/app/models/spree/pages/cart.rb +0 -19
  266. data/app/models/spree/pages/checkout.rb +0 -15
  267. data/app/models/spree/pages/custom.rb +0 -38
  268. data/app/models/spree/pages/homepage.rb +0 -72
  269. data/app/models/spree/pages/login.rb +0 -19
  270. data/app/models/spree/pages/password.rb +0 -59
  271. data/app/models/spree/pages/post.rb +0 -27
  272. data/app/models/spree/pages/post_list.rb +0 -36
  273. data/app/models/spree/pages/product_details.rb +0 -30
  274. data/app/models/spree/pages/search_results.rb +0 -43
  275. data/app/models/spree/pages/shop_all.rb +0 -40
  276. data/app/models/spree/pages/taxon.rb +0 -29
  277. data/app/models/spree/pages/taxon_list.rb +0 -41
  278. data/app/models/spree/pages/wishlist.rb +0 -15
  279. data/app/models/spree/theme.rb +0 -233
  280. data/app/models/spree/themes/default.rb +0 -97
  281. data/app/services/spree/taxons/touch_featured_sections.rb +0 -21
  282. data/db/migrate/20250120094216_create_page_builder_models.rb +0 -78
  283. data/db/migrate/20250305121352_remove_page_builder_indices.rb +0 -11
  284. data/db/migrate/20250825175217_add_missing_page_builder_indexes.rb +0 -7
  285. data/db/migrate/20250913130044_add_page_links_counter_cache_to_spree_stores.rb +0 -10
  286. data/lib/generators/spree/dummy/templates/initializers/devise.rb +0 -3
  287. data/lib/generators/spree/install/install_generator.rb +0 -219
  288. data/lib/generators/spree/install/templates/config/initializers/spree.rb +0 -126
  289. data/lib/spree/core/webhooks.rb +0 -21
  290. data/lib/spree/testing_support/factories/page_block_factory.rb +0 -22
  291. data/lib/spree/testing_support/factories/page_factory.rb +0 -33
  292. data/lib/spree/testing_support/factories/page_link_factory.rb +0 -7
  293. data/lib/spree/testing_support/factories/page_section_factory.rb +0 -27
  294. data/lib/spree/testing_support/factories/theme_factory.rb +0 -14
@@ -1,5 +1,5 @@
1
1
  module Spree
2
- VERSION = '5.2.6'.freeze
2
+ VERSION = '5.3.0.rc1'.freeze
3
3
 
4
4
  def self.version
5
5
  VERSION
data/lib/spree/core.rb CHANGED
@@ -14,12 +14,10 @@ require 'action_mailer/railtie'
14
14
  require 'active_merchant'
15
15
  require 'acts_as_list'
16
16
  require 'acts-as-taggable-on'
17
- require 'auto_strip_attributes'
18
17
  require 'awesome_nested_set'
19
18
  require 'cancan'
20
19
  require 'countries/global'
21
20
  require 'friendly_id'
22
- require 'kaminari'
23
21
  require 'monetize'
24
22
  require 'mobility'
25
23
  require 'name_of_person'
@@ -32,6 +30,7 @@ require 'wannabe_bool'
32
30
  require 'geocoder'
33
31
  require 'oembed'
34
32
  require 'safely_block'
33
+ require 'ar_lazy_preload'
35
34
 
36
35
  # This is required because ActiveModel::Validations#invalid? conflicts with the
37
36
  # invalid state of a Payment. In the future this should be removed.
@@ -40,7 +39,7 @@ StateMachines::Machine.ignore_method_conflicts = true
40
39
  module Spree
41
40
  mattr_accessor :base_class, :user_class, :admin_user_class,
42
41
  :private_storage_service_name, :public_storage_service_name,
43
- :cdn_host, :root_domain, :searcher_class, :queues,
42
+ :cdn_host, :root_domain, :searcher_class, :events_adapter_class, :queues,
44
43
  :google_places_api_key, :screenshot_api_token
45
44
 
46
45
  def self.base_class(constantize: true)
@@ -97,18 +96,20 @@ module Spree
97
96
  def self.queues
98
97
  @@queues ||= OpenStruct.new(
99
98
  default: :default,
99
+ events: :default,
100
100
  exports: :default,
101
101
  images: :default,
102
102
  imports: :default,
103
+ products: :default,
103
104
  reports: :default,
104
105
  variants: :default,
105
106
  taxons: :default,
106
107
  stock_location_stock_items: :default,
107
108
  coupon_codes: :default,
108
- webhooks: :default,
109
109
  themes: :default,
110
110
  addresses: :default,
111
- gift_cards: :default
111
+ gift_cards: :default,
112
+ webhooks: :default
112
113
  )
113
114
  end
114
115
 
@@ -122,6 +123,23 @@ module Spree
122
123
  end
123
124
  end
124
125
 
126
+ # Returns the events adapter class used for publishing and subscribing to events.
127
+ #
128
+ # @example Using a custom adapter
129
+ # Spree.events_adapter_class = 'MyApp::Events::KafkaAdapter'
130
+ #
131
+ # @param constantize [Boolean] whether to return the class or the string
132
+ # @return [Class, String] the adapter class or its name
133
+ def self.events_adapter_class(constantize: true)
134
+ @@events_adapter_class ||= 'Spree::Events::Adapters::ActiveSupportNotifications'
135
+
136
+ if @@events_adapter_class.is_a?(Class)
137
+ raise 'Spree.events_adapter_class MUST be a String or Symbol object, not a Class object.'
138
+ elsif @@events_adapter_class.is_a?(String) || @@events_adapter_class.is_a?(Symbol)
139
+ constantize ? @@events_adapter_class.to_s.constantize : @@events_adapter_class.to_s
140
+ end
141
+ end
142
+
125
143
  def self.google_places_api_key
126
144
  @@google_places_api_key
127
145
  end
@@ -288,51 +306,25 @@ module Spree
288
306
  Rails.application.config.spree.integrations = value
289
307
  end
290
308
 
291
- # Page Builder configuration accessor
292
- def self.page_builder
293
- @page_builder ||= PageBuilderConfig.new
309
+ # Event subscribers that handle lifecycle and custom events
310
+ # @example Adding a custom subscriber
311
+ # Spree.subscribers << MyApp::OrderNotificationSubscriber
312
+ # @example Removing a built-in subscriber
313
+ # Spree.subscribers.delete(Spree::ExportSubscriber)
314
+ def self.subscribers
315
+ Rails.application.config.spree.subscribers
294
316
  end
295
317
 
296
- class PageBuilderConfig
297
- def themes
298
- Rails.application.config.spree.themes
299
- end
300
-
301
- def themes=(value)
302
- Rails.application.config.spree.themes = value
303
- end
304
-
305
- def theme_layout_sections
306
- Rails.application.config.spree.theme_layout_sections
307
- end
308
-
309
- def theme_layout_sections=(value)
310
- Rails.application.config.spree.theme_layout_sections = value
311
- end
312
-
313
- def pages
314
- Rails.application.config.spree.pages
315
- end
316
-
317
- def pages=(value)
318
- Rails.application.config.spree.pages = value
319
- end
320
-
321
- def page_sections
322
- Rails.application.config.spree.page_sections
323
- end
324
-
325
- def page_sections=(value)
326
- Rails.application.config.spree.page_sections = value
327
- end
318
+ def self.subscribers=(value)
319
+ Rails.application.config.spree.subscribers = value
320
+ end
328
321
 
329
- def page_blocks
330
- Rails.application.config.spree.page_blocks
331
- end
322
+ def self.pricing
323
+ Rails.application.config.spree.pricing
324
+ end
332
325
 
333
- def page_blocks=(value)
334
- Rails.application.config.spree.page_blocks = value
335
- end
326
+ def self.pricing=(value)
327
+ Rails.application.config.spree.pricing = value
336
328
  end
337
329
 
338
330
  def self.analytics
@@ -444,6 +436,8 @@ require 'spree/permitted_attributes'
444
436
  require 'spree/service_module'
445
437
  require 'spree/database_type_utilities'
446
438
  require 'spree/analytics'
439
+ require 'spree/events'
440
+ require 'spree/webhooks'
447
441
 
448
442
  require 'spree/core/partials'
449
443
  require 'spree/core/importer'
@@ -462,6 +456,7 @@ require 'spree/core/preferences/store'
462
456
  require 'spree/core/preferences/scoped_store'
463
457
  require 'spree/core/preferences/runtime_configuration'
464
458
 
465
- require 'spree/core/webhooks'
466
459
  require 'spree/core/permission_configuration'
467
460
  require 'spree/core/ransack_configuration'
461
+ require 'spree/core/pricing/context'
462
+ require 'spree/core/pricing/resolver'
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Events
5
+ module Adapters
6
+ # Adapter for ActiveSupport::Notifications backend.
7
+ #
8
+ # This adapter wraps Rails' built-in notification system to provide
9
+ # the Spree event infrastructure. It can be swapped out for other
10
+ # implementations (e.g., Redis pub/sub, Kafka) without changing
11
+ # the subscriber API.
12
+ #
13
+ # @example Publishing an event
14
+ # adapter = ActiveSupportNotifications.new(registry)
15
+ # adapter.publish('order.complete', { id: 1 })
16
+ #
17
+ class ActiveSupportNotifications < Base
18
+ NAMESPACE = 'spree'
19
+
20
+ def initialize(registry)
21
+ super
22
+ @as_subscription = nil
23
+ @mutex = Mutex.new
24
+ end
25
+
26
+ # Publish an event to all matching subscribers
27
+ #
28
+ # @param event_name [String] The event name
29
+ # @param payload [Hash] The event payload
30
+ # @param metadata [Hash] Additional metadata
31
+ # @return [Spree::Event] The published event
32
+ def publish(event_name, payload, metadata = {})
33
+ event = build_event(event_name, payload, metadata)
34
+ instrument_name = namespaced_event(event_name)
35
+
36
+ ::ActiveSupport::Notifications.instrument(instrument_name, event: event) do
37
+ # The block is intentionally empty - we use the instrument
38
+ # to trigger subscribers, not to wrap code execution
39
+ end
40
+
41
+ event
42
+ end
43
+
44
+ # Subscribe to an event pattern
45
+ #
46
+ # This method registers a subscriber in the registry.
47
+ # The actual AS::N subscription is created once via activate!
48
+ #
49
+ # @param pattern [String] Event pattern (supports wildcards)
50
+ # @param subscriber [Class, Proc] The subscriber
51
+ # @param options [Hash] Subscription options
52
+ def subscribe(pattern, subscriber, options = {})
53
+ # Register in our registry
54
+ registry.register(pattern, subscriber, options)
55
+
56
+ # Ensure the global AS::N subscription exists
57
+ ensure_as_subscription
58
+ end
59
+
60
+ # Unsubscribe from an event pattern
61
+ #
62
+ # @param pattern [String] Event pattern
63
+ # @param subscriber [Class, Proc] The subscriber to remove
64
+ def unsubscribe(pattern, subscriber)
65
+ registry.unregister(pattern, subscriber)
66
+ end
67
+
68
+ # Activate all registered subscriptions
69
+ #
70
+ # This is called during Rails initialization to set up
71
+ # the single AS::N subscription that catches all Spree events.
72
+ def activate!
73
+ ensure_as_subscription
74
+ end
75
+
76
+ # Deactivate all AS::N subscriptions
77
+ def deactivate!
78
+ @mutex.synchronize do
79
+ if @as_subscription
80
+ ::ActiveSupport::Notifications.unsubscribe(@as_subscription)
81
+ @as_subscription = nil
82
+ end
83
+ end
84
+ end
85
+
86
+ private
87
+
88
+ def namespaced_event(event_name)
89
+ "#{event_name}.#{NAMESPACE}"
90
+ end
91
+
92
+ # Create a single AS::N subscription that catches all Spree events
93
+ def ensure_as_subscription
94
+ @mutex.synchronize do
95
+ return if @as_subscription
96
+
97
+ # Match all events ending with .spree
98
+ @as_subscription = ::ActiveSupport::Notifications.subscribe(/\.#{NAMESPACE}$/) do |_name, _start, _finish, _id, as_payload|
99
+ # Extract our event from the AS::N payload
100
+ event = as_payload[:event]
101
+ next unless event
102
+
103
+ # Find and invoke all matching subscribers
104
+ invoke_subscribers(event)
105
+ end
106
+ end
107
+ end
108
+
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,193 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Events
5
+ module Adapters
6
+ # Base class for event adapters.
7
+ #
8
+ # Adapters are responsible for the actual publishing and subscription
9
+ # management of events. The default adapter uses ActiveSupport::Notifications,
10
+ # but you can create custom adapters for other backends like Kafka, RabbitMQ,
11
+ # or Redis Pub/Sub.
12
+ #
13
+ # @example Creating a custom adapter
14
+ # class MyApp::Events::KafkaAdapter < Spree::Events::Adapters::Base
15
+ # def publish(event_name, payload, metadata = {})
16
+ # event = Spree::Event.new(name: event_name, payload: payload, metadata: metadata)
17
+ # kafka_producer.produce(event.to_json, topic: event_name)
18
+ # event
19
+ # end
20
+ #
21
+ # def activate!
22
+ # @kafka_producer = Kafka.new.producer
23
+ # end
24
+ #
25
+ # def deactivate!
26
+ # @kafka_producer&.shutdown
27
+ # end
28
+ # end
29
+ #
30
+ # @example Configuring Spree to use your adapter
31
+ # # config/initializers/spree.rb
32
+ # Spree.events_adapter_class = 'MyApp::Events::KafkaAdapter'
33
+ #
34
+ class Base
35
+ attr_reader :registry
36
+
37
+ # Initialize the adapter with a registry.
38
+ #
39
+ # @param registry [Spree::Events::Registry] the subscription registry
40
+ def initialize(registry)
41
+ @registry = registry
42
+ end
43
+
44
+ # Publish an event to all matching subscribers.
45
+ #
46
+ # @param event_name [String] the event name (e.g., 'order.complete')
47
+ # @param payload [Hash] the event payload (should be serializable)
48
+ # @param metadata [Hash] additional metadata for the event
49
+ # @return [Spree::Event] the published event
50
+ #
51
+ # @example
52
+ # adapter.publish('order.complete', order.serializable_hash, { user_id: 1 })
53
+ #
54
+ def publish(event_name, payload, metadata = {})
55
+ raise NotImplementedError, "#{self.class}#publish must be implemented"
56
+ end
57
+
58
+ # Subscribe to an event pattern.
59
+ #
60
+ # This method should register the subscriber in the registry.
61
+ # The adapter is responsible for ensuring events are routed to
62
+ # matching subscribers.
63
+ #
64
+ # @param pattern [String] event pattern (supports wildcards like 'order.*')
65
+ # @param subscriber [Class, Proc] the subscriber class or callable
66
+ # @param options [Hash] subscription options
67
+ # @option options [Boolean] :async (true) whether to run async via ActiveJob
68
+ #
69
+ # @example
70
+ # adapter.subscribe('order.complete', MySubscriber)
71
+ # adapter.subscribe('order.*', AuditLogger, async: false)
72
+ #
73
+ def subscribe(pattern, subscriber, options = {})
74
+ raise NotImplementedError, "#{self.class}#subscribe must be implemented"
75
+ end
76
+
77
+ # Unsubscribe from an event pattern.
78
+ #
79
+ # @param pattern [String] event pattern
80
+ # @param subscriber [Class, Proc] the subscriber to remove
81
+ # @return [Boolean] true if removed, false if not found
82
+ #
83
+ def unsubscribe(pattern, subscriber)
84
+ raise NotImplementedError, "#{self.class}#unsubscribe must be implemented"
85
+ end
86
+
87
+ # Activate the adapter.
88
+ #
89
+ # Called during Rails initialization. Use this to set up connections,
90
+ # start consumers, or perform any initialization needed.
91
+ #
92
+ def activate!
93
+ raise NotImplementedError, "#{self.class}#activate! must be implemented"
94
+ end
95
+
96
+ # Deactivate the adapter.
97
+ #
98
+ # Called during shutdown or when resetting the event system.
99
+ # Use this to clean up connections and resources.
100
+ #
101
+ def deactivate!
102
+ raise NotImplementedError, "#{self.class}#deactivate! must be implemented"
103
+ end
104
+
105
+ protected
106
+
107
+ # Helper to create an event object.
108
+ #
109
+ # @param event_name [String]
110
+ # @param payload [Hash]
111
+ # @param metadata [Hash]
112
+ # @return [Spree::Event]
113
+ def build_event(event_name, payload, metadata)
114
+ Spree::Event.new(
115
+ name: event_name,
116
+ payload: payload,
117
+ metadata: metadata
118
+ )
119
+ end
120
+
121
+ # Find and invoke all matching subscribers for an event.
122
+ #
123
+ # Checks if events are enabled and invokes each matching subscriber.
124
+ # Errors are caught and handled via handle_subscriber_error.
125
+ #
126
+ # @param event [Spree::Event] the event to dispatch
127
+ def invoke_subscribers(event)
128
+ return unless Spree::Events.enabled?
129
+
130
+ subscriptions = registry.subscriptions_for(event.name)
131
+
132
+ subscriptions.each do |subscription|
133
+ invoke_subscriber(subscription.subscriber, event, subscription.options)
134
+ rescue StandardError => e
135
+ handle_subscriber_error(e, event, subscription)
136
+ end
137
+ end
138
+
139
+ # Invoke a single subscriber with an event.
140
+ #
141
+ # Handles different subscriber types (Proc, callable, Spree::Subscriber)
142
+ # and async/sync execution via ActiveJob.
143
+ #
144
+ # @param subscriber [Class, Proc, #call] the subscriber
145
+ # @param event [Spree::Event] the event
146
+ # @param options [Hash] subscription options
147
+ def invoke_subscriber(subscriber, event, options)
148
+ async = options.fetch(:async, true)
149
+
150
+ if subscriber.is_a?(Proc)
151
+ # Block subscribers run synchronously
152
+ subscriber.call(event)
153
+ elsif subscriber.respond_to?(:call)
154
+ # Callable objects (including subscriber instances)
155
+ if async && defined?(Spree::Events::SubscriberJob)
156
+ Spree::Events::SubscriberJob.perform_later(subscriber.name, event.to_h)
157
+ else
158
+ subscriber.call(event)
159
+ end
160
+ elsif subscriber.is_a?(Class) && subscriber < Spree::Subscriber
161
+ # Subscriber classes
162
+ if async && defined?(Spree::Events::SubscriberJob)
163
+ Spree::Events::SubscriberJob.perform_later(subscriber.name, event.to_h)
164
+ else
165
+ subscriber.new.call(event)
166
+ end
167
+ else
168
+ raise ArgumentError, "Invalid subscriber: #{subscriber.inspect}. Must be a Proc, callable, or Spree::Subscriber subclass."
169
+ end
170
+ end
171
+
172
+ # Handle errors that occur during subscriber invocation.
173
+ #
174
+ # Reports the error to Rails.error and re-raises in development/test
175
+ # for visibility. In production, the error is swallowed after reporting.
176
+ #
177
+ # @param error [StandardError] the error that occurred
178
+ # @param event [Spree::Event] the event being processed
179
+ # @param subscription [Spree::Events::Registry::Subscription] the subscription
180
+ def handle_subscriber_error(error, event, subscription)
181
+ Rails.error.report(error, context: {
182
+ event_name: event.name,
183
+ subscriber: subscription.subscriber.to_s,
184
+ event_id: event.metadata['event_id']
185
+ })
186
+
187
+ # Re-raise in development/test for visibility
188
+ raise if Rails.env.development? || Rails.env.test?
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Spree
4
+ module Events
5
+ # Registry for managing event subscribers across different adapters.
6
+ #
7
+ # The registry provides an adapter-agnostic way to track subscriptions.
8
+ # This allows Spree to support different event backends (ActiveSupport::Notifications,
9
+ # Kafka, Redis Pub/Sub, etc.) while maintaining a consistent subscription API.
10
+ #
11
+ # Thread safety: Uses a Mutex for safe concurrent access during subscription
12
+ # registration/unregistration, which may happen during Rails initialization
13
+ # or hot reloading in development.
14
+ #
15
+ # @example
16
+ # registry = Spree::Events::Registry.new
17
+ # registry.register('order.*', MySubscriber, async: true)
18
+ # registry.subscriptions_for('order.complete') # => [Subscription]
19
+ #
20
+ class Registry
21
+ # Immutable subscription data using Ruby 3.2+ Data class
22
+ Subscription = Data.define(:pattern, :subscriber, :options) do
23
+ def async?
24
+ options.fetch(:async, true)
25
+ end
26
+ end
27
+
28
+ def initialize
29
+ @subscriptions = []
30
+ @mutex = Mutex.new
31
+ end
32
+
33
+ # Register a subscriber for an event pattern
34
+ #
35
+ # @param pattern [String] Event pattern (supports wildcards like 'order.*')
36
+ # @param subscriber [Class, Proc] The subscriber class or callable
37
+ # @param options [Hash] Subscription options (:async, etc.)
38
+ # @return [Subscription]
39
+ def register(pattern, subscriber, options = {})
40
+ subscription = Subscription.new(
41
+ pattern: pattern.to_s,
42
+ subscriber: subscriber,
43
+ options: options.freeze
44
+ )
45
+
46
+ @mutex.synchronize { @subscriptions << subscription }
47
+ subscription
48
+ end
49
+
50
+ # Unregister a subscriber
51
+ #
52
+ # @param pattern [String] Event pattern
53
+ # @param subscriber [Class, Proc] The subscriber to remove
54
+ # @return [Boolean] true if removed
55
+ def unregister(pattern, subscriber)
56
+ @mutex.synchronize do
57
+ original_size = @subscriptions.size
58
+ @subscriptions.reject! { |s| s.pattern == pattern.to_s && s.subscriber == subscriber }
59
+ @subscriptions.size < original_size
60
+ end
61
+ end
62
+
63
+ # Find all subscriptions matching an event name
64
+ #
65
+ # @param event_name [String] The event name
66
+ # @return [Array<Subscription>]
67
+ def subscriptions_for(event_name)
68
+ @mutex.synchronize do
69
+ @subscriptions.select { |s| Spree::Event.matches?(event_name, s.pattern) }
70
+ end
71
+ end
72
+
73
+ # @return [Array<Subscription>]
74
+ def all_subscriptions
75
+ @mutex.synchronize { @subscriptions.dup }
76
+ end
77
+
78
+ # @return [Array<String>] Unique patterns
79
+ def patterns
80
+ @mutex.synchronize { @subscriptions.map(&:pattern).uniq }
81
+ end
82
+
83
+ # @return [Integer]
84
+ def size
85
+ @mutex.synchronize { @subscriptions.size }
86
+ end
87
+
88
+ # @param pattern [String]
89
+ # @return [Boolean]
90
+ def registered?(pattern)
91
+ @mutex.synchronize { @subscriptions.any? { |s| s.pattern == pattern.to_s } }
92
+ end
93
+
94
+ def clear!
95
+ @mutex.synchronize { @subscriptions.clear }
96
+ end
97
+ end
98
+ end
99
+ end