Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Events] Add subscribers automatically #3571

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/lib/spree/core/engine.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class Engine < ::Rails::Engine
# Setup Event Subscribers
initializer 'spree.core.initialize_subscribers' do |app|
app.reloader.to_prepare do
Spree::Event.require_subscriber_files
Spree::Event.subscribers.each(&:subscribe!)
end

Expand Down
24 changes: 24 additions & 0 deletions core/lib/spree/event.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ def fire(event_name, opts = {})
end
end

# Loads all Solidus' core and application's event subscribers files.
# The latter are loaded automatically only when the preference
# Spree::Config.events.autoload_subscribers is set to a truthy value.
#
# Files must be placed under the directory `app/subscribers` and their
# name must end with `_subscriber.rb`.
#
# Loading the files has the side effect of adding their module to the
# list in Spree::Event.subscribers.
def require_subscriber_files
pattern = "app/subscribers/**/*_subscriber.rb"

# Load Solidus subscribers
# rubocop:disable Rails/DynamicFindBy
solidus_core_dir = Gem::Specification.find_by_name('solidus_core').gem_dir
spaghetticode marked this conversation as resolved.
Show resolved Hide resolved
# rubocop:enable Rails/DynamicFindBy
Dir.glob(File.join(solidus_core_dir, pattern)) { |c| require_dependency(c.to_s) }

# Load application subscribers, only when the flag is set to true:
if Spree::Config.events.autoload_subscribers
Rails.root.glob(pattern) { |c| require_dependency(c.to_s) }
end
end

# Subscribe to an event with the given name. The provided block is executed
# every time the subscribed event is fired.
#
Expand Down
10 changes: 6 additions & 4 deletions core/lib/spree/event/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ module Spree
module Event
class Configuration
def subscribers
@subscribers ||= ::Spree::Core::ClassConstantizer::Set.new.tap do |set|
set << 'Spree::MailerSubscriber'
end
@subscribers ||= ::Spree::Core::ClassConstantizer::Set.new
end

attr_writer :adapter, :suffix
attr_writer :adapter, :suffix, :autoload_subscribers

def autoload_subscribers
@autoload_subscribers.nil? ? true : !!@autoload_subscribers
end

def adapter
@adapter ||= Spree::Event::Adapters::ActiveSupportNotifications
Expand Down
2 changes: 2 additions & 0 deletions core/lib/spree/event/subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ def self.included(base)

base.mattr_accessor :event_actions
base.event_actions = {}

Spree::Event.subscribers << base.name
end

# Declares a method name in the including module that can be subscribed/unsubscribed
Expand Down
48 changes: 48 additions & 0 deletions core/spec/lib/spree/event_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@
allow(subscriber_name).to receive(:to_s).and_return(subscriber_name)
end

after { described_class.subscribers.clear }

it 'accepts the names of constants' do
Spree::Config.events.subscribers << subscriber_name

Expand All @@ -133,4 +135,50 @@
expect(described_class.subscribers.to_a).to eq([subscriber])
end
end

describe '.require_subscriber_files' do
let(:susbcribers_dir) { Rails.root.join('app', 'subscribers', 'spree') }

def create_subscriber_file(constant_name)
FileUtils.mkdir_p(susbcribers_dir)
File.open File.join(susbcribers_dir, "#{constant_name.underscore}.rb"), 'w' do |f|
f.puts "module Spree::#{constant_name}; include Spree::Event::Subscriber; end"
end
end

after { FileUtils.rm_rf(susbcribers_dir) }

context 'when Spree::Config.events.autoload_subscribers is true (default)' do
let(:events_config) { double(autoload_subscribers: true, subscribers: Set.new) }

before { create_subscriber_file('FooSubscriber') }

it 'requires subscriber files and loads them into Spree::Event.subscribers' do
expect do
described_class.require_subscriber_files
end.to change { described_class.subscribers.count }.by 1

expect(defined? Spree::FooSubscriber).to be_truthy
expect(described_class.subscribers).to include(Spree::FooSubscriber)
end
end

context 'when Spree::Config.autoload_subscribers is false' do
let(:events_config) { double(autoload_subscribers: false, subscribers: Set.new) }

before do
stub_spree_preferences(events: events_config)
create_subscriber_file('BarSubscriber')
end

it 'does not requires subscriber files' do
expect do
described_class.require_subscriber_files
end.not_to change { described_class.subscribers.count }

expect(defined? Spree::BarSubscriber).to be_falsey
expect(described_class.subscribers).to be_empty
end
end
end
end
16 changes: 12 additions & 4 deletions guides/source/developers/events/overview.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,18 @@ method allows to map a method of the subscriber module to an event that
happens in the system. If the `event_name` argument is not specified,
the event name and the method name should match.

These subscribers modules are loaded with the rest of your application but
you need to manually subscribe to them.

For example, you could subscribe to them programmatically with something like:
These subscriber modules are loaded with the rest of your application, you
don't need to manually subscribe them when:

* `Spree::Config.events.autoload_subscribers` returns a truthy value (default);
* you put them in the directory (or any subdirectory of) `app/subscribers`;
* their filename ends with `_subscriber.rb`;

On the other hand, if you need to resort to manual subscription because you did
not follow the naming convention explained above, or you prefer to have complete
control on what is loaded and when, you can override the default behaviour by
setting `Spree::Config.events.autoload_subscribers = false` in a initializer.
At that point you can subscribe your event subscribers, with something similar to:

```ruby
if defined?(SmsLibrary)
Expand Down