dry-events 0.1.0 → 0.1.1

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
  SHA256:
3
- metadata.gz: 8df0a07c20aa79e2b47d1f8eda583936b6109e09e5307826593e1e8f792c7675
4
- data.tar.gz: c99dfee0bf7614b62fc70d8ea809d419fcf8fb01400149f83610fd9a9e586019
3
+ metadata.gz: f18471031f2c5dcd9d2ea488ab48f5d42d9bd44de9158241dbb43324dd6fc0b4
4
+ data.tar.gz: '01160358743e52191c19bfc8b3a0550b4a201967f5fe96a8c60dfe50b560b2e1'
5
5
  SHA512:
6
- metadata.gz: 3060f0fe13e7cb9199e81e82c82be67bca90a547a0750b094f0afcdcb193aba0866c3c3a7af2e81923dec194bc1828d9fc4ae7fa099c0c87952cba68f77afff2
7
- data.tar.gz: 33845156cc553098260f562001b3fa0d2aa711c95ee8d056c0f1362eccda69e8b8954040bf0b542b47826a81b813d1a338ced05712e10e36a8770fad4eef867f
6
+ metadata.gz: d5d2e2a9920e5fe2241d5ae0cf42337169f4836353475aeb1bf4d8e621eac86b33143440a160ba5addf0b3cae548772f02c1b42bd86d88283e466835714f42bd
7
+ data.tar.gz: 6b4b8cdc169c69523dc8ba29362113d4fd7b127d3532f80572bf687c44b07c02fbe95788d2356a1316b363e54fab1ad8db3cd4801823aecce0f59926f9ec5e9b
@@ -10,6 +10,10 @@ rvm:
10
10
  - 2.4.3
11
11
  - 2.3.6
12
12
  - jruby-9.1.9.0
13
+ - truffleruby
14
+ matrix:
15
+ allow_failures:
16
+ - rvm: truffleruby
13
17
  env:
14
18
  global:
15
19
  - COVERAGE='true'
@@ -1,3 +1,33 @@
1
- # v0.1.0 to-be-released
1
+ # v0.1.1 2019-03-22
2
+
3
+ ## Added
4
+
5
+ - Subscription filters can be more complex: nested hash inclusion, array inclusion, and proc checks were added (flash-gordon)
6
+ ```ruby
7
+ # nested hash check
8
+ subscribe(:event, logger: { level: :info })
9
+ # pass
10
+ trigger(:event, logger: { level: :info, output: :stdin })
11
+ # filtered out
12
+ trigger(:event, logger: { level: :debug })
13
+ trigger(:event, something: :else)
14
+
15
+ # array inclusion
16
+ subscribe(:event, logger: { level: %i(info warn error) })
17
+ # pass
18
+ trigger(:event, logger: { level: :info })
19
+ trigger(:event, logger: { level: :error })
20
+ trigger(:event, logger: { level: :info, output: :stdin })
21
+ # filtered out
22
+ trigger(:event, logger: { level: :debug })
23
+
24
+ # proc checks
25
+ # here acts as array inclusion example
26
+ subscribe(:event, logger: { level: -> level { %i(info warn error).include?(level) })
27
+ ```
28
+
29
+ [Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-events/compare/v0.1.0...v0.1.1)
30
+
31
+ # v0.1.0 2018-01-02
2
32
 
3
33
  First public release
data/Gemfile CHANGED
@@ -9,5 +9,6 @@ group :test do
9
9
  end
10
10
 
11
11
  group :tools do
12
- gem 'byebug', platform: :mri
12
+ gem 'pry'
13
+ gem 'pry-byebug', platform: :mri
13
14
  end
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  [gem]: https://rubygems.org/gems/dry-events
2
2
  [travis]: https://travis-ci.org/dry-rb/dry-events
3
- [gemnasium]: https://gemnasium.com/dry-rb/dry-events
4
3
  [codeclimate]: https://codeclimate.com/github/dry-rb/dry-events
5
4
  [coveralls]: https://coveralls.io/r/dry-rb/dry-events
6
5
  [inchpages]: http://inch-ci.org/github/dry-rb/dry-events
@@ -9,18 +8,17 @@
9
8
 
10
9
  [![Gem Version](https://badge.fury.io/rb/dry-events.svg)][gem]
11
10
  [![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
11
  [![Code Climate](https://codeclimate.com/github/dry-rb/dry-events/badges/gpa.svg)][codeclimate]
14
12
  [![Test Coverage](https://codeclimate.com/github/dry-rb/dry-events/badges/coverage.svg)][codeclimate]
15
13
  [![Inline docs](http://inch-ci.org/github/dry-rb/dry-events.svg?branch=master)][inchpages]
16
14
 
17
15
  Standalone pub/sub system.
18
16
 
19
- ## Synopsis
17
+ ## Links
20
18
 
21
- ``` ruby
22
- ```
19
+ * [User docs](http://dry-rb.org/gems/dry-events)
20
+ * [API docs](http://rubydoc.info/gems/dry-events)
23
21
 
24
- ## License
22
+ ## LICENSE
25
23
 
26
24
  See `LICENSE` file.
@@ -18,7 +18,6 @@ module Dry
18
18
 
19
19
  # Initialize a new event bus
20
20
  #
21
- # @param [Symbol] id The bus identifier
22
21
  # @param [Hash] events A hash with events
23
22
  # @param [Hash] listeners A hash with listeners
24
23
  #
@@ -29,11 +28,11 @@ module Dry
29
28
  end
30
29
 
31
30
  # @api private
32
- def process(event_id, payload, &block)
33
- listeners[event_id].each do |(listener, query)|
31
+ def process(event_id, payload)
32
+ listeners[event_id].each do |listener, filter|
34
33
  event = events[event_id].payload(payload)
35
34
 
36
- if event.trigger?(query)
35
+ if filter.(payload)
37
36
  yield(event, listener)
38
37
  end
39
38
  end
@@ -47,12 +46,12 @@ module Dry
47
46
  end
48
47
 
49
48
  # @api private
50
- def attach(listener, query)
49
+ def attach(listener, filter)
51
50
  events.each do |id, event|
52
51
  meth = event.listener_method
53
52
 
54
53
  if listener.respond_to?(meth)
55
- listeners[id] << [listener.method(meth), query]
54
+ listeners[id] << [listener.method(meth), filter]
56
55
  end
57
56
  end
58
57
  end
@@ -68,14 +67,14 @@ module Dry
68
67
  end
69
68
 
70
69
  # @api private
71
- def subscribe(event_id, query, &block)
72
- listeners[event_id] << [block, query]
70
+ def subscribe(event_id, filter, &block)
71
+ listeners[event_id] << [block, filter]
73
72
  self
74
73
  end
75
74
 
76
75
  # @api private
77
76
  def subscribed?(listener)
78
- listeners.values.any? { |value| value.any? { |(block, _)| block.equal?(listener) } }
77
+ listeners.values.any? { |value| value.any? { |block, _| block.equal?(listener) } }
79
78
  end
80
79
  end
81
80
  end
@@ -66,11 +66,6 @@ module Dry
66
66
  end
67
67
  end
68
68
 
69
- # @api private
70
- def trigger?(query)
71
- query.empty? || query.all? { |key, value| payload[key] == value }
72
- end
73
-
74
69
  # @api private
75
70
  def listener_method
76
71
  @listener_method ||= :"on_#{id.to_s.gsub(DOT, UNDERSCORE)}"
@@ -0,0 +1,72 @@
1
+ require 'set'
2
+
3
+ module Dry
4
+ module Events
5
+ # Event filter
6
+ #
7
+ # A filter cherry-picks probes payload of events.
8
+ # Events not matching the predicates don't fire callbacks.
9
+ #
10
+ # @api private
11
+ class Filter
12
+ NO_MATCH = Object.new.freeze
13
+
14
+ # @!attribute [r] events
15
+ # @return [Array] A list of lambdas checking payloads
16
+ attr_reader :checks
17
+
18
+ # Create a new filter
19
+ #
20
+ # @param [Hash] filter Source filter
21
+ #
22
+ # @api private
23
+ def initialize(filter)
24
+ @checks = build_checks(filter)
25
+ end
26
+
27
+ # Test event payload against the checks
28
+ #
29
+ # @param [Hash] payload Event payload
30
+ #
31
+ # @api private
32
+ def call(payload = EMPTY_HASH)
33
+ checks.all? { |check| check.(payload) }
34
+ end
35
+
36
+ # Recursively build checks
37
+ #
38
+ # @api private
39
+ def build_checks(filter, checks = EMPTY_ARRAY, keys = EMPTY_ARRAY)
40
+ if filter.is_a?(Hash)
41
+ filter.reduce(checks) do |cs, (key, value)|
42
+ build_checks(value, cs, [*keys, key])
43
+ end
44
+ else
45
+ [*checks, method(:compare).curry.(keys, predicate(filter))]
46
+ end
47
+ end
48
+
49
+ # @api private
50
+ def compare(path, predicate, payload)
51
+ value = path.reduce(payload) do |acc, key|
52
+ if acc.is_a?(Hash) && acc.key?(key)
53
+ acc[key]
54
+ else
55
+ break NO_MATCH
56
+ end
57
+ end
58
+
59
+ predicate.(value)
60
+ end
61
+
62
+ # @api private
63
+ def predicate(value)
64
+ case value
65
+ when Proc then value
66
+ when Array then value.method(:include?)
67
+ else value.method(:==)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -11,7 +11,7 @@ module Dry
11
11
  #
12
12
  # register_event("users.created")
13
13
  # end
14
-
14
+ #
15
15
  # class MyListener
16
16
  # include Dry::Events::Listener[:app]
17
17
  #
@@ -5,6 +5,7 @@ require 'dry/core/class_attributes'
5
5
  require 'dry/events/constants'
6
6
  require 'dry/events/event'
7
7
  require 'dry/events/bus'
8
+ require 'dry/events/filter'
8
9
 
9
10
  module Dry
10
11
  module Events
@@ -116,13 +117,13 @@ module Dry
116
117
  # Subscribe to an event
117
118
  #
118
119
  # @param [Symbol,String] event_id The event identifier
119
- # @param [Hash] query An optional query for conditional listeners
120
+ # @param [Hash] filter_hash An optional filter for conditional listeners
120
121
  #
121
122
  # @return [Class] publisher class
122
123
  #
123
124
  # @api public
124
- def subscribe(event_id, query = EMPTY_HASH, &block)
125
- listeners[event_id] << [block, query]
125
+ def subscribe(event_id, filter_hash = EMPTY_HASH, &block)
126
+ listeners[event_id] << [block, Filter.new(filter_hash)]
126
127
  self
127
128
  end
128
129
 
@@ -180,19 +181,21 @@ module Dry
180
181
 
181
182
  # Subscribe to events.
182
183
  #
183
- # If the query parameter is provided, filters events by payload.
184
+ # If the filter parameter is provided, filters events by payload.
184
185
  #
185
186
  # @param [Symbol,String,Object] object_or_event_id The event identifier or a listener object
186
- # @param [Hash] query An optional event filter
187
+ # @param [Hash] filter_hash An optional event filter
187
188
  #
188
189
  # @return [Object] self
189
190
  #
190
191
  # @api public
191
- def subscribe(object_or_event_id, query = EMPTY_HASH, &block)
192
+ def subscribe(object_or_event_id, filter_hash = EMPTY_HASH, &block)
193
+ filter = Filter.new(filter_hash)
194
+
192
195
  if block
193
- __bus__.subscribe(object_or_event_id, query, &block)
196
+ __bus__.subscribe(object_or_event_id, filter, &block)
194
197
  else
195
- __bus__.attach(object_or_event_id, query)
198
+ __bus__.attach(object_or_event_id, filter)
196
199
  end
197
200
  self
198
201
  end
@@ -217,7 +220,7 @@ module Dry
217
220
 
218
221
  # Utility method which yields event with each of its listeners
219
222
  #
220
- # Listeners are already filtered out when query was provided during
223
+ # Listeners are already filtered out when filter was provided during
221
224
  # subscription
222
225
  #
223
226
  # @param [Symbol,String] event_id The event identifier
@@ -1,5 +1,5 @@
1
1
  module Dry
2
2
  module Events
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '0.1.1'.freeze
4
4
  end
5
5
  end
@@ -6,7 +6,8 @@ if RUBY_ENGINE == 'ruby' && ENV['COVERAGE'] == 'true'
6
6
  end
7
7
 
8
8
  begin
9
- require 'byebug'
9
+ require 'pry'
10
+ require 'pry-byebug'
10
11
  rescue LoadError; end
11
12
 
12
13
  require 'dry-events'
@@ -17,7 +18,9 @@ Dir[SPEC_ROOT.join('shared/**/*.rb')].each(&method(:require))
17
18
  Dir[SPEC_ROOT.join('support/**/*.rb')].each(&method(:require))
18
19
 
19
20
  RSpec.configure do |config|
21
+ config.warnings = true
20
22
  config.disable_monkey_patching!
23
+ config.filter_run_when_matching :focus
21
24
 
22
25
  config.after(:example) do
23
26
  Dry::Events::Publisher.instance_variable_set(:@__registry__, Concurrent::Map.new)
@@ -0,0 +1,74 @@
1
+ RSpec.describe Dry::Events::Filter do
2
+ subject(:filter) { described_class.new(query) }
3
+
4
+ context 'nested hash' do
5
+ let(:query) do
6
+ { logger: { level: :info } }
7
+ end
8
+
9
+ specify do
10
+ expect(filter.()).to be false
11
+ expect(filter.(logger: { level: :info, output: :stdin })).to be true
12
+ expect(filter.(logger: { level: :debug })).to be false
13
+ expect(filter.(logger: :debug)).to be false
14
+ end
15
+ end
16
+
17
+ context 'multi-value check' do
18
+ let(:query) do
19
+ { logger: { level: :info, output: :stdin } }
20
+ end
21
+
22
+ specify do
23
+ expect(filter.()).to be false
24
+ expect(filter.(logger: { level: :info, output: :stdin })).to be true
25
+ expect(filter.(logger: { level: :info })).to be false
26
+ end
27
+ end
28
+
29
+ context 'top-level array' do
30
+ let(:query) { %i(error fatal) }
31
+
32
+ specify do
33
+ expect(filter.()).to be false
34
+ expect(filter.(random: :hash)).to be false
35
+ expect(filter.(:error)).to be true
36
+ end
37
+ end
38
+
39
+ context 'nested array' do
40
+ let(:query) do
41
+ { logger: { level: %i(info warn error fatal) } }
42
+ end
43
+
44
+ specify do
45
+ expect(filter.()).to be false
46
+ expect(filter.(logger: { level: :info, output: :stdin })).to be true
47
+ expect(filter.(logger: { level: :fatal })).to be true
48
+ expect(filter.(logger: { level: :debug })).to be false
49
+ expect(filter.(level: :debug)).to be false
50
+ end
51
+ end
52
+
53
+ context 'nested proc' do
54
+ let(:query) do
55
+ { logger: { level: -> level { %i(error fatal).include?(level) } } }
56
+ end
57
+
58
+ specify do
59
+ expect(filter.()).to be false
60
+ expect(filter.(logger: { level: :error, output: :stdin })).to be true
61
+ end
62
+ end
63
+
64
+ context 'top-level proc' do
65
+ let(:query) do
66
+ -> level: :debug, ** { %i(error fatal).include?(level) }
67
+ end
68
+
69
+ specify do
70
+ expect(filter.()).to be false
71
+ expect(filter.(level: :error, output: :stdin)).to be true
72
+ end
73
+ end
74
+ end
@@ -16,16 +16,30 @@ RSpec.describe Dry::Events::Listener do
16
16
  end
17
17
 
18
18
  describe '.subscribe' do
19
- it 'subscribes a listener at class level' do
20
- result = []
19
+ let(:captured) { [] }
21
20
 
21
+ it 'subscribes a listener at class level' do
22
22
  listener.subscribe(:test_event) do |event|
23
- result << event.id
23
+ captured << event.id
24
24
  end
25
25
 
26
26
  publisher.publish(:test_event)
27
27
 
28
- expect(result).to eql([:test_event])
28
+ expect(captured).to eql([:test_event])
29
+ end
30
+
31
+ describe 'filters' do
32
+ it 'filters events' do
33
+ listener.subscribe(:test_event, level: :info) do |event|
34
+ captured << event.payload
35
+ end
36
+
37
+ publisher.publish(:test_event)
38
+ publisher.publish(:test_event, level: :debug)
39
+ publisher.publish(:test_event, level: :info)
40
+
41
+ expect(captured).to eql([level: :info])
42
+ end
29
43
  end
30
44
  end
31
45
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-events
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Solnica
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-01-02 00:00:00.000000000 Z
11
+ date: 2019-03-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: concurrent-ruby
@@ -116,10 +116,12 @@ files:
116
116
  - lib/dry/events/bus.rb
117
117
  - lib/dry/events/constants.rb
118
118
  - lib/dry/events/event.rb
119
+ - lib/dry/events/filter.rb
119
120
  - lib/dry/events/listener.rb
120
121
  - lib/dry/events/publisher.rb
121
122
  - lib/dry/events/version.rb
122
123
  - spec/spec_helper.rb
124
+ - spec/unit/dry/events/filter_spec.rb
123
125
  - spec/unit/dry/events/listener_spec.rb
124
126
  - spec/unit/dry/events/publisher_spec.rb
125
127
  homepage: https://github.com/dry-rb/dry-events
@@ -141,12 +143,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
143
  - !ruby/object:Gem::Version
142
144
  version: '0'
143
145
  requirements: []
144
- rubyforge_project:
145
- rubygems_version: 2.7.3
146
+ rubygems_version: 3.0.1
146
147
  signing_key:
147
148
  specification_version: 4
148
149
  summary: Pub/sub system
149
150
  test_files:
150
151
  - spec/spec_helper.rb
152
+ - spec/unit/dry/events/filter_spec.rb
151
153
  - spec/unit/dry/events/listener_spec.rb
152
154
  - spec/unit/dry/events/publisher_spec.rb