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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44d7614f5509bb04efddc18f5dd26f4030a4262c00e6a370da13797d1efd99a6
4
- data.tar.gz: 8ea205b3d718e21b01a82cb65b51ae1870068f1065f679af4e4fe94bcdebe87a
3
+ metadata.gz: 0b7d5e8e2cd8390bbb0eef4fc8df4615c1bb2a5f5202ac0711f417b6347b7adc
4
+ data.tar.gz: 2a13ccafc25da88885ce49a309c41d4fc0cda93a7ffbd37cc2454e94359c1248
5
5
  SHA512:
6
- metadata.gz: 5376229dc224f18496764928e4019bd72791ac660bc90f975abba38fe961e305dd9e6d997573aae7ef9acd3b785659ff9171678d2ccd1dd02f89add6fa5b01cb
7
- data.tar.gz: f5a17a4a6f8383b35820914cc658fe97a41b2554df16c2088dcac3857af264e7976c5af3ab5249635e884de1ffe590dd32d270583230e79fa63ad09b5ae5294e
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
- Under the hood it's a wrapper for `ActiveSupport::Notifications`. But it provides a way more handy interface to build reactive apps. Each event has a strict schema described by a separate class. Also, the gem has convenient tooling to write tests.
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 |name, event|
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.
@@ -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
- events = []
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
- @matching_events, @unmatching_events =
62
- events.partition do |actual_event|
63
- (event_class.identifier == actual_event.type) &&
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
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Downstream
4
- VERSION = "1.0.0"
4
+ VERSION = "1.1.0"
5
5
  end
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 subscribe(subscriber = nil, to: nil, &block)
17
- to ||= infer_event_from_subscriber(subscriber) if subscriber.is_a?(Module)
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
- if subscriber.nil?
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
- if to.is_a?(Class) && Event >= to
51
- to.identifier
52
- else
53
- to
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
- def publish(event)
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.0.0
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-28 00:00:00.000000000 Z
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: '5'
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: '5'
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