diff --git a/core/lib/spree/core/engine.rb b/core/lib/spree/core/engine.rb index 4c1db7ccdcc..c6e77e9a79b 100644 --- a/core/lib/spree/core/engine.rb +++ b/core/lib/spree/core/engine.rb @@ -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 diff --git a/core/lib/spree/event.rb b/core/lib/spree/event.rb index 4e9f993ea6b..35a0f00be39 100644 --- a/core/lib/spree/event.rb +++ b/core/lib/spree/event.rb @@ -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 + # 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. # diff --git a/core/lib/spree/event/configuration.rb b/core/lib/spree/event/configuration.rb index ef8d80b0c5f..b2a8599612b 100644 --- a/core/lib/spree/event/configuration.rb +++ b/core/lib/spree/event/configuration.rb @@ -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 diff --git a/core/lib/spree/event/subscriber.rb b/core/lib/spree/event/subscriber.rb index af4b7d77dc5..880cd416bd2 100644 --- a/core/lib/spree/event/subscriber.rb +++ b/core/lib/spree/event/subscriber.rb @@ -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 diff --git a/core/spec/lib/spree/event_spec.rb b/core/spec/lib/spree/event_spec.rb index 32e638f76f8..a30a1b44d9b 100644 --- a/core/spec/lib/spree/event_spec.rb +++ b/core/spec/lib/spree/event_spec.rb @@ -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 @@ -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 diff --git a/guides/source/developers/events/overview.html.md b/guides/source/developers/events/overview.html.md index 23dd758d93c..730e5f5936d 100644 --- a/guides/source/developers/events/overview.html.md +++ b/guides/source/developers/events/overview.html.md @@ -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)