diff --git a/lib/eventception.rb b/lib/eventception.rb index fc2d613..ae47972 100644 --- a/lib/eventception.rb +++ b/lib/eventception.rb @@ -1,5 +1,6 @@ require 'eventception/event' require 'eventception/listener_handler' require 'eventception/dispatcher' +require 'eventception/immutable_dispatcher' require 'eventception/base_subscriber' require 'eventception/version' diff --git a/lib/eventception/dispatcher.rb b/lib/eventception/dispatcher.rb index 01a366f..9115d4f 100644 --- a/lib/eventception/dispatcher.rb +++ b/lib/eventception/dispatcher.rb @@ -69,6 +69,10 @@ def listeners? # Gets all listeners for the specific event sorted by descending priority. # + # == Parameters: + # event_name:: + # The name of the event + # # == Returns: # The event listeners for the specific event sorted by descending priority. # @@ -82,6 +86,10 @@ def listeners_for(event_name:) # Checks whether are any registered listeners for the specific event. # + # == Parameters: + # event_name:: + # The name of the event + # # == Returns: # Boolean # @@ -96,9 +104,14 @@ def listeners_for?(event_name:) # The event to listen on # listener:: # The listener + # listener_method:: + # The name of the method to be executed in the listener # priority:: # The higher this value, the earlier an event listener will be triggered in the chain (defaults to 0) # + # == Returns: + # Nil + # def add_listener(event_name:, listener:, listener_method:, priority: 0) event_listeners[event_name][priority] << ListenerHandler.new(listener, listener_method) sorted.delete(event_name) @@ -106,6 +119,20 @@ def add_listener(event_name:, listener:, listener_method:, priority: 0) nil end + + # Removes an event listener from the specified events. + # + # == Parameters: + # event_name:: + # The event to listen on + # listener:: + # The listener + # listener_method:: + # The name of the method to be executed in the listener + # + # == Returns: + # Nil + # def remove_listener(event_name:, listener:, listener_method:) return unless listeners_for?(event_name: event_name) @@ -118,6 +145,8 @@ def remove_listener(event_name:, listener:, listener_method:) end event_listeners.delete(event_name) if listener_for_event.empty? + + nil end # Add an event subscriber. @@ -128,6 +157,9 @@ def remove_listener(event_name:, listener:, listener_method:) # subscriber:: # The subscriber # + # == Returns: + # Nil + # def add_subscriber(subscriber:) subscriber.subscribed_events.each do |event_subscribed| add_listener( @@ -137,8 +169,21 @@ def add_subscriber(subscriber:) priority: event_subscribed[:priority] || 0, ) end + + nil end + # Removes an event subscriber. + # + # The subscriber is asked for all the events he is interested in and added as a listener for these events. + # + # == Parameters: + # subscriber:: + # The subscriber + # + # == Returns: + # Nil + # def remove_subscriber(subscriber:) subscriber.subscribed_events.each do |event_subscribed| remove_listener( @@ -147,6 +192,8 @@ def remove_subscriber(subscriber:) listener_method: event_subscribed.fetch(:listener_method), ) end + + nil end protected @@ -161,6 +208,9 @@ def remove_subscriber(subscriber:) # event:: # The event # + # == Returns: + # Nil + # def do_dispatch(listeners:, event:) listeners.each do |_priority, priority_listeners| priority_listeners.each do |listener_handler| @@ -181,8 +231,13 @@ def do_dispatch(listeners:, event:) # event_name:: # The event name # + # == Returns: + # Nil + # def sort_listeners(event_name) sorted[event_name] = event_listeners[event_name].sort_by { |key, _| -key }.to_h + + nil end end end diff --git a/lib/eventception/immutable_dispatcher.rb b/lib/eventception/immutable_dispatcher.rb new file mode 100644 index 0000000..d93bc4d --- /dev/null +++ b/lib/eventception/immutable_dispatcher.rb @@ -0,0 +1,102 @@ +# @author Daniel Gomes +module Eventception + class ImmutableDispatcher + private + + attr_reader :dispatcher + + public + + # Creates an unmodifiable proxy for an event dispatcher. + # + # == Parameters: + # listeners:: + # Array of event listeners + # subscribers:: + # Array of event subscribers + # + # == Returns: + # Nil + # + def initialize(listeners: [], subscribers: []) + @dispatcher = Eventception::Dispatcher.new + + listeners.each do |listener| + dispatcher.add_listener( + event_name: listener.fetch(:event_name), + listener: listener.fetch(:listener), + listener_method: listener.fetch(:listener_method), + priority: listener.fetch(:priority, 0), + ) + end + + subscribers.each { |subscriber| dispatcher.add_subscriber(subscriber: subscriber) } + + nil + end + + # Dispatches an event to all registered listeners. + # + # == Parameters: + # event_name:: + # The name of the event to dispatch. The name of + # the event is the name of the method that is + # invoked on listeners. + # event:: + # The event to pass to the event handlers/listeners + # If not supplied, an empty Event instance is created. + # + # == Returns: + # The Event. + # + def dispatch(event_name:, event: Eventception::Event.new) + dispatcher.dispatch(event_name: event_name, event: event) + + event + end + + # Gets all listeners sorted by descending priority. + # + # == Returns: + # All event listeners sorted by event_name and descending priority. + # + def listeners + dispatcher.listeners + end + + # Checks whether are any registered listeners. + # + # == Returns: + # Boolean + # + def listeners? + dispatcher.listeners? + end + + # Gets all listeners for the specific event sorted by descending priority. + # + # == Parameters: + # event_name:: + # The name of the event + # + # == Returns: + # The event listeners for the specific event sorted by descending priority. + # + def listeners_for(event_name:) + dispatcher.listeners_for(event_name: event_name) + end + + # Checks whether are any registered listeners for the specific event. + # + # == Parameters: + # event_name:: + # The name of the event + # + # == Returns: + # Boolean + # + def listeners_for?(event_name:) + dispatcher.listeners_for?(event_name: event_name) + end + end +end diff --git a/spec/eventception/immutable_dispatcher_spec.rb b/spec/eventception/immutable_dispatcher_spec.rb new file mode 100644 index 0000000..0ea9f9f --- /dev/null +++ b/spec/eventception/immutable_dispatcher_spec.rb @@ -0,0 +1,238 @@ +require 'spec_helper' +require 'support/listener' +require 'support/subscriber' +require 'support/test_event' + +describe Eventception::ImmutableDispatcher do + subject(:dispatcher) { described_class.new(listeners: listeners, subscribers: subscribers) } + + let(:listener) { Eventception::Support::Listener.new } + let(:listener_method) { 'on_before' } + let(:listeners) { [] } + let(:subscriber) { Eventception::Support::Subscriber.new } + let(:subscribers) { [] } + let(:event_name) { :on_after } + let(:priority) { 0 } + + describe '#initialize' do + context 'when one listener is provided' do + let(:listeners) { + [ + { event_name: event_name, listener: listener, listener_method: listener_method, priority: priority }, + ] + } + + it 'then a listener is registered' do + expect(dispatcher.listeners?).to be true + end + + it 'then it have one listener registered' do + expect(dispatcher.listeners.size).to eq 1 + end + + it 'then a listener for the event \'on_before\' is registered' do + expect(dispatcher.listeners_for?(event_name: event_name)).to be true + end + + it 'then no listeners are registered for an invalid event name' do + expect(dispatcher.listeners_for?(event_name: :invalid)).to be false + end + end + end + + # describe '#listeners?' do + # context 'when no event_name is provided' do + # context 'and does not have any listeners associated' do + # it do + # expect(dispatcher.listeners?).to be false + # end + # end + # + # context 'and has one or more listeners associated' do + # before do + # dispatcher.add_listener(event_name: event_name, listener: listener, listener_method: listener_method) + # end + # + # it do + # expect(dispatcher.listeners?).to be true + # end + # end + # end + # + # context 'when an event_name is provided' do + # context 'and does not have any listener associated' do + # it do + # expect(dispatcher.listeners_for?(event_name: event_name)).to be false + # end + # end + # + # context 'and has one or more listeners associated' do + # before do + # dispatcher.add_listener(event_name: event_name, listener: listener, listener_method: listener_method) + # end + # + # it do + # expect(dispatcher.listeners_for?(event_name: event_name)).to be true + # end + # + # context 'when a different event_name is provided without any listener associated' do + # it do + # expect(dispatcher.listeners_for?(event_name: :invalid)).to be false + # end + # end + # end + # end + # end + # + # describe '#add_listener' do + # context 'when no priority is provided' do + # before do + # dispatcher.add_listener(event_name: event_name, listener: listener, listener_method: listener_method) + # end + # + # it 'adds a new listener to the event_name with 0 priority' do + # listeners = dispatcher.listeners_for(event_name: event_name) + # + # expect(listeners[priority].count).to eq 1 + # expect(listeners[priority].first.listener).to eq listener + # expect(listeners[priority].first.method).to eq listener_method + # end + # end + # + # context 'when priority of 10 is provided' do + # let(:priority) { 10 } + # + # before do + # dispatcher.add_listener( + # event_name: event_name, + # listener: listener, + # listener_method: listener_method, + # priority: priority, + # ) + # end + # + # it 'adds a new listener to the event_name with a different priority' do + # listeners = dispatcher.listeners_for(event_name: event_name) + # + # expect(listeners[priority].count).to eq 1 + # expect(listeners[priority].first.listener).to eq listener + # expect(listeners[priority].first.method).to eq listener_method + # end + # end + # end + # + # describe '#listeners' do + # context 'when listeners exist' do + # before do + # dispatcher.add_listener(event_name: event_name, listener: listener, listener_method: listener_method) + # end + # + # context 'when no event_name is provided' do + # it 'adds a new listener to the event_name with 0 priority' do + # listeners = dispatcher.listeners + # + # expect(listeners.size).to eq 1 + # end + # end + # + # context 'when an event_name is provided' do + # it 'adds a new listener to the event_name with 10 priority' do + # listeners = dispatcher.listeners_for(event_name: event_name) + # + # expect(listeners[priority].count).to eq 1 + # expect(listeners[priority].first.listener).to eq listener + # expect(listeners[priority].first.method).to eq listener_method + # end + # end + # end + # end + # + # describe '#remove_listener' do + # let(:listener_method2) { 'on_after' } + # + # context 'when two listeners exist for the same event_name' do + # before do + # dispatcher.add_listener(event_name: event_name, listener: listener, listener_method: listener_method) + # dispatcher.add_listener(event_name: event_name, listener: listener, listener_method: listener_method2) + # end + # + # it 'has two listeners' do + # expect(dispatcher.listeners_for?(event_name: event_name)).to be true + # expect(dispatcher.listeners_for(event_name: event_name)[priority].size).to eq 2 + # end + # + # context 'and one of the listeners is removed' do + # it 'has only one listener' do + # dispatcher.remove_listener(event_name: event_name, listener: listener, listener_method: listener_method) + # expect(dispatcher.listeners_for(event_name: event_name)[priority].size).to eq 1 + # end + # end + # + # context 'and both listeners are removed' do + # it 'has zero listeners' do + # dispatcher.remove_listener(event_name: event_name, listener: listener, listener_method: listener_method) + # dispatcher.remove_listener(event_name: event_name, listener: listener, listener_method: listener_method2) + # expect(dispatcher.listeners_for?(event_name: event_name)).to be false + # expect(dispatcher.listeners_for(event_name: event_name).size).to eq 0 + # end + # end + # end + # end + # + # describe '#add_subscriber' do + # before do + # dispatcher.add_subscriber(subscriber: subscriber) + # end + # + # it 'subscribes two events' do + # listeners = dispatcher.listeners + # expect(listeners.size).to eq 2 + # listeners.each do |_event_name, listener| + # expect(listener[priority].size).to eq 1 + # end + # end + # end + # + # describe '#remove_subscriber' do + # before do + # dispatcher.add_subscriber(subscriber: subscriber) + # end + # + # it 'removes the subscriber listeners' do + # expect(dispatcher.listeners.size).to eq 2 + # dispatcher.remove_subscriber(subscriber: subscriber) + # expect(dispatcher.listeners?).to be false + # end + # end + # + # describe '#dispatch' do + # context 'when using subscribers' do + # before do + # dispatcher.add_subscriber(subscriber: subscriber) + # end + # + # it 'calls the subscriber methods' do + # expect(subscriber).to receive('on_before').and_call_original + # expect(subscriber).to receive('on_after').and_call_original + # + # expect { + # dispatcher.dispatch(event_name: Eventception::Support::TestEvent::BEFORE) + # }.to output(/on before, propagation stopped/).to_stdout + # expect { + # dispatcher.dispatch(event_name: Eventception::Support::TestEvent::AFTER) + # }.to output(/on after, propagation stopped/).to_stdout + # end + # end + # + # context 'when using listeners' do + # before do + # dispatcher.add_listener(event_name: event_name, listener: listener, listener_method: listener_method) + # end + # + # it 'executes the listener method' do + # expect(listener).to receive(listener_method).and_call_original + # expect { dispatcher.dispatch(event_name: event_name) }.to output(/before/).to_stdout + # end + # end + # end +end