downstream 1.1.0 → 1.2.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 +15 -0
- data/lib/downstream/config.rb +1 -0
- data/lib/downstream/event.rb +40 -1
- data/lib/downstream/pubsub_adapters/stateless/pubsub.rb +2 -2
- data/lib/downstream/pubsub_adapters/stateless/subscriber.rb +39 -5
- data/lib/downstream/pubsub_adapters/stateless/subscriber_job.rb +9 -0
- data/lib/downstream/version.rb +1 -1
- data/lib/downstream.rb +5 -2
- metadata +73 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3806c4f875e378357e47571ed0bd5dbac1b45a80f9a01a78187b3c971378f425
|
4
|
+
data.tar.gz: e038408a8fb1ce3e18dfd7614e9fe3931fd72c7913249457c29fa00b14a22aa4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6f81acf6c0da05267067ac4d4c60d9edcb8aefba35d326643651afcf4ba17df09b88a791b379f10318e0c63aeaa691397d10b3a2cad10e23a38b2c29990fa9bf
|
7
|
+
data.tar.gz: 97bf35f16b9542bb032fb0ed52b6192b24ea92a3157d2f0cfc883546f50ee04e759965d6281fbb956e30029ee8bd716b129b8340b5638c3d619be78ca4494faf
|
data/README.md
CHANGED
@@ -27,6 +27,7 @@ Downstream supports various adapters for event handling. It can be configured in
|
|
27
27
|
```ruby
|
28
28
|
Downstream.configure do |config|
|
29
29
|
config.pubsub = :stateless # it's a default adapter
|
30
|
+
config.async_queue = :high_priority # nil by default
|
30
31
|
end
|
31
32
|
```
|
32
33
|
|
@@ -112,6 +113,20 @@ Downstream.subscribed(subscriber, to: ProfileCreated) do
|
|
112
113
|
end
|
113
114
|
```
|
114
115
|
|
116
|
+
If you want to handle events in a background job, you can pass the `async: true` option:
|
117
|
+
|
118
|
+
```ruby
|
119
|
+
store.subscribe OnProfileCreated::DoThat, async: true
|
120
|
+
```
|
121
|
+
|
122
|
+
By default, a job will be enqueued into `async_queue` name from the Downstream config. You can define your own queue name for a specific subscriber:
|
123
|
+
|
124
|
+
```ruby
|
125
|
+
store.subscribe OnProfileCreated::DoThat, async: {queue: :low_priority}
|
126
|
+
```
|
127
|
+
|
128
|
+
**NOTE:** all subscribers are synchronous by default
|
129
|
+
|
115
130
|
## Testing
|
116
131
|
|
117
132
|
You can test subscribers as normal Ruby objects.
|
data/lib/downstream/config.rb
CHANGED
data/lib/downstream/event.rb
CHANGED
@@ -1,10 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
GlobalID::Locator.use :downstream do |gid|
|
4
|
+
params = gid.params.each_with_object({}) do |(key, value), memo|
|
5
|
+
memo[key.to_sym] = if value.is_a?(String) && value.start_with?("gid://")
|
6
|
+
GlobalID::Locator.locate(value)
|
7
|
+
else
|
8
|
+
value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
gid.model_name.constantize
|
13
|
+
.new(event_id: gid.model_id, **params)
|
14
|
+
end
|
15
|
+
|
3
16
|
module Downstream
|
4
17
|
class Event
|
5
18
|
extend ActiveModel::Naming
|
19
|
+
include GlobalID::Identification
|
6
20
|
|
7
|
-
RESERVED_ATTRIBUTES = %i[event_id type].freeze
|
21
|
+
RESERVED_ATTRIBUTES = %i[id event_id type].freeze
|
8
22
|
|
9
23
|
class << self
|
10
24
|
attr_writer :identifier
|
@@ -57,6 +71,8 @@ module Downstream
|
|
57
71
|
|
58
72
|
attr_reader :event_id, :data, :errors
|
59
73
|
|
74
|
+
alias_method :id, :event_id
|
75
|
+
|
60
76
|
def initialize(event_id: nil, **params)
|
61
77
|
@event_id = event_id || SecureRandom.hex(10)
|
62
78
|
validate_attributes!(params)
|
@@ -77,6 +93,20 @@ module Downstream
|
|
77
93
|
}
|
78
94
|
end
|
79
95
|
|
96
|
+
def to_global_id
|
97
|
+
new_data = data.each_with_object({}) do |(key, value), memo|
|
98
|
+
memo[key] = if value.respond_to?(:to_global_id)
|
99
|
+
value.to_global_id
|
100
|
+
else
|
101
|
+
value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
super(new_data.merge!(app: :downstream))
|
106
|
+
end
|
107
|
+
|
108
|
+
alias_method :to_gid, :to_global_id
|
109
|
+
|
80
110
|
def inspect
|
81
111
|
"#{self.class.name}<#{type}##{event_id}>, data: #{data}"
|
82
112
|
end
|
@@ -85,6 +115,15 @@ module Downstream
|
|
85
115
|
data.fetch(attr)
|
86
116
|
end
|
87
117
|
|
118
|
+
def ==(other)
|
119
|
+
super ||
|
120
|
+
other.instance_of?(self.class) &&
|
121
|
+
!event_id.nil? &&
|
122
|
+
other.event_id == event_id
|
123
|
+
end
|
124
|
+
|
125
|
+
alias_method :eql?, :==
|
126
|
+
|
88
127
|
private
|
89
128
|
|
90
129
|
def validate_attributes!(params)
|
@@ -4,10 +4,10 @@ require_relative "subscriber"
|
|
4
4
|
module Downstream
|
5
5
|
module Stateless
|
6
6
|
class Pubsub < AbstractPubsub
|
7
|
-
def subscribe(identifier, callable)
|
7
|
+
def subscribe(identifier, callable, async: false)
|
8
8
|
ActiveSupport::Notifications.subscribe(
|
9
9
|
identifier,
|
10
|
-
Subscriber.new(callable)
|
10
|
+
Subscriber.new(callable, async: async)
|
11
11
|
)
|
12
12
|
end
|
13
13
|
|
@@ -1,19 +1,53 @@
|
|
1
|
+
require_relative "subscriber_job"
|
2
|
+
|
1
3
|
module Downstream
|
2
4
|
module Stateless
|
3
5
|
class Subscriber
|
4
|
-
|
6
|
+
include AfterCommitEverywhere
|
7
|
+
|
8
|
+
attr_reader :callable, :async
|
5
9
|
|
6
|
-
def initialize(callable)
|
10
|
+
def initialize(callable, async: false)
|
7
11
|
@callable = callable
|
12
|
+
@async = async
|
13
|
+
end
|
14
|
+
|
15
|
+
def async?
|
16
|
+
!!async
|
8
17
|
end
|
9
18
|
|
10
|
-
def call(
|
11
|
-
if
|
12
|
-
callable.
|
19
|
+
def call(_name, event)
|
20
|
+
if async?
|
21
|
+
if callable.is_a?(Proc) || callable.name.nil?
|
22
|
+
raise ArgumentError, "Anonymous subscribers (blocks/procs/lambdas or anonymous modules) cannot be asynchronous"
|
23
|
+
end
|
24
|
+
|
25
|
+
raise ArgumentError, "Async subscriber must be a module/class, not instance" unless callable.is_a?(Module)
|
26
|
+
|
27
|
+
after_commit do
|
28
|
+
SubscriberJob.then do |job|
|
29
|
+
if (queue_name = async_queue_name)
|
30
|
+
job.set(queue: queue_name)
|
31
|
+
else
|
32
|
+
job
|
33
|
+
end
|
34
|
+
end.perform_later(event, callable.name)
|
35
|
+
end
|
13
36
|
else
|
14
37
|
callable.call(event)
|
15
38
|
end
|
16
39
|
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def async_queue_name
|
44
|
+
return @async_queue_name if defined?(@async_queue_name)
|
45
|
+
|
46
|
+
name = async[:queue] if async.is_a?(Hash)
|
47
|
+
name ||= Downstream.config.async_queue
|
48
|
+
|
49
|
+
@async_queue_name = name
|
50
|
+
end
|
17
51
|
end
|
18
52
|
end
|
19
53
|
end
|
data/lib/downstream/version.rb
CHANGED
data/lib/downstream.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "active_support"
|
4
|
+
require "active_job"
|
4
5
|
require "active_model"
|
6
|
+
require "globalid"
|
7
|
+
require "after_commit_everywhere"
|
5
8
|
|
6
9
|
require "downstream/config"
|
7
10
|
require "downstream/event"
|
@@ -20,13 +23,13 @@ module Downstream
|
|
20
23
|
yield config
|
21
24
|
end
|
22
25
|
|
23
|
-
def subscribe(subscriber = nil, to: nil, &block)
|
26
|
+
def subscribe(subscriber = nil, to: nil, async: false, &block)
|
24
27
|
subscriber ||= block if block
|
25
28
|
raise ArgumentError, "Subsriber must be present" if subscriber.nil?
|
26
29
|
|
27
30
|
identifier = construct_identifier(subscriber, to)
|
28
31
|
|
29
|
-
pubsub.subscribe(identifier, subscriber)
|
32
|
+
pubsub.subscribe(identifier, subscriber, async: async)
|
30
33
|
end
|
31
34
|
|
32
35
|
# temporary subscriptions
|
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.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- merkushin.m.s@gmail.com
|
@@ -9,8 +9,36 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2021-
|
12
|
+
date: 2021-11-01 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: after_commit_everywhere
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.0'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: globalid
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0.5'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0.5'
|
14
42
|
- !ruby/object:Gem::Dependency
|
15
43
|
name: rails
|
16
44
|
requirement: !ruby/object:Gem::Requirement
|
@@ -53,6 +81,20 @@ dependencies:
|
|
53
81
|
- - ">="
|
54
82
|
- !ruby/object:Gem::Version
|
55
83
|
version: '1.16'
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: combustion
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '1.3'
|
91
|
+
type: :development
|
92
|
+
prerelease: false
|
93
|
+
version_requirements: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - "~>"
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '1.3'
|
56
98
|
- !ruby/object:Gem::Dependency
|
57
99
|
name: debug
|
58
100
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,6 +137,34 @@ dependencies:
|
|
95
137
|
- - "~>"
|
96
138
|
- !ruby/object:Gem::Version
|
97
139
|
version: '3.0'
|
140
|
+
- !ruby/object:Gem::Dependency
|
141
|
+
name: rspec-rails
|
142
|
+
requirement: !ruby/object:Gem::Requirement
|
143
|
+
requirements:
|
144
|
+
- - "~>"
|
145
|
+
- !ruby/object:Gem::Version
|
146
|
+
version: '5.0'
|
147
|
+
type: :development
|
148
|
+
prerelease: false
|
149
|
+
version_requirements: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - "~>"
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '5.0'
|
154
|
+
- !ruby/object:Gem::Dependency
|
155
|
+
name: sqlite3
|
156
|
+
requirement: !ruby/object:Gem::Requirement
|
157
|
+
requirements:
|
158
|
+
- - "~>"
|
159
|
+
- !ruby/object:Gem::Version
|
160
|
+
version: '1.4'
|
161
|
+
type: :development
|
162
|
+
prerelease: false
|
163
|
+
version_requirements: !ruby/object:Gem::Requirement
|
164
|
+
requirements:
|
165
|
+
- - "~>"
|
166
|
+
- !ruby/object:Gem::Version
|
167
|
+
version: '1.4'
|
98
168
|
- !ruby/object:Gem::Dependency
|
99
169
|
name: standard
|
100
170
|
requirement: !ruby/object:Gem::Requirement
|
@@ -124,6 +194,7 @@ files:
|
|
124
194
|
- lib/downstream/pubsub_adapters/abstract_pubsub.rb
|
125
195
|
- lib/downstream/pubsub_adapters/stateless/pubsub.rb
|
126
196
|
- lib/downstream/pubsub_adapters/stateless/subscriber.rb
|
197
|
+
- lib/downstream/pubsub_adapters/stateless/subscriber_job.rb
|
127
198
|
- lib/downstream/rspec.rb
|
128
199
|
- lib/downstream/rspec/have_published_event.rb
|
129
200
|
- lib/downstream/version.rb
|