event_stream 0.2.0 → 1.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 +4 -4
- data/README.md +17 -1
- data/lib/event_stream.rb +18 -63
- data/lib/event_stream/event.rb +44 -0
- data/lib/event_stream/registry.rb +19 -0
- data/lib/event_stream/stream.rb +45 -0
- data/lib/event_stream/subscriber.rb +18 -0
- data/lib/event_stream/version.rb +1 -1
- data/test/event_stream/event_stream_test.rb +36 -2
- data/test/event_stream/event_test.rb +49 -0
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 283f487e987c22198cfa937ac6a34f1c4492ee9a
|
4
|
+
data.tar.gz: 5eee28a869ccf165a4960d7fe0213974e790753b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5ca1ee9fa91b46e21e0059f14db66cc54bbba9f6fdd63b1fb4b6bfa9136c5305e3ecc0a7c6c4e4366f1ee8fc62fb25003eb609dfb6c642a2d9e46662255ebf70
|
7
|
+
data.tar.gz: b81ebc9b66b60125261f76b7bf67003e9e5b4d2d9bea7bc5f840611da944e12193fe278c5014bb93955ca5c3a94d98155e3b30c8133eec386ab0b3fde21c23cd
|
data/README.md
CHANGED
@@ -17,7 +17,7 @@ Then fire an event:
|
|
17
17
|
EventStream.publish(:my_event, :description => "An example event.")
|
18
18
|
```
|
19
19
|
|
20
|
-
An event is just an
|
20
|
+
An event is just an immutable value object with a name and a bundle of other attributes, so the use of `:description` here is arbitrary.
|
21
21
|
|
22
22
|
Events can be subscribed to by name, as above, but many other ways are supported:
|
23
23
|
|
@@ -49,6 +49,22 @@ stream.subscribe(...) { |e| ... }
|
|
49
49
|
stream.publish(...)
|
50
50
|
```
|
51
51
|
|
52
|
+
### The Stream Registry
|
53
|
+
|
54
|
+
To allow for easy access to different streams from different call points, a registry is available for storing and
|
55
|
+
retrieving streams. To register a stream:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
stream = EventStream::Stream.new
|
59
|
+
EventStream.register_stream(:my_stream_name)
|
60
|
+
```
|
61
|
+
|
62
|
+
To access the stream from the registry and publish an event:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
EventStream[:my_stream_name].publish(...)
|
66
|
+
```
|
67
|
+
|
52
68
|
### Subscriber DSL
|
53
69
|
|
54
70
|
It's sometimes useful to separate the definition of a subscriber action
|
data/lib/event_stream.rb
CHANGED
@@ -1,79 +1,34 @@
|
|
1
1
|
require 'ostruct'
|
2
|
+
require_relative 'event_stream/event'
|
3
|
+
require_relative 'event_stream/stream'
|
4
|
+
require_relative 'event_stream/subscriber'
|
5
|
+
require_relative 'event_stream/registry'
|
2
6
|
require_relative 'event_stream/subscriber_dsl'
|
3
7
|
|
4
8
|
module EventStream
|
5
9
|
class << self
|
6
10
|
extend Forwardable
|
7
11
|
|
8
|
-
#
|
12
|
+
# Returns the stream for a stream name from the stream registry.
|
13
|
+
# @param stream_name [Symbol]
|
9
14
|
# @return [Stream]
|
10
|
-
def
|
11
|
-
|
12
|
-
end
|
13
|
-
|
14
|
-
def_delegators :default_stream, :publish, :subscribe, :clear_subscribers
|
15
|
-
end
|
16
|
-
|
17
|
-
# An Event. Each event is an OpenStruct with a name as well as any number of other optional fields.
|
18
|
-
# @!attribute name
|
19
|
-
# @return [Symbol]
|
20
|
-
class Event < OpenStruct; end
|
21
|
-
|
22
|
-
class Stream
|
23
|
-
def initialize
|
24
|
-
@subscribers = []
|
25
|
-
end
|
26
|
-
|
27
|
-
# Publishes an event to this event stream
|
28
|
-
# @param name [Symbol] name of this event
|
29
|
-
# @param attrs [Hash] optional attributes representing this event
|
30
|
-
def publish(name, attrs = {})
|
31
|
-
e = Event.new(attrs.merge(:name => name))
|
32
|
-
@subscribers.each { |l| l.consume(e) }
|
33
|
-
end
|
34
|
-
|
35
|
-
# Registers a subscriber to this event stream.
|
36
|
-
# @param filter [Object] Filters which events this subscriber will consume.
|
37
|
-
# If a string or regexp is provided, these will be matched against the event name.
|
38
|
-
# A hash will be matched against the attributes of the event.
|
39
|
-
# Or, any arbitrary predicate on events may be provided.
|
40
|
-
# @yield [Event] action to perform when the event occurs.
|
41
|
-
def subscribe(filter = nil, &action)
|
42
|
-
add_subscriber(Subscriber.create(filter, &action))
|
43
|
-
end
|
44
|
-
|
45
|
-
# Clears all subscribers from this event stream.
|
46
|
-
def clear_subscribers
|
47
|
-
@subscribers = []
|
15
|
+
def [](stream_name)
|
16
|
+
Registry.lookup(stream_name)
|
48
17
|
end
|
49
18
|
|
50
|
-
#
|
51
|
-
# @
|
52
|
-
|
53
|
-
|
19
|
+
# Registers a stream, associating it with a specific stream name
|
20
|
+
# @param stream_name [Symbol]
|
21
|
+
# @param stream [Stream]
|
22
|
+
def register_stream(stream_name, stream)
|
23
|
+
Registry.register(stream_name, stream)
|
54
24
|
end
|
55
25
|
|
56
|
-
#
|
57
|
-
# @
|
58
|
-
def
|
59
|
-
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
class Subscriber < Struct.new(:filter, :action)
|
64
|
-
def self.create(filter = nil, &action)
|
65
|
-
filter ||= lambda { |e| true }
|
66
|
-
filter_predicate = case filter
|
67
|
-
when Symbol, String then lambda { |e| e.name.to_s == filter.to_s }
|
68
|
-
when Regexp then lambda { |e| e.name =~ filter }
|
69
|
-
when Hash then lambda { |e| filter.all? { |k,v| e[k] === v } }
|
70
|
-
else filter
|
71
|
-
end
|
72
|
-
new(filter_predicate, action)
|
26
|
+
# The default event stream
|
27
|
+
# @return [Stream]
|
28
|
+
def default_stream
|
29
|
+
self[:default]
|
73
30
|
end
|
74
31
|
|
75
|
-
|
76
|
-
action.call(event) if filter.call(event)
|
77
|
-
end
|
32
|
+
def_delegators :default_stream, :publish, :subscribe, :clear_subscribers
|
78
33
|
end
|
79
34
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module EventStream
|
4
|
+
# Events are immutable collections of fields with convenience methods for accessing and json serialization
|
5
|
+
class Event
|
6
|
+
|
7
|
+
# @param fields [Hash<Symbol, Object>] The attributes of this event
|
8
|
+
def initialize(fields)
|
9
|
+
@fields = Hash[fields.map { |k,v| [k.to_sym, v] }].freeze
|
10
|
+
end
|
11
|
+
|
12
|
+
# An alternate field accessor
|
13
|
+
# @param key [Symbol]
|
14
|
+
# @return [Object]
|
15
|
+
def [](key)
|
16
|
+
@fields[key.to_sym]
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [Hash<Symbol, Object>]
|
20
|
+
def to_h
|
21
|
+
@fields
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String]
|
25
|
+
def to_json
|
26
|
+
JSON.dump(to_h)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Parses an event object from JSON
|
30
|
+
# @param json_event [String] The JSON event representation, such as created by `event.to_json`
|
31
|
+
# @return [Event]
|
32
|
+
def self.from_json(json_event)
|
33
|
+
new(JSON.parse(json_event))
|
34
|
+
end
|
35
|
+
|
36
|
+
def method_missing(method_name, *args)
|
37
|
+
@fields.has_key?(method_name) ? @fields[method_name] : super
|
38
|
+
end
|
39
|
+
|
40
|
+
def respond_to_missing?(method_name, include_private = false)
|
41
|
+
@fields.has_key?(method_name) || super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'active_support/core_ext/class/attribute'
|
2
|
+
|
3
|
+
module EventStream
|
4
|
+
class Registry
|
5
|
+
|
6
|
+
class UnregisteredStream < StandardError; end
|
7
|
+
|
8
|
+
class_attribute :streams
|
9
|
+
self.streams = { default: Stream.new }
|
10
|
+
|
11
|
+
def self.register(stream_name, stream)
|
12
|
+
streams[stream_name] = stream
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.lookup(stream_name)
|
16
|
+
streams[stream_name] || (raise UnregisteredStream)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module EventStream
|
2
|
+
class Stream
|
3
|
+
def initialize
|
4
|
+
@subscribers = []
|
5
|
+
end
|
6
|
+
|
7
|
+
# Publishes an event to this event stream
|
8
|
+
# @param name [Symbol] name of this event
|
9
|
+
# @param attrs [Hash] optional attributes representing this event
|
10
|
+
def publish(name_or_event, attrs = {})
|
11
|
+
event = case name_or_event
|
12
|
+
when Event then name_or_event
|
13
|
+
else Event.new(attrs.merge(:name => name_or_event))
|
14
|
+
end
|
15
|
+
@subscribers.each { |l| l.consume(event) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Registers a subscriber to this event stream.
|
19
|
+
# @param filter [Object] Filters which events this subscriber will consume.
|
20
|
+
# If a string or regexp is provided, these will be matched against the event name.
|
21
|
+
# A hash will be matched against the attributes of the event.
|
22
|
+
# Or, any arbitrary predicate on events may be provided.
|
23
|
+
# @yield [Event] action to perform when the event occurs.
|
24
|
+
def subscribe(filter = nil, &action)
|
25
|
+
add_subscriber(Subscriber.create(filter, &action))
|
26
|
+
end
|
27
|
+
|
28
|
+
# Clears all subscribers from this event stream.
|
29
|
+
def clear_subscribers
|
30
|
+
@subscribers = []
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns all subscribers for this stream
|
34
|
+
# @return [Array<EventStream::Subscriber]
|
35
|
+
def subscribers
|
36
|
+
@subscribers
|
37
|
+
end
|
38
|
+
|
39
|
+
# Adds a subscriber to this stream
|
40
|
+
# @param [EventStream::Subscriber]
|
41
|
+
def add_subscriber(subscriber)
|
42
|
+
@subscribers << subscriber
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module EventStream
|
2
|
+
class Subscriber < Struct.new(:filter, :action)
|
3
|
+
def self.create(filter = nil, &action)
|
4
|
+
filter ||= lambda { |e| true }
|
5
|
+
filter_predicate = case filter
|
6
|
+
when Symbol, String then lambda { |e| e.name.to_s == filter.to_s }
|
7
|
+
when Regexp then lambda { |e| e.name =~ filter }
|
8
|
+
when Hash then lambda { |e| filter.all? { |k,v| e[k] === v } }
|
9
|
+
else filter
|
10
|
+
end
|
11
|
+
new(filter_predicate, action)
|
12
|
+
end
|
13
|
+
|
14
|
+
def consume(event)
|
15
|
+
action.call(event) if filter.call(event)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/event_stream/version.rb
CHANGED
@@ -2,12 +2,12 @@ require_relative '../test_helper'
|
|
2
2
|
|
3
3
|
class EventStreamTest < Minitest::Should::TestCase
|
4
4
|
|
5
|
-
def pub_sub(
|
5
|
+
def pub_sub(name_or_event, attrs = {}, filter = nil)
|
6
6
|
event = nil
|
7
7
|
EventStream.subscribe(filter) do |e|
|
8
8
|
event = e
|
9
9
|
end
|
10
|
-
EventStream.publish(
|
10
|
+
EventStream.publish(name_or_event, attrs)
|
11
11
|
event
|
12
12
|
end
|
13
13
|
|
@@ -22,6 +22,12 @@ class EventStreamTest < Minitest::Should::TestCase
|
|
22
22
|
assert_equal :test, event.name
|
23
23
|
end
|
24
24
|
|
25
|
+
should 'allow publishing of pre-constructed events' do
|
26
|
+
event = EventStream::Event.new(name: 'test', a: 1)
|
27
|
+
subscribed = pub_sub(event)
|
28
|
+
assert_equal event, subscribed
|
29
|
+
end
|
30
|
+
|
25
31
|
should 'expose all event attributes to the subscriber' do
|
26
32
|
event = pub_sub(:test, :x => 1)
|
27
33
|
assert_equal 1, event.x
|
@@ -51,4 +57,32 @@ class EventStreamTest < Minitest::Should::TestCase
|
|
51
57
|
|
52
58
|
end
|
53
59
|
end
|
60
|
+
|
61
|
+
context 'managing multiple event streams' do
|
62
|
+
setup do
|
63
|
+
@stream = EventStream::Stream.new
|
64
|
+
EventStream.register_stream(:test_stream, @stream)
|
65
|
+
end
|
66
|
+
|
67
|
+
should 'allow streams to be registered and retrieved' do
|
68
|
+
assert_equal @stream, EventStream[:test_stream]
|
69
|
+
end
|
70
|
+
|
71
|
+
should 'allow separate publishes and subscriptions to different streams' do
|
72
|
+
test_event = nil
|
73
|
+
|
74
|
+
EventStream[:test_stream].subscribe(//) do |e|
|
75
|
+
test_event = e
|
76
|
+
end
|
77
|
+
|
78
|
+
EventStream[:test_stream].publish(:test_event)
|
79
|
+
assert test_event, "Event was expected to be published to the test stream"
|
80
|
+
assert_equal test_event.name, :test_event
|
81
|
+
|
82
|
+
test_event = nil
|
83
|
+
|
84
|
+
EventStream.publish(:test_event)
|
85
|
+
refute test_event, "No event should have been published to the test stream"
|
86
|
+
end
|
87
|
+
end
|
54
88
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
module EventStream
|
4
|
+
class EventTest < Minitest::Should::TestCase
|
5
|
+
|
6
|
+
context 'an event' do
|
7
|
+
|
8
|
+
context 'creating and accessing values' do
|
9
|
+
should 'exposed values passed in as a hash' do
|
10
|
+
event = Event.new(a: 1, b: 2)
|
11
|
+
assert_equal 1, event.a
|
12
|
+
assert_equal 2, event.b
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'expose values passed in as string keys' do
|
16
|
+
event = Event.new('a' => 1, b: 2)
|
17
|
+
assert_equal 1, event.a
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'properly respond to existing (but not nonexisting) values' do
|
21
|
+
event = Event.new('a' => 1, b: 2)
|
22
|
+
assert event.respond_to?(:a)
|
23
|
+
assert event.respond_to?(:b)
|
24
|
+
refute event.respond_to?(:c)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context '#to_json' do
|
29
|
+
should 'serialize to json' do
|
30
|
+
event = Event.new(a: 1, b: 2)
|
31
|
+
assert_equal '{"a":1,"b":2}', event.to_json
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context '#from_json' do
|
36
|
+
should 'deserialize from json' do
|
37
|
+
json = '{"a":1,"b":2}'
|
38
|
+
event = Event.from_json(json)
|
39
|
+
assert_equal 1, event.a
|
40
|
+
assert_equal 2, event.b
|
41
|
+
end
|
42
|
+
|
43
|
+
should 'raise an error if json is not valid' do
|
44
|
+
assert_raises(JSON::ParserError) { Event.from_json('not valid') }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: event_stream
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Arron Norwell
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-03-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -94,10 +94,15 @@ files:
|
|
94
94
|
- Rakefile
|
95
95
|
- event_stream.gemspec
|
96
96
|
- lib/event_stream.rb
|
97
|
+
- lib/event_stream/event.rb
|
98
|
+
- lib/event_stream/registry.rb
|
99
|
+
- lib/event_stream/stream.rb
|
100
|
+
- lib/event_stream/subscriber.rb
|
97
101
|
- lib/event_stream/subscriber_dsl.rb
|
98
102
|
- lib/event_stream/test_helper.rb
|
99
103
|
- lib/event_stream/version.rb
|
100
104
|
- test/event_stream/event_stream_test.rb
|
105
|
+
- test/event_stream/event_test.rb
|
101
106
|
- test/event_stream/subscriber_dsl_test.rb
|
102
107
|
- test/event_stream/test_helper_test.rb
|
103
108
|
- test/test_helper.rb
|
@@ -127,7 +132,7 @@ specification_version: 4
|
|
127
132
|
summary: A minimal library for synchronously publishing and subscribing to events.
|
128
133
|
test_files:
|
129
134
|
- test/event_stream/event_stream_test.rb
|
135
|
+
- test/event_stream/event_test.rb
|
130
136
|
- test/event_stream/subscriber_dsl_test.rb
|
131
137
|
- test/event_stream/test_helper_test.rb
|
132
138
|
- test/test_helper.rb
|
133
|
-
has_rdoc:
|