acidic_job 0.2.0 → 0.3.1

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: bcdd5a6f2496a5764d0d0b07f1263653a299d2dd2594ac051393278466f59bd2
4
- data.tar.gz: 25de84d0345eb47d2c1866b0f7e6824278214aeac1e212adad3504d9682c70a8
3
+ metadata.gz: 8aa8aee9c23bc58be992813f139864afdb057442ee9c359263f3ff45029e3f28
4
+ data.tar.gz: 9dbb8017fcbf91ce5cbcc5c96968e3b880340c1e1cc7a8c36cb11e744d6b405d
5
5
  SHA512:
6
- metadata.gz: 64ddc179ec70f82ebce87438daad5d50a199daee773fd453f7dff27b21c26af5f7a35fe96f7167405b5a24eb275af6e9519cbe53018194ad9388ae0e2c276412
7
- data.tar.gz: a960d42269f80fedcd3c913ce7398a8c456759c07c58710533476f3648e38acde98921bdc93a4d26be8fa4405852d20ffbd0685802c114d87e5b9a6650941f59
6
+ metadata.gz: 4bb2298f924a2aa2d5db31c7c05201188872333c36dd3e0bafb598ed087ec9a686695c27b1cdd2d33f067f2bbb47178c5bc700201406db856392997ef6605dbc
7
+ data.tar.gz: f842586dc2d669196ce58bcbc279bc2b967fd0c8973d296323622c993e8d08422ce65f55b85cf96407aa4a668af15dea005fdf3f8917e520ca020c2851dd4bfa
data/Gemfile CHANGED
@@ -24,3 +24,5 @@ gem "sqlite3"
24
24
  gem "database_cleaner"
25
25
 
26
26
  gem "simplecov"
27
+
28
+ gem "pry"
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- acidic_job (0.2.0)
4
+ acidic_job (0.3.1)
5
5
  activerecord (>= 4.0.0)
6
6
  activesupport
7
7
 
@@ -37,6 +37,7 @@ GEM
37
37
  zeitwerk (~> 2.3)
38
38
  ast (2.4.2)
39
39
  builder (3.2.4)
40
+ coderay (1.1.3)
40
41
  concurrent-ruby (1.1.9)
41
42
  crass (1.0.6)
42
43
  database_cleaner (2.0.1)
@@ -65,6 +66,9 @@ GEM
65
66
  parallel (1.20.1)
66
67
  parser (3.0.1.1)
67
68
  ast (~> 2.4.1)
69
+ pry (0.14.1)
70
+ coderay (~> 1.1)
71
+ method_source (~> 1.0)
68
72
  racc (1.5.2)
69
73
  rack (2.2.3)
70
74
  rack-test (1.1.0)
@@ -123,6 +127,7 @@ DEPENDENCIES
123
127
  activerecord (~> 6.1.3.2)
124
128
  database_cleaner
125
129
  minitest (~> 5.0)
130
+ pry
126
131
  railties (>= 4.0)
127
132
  rake (~> 13.0)
128
133
  rubocop (~> 1.7)
data/README.md CHANGED
@@ -33,9 +33,9 @@ And then execute:
33
33
 
34
34
  $ bundle install
35
35
 
36
- Or install it yourself as:
36
+ Or simply execute to install the gem yourself:
37
37
 
38
- $ gem install acidic_job
38
+ $ bundle add acidic_job
39
39
 
40
40
  Then, use the following command to copy over the AcidicJobKey migration.
41
41
 
data/acidic_job.gemspec CHANGED
@@ -3,15 +3,15 @@
3
3
  require_relative "lib/acidic_job/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
- spec.name = "acidic_job"
7
- spec.version = AcidicJob::VERSION
8
- spec.authors = ["fractaledmind"]
9
- spec.email = ["stephen.margheim@gmail.com"]
6
+ spec.name = "acidic_job"
7
+ spec.version = AcidicJob::VERSION
8
+ spec.authors = ["fractaledmind"]
9
+ spec.email = ["stephen.margheim@gmail.com"]
10
10
 
11
- spec.summary = "Idempotent operations for Rails apps, built on top of ActiveJob."
12
- spec.description = "Idempotent operations for Rails apps, built on top of ActiveJob."
13
- spec.homepage = "https://github.com/fractaledmind/acidic_job"
14
- spec.license = "MIT"
11
+ spec.summary = "Idempotent operations for Rails apps, built on top of ActiveJob."
12
+ spec.description = "Idempotent operations for Rails apps, built on top of ActiveJob."
13
+ spec.homepage = "https://github.com/fractaledmind/acidic_job"
14
+ spec.license = "MIT"
15
15
  spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
@@ -23,8 +23,8 @@ Gem::Specification.new do |spec|
23
23
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
24
24
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
25
25
  end
26
- spec.bindir = "exe"
27
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
30
  spec.add_dependency "activesupport"
@@ -8,7 +8,7 @@ module AcidicJob
8
8
  def call(key:)
9
9
  key.update!(
10
10
  locked_at: nil,
11
- recovery_point: AcidicJobKey::RECOVERY_POINT_FINISHED
11
+ recovery_point: Key::RECOVERY_POINT_FINISHED
12
12
  )
13
13
  end
14
14
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/acidic_job.rb CHANGED
@@ -18,6 +18,32 @@ module AcidicJob
18
18
 
19
19
  class SerializedTransactionConflict < StandardError; end
20
20
 
21
+ class Key < ActiveRecord::Base
22
+ RECOVERY_POINT_FINISHED = "FINISHED"
23
+
24
+ self.table_name = "acidic_job_keys"
25
+
26
+ serialize :error_object
27
+ serialize :job_args
28
+
29
+ validates :idempotency_key, presence: true, uniqueness: {scope: [:job_name, :job_args]}
30
+ validates :job_name, presence: true
31
+ validates :last_run_at, presence: true
32
+ validates :recovery_point, presence: true
33
+
34
+ def finished?
35
+ recovery_point == RECOVERY_POINT_FINISHED
36
+ end
37
+
38
+ def succeeded?
39
+ finished? && !failed?
40
+ end
41
+
42
+ def failed?
43
+ error_object.present?
44
+ end
45
+ end
46
+
21
47
  extend ActiveSupport::Concern
22
48
 
23
49
  included do
@@ -51,14 +77,14 @@ module AcidicJob
51
77
  phases = define_atomic_phases(defined_steps)
52
78
  # { create_ride_and_audit_record: <#Method >, ... }
53
79
 
54
- # find or create an AcidicJobKey record (our idempotency key) to store all information about this job
80
+ # find or create an Key record (our idempotency key) to store all information about this job
55
81
  # side-effect: will set the @key instance variable
56
82
  #
57
83
  # A key concept here is that if two requests try to insert or update within
58
84
  # close proximity, one of the two will be aborted by Postgres because we're
59
85
  # using a transaction with SERIALIZABLE isolation level. It may not look
60
86
  # it, but this code is safe from races.
61
- ensure_idempotency_key_record(job_id, with, defined_steps.first)
87
+ ensure_idempotency_key_record(job_id, defined_steps.first)
62
88
 
63
89
  # if the key record is already marked as finished, immediately return its result
64
90
  return @key.succeeded? if @key.finished?
@@ -69,7 +95,7 @@ module AcidicJob
69
95
  recovery_point = @key.recovery_point.to_sym
70
96
 
71
97
  case recovery_point
72
- when AcidicJobKey::RECOVERY_POINT_FINISHED.to_sym
98
+ when Key::RECOVERY_POINT_FINISHED.to_sym
73
99
  break
74
100
  else
75
101
  raise UnknownRecoveryPoint unless phases.key? recovery_point
@@ -100,7 +126,7 @@ module AcidicJob
100
126
 
101
127
  phase_result.call(key: key)
102
128
  end
103
- rescue StandardError => e
129
+ rescue => e
104
130
  error = e
105
131
  raise e
106
132
  ensure
@@ -108,7 +134,7 @@ module AcidicJob
108
134
  # key right away so that another request can try again.
109
135
  begin
110
136
  key.update_columns(locked_at: nil, error_object: error) if error.present?
111
- rescue StandardError => e
137
+ rescue => e
112
138
  # We're already inside an error condition, so swallow any additional
113
139
  # errors from here and just send them to logs.
114
140
  puts "Failed to unlock key #{key.id} because of #{e}."
@@ -116,21 +142,22 @@ module AcidicJob
116
142
  end
117
143
  end
118
144
 
119
- def ensure_idempotency_key_record(key_val, job_args, first_step)
145
+ def ensure_idempotency_key_record(key_val, first_step)
120
146
  isolation_level = case ActiveRecord::Base.connection.adapter_name.downcase.to_sym
121
147
  when :sqlite
