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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4eb7f64821b9716f61474ae0fbeb45933544a449
4
- data.tar.gz: 7b2dff88c4593999dffab1121a20cda3bafaaedf
3
+ metadata.gz: 283f487e987c22198cfa937ac6a34f1c4492ee9a
4
+ data.tar.gz: 5eee28a869ccf165a4960d7fe0213974e790753b
5
5
  SHA512:
6
- metadata.gz: 63b1d01de337fcedd119ec1b89435bb0153c886297ef1958420cc6c395571333466781876a71360233113eee119a0ee8d63c9d7417904b492d3c98dfff822970
7
- data.tar.gz: 81f0a46e4dcf4e4dca4637d8c5b0f78cfcae07ac0e0bb5b90e26de4d91a9590f606c00b25005ad1b18a364822abdf5907c7a1f52a646806c0108dbd10e911e03
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 OpenStruct with a name and a bundle of other attributes, so the use of `:description` here is arbitrary.
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
@@ -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
- # The default event stream
12
+ # Returns the stream for a stream name from the stream registry.
13
+ # @param stream_name [Symbol]
9
14
  # @return [Stream]
10
- def default_stream
11
- @default_stream ||= Stream.new
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
- # Returns all subscribers for this stream
51
- # @return [Array<EventStream::Subscriber]
52
- def subscribers
53
- @subscribers
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
- # Adds a subscriber to this stream
57
- # @param [EventStream::Subscriber]
58
- def add_subscriber(subscriber)
59
- @subscribers << subscriber
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
- def consume(event)
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
@@ -1,3 +1,3 @@
1
1
  module EventStream
2
- VERSION = "0.2.0"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -2,12 +2,12 @@ require_relative '../test_helper'
2
2
 
3
3
  class EventStreamTest < Minitest::Should::TestCase
4
4
 
5
- def pub_sub(name, attrs = {}, filter = nil)
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(name, attrs)
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.2.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-01-15 00:00:00.000000000 Z
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: