Module: Spree::Events

Defined in:
lib/spree/events.rb,
lib/spree/events/registry.rb,
lib/spree/events/adapters/base.rb,
app/jobs/spree/events/subscriber_job.rb,
lib/spree/events/adapters/active_support_notifications.rb

Overview

Main entry point for the Spree event system.

This module provides a clean API for publishing events and subscribing to them. It abstracts the underlying implementation (ActiveSupport::Notifications) allowing for future changes without affecting subscriber code.

Examples:

Publishing an event

Spree::Events.publish('order.completed', order.serializable_hash)

Subscribing with a class

Spree::Events.subscribe('order.completed', OrderCompletedHandler)

Subscribing with a block

Spree::Events.subscribe('order.completed') do |event|
  puts "Order completed: #{event.payload['number']}"
end

Pattern matching

Spree::Events.subscribe('order.*', OrderAuditLogger)  # All order events
Spree::Events.subscribe('*', GlobalEventLogger)       # All events

Defined Under Namespace

Modules: Adapters Classes: Registry, SubscriberJob

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.log_subscriptionObject

Reference to the AS::N subscription created by Spree::EventLogSubscriber. Stored here (on a module in lib/, not reloaded by Zeitwerk) so that code reloads in development don’t orphan the subscription and cause events to be logged multiple times.



35
36
37
# File 'lib/spree/events.rb', line 35

def log_subscription
  @log_subscription
end

Class Method Details

.activate!Object

Activate the event system (called during Rails initialization) Also registers all subscribers from Spree.subscribers This method is idempotent - calling it multiple times has no effect



110
111
112
113
114
115
# File 'lib/spree/events.rb', line 110

def activate!
  return if registry.size > 0

  register_subscribers!
  adapter.activate!
end

.adapterObject

Get the adapter instance

The adapter class can be configured via Spree.events_adapter_class Default: Spree::Events::Adapters::ActiveSupportNotifications

Returns:

  • (Object)

    the configured adapter instance



103
104
105
# File 'lib/spree/events.rb', line 103

def adapter
  @adapter ||= Spree.events_adapter_class.new(registry)
end

.disable { ... } ⇒ Object

Temporarily disable events within a block

Examples:

Spree::Events.disable do
  # Events published here won't trigger subscribers
  order.complete!
end

Yields:

  • Block during which events are disabled

Returns:

  • (Object)

    Return value of the block



204
205
206
207
208
209
210
# File 'lib/spree/events.rb', line 204

def disable
  previous = Thread.current[:spree_events_disabled]
  Thread.current[:spree_events_disabled] = true
  yield
ensure
  Thread.current[:spree_events_disabled] = previous
end

.disable!void

This method returns an undefined value.

Globally disable events

Uses a class-level flag that is not affected by per-request cleanup. Useful for disabling events for the entire test suite.



218
219
220
# File 'lib/spree/events.rb', line 218

def disable!
  @globally_disabled = true
end

.enable { ... } ⇒ Object

Temporarily enable events within a block Useful for testing when events are globally disabled

Examples:

Spree::Events.enable do
  # Events published here will trigger subscribers
  order.complete!
end

Yields:

  • Block during which events are enabled

Returns:

  • (Object)

    Return value of the block



234
235
236
237
238
239
240
241
242
243
# File 'lib/spree/events.rb', line 234

def enable
  previous_global = @globally_disabled
  previous_thread = Thread.current[:spree_events_disabled]
  @globally_disabled = false
  Thread.current[:spree_events_disabled] = false
  yield
ensure
  @globally_disabled = previous_global
  Thread.current[:spree_events_disabled] = previous_thread
end

.enable!void

This method returns an undefined value.

Globally enable events Useful for testing



248
249
250
# File 'lib/spree/events.rb', line 248

def enable!
  @globally_disabled = false
end

.enabled?Boolean

Check if events are enabled

Events can be temporarily disabled using Spree::Events.disable { … } or globally with Spree::Events.disable!

Returns:

  • (Boolean)


189
190
191
# File 'lib/spree/events.rb', line 189

def enabled?
  !@globally_disabled && !Thread.current[:spree_events_disabled]
end

.patternsArray<String>

List all registered subscriber patterns

Returns:

  • (Array<String>)


172
173
174
# File 'lib/spree/events.rb', line 172

def patterns
  registry.patterns
end

.publish(name, payload = {}, metadata = {}) ⇒ Spree::Event

Publish an event to all matching subscribers

Examples:

Spree::Events.publish('product.created', product.serializable_hash)
Spree::Events.publish('order.completed', order.serializable_hash, { user_id: current_user.id })

Parameters:

  • name (String)

    The event name (e.g., ‘order.complete’)

  • payload (Hash) (defaults to: {})

    The event payload (should be serializable)

  • metadata (Hash) (defaults to: {})

    Additional metadata for the event

Returns:



48
49
50
# File 'lib/spree/events.rb', line 48

def publish(name, payload = {},  = {})
  adapter.publish(name, payload, )
end

.register_subscribers!void

This method returns an undefined value.

Register all subscribers from Spree.subscribers

This is called automatically during Rails initialization. Can also be called in tests after reset! to re-register subscribers.

In development, class objects in Spree.subscribers may become stale after code reload (Zeitwerk creates new class objects). We resolve the constant fresh from the class name to ensure we’re using the reloaded class.



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/spree/events.rb', line 134

def register_subscribers!
  return unless defined?(Spree) && Spree.respond_to?(:subscribers)

  Spree.subscribers&.each do |subscriber|
    # Resolve the subscriber constant fresh to handle code reload in development
    # The array may contain stale class objects after Zeitwerk reload
    resolved_subscriber = resolve_subscriber(subscriber)
    next unless resolved_subscriber

    resolved_subscriber.subscription_patterns.each do |pattern|
      subscribe(pattern, resolved_subscriber, resolved_subscriber.subscription_options)
    end
  end
end

.registrySpree::Events::Registry

Get the event registry



93
94
95
# File 'lib/spree/events.rb', line 93

def registry
  @registry ||= Registry.new
end

.reset!Object

Reset the event system (useful for testing)



118
119
120
121
122
# File 'lib/spree/events.rb', line 118

def reset!
  adapter.deactivate! if @adapter
  @registry = nil
  @adapter = nil
end

.resolve_subscriber(subscriber) ⇒ Class?

Resolve a subscriber to its current class object

In development, Zeitwerk may have reloaded the class, creating a new class object while the old one is still referenced in Spree.subscribers. This method resolves the constant fresh to get the current class.

Parameters:

  • subscriber (Class, String)

    The subscriber class or class name

Returns:

  • (Class, nil)

    The resolved class or nil if not found



157
158
159
160
161
162
163
164
165
166
167
# File 'lib/spree/events.rb', line 157

def resolve_subscriber(subscriber)
  return subscriber unless Rails.env.development? || Rails.env.test?

  class_name = subscriber.is_a?(String) ? subscriber : subscriber.name
  return nil unless class_name

  class_name.constantize
rescue NameError => e
  Rails.logger.warn "[Spree Events] Could not resolve subscriber #{class_name}: #{e.message}"
  nil
end

.subscribe(pattern, subscriber = nil, options = {}) {|event| ... } ⇒ void

This method returns an undefined value.

Subscribe to an event pattern

Examples:

With a subscriber class

Spree::Events.subscribe('order.completed', SendConfirmationEmail)

With a block

Spree::Events.subscribe('order.completed') { |event| puts event.name }

With pattern matching

Spree::Events.subscribe('order.*', OrderAuditLogger)

Synchronous execution

Spree::Events.subscribe('order.completed', MyHandler, async: false)

Parameters:

  • pattern (String)

    Event pattern (supports wildcards like ‘order.*’ or ‘*’)

  • subscriber (Class, Proc, nil) (defaults to: nil)

    The subscriber class or callable

  • options (Hash) (defaults to: {})

    Subscription options

Options Hash (options):

  • :async (Boolean) — default: true

    Whether to run async via ActiveJob

Yields:

  • (event)

    Block to execute when event occurs (alternative to subscriber param)

Yield Parameters:

Raises:

  • (ArgumentError)


74
75
76
77
78
79
# File 'lib/spree/events.rb', line 74

def subscribe(pattern, subscriber = nil, options = {}, &block)
  subscriber = block if block_given?
  raise ArgumentError, 'Must provide a subscriber class, callable, or block' unless subscriber

  adapter.subscribe(pattern, subscriber, options)
end

.subscriptionsArray<Spree::Events::Registry::Subscription>

List all subscriptions



179
180
181
# File 'lib/spree/events.rb', line 179

def subscriptions
  registry.all_subscriptions
end

.unsubscribe(pattern, subscriber) ⇒ Boolean

Unsubscribe from an event pattern

Parameters:

  • pattern (String)

    Event pattern

  • subscriber (Class, Proc)

    The subscriber to remove

Returns:

  • (Boolean)

    true if removed, false if not found



86
87
88
# File 'lib/spree/events.rb', line 86

def unsubscribe(pattern, subscriber)
  adapter.unsubscribe(pattern, subscriber)
end