122
148
  :read_uncommitted
123
149
  else
124
150
  :serializable
125
- end
151
+ end
152
+ serialized_job_info = serialize
126
153
 
127
154
  ActiveRecord::Base.transaction(isolation: isolation_level) do
128
- @key = AcidicJobKey.find_by(idempotency_key: key_val)
155
+ @key = Key.find_by(idempotency_key: key_val)
129
156
 
130
157
  if @key
131
158
  # Programs enqueuing multiple jobs with different parameters but the
132
159
  # same idempotency key is a bug.
133
- raise MismatchedIdempotencyKeyAndJobArguments if @key.job_args != job_args.as_json
160
+ raise MismatchedIdempotencyKeyAndJobArguments if @key.job_args != serialized_job_info["arguments"]
134
161
 
135
162
  # Only acquire a lock if the key is unlocked or its lock has expired
136
163
  # because the original job was long enough ago.
@@ -139,13 +166,13 @@ module AcidicJob
139
166
  # Lock the key and update latest run unless the job is already finished.
140
167
  @key.update!(last_run_at: Time.current, locked_at: Time.current) unless @key.finished?
141
168
  else
142
- @key = AcidicJobKey.create!(
169
+ @key = Key.create!(
143
170
  idempotency_key: key_val,
144
171
  locked_at: Time.current,
145
172
  last_run_at: Time.current,
146
173
  recovery_point: first_step,
147
- job_name: self.class.name,
148
- job_args: job_args.as_json
174
+ job_name: serialized_job_info["job_class"],
175
+ job_args: serialized_job_info["arguments"]
149
176
  )
150
177
  end
151
178
  end
@@ -163,14 +190,14 @@ module AcidicJob
163
190
  end
164
191
 
165
192
  def define_atomic_phases(defined_steps)
166
- defined_steps << AcidicJobKey::RECOVERY_POINT_FINISHED
193
+ defined_steps << Key::RECOVERY_POINT_FINISHED
167
194
 
168
195
  {}.tap do |phases|
169
196
  defined_steps.each_cons(2).map do |enter_method, exit_method|
170
197
  phases[enter_method] = lambda do
171
198
  method(enter_method).call
172
199
 
173
- if exit_method.to_s == AcidicJobKey::RECOVERY_POINT_FINISHED
200
+ if exit_method.to_s == Key::RECOVERY_POINT_FINISHED
174
201
  Response.new
175
202
  else
176
203
  RecoveryPoint.new(exit_method)
@@ -23,15 +23,16 @@ class AcidicJobGenerator < ActiveRecord::Generators::Base
23
23
  # Copies the migration template to db/migrate.
24
24
  def copy_files
25
25
  migration_template "migration.rb",
26
- "db/migrate/create_acidid_job_keys.rb"
26
+ "db/migrate/create_acidic_job_keys.rb"
27
27
  end
28
28
 
29
29
  protected
30
- def migration_class
31
- if ActiveRecord::VERSION::MAJOR >= 5
32
- ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"]
33
- else
34
- ActiveRecord::Migration
35
- end
30
+
31
+ def migration_class
32
+ if ActiveRecord::VERSION::MAJOR >= 5
33
+ ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"]
34
+ else
35
+ ActiveRecord::Migration
36
36
  end
37
+ end
37
38
  end
@@ -3,15 +3,16 @@ class CreateAcidicJobKeys < <%= migration_class %>
3
3
  create_table :acidic_job_keys do |t|
4
4
  t.string :idempotency_key, null: false
5
5
  t.string :job_name, null: false
6
- t.text :job_args, null: false
6
+ t.text :job_args, null: true
7
7
  t.datetime :last_run_at, null: false, default: -> { "CURRENT_TIMESTAMP" }
8
8
  t.datetime :locked_at, null: true
9
9
  t.string :recovery_point, null: false
10
10
  t.text :error_object
11
11
  t.timestamps
12
12
 
13
- t.index %i[idempotency_key job_name job_args], unique: true,
14
- name: "idx_acidic_job_keys_on_idempotency_key_n_job_name_n_job_args"
13
+ t.index %i[idempotency_key job_name job_args],
14
+ unique: true,
15
+ name: "idx_acidic_job_keys_on_idempotency_key_n_job_name_n_job_args"
15
16
  end
16
17
  end
17
18
  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: 0.2.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - fractaledmind
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-08-19 00:00:00.000000000 Z
11
+ date: 2021-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport