acidic_job 1.0.0.pre9 → 1.0.0.pre10
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/Gemfile.lock +1 -1
- data/lib/acidic_job/extensions/action_mailer.rb +14 -5
- data/lib/acidic_job/extensions/active_job.rb +10 -1
- data/lib/acidic_job/extensions/noticed.rb +13 -4
- data/lib/acidic_job/extensions/sidekiq.rb +10 -1
- data/lib/acidic_job/idempotency_key.rb +48 -6
- data/lib/acidic_job/staging.rb +10 -0
- data/lib/acidic_job/version.rb +1 -1
- data/lib/acidic_job.rb +25 -25
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 95b8fbdebe38a639f9b905df6ac1f5b853b09383d0810696c89c6c9e001efffe
|
4
|
+
data.tar.gz: b7e92f9b98461e2f39122405b6e3278fb7ed8a0edec65963026f7e2665bb5042
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 13b5ea59163e833f5b698625d841fdcdc0a630b46880526df3274926e696b227b41946fd6622e121b9afd90ad3cc94589f6e864188e871d70f03c77168e3ad5e
|
7
|
+
data.tar.gz: 27e5b74147706f227875f4f4a61f084298c42e615149cb0a74670af841df8fcce9eb4f95d11935561912af61d6d8759c21c9ca9862e897f765b49c78b1e42ebd
|
data/Gemfile.lock
CHANGED
@@ -7,18 +7,27 @@ module AcidicJob
|
|
7
7
|
module ActionMailer
|
8
8
|
extend ActiveSupport::Concern
|
9
9
|
|
10
|
-
def deliver_acidicly(_options = {})
|
11
|
-
|
10
|
+
def deliver_acidicly(_options = {}, idempotency_key: nil, unique_by: nil)
|
11
|
+
job_class = ::ActionMailer::MailDeliveryJob
|
12
12
|
|
13
13
|
job_args = [@mailer_class.name, @action.to_s, "deliver_now", @params, *@args]
|
14
14
|
# for Sidekiq, this depends on the Sidekiq::Serialization extension
|
15
|
-
serialized_job =
|
15
|
+
serialized_job = job_class.new(job_args).serialize
|
16
|
+
acidic_identifier = job_class.respond_to?(:acidic_identifier) ? job_class.acidic_identifier : :job_id
|
17
|
+
# use either [1] provided key, [2] provided uniqueness constraint, or [3] computed key
|
18
|
+
key = if idempotency_key
|
19
|
+
idempotency_key
|
20
|
+
elsif unique_by
|
21
|
+
IdempotencyKey.generate(unique_by: unique_by, job_class: job_class.name)
|
22
|
+
else
|
23
|
+
IdempotencyKey.new(acidic_identifier).value_for(serialized_job)
|
24
|
+
end
|
16
25
|
|
17
26
|
AcidicJob::Run.create!(
|
18
27
|
staged: true,
|
19
|
-
job_class:
|
28
|
+
job_class: job_class.name,
|
20
29
|
serialized_job: serialized_job,
|
21
|
-
idempotency_key:
|
30
|
+
idempotency_key: key
|
22
31
|
)
|
23
32
|
end
|
24
33
|
alias deliver_transactionally deliver_acidicly
|
@@ -24,12 +24,21 @@ module AcidicJob
|
|
24
24
|
raise UnsupportedExtension unless defined?(::ActiveJob) && self < ::ActiveJob::Base
|
25
25
|
|
26
26
|
serialized_job = serialize_with_arguments(*args, **kwargs)
|
27
|
+
# use either [1] provided key, [2] provided uniqueness constraint, or [3] computed key
|
28
|
+
key = if kwargs.key?(:idempotency_key) || kwargs.key?("idempotency_key")
|
29
|
+
kwargs[:idempotency_key] || kwargs["idempotency_key"]
|
30
|
+
elsif kwargs.key?(:unique_by) || kwargs.key?("unique_by")
|
31
|
+
unique_by = [kwargs[:unique_by], kwargs["unique_by"]].compact.first
|
32
|
+
IdempotencyKey.generate(unique_by: unique_by, job_class: name)
|
33
|
+
else
|
34
|
+
IdempotencyKey.new(acidic_identifier).value_for(serialized_job)
|
35
|
+
end
|
27
36
|
|
28
37
|
AcidicJob::Run.create!(
|
29
38
|
staged: true,
|
30
39
|
job_class: name,
|
31
40
|
serialized_job: serialized_job,
|
32
|
-
idempotency_key:
|
41
|
+
idempotency_key: key
|
33
42
|
)
|
34
43
|
end
|
35
44
|
alias_method :perform_transactionally, :perform_acidicly
|
@@ -6,12 +6,12 @@ module AcidicJob
|
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
8
|
class_methods do
|
9
|
-
def deliver_acidicly(recipients)
|
10
|
-
new.deliver_acidicly(recipients)
|
9
|
+
def deliver_acidicly(recipients, idempotency_key: nil, unique_by: nil)
|
10
|
+
new.deliver_acidicly(recipients, idempotency_key: idempotency_key, unique_by: unique_by)
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def deliver_acidicly(recipients)
|
14
|
+
def deliver_acidicly(recipients, idempotency_key: nil, unique_by: nil)
|
15
15
|
# THIS IS A HACK THAT COPIES AND PASTES KEY PARTS OF THE `Noticed::Base` CODE
|
16
16
|
# IN ORDER TO ALLOW US TO TRANSACTIONALLY DELIVER NOTIFICATIONS
|
17
17
|
# THIS IS THUS LIABLE TO BREAK WHENEVER THAT GEM IS UPDATED
|
@@ -36,12 +36,21 @@ module AcidicJob
|
|
36
36
|
record: record
|
37
37
|
}
|
38
38
|
serialized_job = job_class.send(:job_or_instantiate, args).serialize
|
39
|
+
acidic_identifier = job_class.respond_to?(:acidic_identifier) ? job_class.acidic_identifier : :job_id
|
40
|
+
# use either [1] provided key, [2] provided uniqueness constraint, or [3] computed key
|
41
|
+
key = if idempotency_key
|
42
|
+
idempotency_key
|
43
|
+
elsif unique_by
|
44
|
+
IdempotencyKey.generate(unique_by: unique_by, job_class: job_class.name)
|
45
|
+
else
|
46
|
+
IdempotencyKey.new(acidic_identifier).value_for(serialized_job)
|
47
|
+
end
|
39
48
|
|
40
49
|
AcidicJob::Run.create!(
|
41
50
|
staged: true,
|
42
51
|
job_class: job_class.name,
|
43
52
|
serialized_job: serialized_job,
|
44
|
-
idempotency_key:
|
53
|
+
idempotency_key: key
|
45
54
|
)
|
46
55
|
end
|
47
56
|
end
|
@@ -58,12 +58,21 @@ module AcidicJob
|
|
58
58
|
class_methods do
|
59
59
|
def perform_acidicly(*args, **kwargs)
|
60
60
|
serialized_job = serialize_with_arguments(*args, **kwargs)
|
61
|
+
# use either [1] provided key, [2] provided uniqueness constraint, or [3] computed key
|
62
|
+
key = if kwargs.key?(:idempotency_key) || kwargs.key?("idempotency_key")
|
63
|
+
kwargs[:idempotency_key] || kwargs["idempotency_key"]
|
64
|
+
elsif kwargs.key?(:unique_by) || kwargs.key?("unique_by")
|
65
|
+
unique_by = [kwargs[:unique_by], kwargs["unique_by"]].compact.first
|
66
|
+
IdempotencyKey.generate(unique_by: unique_by, job_class: name)
|
67
|
+
else
|
68
|
+
IdempotencyKey.new(acidic_identifier).value_for(serialized_job)
|
69
|
+
end
|
61
70
|
|
62
71
|
AcidicJob::Run.create!(
|
63
72
|
staged: true,
|
64
73
|
job_class: name,
|
65
74
|
serialized_job: serialized_job,
|
66
|
-
idempotency_key:
|
75
|
+
idempotency_key: key
|
67
76
|
)
|
68
77
|
end
|
69
78
|
alias_method :perform_transactionally, :perform_acidicly
|
@@ -2,15 +2,57 @@
|
|
2
2
|
|
3
3
|
module AcidicJob
|
4
4
|
class IdempotencyKey
|
5
|
-
def self.
|
6
|
-
|
7
|
-
|
5
|
+
def self.generate(unique_by:, job_class:)
|
6
|
+
new(:job_args).value_for({ "job_class" => job_class }, Marshal.dump(unique_by))
|
7
|
+
end
|
8
|
+
|
9
|
+
def initialize(identifier = :job_id)
|
10
|
+
@identifier = identifier
|
11
|
+
end
|
12
|
+
|
13
|
+
def value_for(hash_or_job, *args, **kwargs)
|
14
|
+
return value_from_job_args(hash_or_job, *args, **kwargs) if @identifier == :job_args
|
15
|
+
|
16
|
+
value = if hash_or_job.is_a?(Hash)
|
17
|
+
value_from_job_id_for_hash(hash_or_job)
|
18
|
+
else
|
19
|
+
value_from_job_id_for_obj(hash_or_job)
|
20
|
+
end
|
21
|
+
|
22
|
+
value || value_from_job_args(hash_or_job, *args, **kwargs)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
8
26
|
|
9
|
-
|
10
|
-
|
27
|
+
def value_from_job_id_for_hash(hash)
|
28
|
+
if hash.key?("job_id")
|
29
|
+
return if hash["job_id"].nil?
|
30
|
+
return if hash["job_id"].empty?
|
31
|
+
|
32
|
+
hash["job_id"]
|
33
|
+
elsif hash.key?("jid")
|
34
|
+
return if hash["jid"].nil?
|
35
|
+
return if hash["jid"].empty?
|
36
|
+
|
37
|
+
hash["jid"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def value_from_job_id_for_obj(obj)
|
42
|
+
if obj.respond_to?(:job_id)
|
43
|
+
return if obj.job_id.nil?
|
44
|
+
return if obj.job_id.empty?
|
45
|
+
|
46
|
+
obj.job_id
|
47
|
+
elsif obj.respond_to?(:jid)
|
48
|
+
return if obj.jid.nil?
|
49
|
+
return if obj.jid.empty?
|
50
|
+
|
51
|
+
obj.jid
|
11
52
|
end
|
12
|
-
|
53
|
+
end
|
13
54
|
|
55
|
+
def value_from_job_args(hash_or_job, *args, **kwargs)
|
14
56
|
worker_class = case hash_or_job
|
15
57
|
when Hash
|
16
58
|
hash_or_job["worker"] || hash_or_job["job_class"]
|
data/lib/acidic_job/staging.rb
CHANGED
@@ -26,5 +26,15 @@ module AcidicJob
|
|
26
26
|
|
27
27
|
GlobalID::Locator.locate(staged_job_gid)
|
28
28
|
end
|
29
|
+
|
30
|
+
def identifier
|
31
|
+
return jid if defined?(jid) && !jid.nil?
|
32
|
+
return job_id if defined?(job_id) && !job_id.nil?
|
33
|
+
|
34
|
+
# might be defined already in `with_acidity` method
|
35
|
+
@__acidic_job_idempotency_key ||= IdempotencyKey.value_for(self, @__acidic_job_args, @__acidic_job_kwargs)
|
36
|
+
|
37
|
+
@__acidic_job_idempotency_key
|
38
|
+
end
|
29
39
|
end
|
30
40
|
end
|
data/lib/acidic_job/version.rb
CHANGED
data/lib/acidic_job.rb
CHANGED
@@ -46,6 +46,10 @@ module AcidicJob
|
|
46
46
|
end
|
47
47
|
|
48
48
|
klass.set_callback :perform, :after, :delete_staged_job_record, if: :was_staged_job?
|
49
|
+
|
50
|
+
klass.instance_variable_set(:@acidic_identifier, :job_id)
|
51
|
+
klass.define_singleton_method(:acidic_by_job_id) { @acidic_identifier = :job_id }
|
52
|
+
klass.define_singleton_method(:acidic_by_job_args) { @acidic_identifier = :job_args }
|
49
53
|
end
|
50
54
|
|
51
55
|
included do
|
@@ -57,33 +61,34 @@ module AcidicJob
|
|
57
61
|
AcidicJob.wire_everything_up(subclass)
|
58
62
|
super
|
59
63
|
end
|
64
|
+
|
65
|
+
def acidic_identifier
|
66
|
+
@acidic_identifier
|
67
|
+
end
|
60
68
|
end
|
61
69
|
|
62
70
|
def with_acidity(providing: {})
|
63
|
-
#
|
71
|
+
# ensure this instance variable is always defined
|
64
72
|
@__acidic_job_steps = []
|
65
|
-
steps
|
73
|
+
# execute the block to gather the info on what steps are defined for this job workflow
|
74
|
+
yield
|
66
75
|
|
67
76
|
# check that the block actually defined at least one step
|
68
77
|
# TODO: WRITE TESTS FOR FAULTY BLOCK VALUES
|
69
78
|
raise NoDefinedSteps if @__acidic_job_steps.nil? || @__acidic_job_steps.empty?
|
70
79
|
|
71
80
|
# convert the array of steps into a hash of recovery_points and next steps
|
72
|
-
workflow = define_workflow(
|
73
|
-
|
74
|
-
# determine the idempotency key value for this job run (`job_id` or `jid`)
|
75
|
-
# might be defined already in `identifier` method
|
76
|
-
# TODO: allow idempotency to be defined by args OR job id
|
77
|
-
@__acidic_job_idempotency_key ||= IdempotencyKey.value_for(self, @__acidic_job_args, @__acidic_job_kwargs)
|
81
|
+
workflow = define_workflow(@__acidic_job_steps)
|
78
82
|
|
79
|
-
@run = ensure_run_record(
|
83
|
+
@run = ensure_run_record(workflow, providing)
|
80
84
|
|
81
85
|
# begin the workflow
|
82
86
|
process_run(@run)
|
83
87
|
end
|
84
88
|
|
85
89
|
# DEPRECATED
|
86
|
-
def idempotently(with
|
90
|
+
def idempotently(with: {}, &blk)
|
91
|
+
ActiveSupport::Deprecation.new("1.0", "AcidicJob").deprecation_warning(:idempotently)
|
87
92
|
with_acidity(providing: with, &blk)
|
88
93
|
end
|
89
94
|
|
@@ -93,11 +98,16 @@ module AcidicJob
|
|
93
98
|
FinishedPoint.new
|
94
99
|
end
|
95
100
|
|
101
|
+
# TODO: allow idempotency to be defined by args OR job id
|
96
102
|
# rubocop:disable Naming/MemoizedInstanceVariableName
|
97
103
|
def idempotency_key
|
98
|
-
|
104
|
+
if defined?(@__acidic_job_idempotency_key) && !@__acidic_job_idempotency_key.nil?
|
105
|
+
return @__acidic_job_idempotency_key
|
106
|
+
end
|
99
107
|
|
100
|
-
|
108
|
+
acidic_identifier = self.class.acidic_identifier
|
109
|
+
@__acidic_job_idempotency_key ||= IdempotencyKey.new(acidic_identifier)
|
110
|
+
.value_for(self, @__acidic_job_args, @__acidic_job_kwargs)
|
101
111
|
end
|
102
112
|
# rubocop:enable Naming/MemoizedInstanceVariableName
|
103
113
|
|
@@ -171,7 +181,7 @@ module AcidicJob
|
|
171
181
|
# { "step 1": { does: "step 1", awaits: [], then: "step 2" }, ... }
|
172
182
|
end
|
173
183
|
|
174
|
-
def ensure_run_record(
|
184
|
+
def ensure_run_record(workflow, accessors)
|
175
185
|
isolation_level = case ActiveRecord::Base.connection.adapter_name.downcase.to_sym
|
176
186
|
when :sqlite
|
177
187
|
:read_uncommitted
|
@@ -180,7 +190,7 @@ module AcidicJob
|
|
180
190
|
end
|
181
191
|
|
182
192
|
ActiveRecord::Base.transaction(isolation: isolation_level) do
|
183
|
-
run = Run.find_by(idempotency_key:
|
193
|
+
run = Run.find_by(idempotency_key: idempotency_key)
|
184
194
|
serialized_job = serialize_job(@__acidic_job_args, @__acidic_job_kwargs)
|
185
195
|
|
186
196
|
if run.present?
|
@@ -201,7 +211,7 @@ module AcidicJob
|
|
201
211
|
else
|
202
212
|
run = Run.create!(
|
203
213
|
staged: false,
|
204
|
-
idempotency_key:
|
214
|
+
idempotency_key: idempotency_key,
|
205
215
|
job_class: self.class.name,
|
206
216
|
locked_at: Time.current,
|
207
217
|
last_run_at: Time.current,
|
@@ -246,14 +256,4 @@ module AcidicJob
|
|
246
256
|
|
247
257
|
true
|
248
258
|
end
|
249
|
-
|
250
|
-
def identifier
|
251
|
-
return jid if defined?(jid) && !jid.nil?
|
252
|
-
return job_id if defined?(job_id) && !job_id.nil?
|
253
|
-
|
254
|
-
# might be defined already in `with_acidity` method
|
255
|
-
@__acidic_job_idempotency_key ||= IdempotencyKey.value_for(self, @__acidic_job_args, @__acidic_job_kwargs)
|
256
|
-
|
257
|
-
@__acidic_job_idempotency_key
|
258
|
-
end
|
259
259
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acidic_job
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.
|
4
|
+
version: 1.0.0.pre10
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- fractaledmind
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-03-
|
11
|
+
date: 2022-03-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|