acidic_job 0.2.1 → 0.4.0

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: ed6991438cd55a757d0c3606591dd3e39cf470a2fca7486afea1d9efbb948aa6
4
- data.tar.gz: d5f078c2111b538ebc7b16df70b2986fa7e1c958716806ad4778c81817b9d3f4
3
+ metadata.gz: 82e7abf5e57a4a6b68691e3c6c69ee8a6bbc2eabcedb69a3ef29e91ac0e99e8c
4
+ data.tar.gz: b73584561b8a558529b2e057e449896dedfff2c21ab859dacf82b070f75c7910
5
5
  SHA512:
6
- metadata.gz: 24aa6c22959133bd68f3947bd15a742b6b314e7fb174b45fede5b598a85a63f04c2ba95a96e53ad3167fbfa1b9c9dcb981c7d59786be87d36520bab825255605
7
- data.tar.gz: 02fad1948a7a17b81c2a6be0e48c271563fc7ce84a8de5ec5b84d3e1bb2ca65dbe6e838c4f4eb3c05eb2497424fe33fb5ea0e2c8e1001861ec75cb0c835a153a
6
+ metadata.gz: 6e42251e87faff4790ecfba572eca0dd70251fc58a4293b906560b526efa4bc14db00e01ee0e90c43a0d3f3dc355b36b8db3a4be5129529c071711f3a0b2506f
7
+ data.tar.gz: 167aa9f07dd033a5a3646edcff171a8dffea485d9cbb943c230526214c6d4eaa68761418321cc4e7795a0326361699181bd142377516ceadaa430d286628aea5
data/.rubocop.yml CHANGED
@@ -12,3 +12,6 @@ Style/StringLiteralsInInterpolation:
12
12
 
13
13
  Layout/LineLength:
14
14
  Max: 120
15
+
16
+ Style/Documentation:
17
+ Enabled: false
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.1)
4
+ acidic_job (0.4.0)
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/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,12 +23,12 @@ 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
- spec.add_dependency "activesupport"
31
30
  spec.add_dependency "activerecord", ">= 4.0.0"
31
+ spec.add_dependency "activesupport"
32
32
  spec.add_development_dependency "railties", ">= 4.0"
33
33
 
34
34
  # For more information and examples about making a new gem, checkout our
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AcidicJob
4
+ class Key < ActiveRecord::Base
5
+ RECOVERY_POINT_FINISHED = "FINISHED"
6
+
7
+ self.table_name = "acidic_job_keys"
8
+
9
+ serialize :error_object
10
+ serialize :job_args
11
+
12
+ validates :idempotency_key, presence: true, uniqueness: { scope: %i[job_name job_args] }
13
+ validates :job_name, presence: true
14
+ validates :last_run_at, presence: true
15
+ validates :recovery_point, presence: true
16
+
17
+ def finished?
18
+ recovery_point == RECOVERY_POINT_FINISHED
19
+ end
20
+
21
+ def succeeded?
22
+ finished? && !failed?
23
+ end
24
+
25
+ def failed?
26
+ error_object.present?
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AcidicJob
4
+ class Staging < ActiveRecord::Base
5
+ self.table_name = "acidic_job_stagings"
6
+
7
+ validates :serialized_params, presence: true
8
+
9
+ serialize :serialized_params
10
+
11
+ after_create_commit :enqueue_job
12
+
13
+ def enqueue_job
14
+ job = ActiveJob::Base.deserialize(serialized_params)
15
+ job.enqueue
16
+ delete
17
+ end
18
+ end
19
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "0.2.1"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/acidic_job.rb CHANGED
@@ -4,9 +4,11 @@ require_relative "acidic_job/version"
4
4
  require_relative "acidic_job/no_op"
5
5
  require_relative "acidic_job/recovery_point"
6
6
  require_relative "acidic_job/response"
7
+ require_relative "acidic_job/key"
8
+ require_relative "acidic_job/staging"
7
9
  require "active_support/concern"
8
10
 
9
- # rubocop:disable Metrics/ModuleLength, Style/Documentation, Metrics/AbcSize, Metrics/MethodLength
11
+ # rubocop:disable Metrics/ModuleLength, Metrics/AbcSize, Metrics/MethodLength
10
12
  module AcidicJob
11
13
  class MismatchedIdempotencyKeyAndJobArguments < StandardError; end
12
14
 
@@ -18,44 +20,27 @@ module AcidicJob
18
20
 
19
21
  class SerializedTransactionConflict < StandardError; end
20
22
 
21
- class Key < ActiveRecord::Base
22
- RECOVERY_POINT_FINISHED = "FINISHED"
23
-
24
- self.table_name = "acidic_job_keys"
25
-
26
- serialize :job_args, Hash
27
- serialize :error_object
28
-
29
- validates :job_name, presence: true
30
- validates :job_args, presence: true
31
- validates :idempotency_key, presence: true
32
- validates :last_run_at, presence: true
33
- validates :recovery_point, presence: true
34
-
35
- def finished?
36
- recovery_point == RECOVERY_POINT_FINISHED
37
- end
23
+ extend ActiveSupport::Concern
38
24
 
39
- def succeeded?
40
- finished? && !failed?
41
- end
25
+ module ActiveJobExtension
26
+ extend ActiveSupport::Concern
42
27
 
43
- def failed?
44
- error_object.present?
28
+ class_methods do
29
+ def perform_transactionally(*args)
30
+ AcidicJob::Staging.create!(
31
+ serialized_params: job_or_instantiate(*args).serialize
32
+ )
33
+ end
45
34
  end
46
35
  end
47
36
 
48
- extend ActiveSupport::Concern
49
-
50
37
  included do
51
38
  attr_reader :key
52
39
 
53
- # discard_on MismatchedIdempotencyKeyAndJobArguments
54
- # discard_on UnknownRecoveryPoint
55
- # discard_on UnknownAtomicPhaseType
56
- # discard_on MissingRequiredAttribute
57
- # retry_on LockedIdempotencyKey
58
- # retry_on ActiveRecord::SerializationFailure
40
+ # Extend ActiveJob only once it has been loaded
41
+ ActiveSupport.on_load(:active_job) do
42
+ send(:include, ActiveJobExtension)
43
+ end
59
44
  end
60
45
 
61
46
  # Number of seconds passed which we consider a held idempotency key lock to be
@@ -65,7 +50,8 @@ module AcidicJob
65
50
  IDEMPOTENCY_KEY_LOCK_TIMEOUT = 90
66
51
 
67
52
  # &block
68
- def idempotently(with:) # &block
53
+ # &block
54
+ def idempotently(with:)
69
55
  # set accessors for each argument passed in to ensure they are available
70
56
  # to the step methods the job will have written
71
57
  define_accessors_for_passed_arguments(with)
@@ -85,7 +71,7 @@ module AcidicJob
85
71
  # close proximity, one of the two will be aborted by Postgres because we're
86
72
  # using a transaction with SERIALIZABLE isolation level. It may not look
87
73
  # it, but this code is safe from races.
88
- ensure_idempotency_key_record(job_id, with, defined_steps.first)
74
+ ensure_idempotency_key_record(job_id, defined_steps.first)
89
75
 
90
76
  # if the key record is already marked as finished, immediately return its result
91
77
  return @key.succeeded? if @key.finished?
@@ -143,13 +129,14 @@ module AcidicJob
143
129
  end
144
130
  end
145
131
 
146
- def ensure_idempotency_key_record(key_val, job_args, first_step)
132
+ def ensure_idempotency_key_record(key_val, first_step)
147
133
  isolation_level = case ActiveRecord::Base.connection.adapter_name.downcase.to_sym
148
134
  when :sqlite
149
135
  :read_uncommitted
150
136
  else
151
137
  :serializable
152
138
  end
139
+ serialized_job_info = serialize
153
140
 
154
141
  ActiveRecord::Base.transaction(isolation: isolation_level) do
155
142
  @key = Key.find_by(idempotency_key: key_val)
@@ -157,7 +144,7 @@ module AcidicJob
157
144
  if @key
158
145
  # Programs enqueuing multiple jobs with different parameters but the
159
146
  # same idempotency key is a bug.
160
- raise MismatchedIdempotencyKeyAndJobArguments if @key.job_args != job_args.as_json
147
+ raise MismatchedIdempotencyKeyAndJobArguments if @key.job_args != serialized_job_info["arguments"]
161
148
 
162
149
  # Only acquire a lock if the key is unlocked or its lock has expired
163
150
  # because the original job was long enough ago.
@@ -171,8 +158,8 @@ module AcidicJob
171
158
  locked_at: Time.current,
172
159
  last_run_at: Time.current,
173
160
  recovery_point: first_step,
174
- job_name: self.class.name,
175
- job_args: job_args.as_json
161
+ job_name: serialized_job_info["job_class"],
162
+ job_args: serialized_job_info["arguments"]
176
163
  )
177
164
  end
178
165
  end
@@ -207,4 +194,4 @@ module AcidicJob
207
194
  end
208
195
  end
209
196
  end
210
- # rubocop:enable Metrics/ModuleLength, Style/Documentation, Metrics/AbcSize, Metrics/MethodLength
197
+ # rubocop:enable Metrics/ModuleLength, Metrics/AbcSize, Metrics/MethodLength
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails/generators"
2
4
  require "rails/generators/active_record"
3
5
 
@@ -22,16 +24,17 @@ class AcidicJobGenerator < ActiveRecord::Generators::Base
22
24
 
23
25
  # Copies the migration template to db/migrate.
24
26
  def copy_files
25
- migration_template "migration.rb",
27
+ migration_template "migration.rb.erb",
26
28
  "db/migrate/create_acidic_job_keys.rb"
27
29
  end
28
30
 
29
31
  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
32
+
33
+ def migration_class
34
+ if ActiveRecord::VERSION::MAJOR >= 5
35
+ ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"]
36
+ else
37
+ ActiveRecord::Migration
36
38
  end
39
+ end
37
40
  end
@@ -3,15 +3,19 @@ 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"
16
+
17
+ create_table :acidic_job_stagings do |t|
18
+ t.text :serialized_params, null: false
15
19
  end
16
20
  end
17
21
  end
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acidic_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.4.0
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-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: activesupport
14
+ name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: 4.0.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: 4.0.0
27
27
  - !ruby/object:Gem::Dependency
28
- name: activerecord
28
+ name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: 4.0.0
33
+ version: '0'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: 4.0.0
40
+ version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: railties
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -72,12 +72,14 @@ files:
72
72
  - bin/console
73
73
  - bin/setup
74
74
  - lib/acidic_job.rb
75
+ - lib/acidic_job/key.rb
75
76
  - lib/acidic_job/no_op.rb
76
77
  - lib/acidic_job/recovery_point.rb
77
78
  - lib/acidic_job/response.rb
79
+ - lib/acidic_job/staging.rb
78
80
  - lib/acidic_job/version.rb
79
81
  - lib/generators/acidic_job_generator.rb
80
- - lib/generators/templates/migration.rb
82
+ - lib/generators/templates/migration.rb.erb
81
83
  homepage: https://github.com/fractaledmind/acidic_job
82
84
  licenses:
83
85
  - MIT