interactor-sidekiq 1.0.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 +7 -0
- data/.github/ci.yml +24 -0
- data/.gitignore +28 -0
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +111 -0
- data/Rakefile +3 -0
- data/interactor-sidekiq.gemspec +24 -0
- data/lib/interactor/sidekiq/version.rb +7 -0
- data/lib/interactor/sidekiq.rb +117 -0
- data/spec/dummy_app/app.rb +5 -0
- data/spec/interactor/sidekiq_perform_spec.rb +77 -0
- data/spec/interactor/sidekiq_worker_spec.rb +379 -0
- data/spec/spec_helper.rb +28 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5a3f2ae3d870f43556bde3c67622cc6ef268b920aad8c9cae4dee04376d46029
|
4
|
+
data.tar.gz: b34487c04f020818817c7554e447e35099690d5a7561e76bc4367adc091cd4e9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e146bebf4e3aaf4a5867ca4096685696e1644775f89e6db37b5b77aa4045df7e9a8bbeddacd2900c186d9ebeabb700afc75f70e97f36174914ba7cef9c728629
|
7
|
+
data.tar.gz: 1903783ad26f3f6fe14a0343dad485bfdf7522e3d9443443176194bb9ca277a3f51ce99a0cd5b240549c8d0e5283d4ab485e0ca3b3af9f65460e62a4fdf75322
|
data/.github/ci.yml
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
name: CI
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
- '*'
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
test:
|
9
|
+
runs-on: ubuntu-latest
|
10
|
+
steps:
|
11
|
+
- name: Check out repository code
|
12
|
+
uses: actions/checkout@v1
|
13
|
+
|
14
|
+
- name: Setup Ruby
|
15
|
+
uses: ruby/setup-ruby@v1
|
16
|
+
with:
|
17
|
+
ruby-version: '3.0.3'
|
18
|
+
bundler-cache: true
|
19
|
+
|
20
|
+
- name: Rspec - Build and run tests
|
21
|
+
run: |
|
22
|
+
gem install bundler
|
23
|
+
bundle install --jobs 4 --retry 3
|
24
|
+
bundle exec rspec spec
|
data/.gitignore
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
*.log
|
2
|
+
*.gem
|
3
|
+
*.rbc
|
4
|
+
.bundle
|
5
|
+
.config
|
6
|
+
.yardoc
|
7
|
+
Gemfile.lock
|
8
|
+
InstalledFiles
|
9
|
+
_yardoc
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.ruby-version
|
24
|
+
.ruby-gemset
|
25
|
+
.idea
|
26
|
+
.gs
|
27
|
+
.byebug_history
|
28
|
+
covarage
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2022, Gabriel Rocha
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Interactor::Sidekiq
|
2
|
+
|
3
|
+
Provides [Interactor](https://github.com/collectiveidea/interactor) with asynchronous action using [Sidekiq](https://github.com/mperham/sidekiq).
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'interactor-sidekiq', '~> 1.0'
|
9
|
+
```
|
10
|
+
|
11
|
+
## #async_call
|
12
|
+
You can now add asynchronous behavior to both types of inetagents (basic interactors and organizers).
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class RegularAction
|
16
|
+
include Interactor
|
17
|
+
|
18
|
+
def call
|
19
|
+
{ context: context.variable }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
23
|
+
With the above example we can already use **async_call**.
|
24
|
+
|
25
|
+
```sh
|
26
|
+
>> RegularAction.call(key: 'value')
|
27
|
+
#<Interactor::Context key="value">
|
28
|
+
>> Sidekiq::Queues.jobs_by_queue
|
29
|
+
{}
|
30
|
+
```
|
31
|
+
```sh
|
32
|
+
>> RegularAction.async_call(key: 'value')
|
33
|
+
#<Interactor::Context key="value">
|
34
|
+
>> Sidekiq::Queues.jobs_by_queue
|
35
|
+
{"default"=>[{"retry"=>true, "queue"=>"default", "args"=>["{\"key\":\"value\",\"interactor_class\":\"RegularAction\"}"], "class"=>"Interactor::SidekiqWorker::Worker", "jid"=>"91a374e10e584b02cb84eec3", "created_at"=>1656283783.3459146, "enqueued_at"=>1656283783.3459556}]}
|
36
|
+
```
|
37
|
+
|
38
|
+
You can pass the **sidekiq_options** and **sidekiq_scheduling_options** to customize the behavior of the **async_call** method.
|
39
|
+
|
40
|
+
#### Passing options from sidekiq
|
41
|
+
|
42
|
+
To set custom [sidekiq_options] (https://github.com/mperham/sidekiq/wiki/Advanced-Options#workers) you can add `sidekiq_options` class method in your interactors - these options will be passed to Sidekiq `` set ` before scheduling the asynchronous worker.
|
43
|
+
|
44
|
+
#### Passing scheduling options
|
45
|
+
|
46
|
+
In order to be able to schedule jobs for future execution following [Scheduled Jobs](https://github.com/mperham/sidekiq/wiki/Scheduled-Jobs), you can add the `sidekiq_schedule_options` class method in your subscriber definition - these options will be passed to Sidekiq's `perform_in` method when the worker is called.
|
47
|
+
|
48
|
+
```sh
|
49
|
+
>> RegularAction.async_call(message: 'hello!', sidekiq_options: { queue: :low_priority }, sidekiq_schedule_options: { perform_in: 5 })
|
50
|
+
|
51
|
+
Interactor::Context message: 'hello!', sidekiq_options: { queue: :low_priority }, sidekiq_schedule_options: { perform_in: 5 }
|
52
|
+
```
|
53
|
+
|
54
|
+
## Failure
|
55
|
+
|
56
|
+
If you pass invalid parameters to sidekiq, you will get an immediate return with the error message.
|
57
|
+
```sh
|
58
|
+
>> result = RegularAction.async_call(message: 'hello!', sidekiq_schedule_options: "error")
|
59
|
+
#<Interactor::Context key="value", sidekiq_options="bad error message", error="undefined method `transform_keys' for \"bad error message\":String">
|
60
|
+
>> result.failure?
|
61
|
+
true
|
62
|
+
>> Sidekiq::Queues.jobs_by_queue
|
63
|
+
{}
|
64
|
+
```
|
65
|
+
|
66
|
+
## Interactor::Async
|
67
|
+
|
68
|
+
Now you need an interactor to always assume asynchronous behavior using: **Interator::Async**.
|
69
|
+
|
70
|
+
#### Passing handle sidekiq exception
|
71
|
+
|
72
|
+
When executing the perform method in sidekiq there may be a problem, thinking about it we have already made it possible for you to handle this error.
|
73
|
+
**If the context is failed during invocation of the interactor in background, the Interactor::Failure is raised**.
|
74
|
+
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
class AsyncAction
|
78
|
+
include Interactor::Async
|
79
|
+
|
80
|
+
def call
|
81
|
+
{ context: context.variable }
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.sidekiq_options
|
85
|
+
{ queue: :low_priority }
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.sidekiq_schedule_options
|
89
|
+
{ perform_in: 5 }
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.handle_sidekiq_exception(error)
|
93
|
+
# Integrate with Application Monitoring and Error Tracking Software
|
94
|
+
end
|
95
|
+
end
|
96
|
+
```
|
97
|
+
|
98
|
+
## Compatibility
|
99
|
+
|
100
|
+
The same Ruby versions as Sidekiq are offically supported, but it should work
|
101
|
+
with any 2.x syntax Ruby including JRuby and Rubinius.
|
102
|
+
|
103
|
+
## Running Specs
|
104
|
+
|
105
|
+
```
|
106
|
+
bundle exec rspec
|
107
|
+
```
|
108
|
+
|
109
|
+
## License
|
110
|
+
|
111
|
+
MIT
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'interactor/sidekiq/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'interactor-sidekiq'
|
9
|
+
spec.version = Interactor::Sidekiq::VERSION
|
10
|
+
spec.authors = ['Gabriel Rocha']
|
11
|
+
spec.email = ['gabrielras100@gmail.com']
|
12
|
+
spec.summary = 'Async Interactor using Sidekiq'
|
13
|
+
spec.description = 'Async Interactor using Sidekiq'
|
14
|
+
spec.homepage = 'https://github.com/gabrielras/interactor-sidekiq'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'interactor', '~> 3.0'
|
23
|
+
spec.add_dependency 'sidekiq', '>=4.1'
|
24
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'interactor'
|
4
|
+
require 'sidekiq'
|
5
|
+
|
6
|
+
module Interactor
|
7
|
+
# Internal: Install Interactor's behavior in the given class.
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
extend ClassMethods
|
11
|
+
extend SidekiqWorker
|
12
|
+
include Hooks
|
13
|
+
|
14
|
+
# Public: Gets the Interactor::Context of the Interactor instance.
|
15
|
+
attr_reader :context
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# based on Sidekiq 4.x #delay method, which is not enabled by default in Sidekiq 5.x
|
20
|
+
# https://github.com/mperham/sidekiq/blob/4.x/lib/sidekiq/extensions/generic_proxy.rb
|
21
|
+
# https://github.com/mperham/sidekiq/blob/4.x/lib/sidekiq/extensions/class_methods.rb
|
22
|
+
|
23
|
+
module SidekiqWorker
|
24
|
+
class Worker
|
25
|
+
include ::Sidekiq::Worker
|
26
|
+
|
27
|
+
def perform(context)
|
28
|
+
interactor_class(context).sync_call(context.except(:interactor_class))
|
29
|
+
rescue Exception => e
|
30
|
+
if interactor_class(context).respond_to?(:handle_sidekiq_exception)
|
31
|
+
interactor_class(context).handle_sidekiq_exception(e)
|
32
|
+
else
|
33
|
+
raise e
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def interactor_class(context)
|
40
|
+
Module.const_get context[:interactor_class]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def sync_call(context = {})
|
45
|
+
new(context).tap(&:run!).context
|
46
|
+
end
|
47
|
+
|
48
|
+
def async_call(context = {})
|
49
|
+
options = handle_sidekiq_options(context)
|
50
|
+
schedule_options = delay_sidekiq_schedule_options(context)
|
51
|
+
|
52
|
+
Worker.set(options).perform_in(schedule_options.fetch(:delay, 0), handle_context_for_sidekiq(context))
|
53
|
+
new(context.to_h).context
|
54
|
+
rescue Exception => e
|
55
|
+
begin
|
56
|
+
new(context.to_h).context.fail!(error: e.message)
|
57
|
+
rescue Failure => e
|
58
|
+
e.context
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def handle_context_for_sidekiq(context)
|
65
|
+
context.to_h.merge(interactor_class: to_s).to_json
|
66
|
+
end
|
67
|
+
|
68
|
+
def handle_sidekiq_options(context)
|
69
|
+
if context[:sidekiq_options].nil?
|
70
|
+
respond_to?(:sidekiq_options) ? sidekiq_options : { queue: :default }
|
71
|
+
else
|
72
|
+
context[:sidekiq_options]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def delay_sidekiq_schedule_options(context)
|
77
|
+
options = handle_sidekiq_schedule_options(context)
|
78
|
+
return {} unless options.key?(:perform_in) || options.key?(:perform_at)
|
79
|
+
|
80
|
+
{ delay: options[:perform_in] || options[:perform_at] }
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_sidekiq_schedule_options(context)
|
84
|
+
if context[:sidekiq_schedule_options].nil?
|
85
|
+
respond_to?(:sidekiq_schedule_options) ? sidekiq_schedule_options : { delay: 0 }
|
86
|
+
else
|
87
|
+
context[:sidekiq_schedule_options]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
module Async
|
93
|
+
def self.included(base)
|
94
|
+
base.class_eval do
|
95
|
+
include Interactor
|
96
|
+
|
97
|
+
extend ClassMethods
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
module ClassMethods
|
102
|
+
def call(context = {})
|
103
|
+
default_async_call(context)
|
104
|
+
end
|
105
|
+
|
106
|
+
def call!(context = {})
|
107
|
+
default_async_call(context)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def default_async_call(context)
|
113
|
+
async_call(context)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'interactor/sidekiq'
|
4
|
+
|
5
|
+
RSpec.describe Interactor::SidekiqWorker::Worker do
|
6
|
+
class AsyncAction
|
7
|
+
include Interactor::Async
|
8
|
+
|
9
|
+
def call
|
10
|
+
{ message: context.variable }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class BadAsyncAction
|
15
|
+
include Interactor::Async
|
16
|
+
|
17
|
+
def call
|
18
|
+
raise StandardError, 'This is an exception'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class CustomizedBadAsyncAction
|
23
|
+
include Interactor::Async
|
24
|
+
|
25
|
+
def call
|
26
|
+
raise StandardError, 'This is an exception'
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.handle_sidekiq_exception(error)
|
30
|
+
{ message: error.respond_to?(:message) ? 'captured error message' : 'not captured error message' }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
shared_examples_for 'there was no new sidekiq worker' do
|
35
|
+
let(:jobs_by_queue) { Sidekiq::Queues.jobs_by_queue }
|
36
|
+
|
37
|
+
before { Sidekiq::Queues.clear_all }
|
38
|
+
before { result }
|
39
|
+
|
40
|
+
it { expect(respond_to?(:jobs_by_queue)).to be_truthy }
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#perform' do
|
44
|
+
let(:sidekiq_options) { { queue: 'default' } }
|
45
|
+
|
46
|
+
context 'when there is no error' do
|
47
|
+
subject(:result) { AsyncAction::SidekiqWorker::Worker.new.perform(context) }
|
48
|
+
let(:context) { { interactor_class: 'AsyncAction', key: 'value' } }
|
49
|
+
|
50
|
+
it { expect(result.class).to eq Interactor::Context }
|
51
|
+
|
52
|
+
it { expect(result.success?).to eq true }
|
53
|
+
|
54
|
+
it { expect(result.to_h).to eq context.except(:interactor_class) }
|
55
|
+
|
56
|
+
it_behaves_like 'there was no new sidekiq worker'
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'when there is no error and it is handled' do
|
60
|
+
subject(:result) { CustomizedBadAsyncAction::SidekiqWorker::Worker.new.perform(context) }
|
61
|
+
let(:context) { { interactor_class: 'CustomizedBadAsyncAction', key: 'value' } }
|
62
|
+
|
63
|
+
it { expect(result).to eq({ message: 'captured error message' }) }
|
64
|
+
|
65
|
+
it_behaves_like 'there was no new sidekiq worker'
|
66
|
+
end
|
67
|
+
|
68
|
+
context 'when there is no error and it is not handled' do
|
69
|
+
subject(:result) { BadAsyncAction::SidekiqWorker::Worker.new.perform(context) }
|
70
|
+
let(:context) { { interactor_class: 'BadAsyncAction', key: 'value' } }
|
71
|
+
|
72
|
+
it 'returns error' do
|
73
|
+
expect { result }.to raise_error(StandardError, 'This is an exception')
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,379 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'interactor/sidekiq'
|
4
|
+
|
5
|
+
RSpec.describe Interactor::SidekiqWorker do
|
6
|
+
class RegularAction
|
7
|
+
include Interactor
|
8
|
+
|
9
|
+
def call
|
10
|
+
{ context: context.variable }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class AsyncAction
|
15
|
+
include Interactor::Async
|
16
|
+
|
17
|
+
def call
|
18
|
+
{ context: context.variable }
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.handle_sidekiq_exception(error); end
|
22
|
+
end
|
23
|
+
|
24
|
+
class AsyncActionWithSidekiqOption
|
25
|
+
include Interactor::Async
|
26
|
+
|
27
|
+
def call
|
28
|
+
{ context: context.variable }
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.sidekiq_options
|
32
|
+
{ queue: 'low_priority' }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
class AsyncActionWithSidekiqScheduleOption
|
37
|
+
include Interactor::Async
|
38
|
+
|
39
|
+
def call
|
40
|
+
{ context: context.variable }
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.sidekiq_schedule_options
|
44
|
+
{ perform_in: 5 }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class CustomizedBadSidekiqOption
|
49
|
+
include Interactor::Async
|
50
|
+
|
51
|
+
def call
|
52
|
+
{ context: context.variable }
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.sidekiq_options
|
56
|
+
'bad error message'
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class CustomizedBadSidekiqScheduleOption
|
61
|
+
include Interactor::Async
|
62
|
+
|
63
|
+
def call
|
64
|
+
{ context: context.variable }
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.sidekiq_schedule_options
|
68
|
+
'bad error message'
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class RegularOrganizer
|
73
|
+
include Interactor::Organizer
|
74
|
+
|
75
|
+
organize RegularAction
|
76
|
+
end
|
77
|
+
|
78
|
+
class OrganizerWithAsyncAction
|
79
|
+
include Interactor::Organizer
|
80
|
+
|
81
|
+
organize RegularAction, AsyncActionWithSidekiqOption, AsyncActionWithSidekiqScheduleOption
|
82
|
+
end
|
83
|
+
|
84
|
+
shared_examples_for 'sidekiq worker' do |elements|
|
85
|
+
let(:jobs_by_queue) { Sidekiq::Queues.jobs_by_queue[(elements[:sidekiq_options][:queue])][0] }
|
86
|
+
|
87
|
+
before { Sidekiq::Queues.clear_all }
|
88
|
+
before { result }
|
89
|
+
|
90
|
+
it { expect(jobs_by_queue['queue']).to eq elements[:sidekiq_options][:queue] }
|
91
|
+
|
92
|
+
it { expect(jobs_by_queue['args']).to eq [context.merge(interactor_class: elements[:interactor_class]).to_json] }
|
93
|
+
end
|
94
|
+
|
95
|
+
shared_examples_for 'there is no sidekiq worker' do
|
96
|
+
let(:jobs_by_queue) { Sidekiq::Queues.jobs_by_queue }
|
97
|
+
|
98
|
+
before { Sidekiq::Queues.clear_all }
|
99
|
+
before { result }
|
100
|
+
|
101
|
+
|
102
|
+
it { expect(respond_to?(:jobs_by_queue)).to be_truthy }
|
103
|
+
end
|
104
|
+
|
105
|
+
shared_examples_for 'interactor success' do
|
106
|
+
it { expect(result.class).to eq Interactor::Context }
|
107
|
+
|
108
|
+
it { expect(result.success?).to eq true }
|
109
|
+
|
110
|
+
it { expect(result.to_h).to eq context }
|
111
|
+
end
|
112
|
+
|
113
|
+
shared_examples_for 'interactor failure' do
|
114
|
+
it { expect(result.class).to eq Interactor::Context }
|
115
|
+
|
116
|
+
it { expect(result.failure?).to eq true }
|
117
|
+
|
118
|
+
it { expect(result.error).not_to be_empty }
|
119
|
+
end
|
120
|
+
|
121
|
+
describe '#async_call' do
|
122
|
+
subject(:result) { interactor.async_call(context) }
|
123
|
+
|
124
|
+
%w[RegularAction AsyncAction RegularOrganizer OrganizerWithAsyncAction].each do |interactor_class|
|
125
|
+
context "when attributes for #{interactor_class} class" do
|
126
|
+
let(:interactor) { Module.const_get interactor_class }
|
127
|
+
|
128
|
+
context 'when attributes are valid and not in context' do
|
129
|
+
let(:context) do
|
130
|
+
{ key: 'value' }
|
131
|
+
end
|
132
|
+
|
133
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'default' },
|
134
|
+
sidekiq_schedule_options: { delay: 0 }
|
135
|
+
|
136
|
+
it_behaves_like 'interactor success'
|
137
|
+
end
|
138
|
+
|
139
|
+
context 'when attributes are valid and in context' do
|
140
|
+
let(:context) do
|
141
|
+
{ key: 'value', sidekiq_options: { queue: 'low_priority' }, sidekiq_schedule_options: { perform_in: 5 } }
|
142
|
+
end
|
143
|
+
|
144
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'low_priority' },
|
145
|
+
sidekiq_schedule_options: { perform_in: 5 }
|
146
|
+
|
147
|
+
it_behaves_like 'interactor success'
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'when attributes are invalid' do
|
151
|
+
let(:context) do
|
152
|
+
{ key: 'value', sidekiq_options: 'bad error message',
|
153
|
+
sidekiq_schedule_options: 'bad error message' }
|
154
|
+
end
|
155
|
+
|
156
|
+
it_behaves_like 'there is no sidekiq worker'
|
157
|
+
|
158
|
+
it_behaves_like 'interactor failure'
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
%w[AsyncActionWithSidekiqOption].each do |interactor_class|
|
164
|
+
context "when attributes for #{interactor_class} class" do
|
165
|
+
let(:interactor) { Module.const_get interactor_class }
|
166
|
+
|
167
|
+
context 'when attributes are valid and not in context' do
|
168
|
+
let(:context) do
|
169
|
+
{ key: 'value' }
|
170
|
+
end
|
171
|
+
|
172
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'low_priority' },
|
173
|
+
sidekiq_schedule_options: { delay: 0 }
|
174
|
+
|
175
|
+
it_behaves_like 'interactor success'
|
176
|
+
end
|
177
|
+
|
178
|
+
context 'when attributes are valid and in context' do
|
179
|
+
let(:context) do
|
180
|
+
{ key: 'value', sidekiq_options: { queue: 'default' }, sidekiq_schedule_options: { perform_in: 5 } }
|
181
|
+
end
|
182
|
+
|
183
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'default' },
|
184
|
+
sidekiq_schedule_options: { perform_in: 5 }
|
185
|
+
|
186
|
+
it_behaves_like 'interactor success'
|
187
|
+
end
|
188
|
+
|
189
|
+
context 'when attributes are invalid' do
|
190
|
+
let(:context) do
|
191
|
+
{ key: 'value', sidekiq_options: 'bad error message' }
|
192
|
+
end
|
193
|
+
|
194
|
+
it_behaves_like 'there is no sidekiq worker'
|
195
|
+
|
196
|
+
it_behaves_like 'interactor failure'
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
%w[AsyncActionWithSidekiqScheduleOption].each do |interactor_class|
|
202
|
+
context "when attributes for #{interactor_class} class" do
|
203
|
+
let(:interactor) { Module.const_get interactor_class }
|
204
|
+
|
205
|
+
context 'when attributes are valid and not in context' do
|
206
|
+
let(:context) do
|
207
|
+
{ key: 'value' }
|
208
|
+
end
|
209
|
+
|
210
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'default' },
|
211
|
+
sidekiq_schedule_options: { perform_in: 5 }
|
212
|
+
|
213
|
+
it_behaves_like 'interactor success'
|
214
|
+
end
|
215
|
+
|
216
|
+
context 'when attributes are valid and in context' do
|
217
|
+
let(:context) do
|
218
|
+
{ key: 'value', sidekiq_options: { queue: 'low_priority' }, sidekiq_schedule_options: { perform_in: 0 } }
|
219
|
+
end
|
220
|
+
|
221
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'low_priority' },
|
222
|
+
sidekiq_schedule_options: { perform_in: 0 }
|
223
|
+
|
224
|
+
it_behaves_like 'interactor success'
|
225
|
+
end
|
226
|
+
|
227
|
+
context "when attributes for #{interactor_class} class are invalid" do
|
228
|
+
let(:context) do
|
229
|
+
{ key: 'value', sidekiq_schedule_options: 'bad error message' }
|
230
|
+
end
|
231
|
+
|
232
|
+
it_behaves_like 'there is no sidekiq worker'
|
233
|
+
|
234
|
+
it_behaves_like 'interactor failure'
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
%w[CustomizedBadSidekiqOption CustomizedBadSidekiqScheduleOption].each do |interactor_class|
|
240
|
+
context "when attributes for #{interactor_class} class" do
|
241
|
+
let(:interactor) { Module.const_get interactor_class }
|
242
|
+
|
243
|
+
context 'when attributes are valid' do
|
244
|
+
let(:context) do
|
245
|
+
{ key: 'value', sidekiq_options: { queue: 'default' }, sidekiq_schedule_options: { delay: 5 } }
|
246
|
+
end
|
247
|
+
|
248
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'default' },
|
249
|
+
sidekiq_schedule_options: { delay: 5 }
|
250
|
+
|
251
|
+
it_behaves_like 'interactor success'
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'when attributes are invalid' do
|
255
|
+
let(:context) { { key: 'value' } }
|
256
|
+
|
257
|
+
it_behaves_like 'there is no sidekiq worker'
|
258
|
+
|
259
|
+
it_behaves_like 'interactor failure'
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe '#call' do
|
266
|
+
subject(:result) { interactor.call(context) }
|
267
|
+
|
268
|
+
%w[RegularAction RegularOrganizer].each do |interactor_class|
|
269
|
+
context "when attributes for #{interactor_class} class" do
|
270
|
+
let(:interactor) { Module.const_get interactor_class }
|
271
|
+
|
272
|
+
context 'when attributes are valid and not in context' do
|
273
|
+
let(:context) do
|
274
|
+
{ key: 'value' }
|
275
|
+
end
|
276
|
+
|
277
|
+
it_behaves_like 'there is no sidekiq worker'
|
278
|
+
|
279
|
+
it_behaves_like 'interactor success'
|
280
|
+
end
|
281
|
+
|
282
|
+
context 'when attributes are valid and in context' do
|
283
|
+
let(:context) do
|
284
|
+
{ key: 'value', sidekiq_options: { queue: 'low_priority' }, sidekiq_schedule_options: { perform_in: 5 } }
|
285
|
+
end
|
286
|
+
|
287
|
+
it_behaves_like 'there is no sidekiq worker'
|
288
|
+
|
289
|
+
it_behaves_like 'interactor success'
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
%w[AsyncAction].each do |interactor_class|
|
295
|
+
context "when attributes for #{interactor_class} class" do
|
296
|
+
let(:interactor) { Module.const_get interactor_class }
|
297
|
+
|
298
|
+
context 'when attributes are valid' do
|
299
|
+
let(:context) do
|
300
|
+
{ key: 'value' }
|
301
|
+
end
|
302
|
+
|
303
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'default' },
|
304
|
+
sidekiq_schedule_options: { delay: 0 }
|
305
|
+
|
306
|
+
it_behaves_like 'interactor success'
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
%w[OrganizerWithAsyncAction].each do |interactor_class|
|
312
|
+
context "when attributes for #{interactor_class} class" do
|
313
|
+
let(:interactor) { Module.const_get interactor_class }
|
314
|
+
|
315
|
+
context 'when attributes are valid' do
|
316
|
+
let(:context) do
|
317
|
+
{ key: 'value' }
|
318
|
+
end
|
319
|
+
|
320
|
+
it_behaves_like 'sidekiq worker', interactor_class: 'AsyncActionWithSidekiqScheduleOption',
|
321
|
+
sidekiq_options: { queue: 'default' },
|
322
|
+
sidekiq_schedule_options: { delay: 0 }
|
323
|
+
|
324
|
+
it_behaves_like 'interactor success'
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
%w[AsyncActionWithSidekiqOption].each do |interactor_class|
|
330
|
+
context "when attributes for #{interactor_class} class" do
|
331
|
+
let(:interactor) { Module.const_get interactor_class }
|
332
|
+
|
333
|
+
context 'when attributes are valid' do
|
334
|
+
let(:context) do
|
335
|
+
{ key: 'value' }
|
336
|
+
end
|
337
|
+
|
338
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'low_priority' },
|
339
|
+
sidekiq_schedule_options: { delay: 0 }
|
340
|
+
|
341
|
+
it_behaves_like 'interactor success'
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
%w[AsyncActionWithSidekiqScheduleOption].each do |interactor_class|
|
347
|
+
context "when attributes for #{interactor_class} class" do
|
348
|
+
let(:interactor) { Module.const_get interactor_class }
|
349
|
+
|
350
|
+
context 'when attributes are valid' do
|
351
|
+
let(:context) do
|
352
|
+
{ key: 'value' }
|
353
|
+
end
|
354
|
+
|
355
|
+
it_behaves_like 'sidekiq worker', interactor_class: interactor_class, sidekiq_options: { queue: 'default' },
|
356
|
+
sidekiq_schedule_options: { perform_in: 5 }
|
357
|
+
|
358
|
+
it_behaves_like 'interactor success'
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
%w[CustomizedBadSidekiqOption CustomizedBadSidekiqScheduleOption].each do |interactor_class|
|
364
|
+
context "when attributes for #{interactor_class} class" do
|
365
|
+
let(:interactor) { Module.const_get interactor_class }
|
366
|
+
|
367
|
+
context 'when attributes are valid' do
|
368
|
+
let(:context) do
|
369
|
+
{ key: 'value' }
|
370
|
+
end
|
371
|
+
|
372
|
+
it_behaves_like 'there is no sidekiq worker'
|
373
|
+
|
374
|
+
it_behaves_like 'interactor failure'
|
375
|
+
end
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sidekiq/testing'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.expect_with :rspec do |expectations|
|
7
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
8
|
+
end
|
9
|
+
|
10
|
+
config.mock_with :rspec do |mocks|
|
11
|
+
mocks.verify_partial_doubles = true
|
12
|
+
end
|
13
|
+
|
14
|
+
config.filter_run :focus
|
15
|
+
config.run_all_when_everything_filtered = true
|
16
|
+
|
17
|
+
config.disable_monkey_patching!
|
18
|
+
|
19
|
+
config.warnings = true
|
20
|
+
|
21
|
+
config.default_formatter = 'doc' if config.files_to_run.one?
|
22
|
+
|
23
|
+
config.profile_examples = 0
|
24
|
+
|
25
|
+
config.order = :random
|
26
|
+
|
27
|
+
Kernel.srand config.seed
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: interactor-sidekiq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabriel Rocha
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-06-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: interactor
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sidekiq
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.1'
|
41
|
+
description: Async Interactor using Sidekiq
|
42
|
+
email:
|
43
|
+
- gabrielras100@gmail.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".github/ci.yml"
|
49
|
+
- ".gitignore"
|
50
|
+
- ".rspec"
|
51
|
+
- Gemfile
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- interactor-sidekiq.gemspec
|
56
|
+
- lib/interactor/sidekiq.rb
|
57
|
+
- lib/interactor/sidekiq/version.rb
|
58
|
+
- spec/dummy_app/app.rb
|
59
|
+
- spec/interactor/sidekiq_perform_spec.rb
|
60
|
+
- spec/interactor/sidekiq_worker_spec.rb
|
61
|
+
- spec/spec_helper.rb
|
62
|
+
homepage: https://github.com/gabrielras/interactor-sidekiq
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubygems_version: 3.3.16
|
82
|
+
signing_key:
|
83
|
+
specification_version: 4
|
84
|
+
summary: Async Interactor using Sidekiq
|
85
|
+
test_files:
|
86
|
+
- spec/dummy_app/app.rb
|
87
|
+
- spec/interactor/sidekiq_perform_spec.rb
|
88
|
+
- spec/interactor/sidekiq_worker_spec.rb
|
89
|
+
- spec/spec_helper.rb
|