wisper-compat 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +19 -0
- data/.gitignore +20 -0
- data/.rspec +4 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +152 -0
- data/CONTRIBUTING.md +57 -0
- data/Gemfile +10 -0
- data/README.md +358 -0
- data/Rakefile +6 -0
- data/lib/wisper/broadcasters/logger_broadcaster.rb +41 -0
- data/lib/wisper/broadcasters/send_broadcaster.rb +9 -0
- data/lib/wisper/configuration.rb +44 -0
- data/lib/wisper/global_listeners.rb +72 -0
- data/lib/wisper/publisher.rb +89 -0
- data/lib/wisper/registration/block.rb +11 -0
- data/lib/wisper/registration/object.rb +43 -0
- data/lib/wisper/registration/registration.rb +18 -0
- data/lib/wisper/temporary_listeners.rb +41 -0
- data/lib/wisper/value_objects/events.rb +61 -0
- data/lib/wisper/value_objects/prefix.rb +29 -0
- data/lib/wisper/version.rb +3 -0
- data/lib/wisper.rb +65 -0
- data/spec/lib/global_listeners_spec.rb +82 -0
- data/spec/lib/integration_spec.rb +56 -0
- data/spec/lib/simple_example_spec.rb +21 -0
- data/spec/lib/temporary_global_listeners_spec.rb +103 -0
- data/spec/lib/wisper/broadcasters/logger_broadcaster_spec.rb +129 -0
- data/spec/lib/wisper/broadcasters/send_broadcaster_spec.rb +68 -0
- data/spec/lib/wisper/configuration/broadcasters_spec.rb +11 -0
- data/spec/lib/wisper/configuration_spec.rb +36 -0
- data/spec/lib/wisper/publisher_spec.rb +311 -0
- data/spec/lib/wisper/registrations/object_spec.rb +14 -0
- data/spec/lib/wisper/value_objects/events_spec.rb +107 -0
- data/spec/lib/wisper/value_objects/prefix_spec.rb +46 -0
- data/spec/lib/wisper_spec.rb +99 -0
- data/spec/spec_helper.rb +21 -0
- data/wisper-compat.gemspec +29 -0
- metadata +102 -0
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Wisper
|
4
|
+
class Configuration
|
5
|
+
attr_reader :broadcasters
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@broadcasters = Broadcasters.new
|
9
|
+
end
|
10
|
+
|
11
|
+
# registers a broadcaster, referenced by key
|
12
|
+
#
|
13
|
+
# @param key [String, #to_s] an arbitrary key
|
14
|
+
# @param broadcaster [#broadcast] a broadcaster
|
15
|
+
def broadcaster(key, broadcaster)
|
16
|
+
@broadcasters[key] = broadcaster
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
# sets the default value for prefixes
|
21
|
+
#
|
22
|
+
# @param [#to_s] value
|
23
|
+
#
|
24
|
+
# @return [String]
|
25
|
+
def default_prefix=(value)
|
26
|
+
ValueObjects::Prefix.default = value
|
27
|
+
end
|
28
|
+
|
29
|
+
class Broadcasters
|
30
|
+
extend Forwardable
|
31
|
+
|
32
|
+
def_delegators :@data, :[], :[]=, :empty?, :include?, :clear, :keys, :to_h
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
@data = {}
|
36
|
+
end
|
37
|
+
|
38
|
+
def fetch(key)
|
39
|
+
raise KeyError, "broadcaster not found for #{key}" unless include?(key)
|
40
|
+
@data[key]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# Handles global subscriptions
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
|
5
|
+
require 'singleton'
|
6
|
+
|
7
|
+
module Wisper
|
8
|
+
class GlobalListeners
|
9
|
+
include Singleton
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@registrations = Set.new
|
13
|
+
@mutex = Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def subscribe(*listeners, **options)
|
17
|
+
with_mutex do
|
18
|
+
listeners.each do |listener|
|
19
|
+
@registrations << ObjectRegistration.new(listener, **options)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def unsubscribe(*listeners)
|
26
|
+
with_mutex do
|
27
|
+
@registrations.delete_if do |registration|
|
28
|
+
listeners.include?(registration.listener)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def registrations
|
35
|
+
with_mutex { @registrations }
|
36
|
+
end
|
37
|
+
|
38
|
+
def listeners
|
39
|
+
registrations.map(&:listener).freeze
|
40
|
+
end
|
41
|
+
|
42
|
+
def clear
|
43
|
+
with_mutex { @registrations.clear }
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.subscribe(*listeners, **options)
|
47
|
+
instance.subscribe(*listeners, **options)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.unsubscribe(*listeners)
|
51
|
+
instance.unsubscribe(*listeners)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.registrations
|
55
|
+
instance.registrations
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.listeners
|
59
|
+
instance.listeners
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.clear
|
63
|
+
instance.clear
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
def with_mutex
|
69
|
+
@mutex.synchronize { yield }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Wisper
|
2
|
+
module Publisher
|
3
|
+
def listeners
|
4
|
+
registrations.map(&:listener).freeze
|
5
|
+
end
|
6
|
+
|
7
|
+
# subscribe a listener
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# my_publisher.subscribe(MyListener.new)
|
11
|
+
#
|
12
|
+
# @return [self]
|
13
|
+
def subscribe(listener, **options)
|
14
|
+
raise ArgumentError, "#{__method__} does not take a block, did you mean to call #on instead?" if block_given?
|
15
|
+
local_registrations << ObjectRegistration.new(listener, **options)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
# subscribe a block
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# my_publisher.on(:order_created) { |args| ... }
|
23
|
+
#
|
24
|
+
# @return [self]
|
25
|
+
def on(*events, &block)
|
26
|
+
raise ArgumentError, 'must give at least one event' if events.empty?
|
27
|
+
raise ArgumentError, 'must pass a block' if !block
|
28
|
+
local_registrations << BlockRegistration.new(block, on: events)
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# broadcasts an event
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# def call
|
36
|
+
# # ...
|
37
|
+
# broadcast(:finished, self)
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# @return [self]
|
41
|
+
def broadcast(event, *args, **kwargs)
|
42
|
+
registrations.each do | registration |
|
43
|
+
registration.broadcast(clean_event(event), self, *args, **kwargs)
|
44
|
+
end
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
alias :publish :broadcast
|
49
|
+
|
50
|
+
private :broadcast, :publish
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
# subscribe a listener
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# MyPublisher.subscribe(MyListener.new)
|
57
|
+
#
|
58
|
+
def subscribe(listener, **options)
|
59
|
+
GlobalListeners.subscribe(listener, **options.merge(:scope => self))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def local_registrations
|
66
|
+
@local_registrations ||= Set.new
|
67
|
+
end
|
68
|
+
|
69
|
+
def global_registrations
|
70
|
+
GlobalListeners.registrations
|
71
|
+
end
|
72
|
+
|
73
|
+
def temporary_registrations
|
74
|
+
TemporaryListeners.registrations
|
75
|
+
end
|
76
|
+
|
77
|
+
def registrations
|
78
|
+
local_registrations + global_registrations + temporary_registrations
|
79
|
+
end
|
80
|
+
|
81
|
+
def clean_event(event)
|
82
|
+
event.to_s.gsub('-', '_')
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.included(base)
|
86
|
+
base.extend(ClassMethods)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# @api private
|
2
|
+
|
3
|
+
module Wisper
|
4
|
+
class ObjectRegistration < Registration
|
5
|
+
attr_reader :with, :prefix, :allowed_classes, :broadcaster
|
6
|
+
|
7
|
+
def initialize(listener, **options)
|
8
|
+
super(listener, **options)
|
9
|
+
@with = options[:with]
|
10
|
+
@prefix = ValueObjects::Prefix.new options[:prefix]
|
11
|
+
@allowed_classes = Array(options[:scope]).map(&:to_s).to_set
|
12
|
+
@broadcaster = map_broadcaster(options[:async] || options[:broadcaster])
|
13
|
+
end
|
14
|
+
|
15
|
+
def broadcast(event, publisher, *args, **kwargs)
|
16
|
+
method_to_call = map_event_to_method(event)
|
17
|
+
if should_broadcast?(event) && listener.respond_to?(method_to_call) && publisher_in_scope?(publisher)
|
18
|
+
broadcaster.broadcast(listener, publisher, method_to_call, *args, **kwargs)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def publisher_in_scope?(publisher)
|
25
|
+
allowed_classes.empty? || publisher.class.ancestors.any? { |ancestor| allowed_classes.include?(ancestor.to_s) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def map_event_to_method(event)
|
29
|
+
prefix + (with || event).to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
def map_broadcaster(value)
|
33
|
+
return value if value.respond_to?(:broadcast)
|
34
|
+
value = :async if value == true
|
35
|
+
value = :default if value == nil
|
36
|
+
configuration.broadcasters.fetch(value)
|
37
|
+
end
|
38
|
+
|
39
|
+
def configuration
|
40
|
+
Wisper.configuration
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# @api private
|
2
|
+
|
3
|
+
module Wisper
|
4
|
+
class Registration
|
5
|
+
attr_reader :on, :listener
|
6
|
+
|
7
|
+
def initialize(listener, **options)
|
8
|
+
@listener = listener
|
9
|
+
@on = ValueObjects::Events.new options[:on]
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def should_broadcast?(event)
|
15
|
+
on.include? event
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Handles temporary global subscriptions
|
2
|
+
|
3
|
+
# @api private
|
4
|
+
|
5
|
+
module Wisper
|
6
|
+
class TemporaryListeners
|
7
|
+
def self.subscribe(*listeners, **options, &block)
|
8
|
+
new.subscribe(*listeners, **options, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.registrations
|
12
|
+
new.registrations
|
13
|
+
end
|
14
|
+
|
15
|
+
def subscribe(*listeners, **options, &_block)
|
16
|
+
new_registrations = build_registrations(*listeners, **options)
|
17
|
+
|
18
|
+
begin
|
19
|
+
registrations.merge new_registrations
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
registrations.subtract new_registrations
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def registrations
|
28
|
+
Thread.current[key] ||= Set.new
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def build_registrations(*listeners, **options)
|
34
|
+
listeners.map { |listener| ObjectRegistration.new(listener, **options) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def key
|
38
|
+
'__wisper_temporary_listeners'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module Wisper
|
2
|
+
module ValueObjects #:nodoc:
|
3
|
+
# Describes allowed events
|
4
|
+
#
|
5
|
+
# Duck-types the argument to quack like array of strings
|
6
|
+
# when responding to the {#include?} method call.
|
7
|
+
class Events
|
8
|
+
|
9
|
+
# @!scope class
|
10
|
+
# @!method new(on)
|
11
|
+
# Initializes a 'list' of events
|
12
|
+
#
|
13
|
+
# @param [NilClass, String, Symbol, Array, Regexp] list
|
14
|
+
#
|
15
|
+
# @raise [ArgumentError]
|
16
|
+
# if an argument if of unsupported type
|
17
|
+
#
|
18
|
+
# @return [undefined]
|
19
|
+
def initialize(list)
|
20
|
+
@list = list
|
21
|
+
end
|
22
|
+
|
23
|
+
# Check if given event is 'included' to the 'list' of events
|
24
|
+
#
|
25
|
+
# @param [#to_s] event
|
26
|
+
#
|
27
|
+
# @return [Boolean]
|
28
|
+
def include?(event)
|
29
|
+
appropriate_method.call(event.to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def methods
|
35
|
+
{
|
36
|
+
NilClass => ->(_event) { true },
|
37
|
+
String => ->(event) { list == event },
|
38
|
+
Symbol => ->(event) { list.to_s == event },
|
39
|
+
Enumerable => ->(event) { list.map(&:to_s).include? event },
|
40
|
+
Regexp => ->(event) { list.match(event) || false }
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
def list
|
45
|
+
@list
|
46
|
+
end
|
47
|
+
|
48
|
+
def appropriate_method
|
49
|
+
@appropriate_method ||= methods[recognized_type]
|
50
|
+
end
|
51
|
+
|
52
|
+
def recognized_type
|
53
|
+
methods.keys.detect(&list.method(:is_a?)) || type_not_recognized
|
54
|
+
end
|
55
|
+
|
56
|
+
def type_not_recognized
|
57
|
+
fail(ArgumentError, "#{list.class} not supported for `on` argument")
|
58
|
+
end
|
59
|
+
end # class Events
|
60
|
+
end # module ValueObjects
|
61
|
+
end # module Wisper
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Wisper
|
2
|
+
module ValueObjects #:nodoc:
|
3
|
+
# Prefix for notifications
|
4
|
+
#
|
5
|
+
# @example
|
6
|
+
# Wisper::ValueObjects::Prefix.new nil # => ""
|
7
|
+
# Wisper::ValueObjects::Prefix.new "when" # => "when_"
|
8
|
+
# Wisper::ValueObjects::Prefix.new true # => "on_"
|
9
|
+
class Prefix < String
|
10
|
+
class << self
|
11
|
+
attr_accessor :default
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [true, nil, #to_s] value
|
15
|
+
#
|
16
|
+
# @return [undefined]
|
17
|
+
def initialize(value = nil)
|
18
|
+
super "#{ (value == true) ? default : value }_"
|
19
|
+
replace "" if self == "_"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def default
|
25
|
+
self.class.default || 'on'
|
26
|
+
end
|
27
|
+
end # class Prefix
|
28
|
+
end # module ValueObjects
|
29
|
+
end # module Wisper
|
data/lib/wisper.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'wisper/version'
|
3
|
+
require 'wisper/configuration'
|
4
|
+
require 'wisper/publisher'
|
5
|
+
require 'wisper/value_objects/prefix'
|
6
|
+
require 'wisper/value_objects/events'
|
7
|
+
require 'wisper/registration/registration'
|
8
|
+
require 'wisper/registration/object'
|
9
|
+
require 'wisper/registration/block'
|
10
|
+
require 'wisper/global_listeners'
|
11
|
+
require 'wisper/temporary_listeners'
|
12
|
+
require 'wisper/broadcasters/send_broadcaster'
|
13
|
+
require 'wisper/broadcasters/logger_broadcaster'
|
14
|
+
|
15
|
+
module Wisper
|
16
|
+
# Examples:
|
17
|
+
#
|
18
|
+
# Wisper.subscribe(AuditRecorder.new)
|
19
|
+
#
|
20
|
+
# Wisper.subscribe(AuditRecorder.new, StatsRecorder.new)
|
21
|
+
#
|
22
|
+
# Wisper.subscribe(AuditRecorder.new, on: 'order_created')
|
23
|
+
#
|
24
|
+
# Wisper.subscribe(AuditRecorder.new, scope: 'MyPublisher')
|
25
|
+
#
|
26
|
+
# Wisper.subscribe(AuditRecorder.new, StatsRecorder.new) do
|
27
|
+
# # ..
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
def self.subscribe(*args, **kwargs, &block)
|
31
|
+
if block_given?
|
32
|
+
TemporaryListeners.subscribe(*args, **kwargs, &block)
|
33
|
+
else
|
34
|
+
GlobalListeners.subscribe(*args, **kwargs)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.unsubscribe(*listeners)
|
39
|
+
GlobalListeners.unsubscribe(*listeners)
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.publisher
|
43
|
+
Publisher
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.clear
|
47
|
+
GlobalListeners.clear
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.configure
|
51
|
+
yield(configuration)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.configuration
|
55
|
+
@configuration ||= Configuration.new
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.setup
|
59
|
+
configure do |config|
|
60
|
+
config.broadcaster(:default, Broadcasters::SendBroadcaster.new)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
Wisper.setup
|
@@ -0,0 +1,82 @@
|
|
1
|
+
describe Wisper::GlobalListeners do
|
2
|
+
let(:global_listener) { double('listener') }
|
3
|
+
let(:local_listener) { double('listener') }
|
4
|
+
let(:publisher) { publisher_class.new }
|
5
|
+
|
6
|
+
describe '.subscribe' do
|
7
|
+
it 'adds given listener to every publisher' do
|
8
|
+
Wisper::GlobalListeners.subscribe(global_listener)
|
9
|
+
expect(global_listener).to receive(:it_happened)
|
10
|
+
publisher.send(:broadcast, :it_happened)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'works with options' do
|
14
|
+
Wisper::GlobalListeners.subscribe(global_listener, :on => :it_happened,
|
15
|
+
:with => :woot)
|
16
|
+
expect(global_listener).to receive(:woot).once
|
17
|
+
expect(global_listener).not_to receive(:it_happened_again)
|
18
|
+
publisher.send(:broadcast, :it_happened)
|
19
|
+
publisher.send(:broadcast, :it_happened_again)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'works along side local listeners' do
|
23
|
+
# global listener
|
24
|
+
Wisper::GlobalListeners.subscribe(global_listener)
|
25
|
+
|
26
|
+
# local listener
|
27
|
+
publisher.subscribe(local_listener)
|
28
|
+
|
29
|
+
expect(global_listener).to receive(:it_happened)
|
30
|
+
expect(local_listener).to receive(:it_happened)
|
31
|
+
|
32
|
+
publisher.send(:broadcast, :it_happened)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'can be scoped to classes' do
|
36
|
+
publisher_1 = publisher_class.new
|
37
|
+
publisher_2 = publisher_class.new
|
38
|
+
publisher_3 = publisher_class.new
|
39
|
+
|
40
|
+
Wisper::GlobalListeners.subscribe(global_listener, :scope => [publisher_1.class,
|
41
|
+
publisher_2.class])
|
42
|
+
|
43
|
+
expect(global_listener).to receive(:it_happened_1).once
|
44
|
+
expect(global_listener).to receive(:it_happened_2).once
|
45
|
+
expect(global_listener).not_to receive(:it_happened_3)
|
46
|
+
|
47
|
+
publisher_1.send(:broadcast, :it_happened_1)
|
48
|
+
publisher_2.send(:broadcast, :it_happened_2)
|
49
|
+
publisher_3.send(:broadcast, :it_happened_3)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'is threadsafe' do
|
53
|
+
num_threads = 100
|
54
|
+
(1..num_threads).to_a.map do
|
55
|
+
Thread.new do
|
56
|
+
Wisper::GlobalListeners.subscribe(Object.new)
|
57
|
+
sleep(rand) # a little chaos
|
58
|
+
end
|
59
|
+
end.each(&:join)
|
60
|
+
|
61
|
+
expect(Wisper::GlobalListeners.listeners.size).to eq num_threads
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '.listeners' do
|
66
|
+
it 'returns collection of global listeners' do
|
67
|
+
Wisper::GlobalListeners.subscribe(global_listener)
|
68
|
+
expect(Wisper::GlobalListeners.listeners).to eq [global_listener]
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns an immutable collection' do
|
72
|
+
expect(Wisper::GlobalListeners.listeners).to be_frozen
|
73
|
+
expect { Wisper::GlobalListeners.listeners << global_listener }.to raise_error(RuntimeError)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
it '.clear clears all global listeners' do
|
78
|
+
Wisper::GlobalListeners.subscribe(global_listener)
|
79
|
+
Wisper::GlobalListeners.clear
|
80
|
+
expect(Wisper::GlobalListeners.listeners).to be_empty
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Example
|
2
|
+
class MyCommand
|
3
|
+
include Wisper::Publisher
|
4
|
+
|
5
|
+
def execute(be_successful)
|
6
|
+
if be_successful
|
7
|
+
broadcast('success', 'hello')
|
8
|
+
else
|
9
|
+
broadcast('failure', 'world')
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe Wisper do
|
15
|
+
|
16
|
+
it 'subscribes object to all published events' do
|
17
|
+
listener = double('listener')
|
18
|
+
expect(listener).to receive(:success).with('hello', **{})
|
19
|
+
|
20
|
+
command = MyCommand.new
|
21
|
+
|
22
|
+
command.subscribe(listener)
|
23
|
+
|
24
|
+
command.execute(true)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'maps events to different methods' do
|
28
|
+
listener_1 = double('listener')
|
29
|
+
listener_2 = double('listener')
|
30
|
+
expect(listener_1).to receive(:happy_days).with('hello', **{})
|
31
|
+
expect(listener_2).to receive(:sad_days).with('world', **{})
|
32
|
+
|
33
|
+
command = MyCommand.new
|
34
|
+
|
35
|
+
command.subscribe(listener_1, :on => :success, :with => :happy_days)
|
36
|
+
command.subscribe(listener_2, :on => :failure, :with => :sad_days)
|
37
|
+
|
38
|
+
command.execute(true)
|
39
|
+
command.execute(false)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'subscribes block can be chained' do
|
43
|
+
insider = double('Insider')
|
44
|
+
|
45
|
+
expect(insider).to receive(:render).with('success')
|
46
|
+
expect(insider).to receive(:render).with('failure')
|
47
|
+
|
48
|
+
command = MyCommand.new
|
49
|
+
|
50
|
+
command.on(:success) { |message| insider.render('success') }
|
51
|
+
.on(:failure) { |message| insider.render('failure') }
|
52
|
+
|
53
|
+
command.execute(true)
|
54
|
+
command.execute(false)
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class MyPublisher
|
2
|
+
include Wisper::Publisher
|
3
|
+
|
4
|
+
def do_something
|
5
|
+
# ...
|
6
|
+
broadcast(:bar, self)
|
7
|
+
broadcast(:foo, self)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'simple publishing' do
|
12
|
+
it 'subscribes listener to events' do
|
13
|
+
listener = double('listener')
|
14
|
+
expect(listener).to receive(:foo).with((instance_of MyPublisher), **{})
|
15
|
+
expect(listener).to receive(:bar).with((instance_of MyPublisher), **{})
|
16
|
+
|
17
|
+
my_publisher = MyPublisher.new
|
18
|
+
my_publisher.subscribe(listener)
|
19
|
+
my_publisher.do_something
|
20
|
+
end
|
21
|
+
end
|