acidic_job 1.0.0.rc5 → 1.0.0.rc7

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: af6f1d0a08680695a243f3b045eb9eedd0bd7d6568e14acf8caefeeca4dc45f6
4
- data.tar.gz: 533f91d24f7a4cfc5cbd9b74ef55f7207abd1ae00984fec545ad5590ca0b73b6
3
+ metadata.gz: 6583544dc3b3545721d81facda79a461cb8a3a7a8078d2dc309dbc887c0dc1a7
4
+ data.tar.gz: 89f94171898b64372b85b6dcca3c4f6134e608f39c4aadeb8a776ce7c1a6e294
5
5
  SHA512:
6
- metadata.gz: 12a444e4a971df69a1f6f15c64ce0d3609104fe0e8a42ffc4ece9a6d948a5f799981e8ee087fcac45befea46b4f2d4f1279ef39b625cdc1a4db6618e04a98098
7
- data.tar.gz: 05b26e35b45823c32063d41e6ee82b9ad49239de0a412c4c38328ad95391cad60e1e404c04638d0811d27c8073666a5f42cebc36912ac5c44a64d84ab46ad859
6
+ metadata.gz: 25fb20afe5782e1e73f23c2c020948ba732f25e9abb9cf32dcc5803eb74fa7f3c3bb4e674aaf6bd80263d235251fc043b5ee23f2176cdb0b2e99397c1e74ef7a
7
+ data.tar.gz: a09e720a54df383804b54d63b1acf8bd6b2bc912e05dea373f2912b87bbaa5f4e7e60413491ae13e4c780ba99e5cc842e73774837a08dbc87d8e223502534a70
data/README.md CHANGED
@@ -183,7 +183,7 @@ class Job < ActiveJob::Base
183
183
  ```
184
184
 
185
185
  > [!TIP]
186
- > You should think carefully about what constitutes a unique execution of a workflow. Imagine you had a workflow job for balance transers. Jill transfers $10 to John. Your system **must** be able to differentiate between retries of this transfer and new independent transfers. If you were only to use the `sender`, `recipient`, and `amount` as your `unique_by` values, then if Jill tries to transfer another $10 to John at some point in the future, that work will be considered a retry of the first transfer and not a new transfer.
186
+ > You should think carefully about what constitutes a unique execution of a workflow. Imagine you had a workflow job for balance transfers. Jill transfers $10 to John. Your system **must** be able to differentiate between retries of this transfer and new independent transfers. If you were only to use the `sender`, `recipient`, and `amount` as your `unique_by` values, then if Jill tries to transfer another $10 to John at some point in the future, that work will be considered a retry of the first transfer and not a new transfer.
187
187
 
188
188
 
189
189
  ### Orchestrating steps
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,4 @@
1
+ module AcidicJob
2
+ class ApplicationController < ActionController::Base
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module AcidicJob
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,6 @@
1
+ module AcidicJob
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: "from@example.com"
4
+ layout "mailer"
5
+ end
6
+ end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- class Record < ActiveRecord::Base
4
+ class ApplicationRecord < ActiveRecord::Base
5
5
  self.abstract_class = true
6
6
 
7
7
  connects_to(**AcidicJob.connects_to) if AcidicJob.connects_to
8
8
  end
9
9
  end
10
10
 
11
- ActiveSupport.run_load_hooks :acidic_job_record, AcidicJob::Record
11
+ ActiveSupport.run_load_hooks :acidic_job_record, AcidicJob::ApplicationRecord
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- class Entry < Record
4
+ class Entry < ApplicationRecord
5
5
  belongs_to :execution, class_name: "AcidicJob::Execution"
6
6
 
7
7
  serialize :data, coder: AcidicJob::Serializer
8
8
 
9
- scope :for_step, -> (step) { where(step: step) }
10
- scope :for_action, -> (action) { where(action: action) }
11
- scope :ordered, -> { order(timestamp: :asc) }
9
+ scope :for_step, ->(step) { where(step: step) }
10
+ scope :for_action, ->(action) { where(action: action) }
11
+ scope :ordered, -> { order(timestamp: :asc, created_at: :asc) }
12
12
 
13
13
  def self.most_recent
14
14
  order(created_at: :desc).first
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- class Execution < Record
4
+ class Execution < ApplicationRecord
5
5
  has_many :entries, class_name: "AcidicJob::Entry", dependent: :destroy
6
6
  has_many :values, class_name: "AcidicJob::Value", dependent: :destroy
7
7
 
@@ -14,9 +14,9 @@ module AcidicJob
14
14
  where(recover_to: FINISHED_RECOVERY_POINT)
15
15
  }
16
16
  scope :outstanding, -> {
17
- where.not(recover_to: FINISHED_RECOVERY_POINT).or(where(recover_to: [nil, ""]))
17
+ where.not(recover_to: FINISHED_RECOVERY_POINT).or(where(recover_to: [ nil, "" ]))
18
18
  }
19
- scope :clearable, -> (finished_before: AcidicJob.clear_finished_executions_after.ago) {
19
+ scope :clearable, ->(finished_before: AcidicJob.clear_finished_executions_after.ago) {
20
20
  finished.where(last_run_at: ...finished_before)
21
21
  }
22
22
 
@@ -34,7 +34,7 @@ module AcidicJob
34
34
  step: step,
35
35
  action: action,
36
36
  timestamp: timestamp,
37
- data: kwargs.except(:ignored),
37
+ data: kwargs.except(:ignored)
38
38
  })
39
39
  end
40
40
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- class Value < Record
4
+ class Value < ApplicationRecord
5
5
  belongs_to :execution, class_name: "AcidicJob::Execution"
6
6
 
7
7
  serialize :value, coder: AcidicJob::Serializer
@@ -0,0 +1,17 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Acidic job</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= yield :head %>
9
+
10
+ <%= stylesheet_link_tag "acidic_job/application", media: "all" %>
11
+ </head>
12
+ <body>
13
+
14
+ <%= yield %>
15
+
16
+ </body>
17
+ </html>
@@ -28,9 +28,9 @@ module AcidicJob
28
28
 
29
29
  definition = {
30
30
  "meta" => {
31
- "version" => VERSION,
31
+ "version" => VERSION
32
32
  },
33
- "steps" => {},
33
+ "steps" => {}
34
34
  }
35
35
 
36
36
  definition.tap do |workflow|
@@ -12,19 +12,19 @@ module AcidicJob
12
12
  {
13
13
  execution_id: @execution.id,
14
14
  key: key,
15
- value: value,
15
+ value: value
16
16
  }
17
17
  end
18
18
 
19
19
  case AcidicJob::Value.connection.adapter_name.downcase.to_sym
20
20
  when :postgresql, :sqlite
21
- AcidicJob::Value.upsert_all(records, unique_by: [:execution_id, :key])
21
+ AcidicJob::Value.upsert_all(records, unique_by: [ :execution_id, :key ])
22
22
  when :mysql2, :mysql, :trilogy
23
23
  AcidicJob::Value.upsert_all(records)
24
24
  else
25
25
  # Fallback for other adapters - try with unique_by first, fall back without
26
26
  begin
27
- AcidicJob::Value.upsert_all(records, unique_by: [:execution_id, :key])
27
+ AcidicJob::Value.upsert_all(records, unique_by: [ :execution_id, :key ])
28
28
  rescue ArgumentError => e
29
29
  if e.message.include?("does not support :unique_by")
30
30
  AcidicJob::Value.upsert_all(records)
@@ -26,14 +26,20 @@ module AcidicJob
26
26
  require_relative "serializers/exception_serializer"
27
27
  require_relative "serializers/new_record_serializer"
28
28
  require_relative "serializers/job_serializer"
29
- require_relative "serializers/range_serializer"
30
-
31
- ActiveJob::Serializers.add_serializers(
32
- Serializers::ExceptionSerializer,
33
- Serializers::NewRecordSerializer,
34
- Serializers::JobSerializer,
35
- Serializers::RangeSerializer
36
- )
29
+
30
+ serializers = [
31
+ Serializers::ExceptionSerializer.instance,
32
+ Serializers::NewRecordSerializer.instance,
33
+ Serializers::JobSerializer.instance
34
+ ]
35
+
36
+ # Rails 7.1+ includes a RangeSerializer, so only add ours for older versions
37
+ unless defined?(ActiveJob::Serializers::RangeSerializer)
38
+ require_relative "serializers/range_serializer"
39
+ serializers << Serializers::RangeSerializer.instance
40
+ end
41
+
42
+ ActiveJob::Serializers.add_serializers(*serializers)
37
43
  end
38
44
  end
39
45
 
@@ -37,7 +37,7 @@ module AcidicJob
37
37
  end
38
38
 
39
39
  private def formatted_error(error)
40
- [error.class, error.message].compact.join(" ")
40
+ [ error.class, error.message ].compact.join(" ")
41
41
  end
42
42
 
43
43
  # Use the logger configured for AcidicJob
@@ -26,6 +26,10 @@ module AcidicJob
26
26
  def serialize?(argument)
27
27
  defined?(Exception) && argument.is_a?(Exception)
28
28
  end
29
+
30
+ def klass
31
+ ::Exception
32
+ end
29
33
  end
30
34
  end
31
35
  end
@@ -22,6 +22,10 @@ module AcidicJob
22
22
  def serialize?(argument)
23
23
  defined?(::ActiveJob::Base) && argument.class < ::ActiveJob::Base
24
24
  end
25
+
26
+ def klass
27
+ ::ActiveJob::Base
28
+ end
25
29
  end
26
30
  end
27
31
  end
@@ -20,6 +20,10 @@ module AcidicJob
20
20
  def serialize?(argument)
21
21
  defined?(::ActiveRecord) && argument.respond_to?(:new_record?) && argument.new_record?
22
22
  end
23
+
24
+ def klass
25
+ ::ActiveRecord::Base
26
+ end
23
27
  end
24
28
  end
25
29
  end
@@ -2,25 +2,25 @@
2
2
 
3
3
  require "active_job/serializers/object_serializer"
4
4
 
5
- # :nocov:
6
5
  module AcidicJob
7
6
  module Serializers
7
+ # This serializer is only used for Rails versions prior to 7.1,
8
+ # which introduced ActiveJob::Serializers::RangeSerializer.
8
9
  class RangeSerializer < ::ActiveJob::Serializers::ObjectSerializer
9
10
  KEYS = %w[begin end exclude_end].freeze
10
11
 
11
12
  def serialize(range)
12
- args = Arguments.serialize([range.begin, range.end, range.exclude_end?])
13
+ args = ::ActiveJob::Arguments.serialize([range.begin, range.end, range.exclude_end?])
13
14
  super(KEYS.zip(args).to_h)
14
15
  end
15
16
 
16
17
  def deserialize(hash)
17
- klass.new(*Arguments.deserialize(hash.values_at(*KEYS)))
18
+ klass.new(*::ActiveJob::Arguments.deserialize(hash.values_at(*KEYS)))
18
19
  end
19
20
 
20
- private def klass
21
+ def klass
21
22
  ::Range
22
23
  end
23
24
  end
24
25
  end
25
26
  end
26
- # :nocov:
@@ -26,7 +26,7 @@ module AcidicJob
26
26
  # except that any `transaction` strategies for any ORMs are replaced with a `deletion` strategy.
27
27
  private def transaction_free_cleaners_for(original_cleaners)
28
28
  non_transaction_cleaners = original_cleaners.dup.to_h do |(orm, opts), cleaner|
29
- [[orm, opts], ensure_no_transaction_strategies_for(cleaner)]
29
+ [ [ orm, opts ], ensure_no_transaction_strategies_for(cleaner) ]
30
30
  end
31
31
  ::DatabaseCleaner::Cleaners.new(non_transaction_cleaners)
32
32
  end
@@ -55,12 +55,12 @@ module AcidicJob
55
55
  .name # "DatabaseCleaner::ActiveRecord::Truncation"
56
56
  .rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
57
57
  .first # "DatabaseCleaner::ActiveRecord"
58
- deletion_strategy_class_name = [strategy_namespace, "::", "Deletion"].join
58
+ deletion_strategy_class_name = [ strategy_namespace, "::", "Deletion" ].join
59
59
  deletion_strategy_class = deletion_strategy_class_name.constantize
60
60
  instance_variable_hash = strategy.instance_variables.to_h do |var|
61
61
  [
62
62
  var.to_s.remove("@"),
63
- strategy.instance_variable_get(var),
63
+ strategy.instance_variable_get(var)
64
64
  ]
65
65
  end
66
66
  options = instance_variable_hash.except("db", "connection_class")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "1.0.0.rc5"
4
+ VERSION = "1.0.0.rc7"
5
5
  end
@@ -31,12 +31,12 @@ module AcidicJob
31
31
  AcidicJob.instrument(:initialize_workflow, definition: workflow_definition) do
32
32
  transaction_args = case ::ActiveRecord::Base.connection.adapter_name.downcase.to_sym
33
33
  # SQLite doesn't support `serializable` transactions
34
- when :sqlite
34
+ when :sqlite
35
35
  {}
36
- else
36
+ else
37
37
  { isolation: :serializable }
38
38
  end
39
- idempotency_key = Digest::SHA256.hexdigest(JSON.generate([self.class.name, unique_by], strict: true))
39
+ idempotency_key = Digest::SHA256.hexdigest(JSON.generate([ self.class.name, unique_by ], strict: true))
40
40
 
41
41
  @__acidic_job_execution__ = ::ActiveRecord::Base.transaction(**transaction_args) do
42
42
  record = Execution.find_by(idempotency_key: idempotency_key)
@@ -162,7 +162,7 @@ module AcidicJob
162
162
  step: curr_step,
163
163
  action: :succeeded,
164
164
  ignored: {
165
- result: result,
165
+ result: result
166
166
  }
167
167
  )
168
168
  next_step
data/lib/acidic_job.rb CHANGED
@@ -22,7 +22,7 @@ module AcidicJob
22
22
 
23
23
  mattr_accessor :logger, default: DEFAULT_LOGGER
24
24
  mattr_accessor :connects_to
25
- mattr_accessor :plugins, default: [Plugins::TransactionalStep]
25
+ mattr_accessor :plugins, default: [ Plugins::TransactionalStep ]
26
26
  mattr_accessor :clear_finished_executions_after, default: 1.week
27
27
 
28
28
  def instrument(channel, **options, &block)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acidic_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc5
4
+ version: 1.0.0.rc7
5
5
  platform: ruby
6
6
  authors:
7
- - fractaledmind
8
- bindir: exe
7
+ - Stephen Margheim
8
+ bindir: bin
9
9
  cert_chain: []
10
- date: 2025-06-13 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: json
@@ -80,35 +80,21 @@ dependencies:
80
80
  - !ruby/object:Gem::Version
81
81
  version: '7.1'
82
82
  - !ruby/object:Gem::Dependency
83
- name: actionmailer
83
+ name: chaotic_job
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - ">="
87
87
  - !ruby/object:Gem::Version
88
- version: '7.1'
88
+ version: 0.11.2
89
89
  type: :development
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - ">="
94
94
  - !ruby/object:Gem::Version
95
- version: '7.1'
95
+ version: 0.11.2
96
96
  - !ruby/object:Gem::Dependency
97
- name: chaotic_job
98
- requirement: !ruby/object:Gem::Requirement
99
- requirements:
100
- - - '='
101
- - !ruby/object:Gem::Version
102
- version: 0.3.0
103
- type: :development
104
- prerelease: false
105
- version_requirements: !ruby/object:Gem::Requirement
106
- requirements:
107
- - - '='
108
- - !ruby/object:Gem::Version
109
- version: 0.3.0
110
- - !ruby/object:Gem::Dependency
111
- name: combustion
97
+ name: simplecov
112
98
  requirement: !ruby/object:Gem::Requirement
113
99
  requirements:
114
100
  - - ">="
@@ -136,7 +122,7 @@ dependencies:
136
122
  - !ruby/object:Gem::Version
137
123
  version: '0'
138
124
  - !ruby/object:Gem::Dependency
139
- name: rake
125
+ name: mysql2
140
126
  requirement: !ruby/object:Gem::Requirement
141
127
  requirements:
142
128
  - - ">="
@@ -150,7 +136,7 @@ dependencies:
150
136
  - !ruby/object:Gem::Version
151
137
  version: '0'
152
138
  - !ruby/object:Gem::Dependency
153
- name: rubocop
139
+ name: pg
154
140
  requirement: !ruby/object:Gem::Requirement
155
141
  requirements:
156
142
  - - ">="
@@ -164,7 +150,7 @@ dependencies:
164
150
  - !ruby/object:Gem::Version
165
151
  version: '0'
166
152
  - !ruby/object:Gem::Dependency
167
- name: simplecov
153
+ name: sqlite3
168
154
  requirement: !ruby/object:Gem::Requirement
169
155
  requirements:
170
156
  - - ">="
@@ -178,7 +164,7 @@ dependencies:
178
164
  - !ruby/object:Gem::Version
179
165
  version: '0'
180
166
  - !ruby/object:Gem::Dependency
181
- name: sqlite3
167
+ name: actionmailer
182
168
  requirement: !ruby/object:Gem::Requirement
183
169
  requirements:
184
170
  - - ">="
@@ -191,7 +177,9 @@ dependencies:
191
177
  - - ">="
192
178
  - !ruby/object:Gem::Version
193
179
  version: '0'
194
- description: Idempotent operations for Rails apps, built on top of ActiveJob.
180
+ description: "Write reliable and repeatable multi-step distributed operations that
181
+ are Atomic ⚛️, Consistent \U0001F916, Isolated \U0001F574\U0001F3FC, and Durable
182
+ ⛰️."
195
183
  email:
196
184
  - stephen.margheim@gmail.com
197
185
  executables: []
@@ -200,10 +188,15 @@ extra_rdoc_files: []
200
188
  files:
201
189
  - LICENSE
202
190
  - README.md
191
+ - app/assets/stylesheets/acidic_job/application.css
192
+ - app/controllers/acidic_job/application_controller.rb
193
+ - app/jobs/acidic_job/application_job.rb
194
+ - app/mailers/acidic_job/application_mailer.rb
195
+ - app/models/acidic_job/application_record.rb
203
196
  - app/models/acidic_job/entry.rb
204
197
  - app/models/acidic_job/execution.rb
205
- - app/models/acidic_job/record.rb
206
198
  - app/models/acidic_job/value.rb
199
+ - app/views/layouts/acidic_job/application.html.erb
207
200
  - lib/acidic_job.rb
208
201
  - lib/acidic_job/arguments.rb
209
202
  - lib/acidic_job/builder.rb
@@ -238,14 +231,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
238
231
  requirements:
239
232
  - - ">="
240
233
  - !ruby/object:Gem::Version
241
- version: 2.7.0
234
+ version: 3.0.0
242
235
  required_rubygems_version: !ruby/object:Gem::Requirement
243
236
  requirements:
244
237
  - - ">="
245
238
  - !ruby/object:Gem::Version
246
239
  version: '0'
247
240
  requirements: []
248
- rubygems_version: 3.6.3
241
+ rubygems_version: 3.6.7
249
242
  specification_version: 4
250
- summary: Idempotent operations for Rails apps, built on top of ActiveJob.
243
+ summary: "\U0001F9EA Durable execution workflows for Active Job"
251
244
  test_files: []