Skip to content

Commit

Permalink
Compatibility layer for the legacy/new event system
Browse files Browse the repository at this point in the history
Adds a `SolidusSupport::LegacyCompat` module to provide helpers for
extensions that need to support both the legacy event system
(`Spree::Event`) and the new one (`Spree::Bus`).
  • Loading branch information
waiting-for-dev committed May 23, 2022
1 parent 4dd0c05 commit e3a960b
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 1 deletion.
1 change: 1 addition & 0 deletions lib/solidus_support.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
require 'solidus_support/version'
require 'solidus_support/migration'
require 'solidus_support/engine_extensions'
require 'solidus_support/legacy_event_compat'
require 'solidus_core'

module SolidusSupport
Expand Down
2 changes: 1 addition & 1 deletion lib/solidus_support/engine_extensions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def activate
# This allows to add event subscribers to extensions without explicitly subscribing them,
# similarly to what happens in Solidus core.
def load_solidus_subscribers_from(path)
if defined? Spree::Event
if SolidusSupport::LegacyEventCompat.using_legacy?
path.glob("**/*_subscriber.rb") do |subscriber_path|
require_dependency(subscriber_path)
end
Expand Down
47 changes: 47 additions & 0 deletions lib/solidus_support/legacy_event_compat.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

require 'solidus_support/legacy_event_compat/bus'
require 'solidus_support/legacy_event_compat/subscriber'

module SolidusSupport
# Compatibility middleman for {Spree::Event} and {Spree::Bus}
#
# Solidus v3.2 changed to use [Omnes](https://github.com/nebulab/omnes) as the
# backbone for event-driven behavior (see {Spree::Bus}) by default. Before
# that, a custom adapter based on {ActiveSupport::Notifications} was used (see
# {Spree::Event}. Both systems are still supported on v3.2.
#
# This module provides compatibility support so that extensions can easily
# target both systems regardless of the underlying circumstances:
#
# - Solidus v3.2 with the new system.
# - Solidus v3.2 with the legacy system.
# - Solidus v2.9 to v3.1, when only {Spree::Event} existed.
# - Possible future versions of Solidus, whether the legacy system is
# eventually removed or not.
module LegacyEventCompat
# Returns whether the application is using the legacy event system
#
# @return [Boolean]
def self.using_legacy?
legacy_present? &&
(legacy_alone? ||
legacy_chosen?)
end

def self.legacy_present?
defined?(Spree::Event)
end
private_class_method :legacy_present?

def self.legacy_alone?
!Spree::Config.respond_to?(:use_legacy_events)
end
private_class_method :legacy_alone?

def self.legacy_chosen?
Spree::Config.use_legacy_events
end
private_class_method :legacy_chosen?
end
end
34 changes: 34 additions & 0 deletions lib/solidus_support/legacy_event_compat/bus.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

module SolidusSupport
module LegacyEventCompat
# Compatibility for some event-driven operations
module Bus
# Publication of an event
#
# If extensions want to support the legacy sytem, they need to use a
# compatible API. That means it's not possible to publish an instance as
# event, which is something supported by Omnes but not the legacy adapter.
# Instead, a payload can be given. E.g.:
#
# ```
# SolidusSupport::LegacyEventCompat::Bus.publish(:foo, bar: :baz)
# ```
#
# Legacy subscribers will receive an
# `ActiveSupport::Notifications::Fanout`, while omnes subscribers will get
# an `Omnes::UnstructuredEvent`. Both instances are compatible as they
# implement a `#payload` method.
#
# @param event_name [Symbol]
# @param payload [Hash<Symbol, Any>]
def self.publish(event_name, **payload)
if SolidusSupport::LegacyEventCompat.using_legacy?
Spree::Event.fire(event_name, payload)
else
Spree::Bus.publish(event_name, **payload, caller_location: caller_locations(1)[0])
end
end
end
end
end
54 changes: 54 additions & 0 deletions lib/solidus_support/legacy_event_compat/subscriber.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# frozen_string_literal: true

begin
require "omnes"
rescue LoadError
end

module SolidusSupport
module LegacyEventCompat
# Compatibility for subscriber modules
#
# Thanks to this module, extensions can create legacy subscriber modules
# (see {Spree::Event::Subscriber}) and translate them automatically to an
# {Omnes::Subscriber}). E.g.:
#
# ```
# module MyExtension
# module MySubscriber
# include Spree::Event::Subscriber
# include SolidusSupport::LegacyEventCompat::Subscriber
#
# event_action :order_finalized
#
# def order_finalized(event)
# event.payload[:order].do_something
# end
# end
# end
#
# MyExtension::MySubscriber.omnes_subscriber.subscribe_to(Spree::Bus)
# ```
#
# The generated omnes subscriptions will call the corresponding legacy
# subscriber method with the omnes event. It'll compatible as long as the
# omnes event responds to the `#payload` method (see
# {Omnes::UnstructuredEvent}).
module Subscriber
# @api private
ADAPTER = lambda do |legacy_subscriber, legacy_subscriber_method, _omnes_subscriber, omnes_event|
legacy_subscriber.send(legacy_subscriber_method, omnes_event)
end

def self.included(legacy_subscriber)
legacy_subscriber.define_singleton_method(:omnes_subscriber) do
@omnes_subscriber ||= Class.new.include(::Omnes::Subscriber).tap do |subscriber|
legacy_subscriber.event_actions.each do |(legacy_subscriber_method, event_name)|
subscriber.handle(event_name.to_sym, with: ADAPTER.curry[legacy_subscriber, legacy_subscriber_method])
end
end.new
end
end
end
end
end
1 change: 1 addition & 0 deletions solidus_support.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ Gem::Specification.new do |s|
s.add_development_dependency 'rubocop'
s.add_development_dependency 'rubocop-rspec'
s.add_development_dependency 'solidus_dev_support'
s.add_development_dependency 'omnes', '~> 0.2.2'
end
31 changes: 31 additions & 0 deletions spec/solidus_support/legacy_event_compat/bus_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

RSpec.describe SolidusSupport::LegacyEventCompat::Bus do
describe '#publish' do
if SolidusSupport::LegacyEventCompat.using_legacy?
it 'forwards to Spree::Event' do
box = nil
subscription = Spree::Event.subscribe(:foo) { |event| box = event.payload[:bar] }

described_class.publish(:foo, bar: :baz)

expect(box).to be(:baz)
ensure
Spree::Event.unsubscribe(subscription)
end
else
it 'forwards to Spree::Bus' do
box = nil
Spree::Bus.register(:foo)
subscription = Spree::Bus.subscribe(:foo) { |event| box = event.payload[:bar] }

described_class.publish(:foo, bar: :baz)

expect(box).to be(:baz)
ensure
Spree::Bus.unsubscribe(subscription)
Spree::Bus.registry.unregister(:foo)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# frozen_string_literal: true

require 'omnes'

RSpec.describe SolidusSupport::LegacyEventCompat::Subscriber do
subject { Module.new.include(Spree::Event::Subscriber).include(described_class) }

describe '#omnes_subscriber' do
it 'returns an Omnes::Subscriber' do
subject.module_eval do
event_action :foo

def foo(_event); end
end

expect(subject.omnes_subscriber.is_a?(Omnes::Subscriber)).to be(true)
end

it 'adds single-event definitions matching legacy event actions' do
subject.module_eval do
event_action :foo

def foo(_event); end
end
bus = Omnes::Bus.new
bus.register(:foo)

subscriptions = subject.omnes_subscriber.subscribe_to(bus)

event = Struct.new(:omnes_event_name).new(:foo)
expect(subscriptions.first.matches?(event)).to be(true)
end

it 'coerces event names given as Strings' do
subject.module_eval do
event_action 'foo'

def foo(_event); end
end
bus = Omnes::Bus.new
bus.register(:foo)

subscriptions = subject.omnes_subscriber.subscribe_to(bus)

event = Struct.new(:omnes_event_name).new(:foo)
expect(subscriptions.first.matches?(event)).to be(true)
end

it 'executes legacy event action methods as handlers with the omnes event' do
subject.module_eval do
event_action :foo

def foo(event)
event[:bar]
end
end
bus = Omnes::Bus.new
bus.register(:foo)

subscriptions = subject.omnes_subscriber.subscribe_to(bus)

expect(
bus.publish(:foo, bar: :baz).executions.first.result
).to be(:baz)
end

it 'distingish when event name is given explicitly' do
subject.module_eval do
event_action :foo, event_name: :bar

def foo(_event)
:bar
end
end
bus = Omnes::Bus.new
bus.register(:bar)

subscriptions = subject.omnes_subscriber.subscribe_to(bus)

expect(
bus.publish(:bar).executions.first.result
).to be(:bar)
end

it "returns the same omnes subscriber instance if called again" do
expect(subject.omnes_subscriber).to be(subject.omnes_subscriber)
end

it "doesn't fail when no event action has been defined" do
expect { subject.omnes_subscriber }.not_to raise_error
end
end
end

0 comments on commit e3a960b

Please sign in to comment.