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.
- checksums.yaml +4 -4
- data/app/helpers/spree/addresses_helper.rb +1 -23
- data/app/helpers/spree/base_helper.rb +25 -77
- data/app/helpers/spree/currency_helper.rb +2 -2
- data/app/helpers/spree/images_helper.rb +5 -0
- data/app/helpers/spree/mail_helper.rb +1 -1
- data/app/helpers/spree/products_helper.rb +1 -1
- data/app/jobs/spree/events/subscriber_job.rb +58 -0
- data/app/jobs/spree/products/refresh_metrics_job.rb +14 -0
- data/app/jobs/spree/products/touch_taxons_job.rb +0 -1
- data/app/jobs/spree/variants/touch_job.rb +9 -0
- data/app/models/concerns/spree/adjustment_source.rb +8 -0
- data/app/models/concerns/spree/parameterizable_name.rb +1 -1
- data/app/models/concerns/spree/product_scopes.rb +54 -8
- data/app/models/concerns/spree/publishable.rb +253 -0
- data/app/models/concerns/spree/unique_name.rb +1 -1
- data/app/models/concerns/spree/user_methods.rb +21 -1
- data/app/models/spree/ability.rb +3 -3
- data/app/models/spree/address.rb +0 -3
- data/app/models/spree/adjustable/promotion_accumulator.rb +1 -1
- data/app/models/spree/adjustment.rb +32 -6
- data/app/models/spree/asset.rb +6 -3
- data/app/models/spree/base.rb +3 -1
- data/app/models/spree/classification.rb +2 -2
- data/app/models/spree/credit_card.rb +0 -3
- data/app/models/spree/current.rb +24 -3
- data/app/models/spree/custom_domain.rb +1 -1
- data/app/models/spree/customer_group.rb +94 -0
- data/app/models/spree/customer_group_user.rb +16 -0
- data/app/models/spree/customer_return.rb +2 -3
- data/app/models/spree/digital.rb +2 -4
- data/app/models/spree/digital_link.rb +2 -3
- data/app/models/spree/event.rb +104 -0
- data/app/models/spree/export.rb +7 -1
- data/app/models/spree/gift_card.rb +13 -1
- data/app/models/spree/gift_card_batch.rb +3 -1
- data/app/models/spree/image.rb +20 -5
- data/app/models/spree/import.rb +17 -12
- data/app/models/spree/import_row.rb +13 -26
- data/app/models/spree/inventory_unit.rb +0 -3
- data/app/models/spree/invitation.rb +12 -6
- data/app/models/spree/line_item.rb +57 -4
- data/app/models/spree/newsletter_subscriber.rb +2 -0
- data/app/models/spree/option_type.rb +2 -5
- data/app/models/spree/option_value.rb +1 -4
- data/app/models/spree/order/checkout.rb +7 -2
- data/app/models/spree/order/emails.rb +3 -11
- data/app/models/spree/order/store_credit.rb +5 -1
- data/app/models/spree/order.rb +80 -31
- data/app/models/spree/order_updater.rb +28 -9
- data/app/models/spree/payment/custom_events.rb +37 -0
- data/app/models/spree/payment.rb +13 -4
- data/app/models/spree/payment_capture_event.rb +0 -4
- data/app/models/spree/payment_method.rb +1 -1
- data/app/models/spree/permission_sets/default_customer.rb +3 -3
- data/app/models/spree/policy.rb +1 -8
- data/app/models/spree/post.rb +2 -7
- data/app/models/spree/post_category.rb +2 -0
- data/app/models/spree/price.rb +8 -6
- data/app/models/spree/price_list.rb +263 -0
- data/app/models/spree/price_rule.rb +26 -0
- data/app/models/spree/price_rules/customer_group_rule.rb +21 -0
- data/app/models/spree/price_rules/user_rule.rb +19 -0
- data/app/models/spree/price_rules/volume_rule.rb +21 -0
- data/app/models/spree/price_rules/zone_rule.rb +19 -0
- data/app/models/spree/product.rb +117 -76
- data/app/models/spree/product_property.rb +2 -2
- data/app/models/spree/promotion/rules/customer_group.rb +22 -0
- data/app/models/spree/promotion/rules/user.rb +2 -2
- data/app/models/spree/promotion.rb +3 -4
- data/app/models/spree/promotion_handler/coupon.rb +3 -3
- data/app/models/spree/property.rb +3 -6
- data/app/models/spree/prototype.rb +0 -3
- data/app/models/spree/refund.rb +2 -3
- data/app/models/spree/reimbursement.rb +5 -3
- data/app/models/spree/report.rb +7 -1
- data/app/models/spree/reports/products_performance.rb +1 -1
- data/app/models/spree/reports/sales_total.rb +1 -1
- data/app/models/spree/return_authorization.rb +7 -3
- data/app/models/spree/return_item.rb +15 -4
- data/app/models/spree/shipment/custom_events.rb +47 -0
- data/app/models/spree/shipment.rb +12 -5
- data/app/models/spree/shipping_category.rb +0 -3
- data/app/models/spree/shipping_method.rb +0 -3
- data/app/models/spree/stock/quantifier.rb +1 -1
- data/app/models/spree/stock_item.rb +2 -0
- data/app/models/spree/stock_location.rb +0 -3
- data/app/models/spree/stock_movement/custom_events.rb +44 -0
- data/app/models/spree/stock_movement.rb +3 -0
- data/app/models/spree/stock_transfer.rb +2 -3
- data/app/models/spree/store.rb +64 -92
- data/app/models/spree/store_credit.rb +3 -4
- data/app/models/spree/store_product.rb +14 -0
- data/app/models/spree/subscriber.rb +186 -0
- data/app/models/spree/tax_category.rb +0 -4
- data/app/models/spree/tax_rate.rb +0 -3
- data/app/models/spree/taxon.rb +3 -36
- data/app/models/spree/taxonomy.rb +0 -3
- data/app/models/spree/variant.rb +114 -21
- data/app/models/spree/webhook_delivery.rb +60 -0
- data/app/models/spree/webhook_endpoint.rb +53 -0
- data/app/models/spree/wished_item.rb +2 -4
- data/app/models/spree/wishlist.rb +2 -3
- data/app/models/spree/zone.rb +0 -9
- data/app/paginators/spree/shared/paginate.rb +4 -0
- data/app/serializers/spree/events/asset_serializer.rb +22 -0
- data/app/serializers/spree/events/base_serializer.rb +61 -0
- data/app/serializers/spree/events/customer_return_serializer.rb +20 -0
- data/app/serializers/spree/events/digital_link_serializer.rb +20 -0
- data/app/serializers/spree/events/digital_serializer.rb +18 -0
- data/app/serializers/spree/events/export_serializer.rb +22 -0
- data/app/serializers/spree/events/gift_card_batch_serializer.rb +24 -0
- data/app/serializers/spree/events/gift_card_serializer.rb +29 -0
- data/app/serializers/spree/events/image_serializer.rb +9 -0
- data/app/serializers/spree/events/import_row_serializer.rb +23 -0
- data/app/serializers/spree/events/import_serializer.rb +24 -0
- data/app/serializers/spree/events/invitation_serializer.rb +28 -0
- data/app/serializers/spree/events/line_item_serializer.rb +31 -0
- data/app/serializers/spree/events/newsletter_subscriber_serializer.rb +21 -0
- data/app/serializers/spree/events/order_serializer.rb +39 -0
- data/app/serializers/spree/events/payment_serializer.rb +24 -0
- data/app/serializers/spree/events/post_category_serializer.rb +20 -0
- data/app/serializers/spree/events/post_serializer.rb +26 -0
- data/app/serializers/spree/events/price_serializer.rb +22 -0
- data/app/serializers/spree/events/product_serializer.rb +24 -0
- data/app/serializers/spree/events/promotion_serializer.rb +32 -0
- data/app/serializers/spree/events/refund_serializer.rb +23 -0
- data/app/serializers/spree/events/reimbursement_serializer.rb +22 -0
- data/app/serializers/spree/events/report_serializer.rb +23 -0
- data/app/serializers/spree/events/return_authorization_serializer.rb +22 -0
- data/app/serializers/spree/events/return_item_serializer.rb +27 -0
- data/app/serializers/spree/events/shipment_serializer.rb +24 -0
- data/app/serializers/spree/events/stock_item_serializer.rb +22 -0
- data/app/serializers/spree/events/stock_movement_serializer.rb +22 -0
- data/app/serializers/spree/events/stock_transfer_serializer.rb +22 -0
- data/app/serializers/spree/events/store_credit_serializer.rb +30 -0
- data/app/serializers/spree/events/user_serializer.rb +18 -0
- data/app/serializers/spree/events/variant_serializer.rb +34 -0
- data/app/serializers/spree/events/wished_item_serializer.rb +20 -0
- data/app/serializers/spree/events/wishlist_serializer.rb +22 -0
- data/app/services/spree/addresses/update.rb +1 -1
- data/app/services/spree/cart/add_item.rb +1 -1
- data/app/services/spree/coupon_codes/coupon_codes_handler.rb +2 -1
- data/app/services/spree/data_feeds/google/required_attributes.rb +4 -4
- data/app/services/spree/newsletter/verify.rb +5 -0
- data/app/services/spree/products/auto_match_taxons.rb +1 -1
- data/app/services/spree/seeds/all.rb +1 -1
- data/app/services/spree/taxons/add_products.rb +8 -4
- data/app/services/spree/taxons/regenerate_products.rb +8 -0
- data/app/services/spree/taxons/remove_products.rb +12 -7
- data/app/subscribers/spree/event_log_subscriber.rb +64 -0
- data/app/subscribers/spree/export_subscriber.rb +26 -0
- data/app/subscribers/spree/invitation_email_subscriber.rb +40 -0
- data/app/subscribers/spree/product_metrics_subscriber.rb +29 -0
- data/app/subscribers/spree/report_subscriber.rb +26 -0
- data/config/locales/en.yml +126 -0
- data/db/migrate/20251110120000_create_spree_price_lists.rb +22 -0
- data/db/migrate/20251110120001_create_spree_price_rules.rb +13 -0
- data/db/migrate/20251110120002_add_price_list_id_to_spree_prices.rb +6 -0
- data/db/migrate/20251110120003_add_price_list_id_to_spree_line_items.rb +5 -0
- data/db/migrate/20251201172118_fix_indexes_on_friendly_id_slugs.rb +8 -0
- data/db/migrate/20251214000001_create_spree_webhook_endpoints.rb +19 -0
- data/db/migrate/20251214000002_create_spree_webhook_deliveries.rb +23 -0
- data/db/migrate/20251222000000_add_performance_indexes_to_spree_adjustments.rb +25 -0
- data/db/migrate/20260112000000_fix_spree_prices_unique_indexes.rb +33 -0
- data/db/migrate/20260115120000_create_spree_customer_groups.rb +14 -0
- data/db/migrate/20260115120001_create_spree_customer_group_users.rb +14 -0
- data/db/migrate/20260117140831_remove_not_null_constraint_from_policy_name.rb +5 -0
- data/db/migrate/20260118120000_add_statistics_to_store_products.rb +11 -0
- data/db/migrate/20260119120000_add_counter_caches_to_spree_products.rb +9 -0
- data/db/migrate/20260119170000_add_counter_caches_to_spree_taxons.rb +9 -0
- data/db/migrate/20260120120000_add_image_count_to_spree_variants.rb +9 -0
- data/lib/generators/spree/dummy/dummy_generator.rb +14 -2
- data/lib/spree/core/configuration.rb +1 -0
- data/lib/spree/core/controller_helpers/auth.rb +0 -15
- data/lib/spree/core/controller_helpers/currency.rb +13 -9
- data/lib/spree/core/controller_helpers/search.rb +1 -1
- data/lib/spree/core/controller_helpers/store.rb +5 -1
- data/lib/spree/core/engine.rb +61 -78
- data/lib/spree/core/importer/order.rb +1 -1
- data/lib/spree/core/importer/product.rb +1 -1
- data/lib/spree/core/preferences/preferable.rb +14 -1
- data/lib/spree/core/pricing/context.rb +63 -0
- data/lib/spree/core/pricing/resolver.rb +129 -0
- data/lib/spree/core/search/base.rb +1 -1
- data/lib/spree/core/token_generator.rb +1 -1
- data/lib/spree/core/version.rb +1 -1
- data/lib/spree/core.rb +42 -47
- data/lib/spree/events/adapters/active_support_notifications.rb +112 -0
- data/lib/spree/events/adapters/base.rb +193 -0
- data/lib/spree/events/registry.rb +99 -0
- data/lib/spree/events.rb +240 -0
- data/lib/spree/permitted_attributes.rb +13 -2
- data/lib/spree/testing_support/common_rake.rb +68 -35
- data/lib/spree/testing_support/factories/customer_group_factory.rb +6 -0
- data/lib/spree/testing_support/factories/customer_group_user_factory.rb +6 -0
- data/lib/spree/testing_support/factories/price_factory.rb +4 -0
- data/lib/spree/testing_support/factories/price_list_factory.rb +34 -0
- data/lib/spree/testing_support/factories/price_rule_factory.rb +49 -0
- data/lib/spree/testing_support/factories/promotion_rule_factory.rb +12 -0
- data/lib/spree/testing_support/factories/stock_item_factory.rb +6 -4
- data/lib/spree/testing_support/factories/store_product_factory.rb +6 -0
- data/lib/spree/testing_support/factories/taxon_factory.rb +0 -1
- data/lib/spree/testing_support/factories/webhook_delivery_factory.rb +48 -0
- data/lib/spree/testing_support/factories/webhook_endpoint_factory.rb +22 -0
- data/lib/spree/testing_support/lifecycle_events.rb +38 -0
- data/lib/spree/testing_support/store.rb +4 -2
- data/lib/spree/webhooks.rb +22 -0
- data/lib/tasks/products.rake +40 -0
- data/lib/tasks/taxons.rake +19 -0
- data/lib/tasks/variants.rake +18 -0
- metadata +112 -114
- data/app/jobs/spree/themes/duplicate_components_job.rb +0 -59
- data/app/jobs/spree/themes/screenshot_job.rb +0 -81
- data/app/models/concerns/spree/has_page_links.rb +0 -53
- data/app/models/spree/page.rb +0 -188
- data/app/models/spree/page_block.rb +0 -73
- data/app/models/spree/page_blocks/buttons.rb +0 -29
- data/app/models/spree/page_blocks/heading.rb +0 -18
- data/app/models/spree/page_blocks/image.rb +0 -20
- data/app/models/spree/page_blocks/link.rb +0 -21
- data/app/models/spree/page_blocks/mega_nav.rb +0 -33
- data/app/models/spree/page_blocks/mega_nav_with_subcategories.rb +0 -32
- data/app/models/spree/page_blocks/metafields.rb +0 -18
- data/app/models/spree/page_blocks/nav.rb +0 -15
- data/app/models/spree/page_blocks/newsletter_form.rb +0 -18
- data/app/models/spree/page_blocks/products/brand.rb +0 -15
- data/app/models/spree/page_blocks/products/buy_buttons.rb +0 -24
- data/app/models/spree/page_blocks/products/description.rb +0 -18
- data/app/models/spree/page_blocks/products/price.rb +0 -18
- data/app/models/spree/page_blocks/products/quantity_selector.rb +0 -20
- data/app/models/spree/page_blocks/products/share.rb +0 -8
- data/app/models/spree/page_blocks/products/title.rb +0 -19
- data/app/models/spree/page_blocks/products/variant_picker.rb +0 -13
- data/app/models/spree/page_blocks/subheading.rb +0 -17
- data/app/models/spree/page_blocks/text.rb +0 -16
- data/app/models/spree/page_link.rb +0 -60
- data/app/models/spree/page_section.rb +0 -222
- data/app/models/spree/page_sections/announcement_bar.rb +0 -28
- data/app/models/spree/page_sections/breadcrumbs.rb +0 -12
- data/app/models/spree/page_sections/collection_banner.rb +0 -18
- data/app/models/spree/page_sections/custom_code.rb +0 -11
- data/app/models/spree/page_sections/featured_posts.rb +0 -45
- data/app/models/spree/page_sections/featured_product.rb +0 -50
- data/app/models/spree/page_sections/featured_taxon.rb +0 -90
- data/app/models/spree/page_sections/featured_taxons.rb +0 -45
- data/app/models/spree/page_sections/footer.rb +0 -101
- data/app/models/spree/page_sections/header.rb +0 -62
- data/app/models/spree/page_sections/image_banner.rb +0 -55
- data/app/models/spree/page_sections/image_with_text.rb +0 -65
- data/app/models/spree/page_sections/main_password_footer.rb +0 -18
- data/app/models/spree/page_sections/main_password_header.rb +0 -20
- data/app/models/spree/page_sections/newsletter.rb +0 -54
- data/app/models/spree/page_sections/page_title.rb +0 -19
- data/app/models/spree/page_sections/post_details.rb +0 -19
- data/app/models/spree/page_sections/post_grid.rb +0 -19
- data/app/models/spree/page_sections/product_details.rb +0 -53
- data/app/models/spree/page_sections/product_grid.rb +0 -13
- data/app/models/spree/page_sections/related_products.rb +0 -58
- data/app/models/spree/page_sections/rich_text.rb +0 -31
- data/app/models/spree/page_sections/taxon_banner.rb +0 -18
- data/app/models/spree/page_sections/taxon_grid.rb +0 -17
- data/app/models/spree/page_sections/video.rb +0 -107
- data/app/models/spree/pages/account.rb +0 -19
- data/app/models/spree/pages/cart.rb +0 -19
- data/app/models/spree/pages/checkout.rb +0 -15
- data/app/models/spree/pages/custom.rb +0 -38
- data/app/models/spree/pages/homepage.rb +0 -72
- data/app/models/spree/pages/login.rb +0 -19
- data/app/models/spree/pages/password.rb +0 -59
- data/app/models/spree/pages/post.rb +0 -27
- data/app/models/spree/pages/post_list.rb +0 -36
- data/app/models/spree/pages/product_details.rb +0 -30
- data/app/models/spree/pages/search_results.rb +0 -43
- data/app/models/spree/pages/shop_all.rb +0 -40
- data/app/models/spree/pages/taxon.rb +0 -29
- data/app/models/spree/pages/taxon_list.rb +0 -41
- data/app/models/spree/pages/wishlist.rb +0 -15
- data/app/models/spree/theme.rb +0 -233
- data/app/models/spree/themes/default.rb +0 -97
- data/app/services/spree/taxons/touch_featured_sections.rb +0 -21
- data/db/migrate/20250120094216_create_page_builder_models.rb +0 -78
- data/db/migrate/20250305121352_remove_page_builder_indices.rb +0 -11
- data/db/migrate/20250825175217_add_missing_page_builder_indexes.rb +0 -7
- data/db/migrate/20250913130044_add_page_links_counter_cache_to_spree_stores.rb +0 -10
- data/lib/generators/spree/dummy/templates/initializers/devise.rb +0 -3
- data/lib/generators/spree/install/install_generator.rb +0 -219
- data/lib/generators/spree/install/templates/config/initializers/spree.rb +0 -126
- data/lib/spree/core/webhooks.rb +0 -21
- data/lib/spree/testing_support/factories/page_block_factory.rb +0 -22
- data/lib/spree/testing_support/factories/page_factory.rb +0 -33
- data/lib/spree/testing_support/factories/page_link_factory.rb +0 -7
- data/lib/spree/testing_support/factories/page_section_factory.rb +0 -27
- data/lib/spree/testing_support/factories/theme_factory.rb +0 -14
data/lib/spree/core/version.rb
CHANGED
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
|
-
#
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
330
|
-
|
|
331
|
-
|
|
322
|
+
def self.pricing
|
|
323
|
+
Rails.application.config.spree.pricing
|
|
324
|
+
end
|
|
332
325
|
|
|
333
|
-
|
|
334
|
-
|
|
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
|