event_stream 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4eb7f64821b9716f61474ae0fbeb45933544a449
4
+ data.tar.gz: 7b2dff88c4593999dffab1121a20cda3bafaaedf
5
+ SHA512:
6
+ metadata.gz: 63b1d01de337fcedd119ec1b89435bb0153c886297ef1958420cc6c395571333466781876a71360233113eee119a0ee8d63c9d7417904b492d3c98dfff822970
7
+ data.tar.gz: 81f0a46e4dcf4e4dca4637d8c5b0f78cfcae07ac0e0bb5b90e26de4d91a9590f606c00b25005ad1b18a364822abdf5907c7a1f52a646806c0108dbd10e911e03
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in event_stream.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem "pry"
8
+ gem "awesome_print"
9
+ gem 'm', :git => 'git@github.com:ANorwell/m.git', :branch => 'minitest_5'
10
+ end
11
+
12
+ group :test do
13
+ gem 'minitest_should', :git => 'git@github.com:citrus/minitest_should.git'
14
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Datto
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,99 @@
1
+ # EventStream
2
+
3
+ A minimal library for synchronously publishing and subscribing to events.
4
+
5
+ ## Usage
6
+
7
+ Create an event subscription:
8
+
9
+ ```ruby
10
+ EventStream.subscribe(:my_event) do |event|
11
+ log("#{event.name}, #{event.description}")
12
+ end
13
+ ```
14
+ Then fire an event:
15
+
16
+ ```ruby
17
+ EventStream.publish(:my_event, :description => "An example event.")
18
+ ```
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.
21
+
22
+ Events can be subscribed to by name, as above, but many other ways are supported:
23
+
24
+ ```ruby
25
+ # Subscribe to all events
26
+ EventStream.subscribe { |e| ... }
27
+
28
+ # Subscribe to events with a given name
29
+ EventStream.subscribe(:my_event) { |e| ... }
30
+
31
+ # Subscribe to events based on a name regex
32
+ EventStream.subscribe(/my/) { |e| ... }
33
+
34
+ # Subscribe to events based on their attributes
35
+ EventStream.subscribe(:user_id => 1) { |e| ... }
36
+
37
+ # Subscribe to events based on an arbitrary filter
38
+ filter = lambda { |e| e.size > 3 }
39
+ EventStream.subscribe(filter) { |e| ... }
40
+ ```
41
+
42
+ ### Event Streams
43
+
44
+ If you wish, you may create more than one event stream:
45
+
46
+ ```ruby
47
+ stream = EventStream::Stream.new
48
+ stream.subscribe(...) { |e| ... }
49
+ stream.publish(...)
50
+ ```
51
+
52
+ ### Subscriber DSL
53
+
54
+ It's sometimes useful to separate the definition of a subscriber action
55
+ from the registration of the subscriber. The `SubscriberDSL` module
56
+ provides a way to do this.
57
+
58
+ Define a subscriber by mixing in the module:
59
+
60
+ ```ruby
61
+ class MySubscriber
62
+ include EventStream::SubscriberDSL
63
+
64
+ # Which event_stream to use. If not specified, the default will be used.
65
+ event_stream EventStream.default_stream
66
+
67
+ # Sets up a subscriber using a block
68
+ on(:my_event) { |event| puts event.name }
69
+ end
70
+ ```
71
+
72
+ The definition of this class does NOT subscribe to any events. To subscribe
73
+ to the :my_event event, it is necessary to call `MySubscriber.subscribe`.
74
+
75
+ It is possible to use multiple `on` statements in a single class.
76
+ `MySubscriber.subscribe` will register all subscriptions.
77
+
78
+ ## Application Testing
79
+
80
+ event_stream includes a test_helper module that provides some assertions, like `assert_event_published`. To use:
81
+ 1. `require 'event_stream/test_helper'`
82
+ 2. Call `EventStream::Assertions.setup_test_subscription` in the setup block of your tests
83
+ 3. Include the `EventStream::Assertions` module where needed
84
+
85
+ ## Installation
86
+
87
+ Add this line to your application's Gemfile:
88
+
89
+ ```ruby
90
+ gem 'event_stream', :git => 'git@github.com:backupify/event_stream.git'
91
+ ```
92
+
93
+ And then execute:
94
+
95
+ $ bundle
96
+
97
+ Or install it yourself as:
98
+
99
+ $ gem install event_stream
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.pattern = 'test/event_stream/**/*_test.rb'
7
+ t.libs.push 'test'
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'event_stream/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "event_stream"
8
+ spec.version = EventStream::VERSION
9
+ spec.authors = ["Arron Norwell"]
10
+ spec.email = ["anorwell@datto.com"]
11
+ spec.summary = %q{A minimal library for synchronously publishing and subscribing to events.}
12
+ spec.homepage = ""
13
+ spec.license = "MIT"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.7"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "minitest"
23
+ spec.add_development_dependency "minitest-reporters"
24
+
25
+ spec.add_dependency 'activesupport'
26
+ end
@@ -0,0 +1,79 @@
1
+ require 'ostruct'
2
+ require_relative 'event_stream/subscriber_dsl'
3
+
4
+ module EventStream
5
+ class << self
6
+ extend Forwardable
7
+
8
+ # The default event stream
9
+ # @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 = []
48
+ end
49
+
50
+ # Returns all subscribers for this stream
51
+ # @return [Array<EventStream::Subscriber]
52
+ def subscribers
53
+ @subscribers
54
+ end
55
+
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)
73
+ end
74
+
75
+ def consume(event)
76
+ action.call(event) if filter.call(event)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,49 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/class/attribute'
3
+
4
+ module EventStream
5
+ # Provides a DSL with which to create Subscribers.
6
+ # For example:
7
+ #
8
+ # class MySubscriber
9
+ # include EventStream::SubscriberDSL
10
+ #
11
+ # # Which event_stream to use. If not specified, the default will be used.
12
+ # event_stream EventStream.default_stream
13
+ #
14
+ # # Sets up a subscriber using a block
15
+ # on(:my_other_event) { |event| puts event.name }
16
+ # end
17
+ #
18
+ # Note that this does NOT register subscribers. To register subscribers, call:
19
+ #
20
+ # MySubscriber.subscribe
21
+ #
22
+ # This registers all subscribers to the provided event_stream (or to the default).
23
+ #
24
+ module SubscriberDSL
25
+ extend ActiveSupport::Concern
26
+
27
+ included do
28
+ class_attribute :_event_subscribers
29
+ class_attribute :_event_stream
30
+ attr_reader :event
31
+ self._event_subscribers = []
32
+ self._event_stream = EventStream.default_stream
33
+ end
34
+
35
+ module ClassMethods
36
+ def event_stream(event_stream)
37
+ self._event_stream = event_stream
38
+ end
39
+
40
+ def on(filter, &action)
41
+ self._event_subscribers << Subscriber.create(filter, &action)
42
+ end
43
+
44
+ def subscribe
45
+ _event_subscribers.each { |subscriber| _event_stream.add_subscriber(subscriber) }
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,34 @@
1
+ module EventStream
2
+
3
+ module TestEventStream
4
+ class << self
5
+ attr_accessor :events
6
+ end
7
+ end
8
+
9
+ module Assertions
10
+ def self.setup_test_subscription
11
+ TestEventStream.events = []
12
+
13
+ EventStream.subscribe(//) do |event|
14
+ TestEventStream.events << event
15
+ end
16
+ end
17
+
18
+ def assert_event_matching(message = nil, &predicate)
19
+ if TestEventStream.events.nil?
20
+ raise "Call EventStream::TestHelper.setup prior to using event_stream test assertions!"
21
+ end
22
+
23
+ assert TestEventStream.events.any?(&predicate), "Event stream did not include a matching event: #{message}"
24
+ end
25
+
26
+ def assert_event_published(event_name)
27
+ assert_event_matching("No event with name #{event_name}") { |event| event.name == event_name }
28
+ end
29
+
30
+ def find_published_event(&predicate)
31
+ TestEventStream.events.find(&predicate)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module EventStream
2
+ VERSION = "0.2.0"
3
+ end
@@ -0,0 +1,54 @@
1
+ require_relative '../test_helper'
2
+
3
+ class EventStreamTest < Minitest::Should::TestCase
4
+
5
+ def pub_sub(name, attrs = {}, filter = nil)
6
+ event = nil
7
+ EventStream.subscribe(filter) do |e|
8
+ event = e
9
+ end
10
+ EventStream.publish(name, attrs)
11
+ event
12
+ end
13
+
14
+ teardown do
15
+ EventStream.default_stream.clear_subscribers
16
+ end
17
+
18
+ context 'an event stream' do
19
+ should 'publish an event and allow a subscriber to consume it' do
20
+ event = pub_sub(:test)
21
+ assert event
22
+ assert_equal :test, event.name
23
+ end
24
+
25
+ should 'expose all event attributes to the subscriber' do
26
+ event = pub_sub(:test, :x => 1)
27
+ assert_equal 1, event.x
28
+ end
29
+
30
+ context 'filtering events' do
31
+ should 'allow subscription to event names' do
32
+ assert pub_sub(:test, {}, :test)
33
+ refute pub_sub(:test, {}, :other_name)
34
+ end
35
+
36
+ should 'allow subscription to event names by regex' do
37
+ assert pub_sub(:test_event, {}, /test/)
38
+ refute pub_sub(:test_event, {}, /no_match/)
39
+ end
40
+
41
+ should 'allow subscription by event attributes' do
42
+ assert pub_sub(:test, { :x => 1, :y => :attr}, :y => :attr)
43
+ refute pub_sub(:test, { :x => 1, :y => :other}, :y => :attr)
44
+ end
45
+
46
+ should 'allow subscription via arbitrary predicate' do
47
+ predicate = lambda { |e| e.x > 1 }
48
+ assert pub_sub(:test, { :x => 2, :y => :attr}, predicate)
49
+ refute pub_sub(:test, { :x => 1, :y => :attr}, predicate)
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,54 @@
1
+ require_relative '../test_helper'
2
+
3
+ module EventStream
4
+ class SubscriberDSLTest < Minitest::Should::TestCase
5
+
6
+ class MySubscriber
7
+ include SubscriberDSL
8
+
9
+ class << self
10
+ attr_accessor :events
11
+ end
12
+
13
+ on(:event) { |event| self.events << event }
14
+
15
+ def a_method(event)
16
+ self.class.events << event
17
+ end
18
+ end
19
+
20
+ context 'The Subscriber DSL' do
21
+ setup do
22
+ MySubscriber.events = []
23
+ end
24
+
25
+ context 'the default stream' do
26
+ setup do
27
+ EventStream.clear_subscribers
28
+ MySubscriber.event_stream(EventStream.default_stream)
29
+ MySubscriber.subscribe
30
+ end
31
+
32
+ should 'handle a published event' do
33
+ EventStream.publish(:event, test: true)
34
+ assert_equal 1, MySubscriber.events.count
35
+ end
36
+ end
37
+
38
+ context 'other event streams' do
39
+ setup do
40
+ @stream = EventStream::Stream.new
41
+ MySubscriber.event_stream(@stream)
42
+
43
+ @stream.clear_subscribers
44
+ MySubscriber.subscribe
45
+ end
46
+
47
+ should 'handle a published event' do
48
+ @stream.publish(:event, test: true)
49
+ assert_equal 1, MySubscriber.events.count
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,42 @@
1
+ require_relative '../test_helper'
2
+ require 'event_stream/test_helper'
3
+
4
+ module EventStream
5
+ class TestHelperTest < Minitest::Should::TestCase
6
+ include Assertions
7
+
8
+ setup do
9
+ EventStream.clear_subscribers
10
+ Assertions.setup_test_subscription
11
+ end
12
+
13
+ context '#assert_event_published' do
14
+ should 'raise no error when an event has been published' do
15
+ EventStream.publish(:test_event)
16
+ assert_event_published(:test_event)
17
+ end
18
+
19
+ should 'raise an error if the event has not been published' do
20
+ assert_raises(Minitest::Assertion) { assert_event_published(:test_event) }
21
+ end
22
+ end
23
+
24
+ context 'registering the test subscription' do
25
+ should 'not register multiple subscriptions' do
26
+ assert_equal 1, EventStream.default_stream.subscribers.length
27
+ end
28
+ end
29
+
30
+ context '#find_published_event' do
31
+ should 'find an event if one is present' do
32
+ EventStream.publish(:test_event, key: :val)
33
+ assert find_published_event { |evt| evt.key == :val }
34
+ end
35
+
36
+ should 'not find an event if it does not match' do
37
+ EventStream.publish(:test_event, key: :other)
38
+ refute find_published_event { |evt| evt.key == :val }
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'pry'
3
+ require 'minitest/autorun'
4
+ require 'minitest/should'
5
+
6
+
7
+ require 'event_stream'
8
+
9
+ class Minitest::Should::TestCase
10
+ def self.xshould(*args)
11
+ puts "Disabled test: #{args}"
12
+ end
13
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: event_stream
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Arron Norwell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-01-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest-reporters
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ description:
84
+ email:
85
+ - anorwell@datto.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - Gemfile
92
+ - LICENSE.txt
93
+ - README.md
94
+ - Rakefile
95
+ - event_stream.gemspec
96
+ - lib/event_stream.rb
97
+ - lib/event_stream/subscriber_dsl.rb
98
+ - lib/event_stream/test_helper.rb
99
+ - lib/event_stream/version.rb
100
+ - test/event_stream/event_stream_test.rb
101
+ - test/event_stream/subscriber_dsl_test.rb
102
+ - test/event_stream/test_helper_test.rb
103
+ - test/test_helper.rb
104
+ homepage: ''
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.2.2
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: A minimal library for synchronously publishing and subscribing to events.
128
+ test_files:
129
+ - test/event_stream/event_stream_test.rb
130
+ - test/event_stream/subscriber_dsl_test.rb
131
+ - test/event_stream/test_helper_test.rb
132
+ - test/test_helper.rb
133
+ has_rdoc: