dry-events 0.1.0 → 0.1.1

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
  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