downstream 1.0.0 → 1.1.0

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