downstream 1.1.0 → 1.2.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: 0b7d5e8e2cd8390bbb0eef4fc8df4615c1bb2a5f5202ac0711f417b6347b7adc
4
- data.tar.gz: 2a13ccafc25da88885ce49a309c41d4fc0cda93a7ffbd37cc2454e94359c1248
3
+ metadata.gz: 3806c4f875e378357e47571ed0bd5dbac1b45a80f9a01a78187b3c971378f425
4
+ data.tar.gz: e038408a8fb1ce3e18dfd7614e9fe3931fd72c7913249457c29fa00b14a22aa4
5
5
  SHA512:
6
- metadata.gz: 5b1cc9d3110a38b6575363b929cf8d2b8a6df186e4099268a9f66f6c20f4d04d3e1c62df9b0c308ee21a5a8e1fcdf9c1f62070e39708db22be739173f8a0d788
7
- data.tar.gz: 4ff6301363da1ebff02ba743571166b89eaf35151427a3f6f8e70d54e8bd18a244ae863cdab4247afb412f97ed1a7a7b01a91a3c54d0ab35a56810584d87ef39
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.
@@ -4,6 +4,7 @@ require "active_support/inflections"
4
4
 
5
5
  module Downstream
6
6
  class Config
7
+ attr_accessor :async_queue
7
8
  attr_writer :namespace
8
9
 
9
10
  def namespace
@@ -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
- attr_reader :callable
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(name, event)
11
- if (callable.respond_to?(:arity) && callable.arity == 2) || callable.method(:call).arity == 2
12
- callable.call(name, event)
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
@@ -0,0 +1,9 @@
1
+ module Downstream
2
+ module Stateless
3
+ class SubscriberJob < ActiveJob::Base
4
+ def perform(event, callable)
5
+ callable.constantize.call(event)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Downstream
4
- VERSION = "1.1.0"
4
+ VERSION = "1.2.0"
5
5
  end
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.1.0
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-10-29 00:00:00.000000000 Z
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