dry-events 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8df0a07c20aa79e2b47d1f8eda583936b6109e09e5307826593e1e8f792c7675
4
+ data.tar.gz: c99dfee0bf7614b62fc70d8ea809d419fcf8fb01400149f83610fd9a9e586019
5
+ SHA512:
6
+ metadata.gz: 3060f0fe13e7cb9199e81e82c82be67bca90a547a0750b094f0afcdcb193aba0866c3c3a7af2e81923dec194bc1828d9fc4ae7fa099c0c87952cba68f77afff2
7
+ data.tar.gz: 33845156cc553098260f562001b3fa0d2aa711c95ee8d056c0f1362eccda69e8b8954040bf0b542b47826a81b813d1a338ced05712e10e36a8770fad4eef867f
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ coverage
3
+ /.bundle
4
+ vendor/bundle
5
+ bin/
6
+ tmp/
7
+ .idea/
8
+ Gemfile.lock
9
+ spec/test_logs/*.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --require spec_helper
3
+ --order random
data/.travis.yml ADDED
@@ -0,0 +1,22 @@
1
+ language: ruby
2
+ cache: bundler
3
+ bundler_args: --without benchmarks console tools
4
+ script: "bundle exec rake ci"
5
+ before_install: gem update --system
6
+ after_success:
7
+ - '[ "${TRAVIS_JOB_NUMBER#*.}" = "1" ] && [ "$TRAVIS_BRANCH" = "master" ] && bundle exec codeclimate-test-reporter'
8
+ rvm:
9
+ - 2.5.0
10
+ - 2.4.3
11
+ - 2.3.6
12
+ - jruby-9.1.9.0
13
+ env:
14
+ global:
15
+ - COVERAGE='true'
16
+ notifications:
17
+ webhooks:
18
+ urls:
19
+ - https://webhooks.gitter.im/e/19098b4253a72c9796db
20
+ on_success: change
21
+ on_failure: always
22
+ on_start: false
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # v0.1.0 to-be-released
2
+
3
+ First public release
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,29 @@
1
+ # Issue Guidelines
2
+
3
+ ## Reporting bugs
4
+
5
+ If you found a bug, report an issue and describe what's the expected behavior versus what actually happens. If the bug causes a crash, attach a full backtrace. If possible, a reproduction script showing the problem is highly appreciated.
6
+
7
+ ## Reporting feature requests
8
+
9
+ Report a feature request **only after discussing it first on [discuss.dry-rb.org](https://discuss.dry-rb.org)** where it was accepted. Please provide a concise description of the feature, don't link to a discussion thread, and instead summarize what was discussed.
10
+
11
+ ## Reporting questions, support requests, ideas, concerns etc.
12
+
13
+ **PLEASE DON'T** - use [discuss.dry-rb.org](http://discuss.dry-rb.org) instead.
14
+
15
+ # Pull Request Guidelines
16
+
17
+ A Pull Request will only be accepted if it addresses a specific issue that was reported previously, or fixes typos, mistakes in documentation etc.
18
+
19
+ Other requirements:
20
+
21
+ 1) Do not open a pull request if you can't provide tests along with it. If you have problems writing tests, ask for help in the related issue.
22
+ 2) Follow the style conventions of the surrounding code. In most cases, this is standard ruby style.
23
+ 3) Add API documentation if it's a new feature
24
+ 4) Update API documentation if it changes an existing feature
25
+ 5) Bonus points for sending a PR to [github.com/dry-rb/dry-rb.org](github.com/dry-rb/dry-rb.org) which updates user documentation and guides
26
+
27
+ # Asking for help
28
+
29
+ If these guidelines aren't helpful, and you're stuck, please post a message on [discuss.dry-rb.org](https://discuss.dry-rb.org).
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'rack'
7
+ gem 'simplecov', platform: :mri, require: false
8
+ gem 'codeclimate-test-reporter', require: false
9
+ end
10
+
11
+ group :tools do
12
+ gem 'byebug', platform: :mri
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 dry-rb team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9
+ the Software, and to permit persons to whom the Software is furnished to do so,
10
+ subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,26 @@
1
+ [gem]: https://rubygems.org/gems/dry-events
2
+ [travis]: https://travis-ci.org/dry-rb/dry-events
3
+ [gemnasium]: https://gemnasium.com/dry-rb/dry-events
4
+ [codeclimate]: https://codeclimate.com/github/dry-rb/dry-events
5
+ [coveralls]: https://coveralls.io/r/dry-rb/dry-events
6
+ [inchpages]: http://inch-ci.org/github/dry-rb/dry-events
7
+
8
+ # dry-events [![Join the chat at https://gitter.im/dry-rb/chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/dry-rb/chat)
9
+
10
+ [![Gem Version](https://badge.fury.io/rb/dry-events.svg)][gem]
11
+ [![Build Status](https://travis-ci.org/dry-rb/dry-events.svg?branch=master)][travis]
12
+ [![Dependency Status](https://gemnasium.com/dry-rb/dry-events.svg)][gemnasium]
13
+ [![Code Climate](https://codeclimate.com/github/dry-rb/dry-events/badges/gpa.svg)][codeclimate]
14
+ [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-events/badges/coverage.svg)][codeclimate]
15
+ [![Inline docs](http://inch-ci.org/github/dry-rb/dry-events.svg?branch=master)][inchpages]
16
+
17
+ Standalone pub/sub system.
18
+
19
+ ## Synopsis
20
+
21
+ ``` ruby
22
+ ```
23
+
24
+ ## License
25
+
26
+ See `LICENSE` file.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+
4
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), 'lib'))
5
+
6
+ require 'rspec/core'
7
+ require 'rspec/core/rake_task'
8
+
9
+ task default: :spec
10
+
11
+ desc 'Run all specs in spec directory'
12
+ RSpec::Core::RakeTask.new(:spec)
13
+
14
+ desc "Run CI tasks"
15
+ task ci: [:spec]
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ require File.expand_path('../lib/dry/events/version', __FILE__)
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'dry-events'
6
+ spec.version = Dry::Events::VERSION
7
+ spec.authors = ['Piotr Solnica']
8
+ spec.email = ['piotr.solnica+oss@gmail.com']
9
+ spec.summary = 'Pub/sub system'
10
+ spec.homepage = 'https://github.com/dry-rb/dry-events'
11
+ spec.license = 'MIT'
12
+
13
+ spec.files = `git ls-files -z`.split("\x0")
14
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
15
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
+ spec.require_paths = ['lib']
17
+
18
+ spec.add_runtime_dependency 'concurrent-ruby', '~> 1.0'
19
+ spec.add_runtime_dependency 'dry-core', '~> 0.4'
20
+ spec.add_runtime_dependency 'dry-equalizer', '~> 0.2'
21
+
22
+ spec.add_development_dependency 'bundler'
23
+ spec.add_development_dependency 'rake'
24
+ spec.add_development_dependency 'rspec'
25
+ end
data/lib/dry-events.rb ADDED
@@ -0,0 +1 @@
1
+ require 'dry/events'
data/lib/dry/events.rb ADDED
@@ -0,0 +1 @@
1
+ require 'dry/events/publisher'
@@ -0,0 +1,82 @@
1
+ require 'dry/events/constants'
2
+
3
+ module Dry
4
+ module Events
5
+ # Event bus
6
+ #
7
+ # An event bus stores listeners (callbacks) and events
8
+ #
9
+ # @api private
10
+ class Bus
11
+ # @!attribute [r] events
12
+ # @return [Hash] A hash with events registered within a bus
13
+ attr_reader :events
14
+
15
+ # @!attribute [r] listeners
16
+ # @return [Hash] A hash with event listeners registered within a bus
17
+ attr_reader :listeners
18
+
19
+ # Initialize a new event bus
20
+ #
21
+ # @param [Symbol] id The bus identifier
22
+ # @param [Hash] events A hash with events
23
+ # @param [Hash] listeners A hash with listeners
24
+ #
25
+ # @api private
26
+ def initialize(events: EMPTY_HASH, listeners: LISTENERS_HASH.dup)
27
+ @listeners = listeners
28
+ @events = events
29
+ end
30
+
31
+ # @api private
32
+ def process(event_id, payload, &block)
33
+ listeners[event_id].each do |(listener, query)|
34
+ event = events[event_id].payload(payload)
35
+
36
+ if event.trigger?(query)
37
+ yield(event, listener)
38
+ end
39
+ end
40
+ end
41
+
42
+ # @api private
43
+ def publish(event_id, payload)
44
+ process(event_id, payload) do |event, listener|
45
+ listener.(event)
46
+ end
47
+ end
48
+
49
+ # @api private
50
+ def attach(listener, query)
51
+ events.each do |id, event|
52
+ meth = event.listener_method
53
+
54
+ if listener.respond_to?(meth)
55
+ listeners[id] << [listener.method(meth), query]
56
+ end
57
+ end
58
+ end
59
+
60
+ # @api private
61
+ def detach(listener)
62
+ listeners.each do |id, memo|
63
+ memo.each do |tuple|
64
+ current_listener, _ = tuple
65
+ listeners[id].delete(tuple) if current_listener.receiver.equal?(listener)
66
+ end
67
+ end
68
+ end
69
+
70
+ # @api private
71
+ def subscribe(event_id, query, &block)
72
+ listeners[event_id] << [block, query]
73
+ self
74
+ end
75
+
76
+ # @api private
77
+ def subscribed?(listener)
78
+ listeners.values.any? { |value| value.any? { |(block, _)| block.equal?(listener) } }
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,10 @@
1
+ require 'concurrent/map'
2
+ require 'dry/core/constants'
3
+
4
+ module Dry
5
+ module Events
6
+ include Dry::Core::Constants
7
+
8
+ LISTENERS_HASH = Concurrent::Map.new { |h, k| h[k] = [] }
9
+ end
10
+ end
@@ -0,0 +1,81 @@
1
+ require 'dry/equalizer'
2
+ require 'dry/events/constants'
3
+
4
+ module Dry
5
+ module Events
6
+ # Event object
7
+ #
8
+ # @api public
9
+ class Event
10
+ include Dry::Equalizer(:id, :payload)
11
+
12
+ DOT = '.'.freeze
13
+ UNDERSCORE = '_'.freeze
14
+
15
+ # @!attribute [r] id
16
+ # @return [Symbol] The event identifier
17
+ attr_reader :id
18
+
19
+ # Initialize a new event
20
+ #
21
+ # @param [Symbol] id The event identifier
22
+ # @param [Hash] payload Optional payload
23
+ #
24
+ # @return [Event]
25
+ #
26
+ # @api private
27
+ def initialize(id, payload = EMPTY_HASH)
28
+ @id = id
29
+ @payload = payload
30
+ end
31
+
32
+ # Get data from the payload
33
+ #
34
+ # @param [String,Symbol] name
35
+ #
36
+ # @api public
37
+ def [](name)
38
+ @payload.fetch(name)
39
+ end
40
+
41
+ # Coerce an event to a hash
42
+ #
43
+ # @return [Hash]
44
+ #
45
+ # @api public
46
+ def to_h
47
+ @payload
48
+ end
49
+ alias_method :to_hash, :to_h
50
+
51
+ # Get or set a payload
52
+ #
53
+ # @overload
54
+ # @return [Hash] payload
55
+ #
56
+ # @overload payload(data)
57
+ # @param [Hash] data A new payload
58
+ # @return [Event] A copy of the event with the provided payload
59
+ #
60
+ # @api public
61
+ def payload(data = nil)
62
+ if data
63
+ self.class.new(id, @payload.merge(data))
64
+ else
65
+ @payload
66
+ end
67
+ end
68
+
69
+ # @api private
70
+ def trigger?(query)
71
+ query.empty? || query.all? { |key, value| payload[key] == value }
72
+ end
73
+
74
+ # @api private
75
+ def listener_method
76
+ @listener_method ||= :"on_#{id.to_s.gsub(DOT, UNDERSCORE)}"
77
+ end
78
+ end
79
+ end
80
+ end
81
+
@@ -0,0 +1,57 @@
1
+ require 'dry/equalizer'
2
+ require 'dry/events/publisher'
3
+
4
+ module Dry
5
+ module Events
6
+ # Extension for objects that can listen to events
7
+ #
8
+ # @example
9
+ # class AppEvents
10
+ # include Dry::Events::Publisher[:app]
11
+ #
12
+ # register_event("users.created")
13
+ # end
14
+
15
+ # class MyListener
16
+ # include Dry::Events::Listener[:app]
17
+ #
18
+ # subscribe("users.created") do |event|
19
+ # # do something
20
+ # end
21
+ # end
22
+ #
23
+ # @api public
24
+ class Listener < Module
25
+ include Dry::Equalizer(:id)
26
+
27
+ # @!attribute [r] :id
28
+ # @return [Symbol,String] The publisher identifier
29
+ # @api private
30
+ attr_reader :id
31
+
32
+ # Create a listener extension for a specific publisher
33
+ #
34
+ # @return [Module]
35
+ #
36
+ # @api public
37
+ def self.[](id)
38
+ new(id)
39
+ end
40
+
41
+ # @api private
42
+ def initialize(id)
43
+ @id = id
44
+
45
+ define_method(:subscribe) do |event_id, query = EMPTY_HASH, &block|
46
+ Publisher.registry[id].subscribe(event_id, query, &block)
47
+ end
48
+ end
49
+
50
+ # @api private
51
+ def included(klass)
52
+ klass.extend(self)
53
+ super
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,242 @@
1
+ require 'concurrent/map'
2
+
3
+ require 'dry/core/class_attributes'
4
+
5
+ require 'dry/events/constants'
6
+ require 'dry/events/event'
7
+ require 'dry/events/bus'
8
+
9
+ module Dry
10
+ module Events
11
+ # Exception raised when the same publisher is registered more than once
12
+ #
13
+ # @api public
14
+ PublisherAlreadyRegisteredError = Class.new(StandardError) do
15
+ # @api private
16
+ def initialize(id)
17
+ super("publisher with id #{id.inspect} already registered as: #{Publisher.registry[id]}")
18
+ end
19
+ end
20
+
21
+ # Extension used for classes that can publish events
22
+ #
23
+ # @example
24
+ # class AppEvents
25
+ # include Dry::Events::Publisher[:app]
26
+ #
27
+ # register_event('users.created')
28
+ # end
29
+ #
30
+ # class CreateUser
31
+ # attr_reader :events
32
+ #
33
+ # def initialize(events)
34
+ # @events = events
35
+ # end
36
+ #
37
+ # def call(user)
38
+ # # do your thing
39
+ # events.publish('users.created', user: user, time: Time.now)
40
+ # end
41
+ # end
42
+ #
43
+ # app_events = AppEvents.new
44
+ # create_user = CreateUser.new(app_events)
45
+ #
46
+ # # this will publish "users.created" event with its payload
47
+ # create_user.call(name: "Jane")
48
+ #
49
+ # @api public
50
+ class Publisher < Module
51
+ include Dry::Equalizer(:id)
52
+
53
+ # Internal publisher registry, which is used to identify them globally
54
+ #
55
+ # This allows us to have listener classes that can subscribe to events
56
+ # without having access to instances of publishers yet.
57
+ #
58
+ # @api private
59
+ def self.registry
60
+ @__registry__ ||= Concurrent::Map.new
61
+ end
62
+
63
+ # @!attribute [r] :id
64
+ # @return [Symbol,String] the publisher identifier
65
+ # @api private
66
+ attr_reader :id
67
+
68
+ # Create a publisher extension with the provided identifier
69
+ #
70
+ # @param [Symbol,String] id The identifier
71
+ #
72
+ # @return [Publisher]
73
+ #
74
+ # @raise PublisherAlreadyRegisteredError
75
+ #
76
+ # @api public
77
+ def self.[](id)
78
+ raise PublisherAlreadyRegisteredError.new(id) if registry.key?(id)
79
+ new(id)
80
+ end
81
+
82
+ # @api private
83
+ def initialize(id)
84
+ @id = id
85
+ end
86
+
87
+ # Hook for inclusions/extensions
88
+ #
89
+ # It registers the publisher class under global registry using the id
90
+ #
91
+ # @api private
92
+ def included(klass)
93
+ klass.extend(ClassMethods)
94
+ klass.include(InstanceMethods)
95
+
96
+ self.class.registry[id] = klass
97
+
98
+ super
99
+ end
100
+
101
+ # Class interface for publisher classes
102
+ #
103
+ # @api public
104
+ module ClassMethods
105
+ # Register an event
106
+ #
107
+ # @param [String] event_id The event identifier
108
+ # @param [Hash] payload Optional default payload
109
+ #
110
+ # @api public
111
+ def register_event(event_id, payload = EMPTY_HASH)
112
+ events[event_id] = Event.new(event_id, payload)
113
+ self
114
+ end
115
+
116
+ # Subscribe to an event
117
+ #
118
+ # @param [Symbol,String] event_id The event identifier
119
+ # @param [Hash] query An optional query for conditional listeners
120
+ #
121
+ # @return [Class] publisher class
122
+ #
123
+ # @api public
124
+ def subscribe(event_id, query = EMPTY_HASH, &block)
125
+ listeners[event_id] << [block, query]
126
+ self
127
+ end
128
+
129
+ # Sets up event bus for publisher instances
130
+ #
131
+ # @return [Bus]
132
+ #
133
+ # @api private
134
+ def new_bus
135
+ Bus.new(events: events.dup, listeners: listeners.dup)
136
+ end
137
+
138
+ # Global registry with events
139
+ #
140
+ # @api private
141
+ def events
142
+ @__events__ ||= Concurrent::Map.new
143
+ end
144
+
145
+ # Global registry with listeners
146
+ #
147
+ # @api private
148
+ def listeners
149
+ @__listeners__ ||= LISTENERS_HASH.dup
150
+ end
151
+ end
152
+
153
+ # Instance interface for publishers
154
+ #
155
+ # @api public
156
+ module InstanceMethods
157
+ # Register a new event type at instance level
158
+ #
159
+ # @param [Symbol,String] event_id The event identifier
160
+ # @param [Hash] payload Optional default payload
161
+ #
162
+ # @return [self]
163
+ #
164
+ # @api public
165
+ def register_event(event_id, payload = EMPTY_HASH)
166
+ __bus__.events[event_id] = Event.new(event_id, payload)
167
+ self
168
+ end
169
+ # Publish an event
170
+ #
171
+ # @param [String] event_id The event identifier
172
+ # @param [Hash] payload An optional payload
173
+ #
174
+ # @api public
175
+ def publish(event_id, payload = EMPTY_HASH)
176
+ __bus__.publish(event_id, payload)
177
+ self
178
+ end
179
+ alias_method :trigger, :publish
180
+
181
+ # Subscribe to events.
182
+ #
183
+ # If the query parameter is provided, filters events by payload.
184
+ #
185
+ # @param [Symbol,String,Object] object_or_event_id The event identifier or a listener object
186
+ # @param [Hash] query An optional event filter
187
+ #
188
+ # @return [Object] self
189
+ #
190
+ # @api public
191
+ def subscribe(object_or_event_id, query = EMPTY_HASH, &block)
192
+ if block
193
+ __bus__.subscribe(object_or_event_id, query, &block)
194
+ else
195
+ __bus__.attach(object_or_event_id, query)
196
+ end
197
+ self
198
+ end
199
+
200
+ # Unsubscribe a listener
201
+ #
202
+ # @param [Object] listener The listener object
203
+ #
204
+ # @return [self]
205
+ #
206
+ # @api public
207
+ def unsubscribe(listener)
208
+ __bus__.detach(listener)
209
+ end
210
+
211
+ # Return true if a given listener has been subscribed to any event
212
+ #
213
+ # @api public
214
+ def subscribed?(listener)
215
+ __bus__.subscribed?(listener)
216
+ end
217
+
218
+ # Utility method which yields event with each of its listeners
219
+ #
220
+ # Listeners are already filtered out when query was provided during
221
+ # subscription
222
+ #
223
+ # @param [Symbol,String] event_id The event identifier
224
+ # param [Hash] payload An optional payload
225
+ #
226
+ # @api public
227
+ def process(event_id, payload = EMPTY_HASH, &block)
228
+ __bus__.process(event_id, payload, &block)
229
+ end
230
+
231
+ # Internal event bus
232
+ #
233
+ # @return [Bus]
234
+ #
235
+ # @api private
236
+ def __bus__
237
+ @__bus__ ||= self.class.new_bus
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,5 @@
1
+ module Dry
2
+ module Events
3
+ VERSION = '0.1.0'.freeze
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ if RUBY_ENGINE == 'ruby' && ENV['COVERAGE'] == 'true'
2
+ require 'simplecov'
3
+ SimpleCov.start do
4
+ add_filter '/spec/'
5
+ end
6
+ end
7
+
8
+ begin
9
+ require 'byebug'
10
+ rescue LoadError; end
11
+
12
+ require 'dry-events'
13
+
14
+ SPEC_ROOT = Pathname(__dir__)
15
+
16
+ Dir[SPEC_ROOT.join('shared/**/*.rb')].each(&method(:require))
17
+ Dir[SPEC_ROOT.join('support/**/*.rb')].each(&method(:require))
18
+
19
+ RSpec.configure do |config|
20
+ config.disable_monkey_patching!
21
+
22
+ config.after(:example) do
23
+ Dry::Events::Publisher.instance_variable_set(:@__registry__, Concurrent::Map.new)
24
+ end
25
+ end
@@ -0,0 +1,31 @@
1
+ require 'dry/events/listener'
2
+
3
+ RSpec.describe Dry::Events::Listener do
4
+ subject(:listener) do
5
+ Class.new {
6
+ include Dry::Events::Listener[:test_publisher]
7
+ }
8
+ end
9
+
10
+ let!(:publisher) do
11
+ Class.new {
12
+ include Dry::Events::Publisher[:test_publisher]
13
+
14
+ register_event :test_event
15
+ }.new
16
+ end
17
+
18
+ describe '.subscribe' do
19
+ it 'subscribes a listener at class level' do
20
+ result = []
21
+
22
+ listener.subscribe(:test_event) do |event|
23
+ result << event.id
24
+ end
25
+
26
+ publisher.publish(:test_event)
27
+
28
+ expect(result).to eql([:test_event])
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,125 @@
1
+ require 'dry/events/publisher'
2
+
3
+ RSpec.describe Dry::Events::Publisher do
4
+ subject(:publisher) do
5
+ Class.new {
6
+ include Dry::Events::Publisher[:test_publisher]
7
+
8
+ register_event :test_event
9
+ }.new
10
+ end
11
+
12
+ describe '.[]' do
13
+ it 'creates a publisher extension with provided id' do
14
+ publisher = Class.new do
15
+ include Dry::Events::Publisher[:my_publisher]
16
+ end
17
+
18
+ expect(Dry::Events::Publisher.registry[:my_publisher]).to be(publisher)
19
+ end
20
+
21
+ it 'does not allow same id to be used for than once' do
22
+ create_publisher = -> do
23
+ Class.new do
24
+ include Dry::Events::Publisher[:my_publisher]
25
+ end
26
+ end
27
+
28
+ create_publisher.()
29
+
30
+ expect { create_publisher.() }.to raise_error(Dry::Events::PublisherAlreadyRegisteredError, /my_publisher/)
31
+ end
32
+ end
33
+
34
+ describe '.subscribe' do
35
+ it 'subscribes a listener at class level' do
36
+ listener = -> * { }
37
+
38
+ publisher.class.subscribe(:test_event, &listener)
39
+
40
+ expect(publisher.subscribed?(listener)).to be(true)
41
+ end
42
+ end
43
+
44
+ describe '#register_event' do
45
+ it 'registers a new event at instance level' do
46
+ listener = -> * { }
47
+
48
+ publisher.register_event(:test_another_event).subscribe(:test_another_event, &listener)
49
+
50
+ expect(publisher.subscribed?(listener)).to be(true)
51
+ end
52
+ end
53
+
54
+ describe '#subscribe' do
55
+ it 'subscribes a listener function' do
56
+ listener = -> * { }
57
+
58
+ publisher.subscribe(:test_event, &listener)
59
+
60
+ expect(publisher.subscribed?(listener)).to be(true)
61
+ end
62
+
63
+ it 'subscribes a listener object' do
64
+ listener = Class.new do
65
+ attr_reader :captured
66
+
67
+ def initialize
68
+ @captured = []
69
+ end
70
+
71
+ def on_test_event(event)
72
+ captured << event[:message]
73
+ end
74
+ end.new
75
+
76
+ publisher.subscribe(listener).publish(:test_event, message: 'it works')
77
+
78
+ expect(listener.captured).to eql(['it works'])
79
+
80
+ publisher.unsubscribe(listener)
81
+
82
+ publisher.publish(:test_event, message: 'it works')
83
+
84
+ expect(listener.captured).to eql(['it works'])
85
+ end
86
+ end
87
+
88
+ describe '#publish' do
89
+ it 'publishes an event' do
90
+ result = []
91
+ listener = -> event { result << event[:message] }
92
+
93
+ publisher.subscribe(:test_event, &listener).publish(:test_event, message: 'it works')
94
+
95
+ expect(result).to eql(['it works'])
96
+ end
97
+
98
+ it 'publishes an event filtered by a query' do
99
+ result = []
100
+ listener = -> test: { result << test }
101
+
102
+ publisher.
103
+ subscribe(:test_event, test: true, &listener).
104
+ publish(:test_event, test: false).
105
+ publish(:test_event, test: true)
106
+
107
+ expect(result).to eql([true])
108
+ end
109
+ end
110
+
111
+ describe '#process' do
112
+ it 'yields event and its listeners' do
113
+ result = []
114
+ listener = -> event { result << event.id }
115
+
116
+ publisher.subscribe(:test_event, &listener)
117
+
118
+ publisher.process(:test_event) do |event, listener|
119
+ listener.(event)
120
+ end
121
+
122
+ expect(result).to eql([:test_event])
123
+ end
124
+ end
125
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dry-events
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Piotr Solnica
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: concurrent-ruby
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-core
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: dry-equalizer
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
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: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - piotr.solnica+oss@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rspec"
106
+ - ".travis.yml"
107
+ - CHANGELOG.md
108
+ - CONTRIBUTING.md
109
+ - Gemfile
110
+ - LICENSE
111
+ - README.md
112
+ - Rakefile
113
+ - dry-events.gemspec
114
+ - lib/dry-events.rb
115
+ - lib/dry/events.rb
116
+ - lib/dry/events/bus.rb
117
+ - lib/dry/events/constants.rb
118
+ - lib/dry/events/event.rb
119
+ - lib/dry/events/listener.rb
120
+ - lib/dry/events/publisher.rb
121
+ - lib/dry/events/version.rb
122
+ - spec/spec_helper.rb
123
+ - spec/unit/dry/events/listener_spec.rb
124
+ - spec/unit/dry/events/publisher_spec.rb
125
+ homepage: https://github.com/dry-rb/dry-events
126
+ licenses:
127
+ - MIT
128
+ metadata: {}
129
+ post_install_message:
130
+ rdoc_options: []
131
+ require_paths:
132
+ - lib
133
+ required_ruby_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ required_rubygems_version: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - ">="
141
+ - !ruby/object:Gem::Version
142
+ version: '0'
143
+ requirements: []
144
+ rubyforge_project:
145
+ rubygems_version: 2.7.3
146
+ signing_key:
147
+ specification_version: 4
148
+ summary: Pub/sub system
149
+ test_files:
150
+ - spec/spec_helper.rb
151
+ - spec/unit/dry/events/listener_spec.rb
152
+ - spec/unit/dry/events/publisher_spec.rb