downstream 1.0.0 → 1.1.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 +4 -4
- data/README.md +21 -2
- data/lib/downstream/config.rb +30 -0
- data/lib/downstream/pubsub_adapters/abstract_pubsub.rb +15 -0
- data/lib/downstream/pubsub_adapters/stateless/pubsub.rb +27 -0
- data/lib/downstream/pubsub_adapters/stateless/subscriber.rb +19 -0
- data/lib/downstream/rspec/have_published_event.rb +10 -19
- data/lib/downstream/version.rb +1 -1
- data/lib/downstream.rb +30 -31
- metadata +7 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b7d5e8e2cd8390bbb0eef4fc8df4615c1bb2a5f5202ac0711f417b6347b7adc
|
4
|
+
data.tar.gz: 2a13ccafc25da88885ce49a309c41d4fc0cda93a7ffbd37cc2454e94359c1248
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5b1cc9d3110a38b6575363b929cf8d2b8a6df186e4099268a9f66f6c20f4d04d3e1c62df9b0c308ee21a5a8e1fcdf9c1f62070e39708db22be739173f8a0d788
|
7
|
+
data.tar.gz: 4ff6301363da1ebff02ba743571166b89eaf35151427a3f6f8e70d54e8bd18a244ae863cdab4247afb412f97ed1a7a7b01a91a3c54d0ab35a56810584d87ef39
|
data/README.md
CHANGED
@@ -20,7 +20,17 @@ gem "downstream", "~> 1.0"
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
|
23
|
+
Downstream provides a way more handy interface to build reactive apps. Each event has a strict schema described by a separate class. The gem has convenient tooling to write tests.
|
24
|
+
|
25
|
+
Downstream supports various adapters for event handling. It can be configured in a Rails initializer `config/initializers/downstream.rb`:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
Downstream.configure do |config|
|
29
|
+
config.pubsub = :stateless # it's a default adapter
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
For now, it's implemented only one adapter. The `stateless` adapter is based on `ActiveSupport::Notifications`, and it doesn't store history events anywhere. All event invocations are synchronous. Adding asynchronous subscribers are on my road map.
|
24
34
|
|
25
35
|
### Describe events
|
26
36
|
|
@@ -77,7 +87,7 @@ initializer "my_engine.subscribe_to_events" do
|
|
77
87
|
store.subscribe MyEventHandler, to: ProfileCreated
|
78
88
|
|
79
89
|
# anonymous handler (could only be synchronous)
|
80
|
-
store.subscribe(to: ProfileCreated) do |
|
90
|
+
store.subscribe(to: ProfileCreated) do |event|
|
81
91
|
# do something
|
82
92
|
end
|
83
93
|
|
@@ -93,6 +103,15 @@ end
|
|
93
103
|
|
94
104
|
Although subscriber could be any callable Ruby object, that have specific input format (event); thus we suggest putting subscribers under `app/subscribers/on_<event_type>/<subscriber.rb>`, e.g. `app/subscribers/on_profile_created/create_chat_user.rb`).
|
95
105
|
|
106
|
+
Sometimes, you may be interested in using temporary subscriptions. For that, you can use this:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
subscriber = ->(event) { my_event_handler(event) }
|
110
|
+
Downstream.subscribed(subscriber, to: ProfileCreated) do
|
111
|
+
some_invocation
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
96
115
|
## Testing
|
97
116
|
|
98
117
|
You can test subscribers as normal Ruby objects.
|
data/lib/downstream/config.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_support/inflections"
|
4
|
+
|
3
5
|
module Downstream
|
4
6
|
class Config
|
5
7
|
attr_writer :namespace
|
@@ -7,5 +9,33 @@ module Downstream
|
|
7
9
|
def namespace
|
8
10
|
@namespace ||= "downstream-events"
|
9
11
|
end
|
12
|
+
|
13
|
+
def pubsub
|
14
|
+
@pubsub ||= lookup_pubsub(:stateless)
|
15
|
+
end
|
16
|
+
|
17
|
+
def pubsub=(value)
|
18
|
+
@pubsub = case value
|
19
|
+
when String, Symbol
|
20
|
+
lookup_pubsub(value)
|
21
|
+
else
|
22
|
+
value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def lookup_pubsub(name)
|
29
|
+
klass = name.camelize.safe_constantize if name.is_a?(String)
|
30
|
+
|
31
|
+
klass ||= begin
|
32
|
+
require "downstream/pubsub_adapters/#{name}/pubsub"
|
33
|
+
"Downstream::#{name.to_s.camelize}::Pubsub".safe_constantize
|
34
|
+
end
|
35
|
+
|
36
|
+
raise ArgumentError, "Uknown downstream pubsub adapter: #{name}" unless klass
|
37
|
+
|
38
|
+
klass.new
|
39
|
+
end
|
10
40
|
end
|
11
41
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Downstream
|
2
|
+
class AbstractPubsub
|
3
|
+
def subscribe(identifier, callable)
|
4
|
+
raise NotImplementedError
|
5
|
+
end
|
6
|
+
|
7
|
+
def subscribed(identifier, callable, &block)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def publish(identifier, event)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "active_support/notifications"
|
2
|
+
require_relative "subscriber"
|
3
|
+
|
4
|
+
module Downstream
|
5
|
+
module Stateless
|
6
|
+
class Pubsub < AbstractPubsub
|
7
|
+
def subscribe(identifier, callable)
|
8
|
+
ActiveSupport::Notifications.subscribe(
|
9
|
+
identifier,
|
10
|
+
Subscriber.new(callable)
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def subscribed(identifier, callable, &block)
|
15
|
+
ActiveSupport::Notifications.subscribed(
|
16
|
+
Subscriber.new(callable),
|
17
|
+
identifier,
|
18
|
+
&block
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def publish(identifier, event)
|
23
|
+
ActiveSupport::Notifications.publish(identifier, event)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Downstream
|
2
|
+
module Stateless
|
3
|
+
class Subscriber
|
4
|
+
attr_reader :callable
|
5
|
+
|
6
|
+
def initialize(callable)
|
7
|
+
@callable = callable
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(name, event)
|
11
|
+
if (callable.respond_to?(:arity) && callable.arity == 2) || callable.method(:call).arity == 2
|
12
|
+
callable.call(name, event)
|
13
|
+
else
|
14
|
+
callable.call(event)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -52,17 +52,17 @@ module Downstream
|
|
52
52
|
def matches?(block)
|
53
53
|
raise ArgumentError, "have_published_event only supports block expectations" unless block.is_a?(Proc)
|
54
54
|
|
55
|
-
|
56
|
-
namespace = /^#{Downstream.config.namespace}\./
|
57
|
-
ActiveSupport::Notifications.subscribed(->(name, event) { events << event }, namespace) do
|
58
|
-
block.call
|
59
|
-
end
|
55
|
+
@matching_events = []
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
(attributes.nil? || attributes_match?(actual_event))
|
57
|
+
subscriber = ->(event) do
|
58
|
+
if attributes.nil? || attributes_match?(event)
|
59
|
+
@matching_events << event
|
65
60
|
end
|
61
|
+
end
|
62
|
+
|
63
|
+
Downstream.subscribed(subscriber, to: event_class) do
|
64
|
+
block.call
|
65
|
+
end
|
66
66
|
|
67
67
|
@matching_count = @matching_events.size
|
68
68
|
|
@@ -75,16 +75,7 @@ module Downstream
|
|
75
75
|
|
76
76
|
def failure_message
|
77
77
|
(+"expected to publish #{event_class.identifier} event").tap do |msg|
|
78
|
-
msg << " #{message_expectation_modifier}, but"
|
79
|
-
|
80
|
-
if @unmatching_events.any?
|
81
|
-
msg << " published the following events:"
|
82
|
-
@unmatching_events.each do |unmatching_event|
|
83
|
-
msg << "\n #{unmatching_event.inspect}"
|
84
|
-
end
|
85
|
-
else
|
86
|
-
msg << " haven't published anything"
|
87
|
-
end
|
78
|
+
msg << " #{message_expectation_modifier}, but haven't published"
|
88
79
|
end
|
89
80
|
end
|
90
81
|
|
data/lib/downstream/version.rb
CHANGED
data/lib/downstream.rb
CHANGED
@@ -5,40 +5,46 @@ require "active_model"
|
|
5
5
|
|
6
6
|
require "downstream/config"
|
7
7
|
require "downstream/event"
|
8
|
+
require "downstream/pubsub_adapters/abstract_pubsub"
|
8
9
|
require "downstream/rspec" if defined?(RSpec)
|
9
10
|
|
10
11
|
module Downstream
|
11
12
|
class << self
|
13
|
+
delegate :pubsub, to: :config
|
14
|
+
|
12
15
|
def config
|
13
16
|
@config ||= Config.new
|
14
17
|
end
|
15
18
|
|
16
|
-
def
|
17
|
-
|
18
|
-
|
19
|
-
if to.nil?
|
20
|
-
raise ArgumentError, "Couldn't infer event from subscriber. " \
|
21
|
-
"Please, specify event using `to:` option"
|
22
|
-
end
|
19
|
+
def configure
|
20
|
+
yield config
|
21
|
+
end
|
23
22
|
|
23
|
+
def subscribe(subscriber = nil, to: nil, &block)
|
24
24
|
subscriber ||= block if block
|
25
|
+
raise ArgumentError, "Subsriber must be present" if subscriber.nil?
|
25
26
|
|
26
|
-
|
27
|
-
raise ArgumentError, "Subsriber must be present"
|
28
|
-
end
|
27
|
+
identifier = construct_identifier(subscriber, to)
|
29
28
|
|
30
|
-
identifier
|
31
|
-
if to.is_a?(Class) && Event >= to
|
32
|
-
to.identifier
|
33
|
-
else
|
34
|
-
to
|
35
|
-
end
|
36
|
-
|
37
|
-
ActiveSupport::Notifications.subscribe("#{config.namespace}.#{identifier}", subscriber)
|
29
|
+
pubsub.subscribe(identifier, subscriber)
|
38
30
|
end
|
39
31
|
|
40
32
|
# temporary subscriptions
|
41
33
|
def subscribed(subscriber, to: nil, &block)
|
34
|
+
raise ArgumentError, "Subsriber must be present" if subscriber.nil?
|
35
|
+
|
36
|
+
identifier = construct_identifier(subscriber, to)
|
37
|
+
|
38
|
+
pubsub.subscribed(identifier, subscriber, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def publish(event)
|
42
|
+
pubsub.publish("#{config.namespace}.#{event.type}", event)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def construct_identifier(subscriber, to)
|
42
48
|
to ||= infer_event_from_subscriber(subscriber) if subscriber.is_a?(Module)
|
43
49
|
|
44
50
|
if to.nil?
|
@@ -46,22 +52,15 @@ module Downstream
|
|
46
52
|
"Please, specify event using `to:` option"
|
47
53
|
end
|
48
54
|
|
49
|
-
identifier =
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
end
|
55
|
-
|
56
|
-
ActiveSupport::Notifications.subscribed(subscriber, "#{config.namespace}.#{identifier}", &block)
|
57
|
-
end
|
55
|
+
identifier = if to.is_a?(Class) && Event >= to
|
56
|
+
to.identifier
|
57
|
+
else
|
58
|
+
to
|
59
|
+
end
|
58
60
|
|
59
|
-
|
60
|
-
ActiveSupport::Notifications.publish("#{config.namespace}.#{event.type}", event)
|
61
|
+
"#{config.namespace}.#{identifier}"
|
61
62
|
end
|
62
63
|
|
63
|
-
private
|
64
|
-
|
65
64
|
def infer_event_from_subscriber(subscriber)
|
66
65
|
event_class_name = subscriber.name.split("::").yield_self do |parts|
|
67
66
|
# handle explicti top-level name, e.g. ::Some::Event
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: downstream
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- merkushin.m.s@gmail.com
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-10-
|
12
|
+
date: 2021-10-29 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -17,14 +17,14 @@ dependencies:
|
|
17
17
|
requirements:
|
18
18
|
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
|
-
version: '
|
20
|
+
version: '6'
|
21
21
|
type: :runtime
|
22
22
|
prerelease: false
|
23
23
|
version_requirements: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version: '
|
27
|
+
version: '6'
|
28
28
|
- !ruby/object:Gem::Dependency
|
29
29
|
name: appraisal
|
30
30
|
requirement: !ruby/object:Gem::Requirement
|
@@ -121,6 +121,9 @@ files:
|
|
121
121
|
- lib/downstream/config.rb
|
122
122
|
- lib/downstream/engine.rb
|
123
123
|
- lib/downstream/event.rb
|
124
|
+
- lib/downstream/pubsub_adapters/abstract_pubsub.rb
|
125
|
+
- lib/downstream/pubsub_adapters/stateless/pubsub.rb
|
126
|
+
- lib/downstream/pubsub_adapters/stateless/subscriber.rb
|
124
127
|
- lib/downstream/rspec.rb
|
125
128
|
- lib/downstream/rspec/have_published_event.rb
|
126
129
|
- lib/downstream/version.rb
|