event_stream 0.2.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: