acidic_job 1.0.0.rc2 → 1.0.0.rc4
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/.rubocop-https---www-goodcop-style-base-yml +1051 -0
- data/.rubocop.yml +4 -76
- data/Gemfile.lock +89 -81
- data/README.md +6 -6
- data/acidic_job.gemspec +0 -2
- data/app/models/acidic_job/entry.rb +9 -7
- data/app/models/acidic_job/execution.rb +45 -11
- data/app/models/acidic_job/value.rb +2 -0
- data/bin/console +3 -3
- data/lib/acidic_job/builder.rb +22 -6
- data/lib/acidic_job/context.rb +36 -20
- data/lib/acidic_job/errors.rb +22 -1
- data/lib/acidic_job/log_subscriber.rb +11 -13
- data/lib/acidic_job/plugin_context.rb +74 -0
- data/lib/acidic_job/plugins/transactional_step.rb +38 -0
- data/lib/acidic_job/serializer.rb +28 -0
- data/lib/acidic_job/serializers/job_serializer.rb +1 -1
- data/lib/acidic_job/serializers/range_serializer.rb +1 -3
- data/lib/acidic_job/testing.rb +10 -12
- data/lib/acidic_job/version.rb +1 -1
- data/lib/acidic_job/workflow.rb +104 -52
- data/lib/acidic_job.rb +7 -2
- data/lib/generators/acidic_job/install_generator.rb +5 -5
- data/lib/generators/acidic_job/templates/create_acidic_job_tables_migration.rb.erb +12 -11
- metadata +6 -30
data/.rubocop.yml
CHANGED
@@ -1,83 +1,11 @@
|
|
1
|
-
require:
|
2
|
-
- rubocop-minitest
|
3
|
-
- rubocop-rake
|
4
|
-
|
5
1
|
AllCops:
|
6
|
-
Exclude:
|
7
|
-
- inline.rb
|
8
|
-
- test/simulation.rb
|
9
|
-
- vendor/bundle/**/*
|
10
2
|
TargetRubyVersion: 2.7
|
11
3
|
NewCops: enable
|
4
|
+
DisabledByDefault: true
|
5
|
+
SuggestExtensions: false
|
12
6
|
|
13
|
-
|
14
|
-
Enabled: true
|
15
|
-
EnforcedStyle: double_quotes
|
16
|
-
|
17
|
-
Style/StringLiteralsInInterpolation:
|
18
|
-
Enabled: true
|
19
|
-
EnforcedStyle: double_quotes
|
20
|
-
|
21
|
-
Layout/LineLength:
|
22
|
-
Max: 120
|
23
|
-
|
24
|
-
Gemspec/DevelopmentDependencies:
|
25
|
-
Enabled: false
|
26
|
-
|
27
|
-
Style/Documentation:
|
28
|
-
Enabled: false
|
29
|
-
|
30
|
-
Metrics/ModuleLength:
|
31
|
-
Enabled: false
|
32
|
-
|
33
|
-
Metrics/AbcSize:
|
34
|
-
Enabled: false
|
35
|
-
|
36
|
-
Metrics/MethodLength:
|
37
|
-
Enabled: false
|
38
|
-
|
39
|
-
Metrics/BlockLength:
|
40
|
-
Enabled: false
|
41
|
-
|
42
|
-
Metrics/CyclomaticComplexity:
|
43
|
-
Enabled: false
|
44
|
-
|
45
|
-
Metrics/PerceivedComplexity:
|
46
|
-
Enabled: false
|
47
|
-
|
48
|
-
Metrics/ClassLength:
|
49
|
-
Enabled: false
|
50
|
-
|
51
|
-
Minitest/MultipleAssertions:
|
52
|
-
Enabled: false
|
53
|
-
|
54
|
-
Style/ModuleFunction:
|
55
|
-
Enabled: false
|
56
|
-
|
57
|
-
Style/RaiseArgs:
|
58
|
-
EnforcedStyle: compact
|
59
|
-
|
60
|
-
Style/NegatedIf:
|
61
|
-
Enabled: false
|
62
|
-
|
63
|
-
Style/ClassAndModuleChildren:
|
64
|
-
Exclude:
|
65
|
-
- test/**/*_test.rb
|
66
|
-
|
67
|
-
Naming/VariableNumber:
|
68
|
-
Exclude:
|
69
|
-
- test/**/*_test.rb
|
70
|
-
|
71
|
-
Style/SingleLineMethods:
|
72
|
-
Exclude:
|
73
|
-
- test/**/*_test.rb
|
7
|
+
inherit_from: "https://www.goodcop.style/base.yml"
|
74
8
|
|
75
9
|
Lint/ConstantDefinitionInBlock:
|
76
10
|
Exclude:
|
77
|
-
- test/**/*
|
78
|
-
|
79
|
-
Minitest/AssertTruthy:
|
80
|
-
Enabled: false
|
81
|
-
|
82
|
-
Minitest/RefuteFalse:
|
83
|
-
Enabled: false
|
11
|
+
- 'test/**/*'
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
acidic_job (1.0.0.
|
4
|
+
acidic_job (1.0.0.rc4)
|
5
5
|
activejob (>= 7.1)
|
6
6
|
activerecord (>= 7.1)
|
7
7
|
activesupport (>= 7.1)
|
@@ -11,41 +11,41 @@ PATH
|
|
11
11
|
GEM
|
12
12
|
remote: https://rubygems.org/
|
13
13
|
specs:
|
14
|
-
actionmailer (
|
15
|
-
actionpack (=
|
16
|
-
actionview (=
|
17
|
-
activejob (=
|
18
|
-
activesupport (=
|
14
|
+
actionmailer (8.0.2)
|
15
|
+
actionpack (= 8.0.2)
|
16
|
+
actionview (= 8.0.2)
|
17
|
+
activejob (= 8.0.2)
|
18
|
+
activesupport (= 8.0.2)
|
19
19
|
mail (>= 2.8.0)
|
20
20
|
rails-dom-testing (~> 2.2)
|
21
|
-
actionpack (
|
22
|
-
actionview (=
|
23
|
-
activesupport (=
|
21
|
+
actionpack (8.0.2)
|
22
|
+
actionview (= 8.0.2)
|
23
|
+
activesupport (= 8.0.2)
|
24
24
|
nokogiri (>= 1.8.5)
|
25
|
-
|
26
|
-
rack (>= 2.2.4, < 3.2)
|
25
|
+
rack (>= 2.2.4)
|
27
26
|
rack-session (>= 1.0.1)
|
28
27
|
rack-test (>= 0.6.3)
|
29
28
|
rails-dom-testing (~> 2.2)
|
30
29
|
rails-html-sanitizer (~> 1.6)
|
31
30
|
useragent (~> 0.16)
|
32
|
-
actionview (
|
33
|
-
activesupport (=
|
31
|
+
actionview (8.0.2)
|
32
|
+
activesupport (= 8.0.2)
|
34
33
|
builder (~> 3.1)
|
35
34
|
erubi (~> 1.11)
|
36
35
|
rails-dom-testing (~> 2.2)
|
37
36
|
rails-html-sanitizer (~> 1.6)
|
38
|
-
activejob (
|
39
|
-
activesupport (=
|
37
|
+
activejob (8.0.2)
|
38
|
+
activesupport (= 8.0.2)
|
40
39
|
globalid (>= 0.3.6)
|
41
|
-
activemodel (
|
42
|
-
activesupport (=
|
43
|
-
activerecord (
|
44
|
-
activemodel (=
|
45
|
-
activesupport (=
|
40
|
+
activemodel (8.0.2)
|
41
|
+
activesupport (= 8.0.2)
|
42
|
+
activerecord (8.0.2)
|
43
|
+
activemodel (= 8.0.2)
|
44
|
+
activesupport (= 8.0.2)
|
46
45
|
timeout (>= 0.4.0)
|
47
|
-
activesupport (
|
46
|
+
activesupport (8.0.2)
|
48
47
|
base64
|
48
|
+
benchmark (>= 0.3)
|
49
49
|
bigdecimal
|
50
50
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
51
51
|
connection_pool (>= 2.2.5)
|
@@ -55,33 +55,38 @@ GEM
|
|
55
55
|
minitest (>= 5.1)
|
56
56
|
securerandom (>= 0.3)
|
57
57
|
tzinfo (~> 2.0, >= 2.0.5)
|
58
|
-
|
58
|
+
uri (>= 0.13.1)
|
59
|
+
ast (2.4.3)
|
59
60
|
base64 (0.2.0)
|
60
|
-
|
61
|
+
benchmark (0.4.0)
|
62
|
+
bigdecimal (3.1.9)
|
61
63
|
builder (3.3.0)
|
62
64
|
chaotic_job (0.3.0)
|
63
|
-
combustion (1.
|
65
|
+
combustion (1.5.0)
|
64
66
|
activesupport (>= 3.0.0)
|
65
67
|
railties (>= 3.0.0)
|
66
68
|
thor (>= 0.14.6)
|
67
|
-
concurrent-ruby (1.3.
|
68
|
-
connection_pool (2.
|
69
|
+
concurrent-ruby (1.3.5)
|
70
|
+
connection_pool (2.5.3)
|
69
71
|
crass (1.0.6)
|
70
72
|
date (3.4.1)
|
71
|
-
docile (1.4.
|
73
|
+
docile (1.4.1)
|
72
74
|
drb (2.2.1)
|
73
|
-
erubi (1.13.
|
75
|
+
erubi (1.13.1)
|
74
76
|
globalid (1.2.1)
|
75
77
|
activesupport (>= 6.1)
|
76
|
-
i18n (1.14.
|
78
|
+
i18n (1.14.7)
|
77
79
|
concurrent-ruby (~> 1.0)
|
78
|
-
io-console (0.
|
79
|
-
irb (1.
|
80
|
+
io-console (0.8.0)
|
81
|
+
irb (1.15.2)
|
82
|
+
pp (>= 0.6.0)
|
80
83
|
rdoc (>= 4.0.0)
|
81
84
|
reline (>= 0.4.2)
|
82
|
-
json (2.
|
83
|
-
|
84
|
-
|
85
|
+
json (2.12.0)
|
86
|
+
language_server-protocol (3.17.0.5)
|
87
|
+
lint_roller (1.1.0)
|
88
|
+
logger (1.7.0)
|
89
|
+
loofah (2.24.1)
|
85
90
|
crass (~> 1.0.2)
|
86
91
|
nokogiri (>= 1.12.0)
|
87
92
|
mail (2.8.1)
|
@@ -90,46 +95,52 @@ GEM
|
|
90
95
|
net-pop
|
91
96
|
net-smtp
|
92
97
|
mini_mime (1.1.5)
|
93
|
-
mini_portile2 (2.8.
|
94
|
-
minitest (5.25.
|
95
|
-
net-imap (0.5.
|
98
|
+
mini_portile2 (2.8.9)
|
99
|
+
minitest (5.25.5)
|
100
|
+
net-imap (0.5.8)
|
96
101
|
date
|
97
102
|
net-protocol
|
98
103
|
net-pop (0.1.2)
|
99
104
|
net-protocol
|
100
105
|
net-protocol (0.2.2)
|
101
106
|
timeout
|
102
|
-
net-smtp (0.5.
|
107
|
+
net-smtp (0.5.1)
|
103
108
|
net-protocol
|
104
|
-
nokogiri (1.
|
109
|
+
nokogiri (1.18.8)
|
105
110
|
mini_portile2 (~> 2.8.2)
|
106
111
|
racc (~> 1.4)
|
107
|
-
nokogiri (1.
|
112
|
+
nokogiri (1.18.8-x86_64-darwin)
|
108
113
|
racc (~> 1.4)
|
109
|
-
parallel (1.
|
110
|
-
parser (3.
|
114
|
+
parallel (1.27.0)
|
115
|
+
parser (3.3.8.0)
|
111
116
|
ast (~> 2.4.1)
|
112
|
-
|
117
|
+
racc
|
118
|
+
pp (0.6.2)
|
119
|
+
prettyprint
|
120
|
+
prettyprint (0.2.0)
|
121
|
+
prism (1.4.0)
|
122
|
+
psych (5.2.6)
|
123
|
+
date
|
113
124
|
stringio
|
114
125
|
racc (1.8.1)
|
115
|
-
rack (3.1.
|
116
|
-
rack-session (2.
|
126
|
+
rack (3.1.15)
|
127
|
+
rack-session (2.1.1)
|
128
|
+
base64 (>= 0.1.0)
|
117
129
|
rack (>= 3.0.0)
|
118
|
-
rack-test (2.
|
130
|
+
rack-test (2.2.0)
|
119
131
|
rack (>= 1.3)
|
120
|
-
rackup (2.1
|
132
|
+
rackup (2.2.1)
|
121
133
|
rack (>= 3)
|
122
|
-
webrick (~> 1.8)
|
123
134
|
rails-dom-testing (2.2.0)
|
124
135
|
activesupport (>= 5.0.0)
|
125
136
|
minitest
|
126
137
|
nokogiri (>= 1.6)
|
127
|
-
rails-html-sanitizer (1.6.
|
138
|
+
rails-html-sanitizer (1.6.2)
|
128
139
|
loofah (~> 2.21)
|
129
|
-
nokogiri (
|
130
|
-
railties (
|
131
|
-
actionpack (=
|
132
|
-
activesupport (=
|
140
|
+
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
141
|
+
railties (8.0.2)
|
142
|
+
actionpack (= 8.0.2)
|
143
|
+
activesupport (= 8.0.2)
|
133
144
|
irb (~> 1.13)
|
134
145
|
rackup (>= 1.0.0)
|
135
146
|
rake (>= 12.2)
|
@@ -137,48 +148,47 @@ GEM
|
|
137
148
|
zeitwerk (~> 2.6)
|
138
149
|
rainbow (3.1.1)
|
139
150
|
rake (13.2.1)
|
140
|
-
rdoc (6.
|
151
|
+
rdoc (6.13.1)
|
141
152
|
psych (>= 4.0.0)
|
142
|
-
regexp_parser (2.
|
143
|
-
reline (0.
|
153
|
+
regexp_parser (2.10.0)
|
154
|
+
reline (0.6.1)
|
144
155
|
io-console (~> 0.5)
|
145
|
-
|
146
|
-
rubocop (1.50.2)
|
156
|
+
rubocop (1.75.6)
|
147
157
|
json (~> 2.3)
|
158
|
+
language_server-protocol (~> 3.17.0.2)
|
159
|
+
lint_roller (~> 1.1.0)
|
148
160
|
parallel (~> 1.10)
|
149
|
-
parser (>= 3.
|
161
|
+
parser (>= 3.3.0.2)
|
150
162
|
rainbow (>= 2.2.2, < 4.0)
|
151
|
-
regexp_parser (>=
|
152
|
-
|
153
|
-
rubocop-ast (>= 1.28.0, < 2.0)
|
163
|
+
regexp_parser (>= 2.9.3, < 3.0)
|
164
|
+
rubocop-ast (>= 1.44.0, < 2.0)
|
154
165
|
ruby-progressbar (~> 1.7)
|
155
|
-
unicode-display_width (>= 2.4.0, <
|
156
|
-
rubocop-ast (1.
|
157
|
-
parser (>= 3.
|
158
|
-
|
159
|
-
rubocop (>= 1.39, < 2.0)
|
160
|
-
rubocop-rake (0.6.0)
|
161
|
-
rubocop (~> 1.0)
|
166
|
+
unicode-display_width (>= 2.4.0, < 4.0)
|
167
|
+
rubocop-ast (1.44.1)
|
168
|
+
parser (>= 3.3.7.2)
|
169
|
+
prism (~> 1.4)
|
162
170
|
ruby-progressbar (1.13.0)
|
163
|
-
securerandom (0.
|
171
|
+
securerandom (0.4.1)
|
164
172
|
simplecov (0.22.0)
|
165
173
|
docile (~> 1.1)
|
166
174
|
simplecov-html (~> 0.11)
|
167
175
|
simplecov_json_formatter (~> 0.1)
|
168
|
-
simplecov-html (0.
|
176
|
+
simplecov-html (0.13.1)
|
169
177
|
simplecov_json_formatter (0.1.4)
|
170
|
-
sqlite3 (
|
178
|
+
sqlite3 (2.6.0)
|
171
179
|
mini_portile2 (~> 2.8.0)
|
172
|
-
sqlite3 (
|
173
|
-
stringio (3.1.
|
180
|
+
sqlite3 (2.6.0-x86_64-darwin)
|
181
|
+
stringio (3.1.7)
|
174
182
|
thor (1.3.2)
|
175
|
-
timeout (0.4.
|
183
|
+
timeout (0.4.3)
|
176
184
|
tzinfo (2.0.6)
|
177
185
|
concurrent-ruby (~> 1.0)
|
178
|
-
unicode-display_width (
|
179
|
-
|
180
|
-
|
181
|
-
|
186
|
+
unicode-display_width (3.1.4)
|
187
|
+
unicode-emoji (~> 4.0, >= 4.0.4)
|
188
|
+
unicode-emoji (4.0.4)
|
189
|
+
uri (1.0.3)
|
190
|
+
useragent (0.16.11)
|
191
|
+
zeitwerk (2.7.2)
|
182
192
|
|
183
193
|
PLATFORMS
|
184
194
|
ruby
|
@@ -192,8 +202,6 @@ DEPENDENCIES
|
|
192
202
|
minitest
|
193
203
|
rake
|
194
204
|
rubocop
|
195
|
-
rubocop-minitest
|
196
|
-
rubocop-rake
|
197
205
|
simplecov
|
198
206
|
sqlite3
|
199
207
|
|
data/README.md
CHANGED
@@ -24,7 +24,7 @@ With AcidicJob, you can write reliable and repeatable multi-step distributed ope
|
|
24
24
|
Install the gem and add to the application's Gemfile by executing:
|
25
25
|
|
26
26
|
```sh
|
27
|
-
bundle add acidic_job --version "1.0.0.
|
27
|
+
bundle add acidic_job --version "1.0.0.rc3"
|
28
28
|
```
|
29
29
|
|
30
30
|
If `bundler` is not being used to manage dependencies, install the gem by executing:
|
@@ -109,7 +109,7 @@ The block passed to `execute_workflow` is where you define the steps of the work
|
|
109
109
|
The `step` method is the only method available on the yielded workflow builder object, and it simply takes the name of a method available in the job.
|
110
110
|
|
111
111
|
> [!IMPORTANT]
|
112
|
-
> In order to craft resilient workflows, you need to ensure that each step method wraps a single unit of IO-bound work. You **
|
112
|
+
> In order to craft resilient workflows, you need to ensure that each step method wraps a single unit of IO-bound work. You **should not** have a step method that performs multiple IO-bound operations, like writing to your database and calling an external API. Steps should be as granular and self-contained as possible. This allows your own logic to be more durable in case of failures in third-party APIs, network errors, and so on. So, the rule of thumb is to have only one _state mutation_ per step. And this rule of thumb graduates to a hard and fast rule for _foreign state mutations_. You **must** only have **one** foreign state mutation per step, where a foreign state mutation is any operation that writes to a system beyond your own boundaries. This might be creating a charge on Stripe, adding a DNS record, or sending an email.[^1]
|
113
113
|
|
114
114
|
[^1]: I first learned this rule from [Brandur Leach](https://twitter.com/brandur) reminds in his post on [Implementing Stripe-like Idempotency Keys in Postgres](https://brandur.org/idempotency-keys).
|
115
115
|
|
@@ -188,7 +188,7 @@ class Job < ActiveJob::Base
|
|
188
188
|
|
189
189
|
### Orchestrating steps
|
190
190
|
|
191
|
-
In addition to the workflow definition setup, `AcidicJob` also provides a couple of methods to precisely control the workflow step execution. From within any step method, you can call either `repeat_step!` or `
|
191
|
+
In addition to the workflow definition setup, `AcidicJob` also provides a couple of methods to precisely control the workflow step execution. From within any step method, you can call either `repeat_step!` or `halt_workflow!`.
|
192
192
|
|
193
193
|
`repeat_step!` will cause the current step to be re-executed on the next iteration of the workflow. This is useful when you need to traverse a collection of items and perform the same operation on each item. For example, if you need to send an email to each user in a collection, you could do something like this:
|
194
194
|
|
@@ -218,7 +218,7 @@ end
|
|
218
218
|
|
219
219
|
This example demonstrates how you can leverage the basic building blocks provided by `AcidicJob` to orchestrate complex workflows. In this case, the `notify_users` step sends an email to each user in the collection, one at a time, and resiliently handles errors by storing a cursor in the `ctx` object to keep track of the current user being processed. If any error occurs while traversing the `@users` collection, the job will be retried, and the `notify_users` step will be re-executed from the last successful cursor position.
|
220
220
|
|
221
|
-
The `
|
221
|
+
The `halt_workflow!` method, on the other hand, stops not just the execution of the current step but the job as a whole. This is useful when you either need to conditionally stop the workflow based on some criteria or need to delay the job for some amount of time before being restarted. For example, if you need to send a follow-up email to a user 14 days after they sign up, you could do something like this:
|
222
222
|
|
223
223
|
```ruby
|
224
224
|
class Job < ActiveJob::Base
|
@@ -240,7 +240,7 @@ class Job < ActiveJob::Base
|
|
240
240
|
def send_welcome_email
|
241
241
|
if ctx[:halt]
|
242
242
|
ctx[:halt] = false
|
243
|
-
|
243
|
+
halt_workflow!
|
244
244
|
end
|
245
245
|
UserMailer.with(user: @user).welcome_email.deliver_later
|
246
246
|
end
|
@@ -252,7 +252,7 @@ In this example, the `delay` step creates a new instance of the job and enqueues
|
|
252
252
|
|
253
253
|
### Overview
|
254
254
|
|
255
|
-
`AcidicJob` is a library that provides a small yet powerful set of tools to build cohesive and resilient workflows in your Active Jobs. All of the tools are made available by `include`ing the `AcidicJob::Workflow` module. The primary and most important tool is the `execute_workflow` method, which you call within your `perform` method. Then, if you need to store any contextual data, you use the `ctx` objects setters and getters. Finally, within any step methods, you can call `repeat_step!` or `
|
255
|
+
`AcidicJob` is a library that provides a small yet powerful set of tools to build cohesive and resilient workflows in your Active Jobs. All of the tools are made available by `include`ing the `AcidicJob::Workflow` module. The primary and most important tool is the `execute_workflow` method, which you call within your `perform` method. Then, if you need to store any contextual data, you use the `ctx` objects setters and getters. Finally, within any step methods, you can call `repeat_step!` or `halt_workflow!` to control the execution of the workflow. If you need, you can also access the `execution` Active Record object to get information about the current execution of the workflow. With these lightweight tools, you can build complex workflows that are resilient to failures and can handle a wide range of use cases.
|
256
256
|
|
257
257
|
|
258
258
|
## Testing
|
data/acidic_job.gemspec
CHANGED
@@ -41,8 +41,6 @@ Gem::Specification.new do |spec|
|
|
41
41
|
spec.add_development_dependency "minitest"
|
42
42
|
spec.add_development_dependency "rake"
|
43
43
|
spec.add_development_dependency "rubocop"
|
44
|
-
spec.add_development_dependency "rubocop-minitest"
|
45
|
-
spec.add_development_dependency "rubocop-rake"
|
46
44
|
spec.add_development_dependency "simplecov"
|
47
45
|
spec.add_development_dependency "sqlite3"
|
48
46
|
|
@@ -4,16 +4,18 @@ module AcidicJob
|
|
4
4
|
class Entry < Record
|
5
5
|
belongs_to :execution, class_name: "AcidicJob::Execution"
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
serialize :data, coder: AcidicJob::Serializer
|
8
|
+
|
9
|
+
scope :for_step, -> (step) { where(step: step) }
|
10
|
+
scope :for_action, -> (action) { where(action: action) }
|
11
|
+
scope :ordered, -> { order(timestamp: :asc) }
|
10
12
|
|
11
|
-
def
|
12
|
-
|
13
|
+
def self.most_recent
|
14
|
+
order(created_at: :desc).first
|
13
15
|
end
|
14
16
|
|
15
|
-
def
|
16
|
-
action ==
|
17
|
+
def action?(check)
|
18
|
+
action == check
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -2,25 +2,40 @@
|
|
2
2
|
|
3
3
|
module AcidicJob
|
4
4
|
class Execution < Record
|
5
|
-
has_many :entries, class_name: "AcidicJob::Entry"
|
6
|
-
has_many :values, class_name: "AcidicJob::Value"
|
5
|
+
has_many :entries, class_name: "AcidicJob::Entry", dependent: :destroy
|
6
|
+
has_many :values, class_name: "AcidicJob::Value", dependent: :destroy
|
7
|
+
|
8
|
+
serialize :definition, coder: AcidicJob::Serializer
|
7
9
|
|
8
10
|
validates :idempotency_key, presence: true # uniqueness constraint is enforced at the database level
|
9
11
|
validates :serialized_job, presence: true
|
10
12
|
|
11
|
-
scope :finished, -> {
|
12
|
-
|
13
|
-
|
14
|
-
|
13
|
+
scope :finished, -> {
|
14
|
+
where(recover_to: FINISHED_RECOVERY_POINT)
|
15
|
+
}
|
16
|
+
scope :outstanding, -> {
|
17
|
+
where.not(recover_to: FINISHED_RECOVERY_POINT).or(where(recover_to: [nil, ""]))
|
18
|
+
}
|
19
|
+
scope :clearable, -> (finished_before: AcidicJob.clear_finished_executions_after.ago) {
|
20
|
+
finished.where(last_run_at: ...finished_before)
|
21
|
+
}
|
15
22
|
|
16
|
-
def
|
23
|
+
def self.clear_finished_in_batches(batch_size: 500, finished_before: AcidicJob.clear_finished_executions_after.ago, sleep_between_batches: 0)
|
24
|
+
loop do
|
25
|
+
records_deleted = clearable(finished_before: finished_before).limit(batch_size).delete_all
|
26
|
+
sleep(sleep_between_batches) if sleep_between_batches > 0
|
27
|
+
break if records_deleted == 0
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def record!(step:, action:, timestamp: Time.current, **kwargs)
|
17
32
|
AcidicJob.instrument(:record_entry, step: step, action: action, timestamp: timestamp, data: kwargs) do
|
18
|
-
entries.
|
33
|
+
entries.insert!({
|
19
34
|
step: step,
|
20
35
|
action: action,
|
21
36
|
timestamp: timestamp,
|
22
|
-
data: kwargs.
|
23
|
-
)
|
37
|
+
data: kwargs.except(:ignored),
|
38
|
+
})
|
24
39
|
end
|
25
40
|
end
|
26
41
|
|
@@ -29,7 +44,26 @@ module AcidicJob
|
|
29
44
|
end
|
30
45
|
|
31
46
|
def finished?
|
32
|
-
recover_to.to_s == FINISHED_RECOVERY_POINT
|
47
|
+
recover_to.to_s == FINISHED_RECOVERY_POINT ||
|
48
|
+
recover_to.to_s == "FINISHED" # old value pre-1.0, remove at v1.0
|
49
|
+
end
|
50
|
+
|
51
|
+
def defined?(step)
|
52
|
+
if definition.key?("steps")
|
53
|
+
definition["steps"].key?(step)
|
54
|
+
else
|
55
|
+
# TODO: add deprecation warning
|
56
|
+
definition.key?(step)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def definition_for(step)
|
61
|
+
if definition.key?("steps")
|
62
|
+
definition["steps"].fetch(step)
|
63
|
+
else
|
64
|
+
# TODO: add deprecation warning
|
65
|
+
definition.fetch(step)
|
66
|
+
end
|
33
67
|
end
|
34
68
|
|
35
69
|
def deserialized_job
|
data/bin/console
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require "bundler/setup"
|
5
|
-
require "rails"
|
6
|
-
require "acidic_job"
|
7
5
|
|
8
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
9
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -11,7 +9,9 @@ require "acidic_job"
|
|
11
9
|
require "combustion"
|
12
10
|
require "sqlite3"
|
13
11
|
Combustion.path = "test/combustion"
|
14
|
-
Combustion.initialize! :active_record
|
12
|
+
Combustion.initialize! :active_record, :active_job
|
13
|
+
|
14
|
+
require "acidic_job"
|
15
15
|
|
16
16
|
# (If you use this, don't forget to add pry to your Gemfile!)
|
17
17
|
# require "pry"
|
data/lib/acidic_job/builder.rb
CHANGED
@@ -4,12 +4,21 @@ module AcidicJob
|
|
4
4
|
class Builder
|
5
5
|
attr_reader :steps
|
6
6
|
|
7
|
-
def initialize
|
7
|
+
def initialize(plugins)
|
8
|
+
@plugins = plugins
|
8
9
|
@steps = []
|
9
10
|
end
|
10
11
|
|
11
|
-
def step(method_name,
|
12
|
-
|
12
|
+
def step(method_name, **kwargs)
|
13
|
+
step = { "does" => method_name.to_s }
|
14
|
+
|
15
|
+
@plugins.each do |plugin|
|
16
|
+
next unless kwargs.key?(plugin.keyword)
|
17
|
+
|
18
|
+
step[plugin.keyword.to_s] = plugin.validate(kwargs[plugin.keyword])
|
19
|
+
end
|
20
|
+
|
21
|
+
@steps << step
|
13
22
|
@steps
|
14
23
|
end
|
15
24
|
|
@@ -17,13 +26,20 @@ module AcidicJob
|
|
17
26
|
# [ { does: "step 1", transactional: true }, { does: "step 2", transactional: false }, ... ]
|
18
27
|
@steps << { "does" => FINISHED_RECOVERY_POINT }
|
19
28
|
|
20
|
-
|
29
|
+
definition = {
|
30
|
+
"meta" => {
|
31
|
+
"version" => VERSION,
|
32
|
+
},
|
33
|
+
"steps" => {},
|
34
|
+
}
|
35
|
+
|
36
|
+
definition.tap do |workflow|
|
21
37
|
@steps.each_cons(2).map do |enter_step, exit_step|
|
22
38
|
enter_name = enter_step["does"]
|
23
|
-
workflow[enter_name] = enter_step.merge("then" => exit_step["does"])
|
39
|
+
workflow["steps"][enter_name] = enter_step.merge("then" => exit_step["does"])
|
24
40
|
end
|
25
41
|
end
|
26
|
-
# { "step 1": { does: "step 1", transactional: true, then: "step 2" }, ... }
|
42
|
+
# { meta: { ... }, steps: { "step 1": { does: "step 1", transactional: true, then: "step 2" }, ... } }
|
27
43
|
end
|
28
44
|
end
|
29
45
|
end
|