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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3971aa5af368952620c483730c9a8a28efd9e01bffecc38d015d322e165bdffe
4
- data.tar.gz: a6c7374d7e0f2121a0fc92b988c28cee29d488f32506dd84e9e84c15b0117eda
3
+ metadata.gz: 95b8fbdebe38a639f9b905df6ac1f5b853b09383d0810696c89c6c9e001efffe
4
+ data.tar.gz: b7e92f9b98461e2f39122405b6e3278fb7ed8a0edec65963026f7e2665bb5042
5
5
  SHA512:
6
- metadata.gz: 7a890818e15da5fb4f5990f9753545d2e33c2bfb6d6e2a9793a42efeed34fcb6dbef076b18111980b0bfe32fe58f490f5fad0d70640b8d6624c8ae0210b4dd8a
7
- data.tar.gz: b988408714e535184d553982a8220e32c83e7551ea129a7a5c001f1e83462d03d64f86e7df4112fed3af51f1a8a197f38c587fd01a9dc0b4d77ea8051a43aee0
6
+ metadata.gz: 13b5ea59163e833f5b698625d841fdcdc0a630b46880526df3274926e696b227b41946fd6622e121b9afd90ad3cc94589f6e864188e871d70f03c77168e3ad5e
7
+ data.tar.gz: 27e5b74147706f227875f4f4a61f084298c42e615149cb0a74670af841df8fcce9eb4f95d11935561912af61d6d8759c21c9ca9862e897f765b49c78b1e42ebd
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- acidic_job (1.0.0.pre9)
4
+ acidic_job (1.0.0.pre10)
5
5
  activerecord (>= 6.1.0)
6
6
  activesupport
7
7
 
@@ -7,18 +7,27 @@ module AcidicJob
7
7
  module ActionMailer
8
8
  extend ActiveSupport::Concern
9
9
 
10
- def deliver_acidicly(_options = {})
11
- job = ::ActionMailer::MailDeliveryJob
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 = job.new(job_args).serialize
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: job.name,
28
+ job_class: job_class.name,
20
29
  serialized_job: serialized_job,
21
- idempotency_key: IdempotencyKey.value_for(serialized_job)
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: IdempotencyKey.value_for(serialized_job)
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: IdempotencyKey.value_for(serialized_job)
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: IdempotencyKey.value_for(serialized_job)
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.value_for(hash_or_job, *args, **kwargs)
6
- return hash_or_job.job_id if hash_or_job.respond_to?(:job_id) && !hash_or_job.job_id.nil?
7
- return hash_or_job.jid if hash_or_job.respond_to?(:jid) && !hash_or_job.jid.nil?
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
- if hash_or_job.is_a?(Hash) && hash_or_job.key?("job_id") && !hash_or_job["job_id"].nil?
10
- return hash_or_job["job_id"]
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
- return hash_or_job["jid"] if hash_or_job.is_a?(Hash) && hash_or_job.key?("jid") && !hash_or_job["jid"].nil?
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"]
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "1.0.0.pre9"
4
+ VERSION = "1.0.0.pre10"
5
5
  end
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
- # execute the block to gather the info on what steps are defined for this job workflow
71
+ # ensure this instance variable is always defined
64
72
  @__acidic_job_steps = []
65
- steps = yield || []
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(steps)
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(@__acidic_job_idempotency_key, workflow, providing)
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:, &blk)
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
- return @__acidic_job_idempotency_key if defined? @__acidic_job_idempotency_key
104
+ if defined?(@__acidic_job_idempotency_key) && !@__acidic_job_idempotency_key.nil?
105
+ return @__acidic_job_idempotency_key
106
+ end
99
107
 
100
- @__acidic_job_idempotency_key ||= IdempotencyKey.value_for(self, @__acidic_job_args, @__acidic_job_kwargs)
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(key_val, workflow, accessors)
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: key_val)
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: key_val,
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.pre9
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-12 00:00:00.000000000 Z
11
+ date: 2022-03-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord