acidic_job 0.1.0 → 0.1.5

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: 34c65a77db46e2a7e38aaee61428a0739027820a7a796edf42b91e864818af22
4
- data.tar.gz: cd0cfaa3efe9244fc82ea32f197099c29dff0326534a44411e73569158396759
3
+ metadata.gz: 1c51b3ed20bebb6322ab050f122c83d863fe16436e302b61f91eea84fd9cc117
4
+ data.tar.gz: 7f65a1d6c99d23930c4730557388833b3ebd277117394b18d6665978bf35119d
5
5
  SHA512:
6
- metadata.gz: 20158e851d244eaa05881e0ca3723397f4e48cc5b17537956f114bf5507831e9e2ddb17ffb9fa35496a50c067539dc3aa5fb4a9051dd61da71dab0e9e04c1833
7
- data.tar.gz: 52272d8d404339ecd6442118e5a5c6718febc9688905dece63bda85d9837d43edbbdfe7386284abcd773660c6b95b98b8caeb7ae6af83267ed2f6778ed952d21
6
+ metadata.gz: 3a1910297ba003ca354ede4dd5312d12af4f6662d8cb0eca451f4f01e51ba54d80f3f27632785d2ba7c69d5504da042861d122c7a81356d19ca04046097928cd
7
+ data.tar.gz: 6d57ce3ec53d5ed22bfd91f284611c505400ad7f524f0fbfe0bec4fbf335e94fd4c91781248980342d127186c5973dfed9cd32011c2e6b2e1c6ea72531247f87
@@ -0,0 +1,18 @@
1
+ name: Ruby
2
+
3
+ on: [push,pull_request]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v2
10
+ - name: Set up Ruby
11
+ uses: ruby/setup-ruby@v1
12
+ with:
13
+ ruby-version: 2.7.1
14
+ - name: Run the default task
15
+ run: |
16
+ gem install bundler -v 2.2.5
17
+ bundle install
18
+ bundle exec rake
data/.gitignore CHANGED
@@ -6,6 +6,5 @@
6
6
  /pkg/
7
7
  /spec/reports/
8
8
  /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
9
+ .DS_Store
10
+ /test/database.sqlite
data/.rubocop.yml CHANGED
@@ -1,5 +1,6 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.4
2
+ TargetRubyVersion: 2.7
3
+ NewCops: enable
3
4
 
4
5
  Style/StringLiterals:
5
6
  Enabled: true
data/.ruby_version ADDED
@@ -0,0 +1 @@
1
+ 2.7.1
data/Gemfile CHANGED
@@ -7,6 +7,20 @@ gemspec
7
7
 
8
8
  gem "rake", "~> 13.0"
9
9
 
10
- gem "rspec", "~> 3.0"
10
+ gem "minitest", "~> 5.0"
11
11
 
12
12
  gem "rubocop", "~> 1.7"
13
+
14
+ gem "rubocop-minitest"
15
+
16
+ gem "rubocop-rake"
17
+
18
+ gem "activerecord", "~> 6.1.3.2"
19
+
20
+ gem "activejob", "~> 6.1.3.2"
21
+
22
+ gem "sqlite3"
23
+
24
+ gem "database_cleaner"
25
+
26
+ gem "simplecov"
data/Gemfile.lock ADDED
@@ -0,0 +1,91 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ acidic_job (0.1.5)
5
+ activesupport
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activejob (6.1.3.2)
11
+ activesupport (= 6.1.3.2)
12
+ globalid (>= 0.3.6)
13
+ activemodel (6.1.3.2)
14
+ activesupport (= 6.1.3.2)
15
+ activerecord (6.1.3.2)
16
+ activemodel (= 6.1.3.2)
17
+ activesupport (= 6.1.3.2)
18
+ activesupport (6.1.3.2)
19
+ concurrent-ruby (~> 1.0, >= 1.0.2)
20
+ i18n (>= 1.6, < 2)
21
+ minitest (>= 5.1)
22
+ tzinfo (~> 2.0)
23
+ zeitwerk (~> 2.3)
24
+ ast (2.4.2)
25
+ concurrent-ruby (1.1.9)
26
+ database_cleaner (2.0.1)
27
+ database_cleaner-active_record (~> 2.0.0)
28
+ database_cleaner-active_record (2.0.1)
29
+ activerecord (>= 5.a)
30
+ database_cleaner-core (~> 2.0.0)
31
+ database_cleaner-core (2.0.1)
32
+ docile (1.4.0)
33
+ globalid (0.4.2)
34
+ activesupport (>= 4.2.0)
35
+ i18n (1.8.10)
36
+ concurrent-ruby (~> 1.0)
37
+ minitest (5.14.4)
38
+ parallel (1.20.1)
39
+ parser (3.0.1.1)
40
+ ast (~> 2.4.1)
41
+ rainbow (3.0.0)
42
+ rake (13.0.4)
43
+ regexp_parser (2.1.1)
44
+ rexml (3.2.5)
45
+ rubocop (1.18.3)
46
+ parallel (~> 1.10)
47
+ parser (>= 3.0.0.0)
48
+ rainbow (>= 2.2.2, < 4.0)
49
+ regexp_parser (>= 1.8, < 3.0)
50
+ rexml
51
+ rubocop-ast (>= 1.7.0, < 2.0)
52
+ ruby-progressbar (~> 1.7)
53
+ unicode-display_width (>= 1.4.0, < 3.0)
54
+ rubocop-ast (1.7.0)
55
+ parser (>= 3.0.1.1)
56
+ rubocop-minitest (0.14.0)
57
+ rubocop (>= 0.90, < 2.0)
58
+ rubocop-rake (0.6.0)
59
+ rubocop (~> 1.0)
60
+ ruby-progressbar (1.11.0)
61
+ simplecov (0.21.2)
62
+ docile (~> 1.1)
63
+ simplecov-html (~> 0.11)
64
+ simplecov_json_formatter (~> 0.1)
65
+ simplecov-html (0.12.3)
66
+ simplecov_json_formatter (0.1.3)
67
+ sqlite3 (1.4.2)
68
+ tzinfo (2.0.4)
69
+ concurrent-ruby (~> 1.0)
70
+ unicode-display_width (2.0.0)
71
+ zeitwerk (2.4.2)
72
+
73
+ PLATFORMS
74
+ ruby
75
+ x86_64-darwin-17
76
+
77
+ DEPENDENCIES
78
+ acidic_job!
79
+ activejob (~> 6.1.3.2)
80
+ activerecord (~> 6.1.3.2)
81
+ database_cleaner
82
+ minitest (~> 5.0)
83
+ rake (~> 13.0)
84
+ rubocop (~> 1.7)
85
+ rubocop-minitest
86
+ rubocop-rake
87
+ simplecov
88
+ sqlite3
89
+
90
+ BUNDLED WITH
91
+ 2.2.5
data/README.md CHANGED
@@ -1,8 +1,25 @@
1
1
  # AcidicJob
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/acidic_job`. To experiment with that code, run `bin/console` for an interactive prompt.
3
+ ### Idempotent operations for Rails apps, built on top of ActiveJob.
4
4
 
5
- TODO: Delete this and the text above, and describe your gem
5
+ At the conceptual heart of basically any software are "operations"—the discrete actions the software performs. Rails provides a powerful abstraction layer for building operations in the form of `ActiveJob`. With `ActiveJob`, we can easily trigger from other Ruby code throughout our Rails application (controller actions, model methods, model callbacks, etc.); we can run operations both synchronously (blocking execution and then returning its response to the caller) and asychronously (non-blocking and the caller doesn't know its response); and we can also retry a specific operation if needed seemlessly.
6
+
7
+ However, in order to ensure that our operational jobs are _robust_, we need to ensure that they are properly [idempotent and transactional](https://github.com/mperham/sidekiq/wiki/Best-Practices#2-make-your-job-idempotent-and-transactional). As stated in the [GitLab Sidekiq Style Guide](https://docs.gitlab.com/ee/development/sidekiq_style_guide.html#idempotent-jobs):
8
+
9
+ >As a general rule, a worker can be considered idempotent if:
10
+ > * It can safely run multiple times with the same arguments.
11
+ > * Application side-effects are expected to happen only once (or side-effects of a second run do not have an effect).
12
+
13
+ This is, of course, far easier said than done. Thus, `AcidicJob`.
14
+
15
+ `AcidicJob` provides a framework to help you make your operational jobs atomic ⚛️, consistent 🤖, isolated 🕴🏼, and durable ⛰️. Its conceptual framework is directly inspired by a truly wonderful loosely collected series of articles written by Brandur Leach, which together lay out core techniques and principles required to make an HTTP API properly ACIDic:
16
+
17
+ 1. https://brandur.org/acid
18
+ 2. https://brandur.org/http-transactions
19
+ 3. https://brandur.org/job-drain
20
+ 4. https://brandur.org/idempotency-keys
21
+
22
+ `AcididJob` brings these techniques and principles into the world of a standard Rails application.
6
23
 
7
24
  ## Installation
8
25
 
@@ -20,13 +37,49 @@ Or install it yourself as:
20
37
 
21
38
  $ gem install acidic_job
22
39
 
40
+ Then, use the following command to copy over the AcidicJobKey migration.
41
+
42
+ ```
43
+ rails generate acidic_job:key
44
+ ```
45
+
23
46
  ## Usage
24
47
 
25
- TODO: Write usage instructions here
48
+ `AcidicJob` is a concern that you `include` into your operation jobs which provides two public methods to help you make your jobs idempotent and robust—`idempotently` and `step`. You can see them "in action" in the example job below:
49
+
50
+ ```ruby
51
+ class RideCreateJob < ActiveJob::Base
52
+ include AcidicJob
53
+
54
+ def perform(ride_params)
55
+ idempotently with: { user: current_user, params: ride_params, ride: nil } do
56
+ step :create_ride_and_audit_record
57
+ step :create_stripe_charge
58
+ step :send_receipt
59
+ end
60
+ end
61
+
62
+ def create_ride_and_audit_record
63
+ # ...
64
+ end
65
+
66
+ def create_stripe_charge
67
+ # ...
68
+ end
69
+
70
+ def send_receipt
71
+ # ...
72
+ end
73
+ end
74
+ ```
75
+
76
+ `idempotently` takes only the `with:` named parameter and a block where you define the steps of this operation. `step` simply takes the name of a method available in the job. That's all!
77
+
78
+ So, how does `AcidicJob` make this operation idempotent and robust then? In simplest form, `AcidicJob` creates an "idempotency key" record for each job run, where it stores information about that job run, like the parameters passed in and the step the job is on. It then wraps each of your step methods in a database transaction to ensure that each step in the operation is transactionally secure. Finally, it handles a variety of edge-cases and error conditions for you as well. But, basically, by explicitly breaking your operation into steps and storing a record of each job run and updating its current step as it runs, we level up the `ActiveJob` retry mechanism to ensure that we don't retry already finished steps if something goes wrong and the job has to retry. Then, by wrapping each step in a transaction, we ensure each individual step is ACIDic. Taken together, these two strategies help us to ensure that our operational jobs are both idempotent and ACIDic.
26
79
 
27
80
  ## Development
28
81
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
82
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
83
 
31
84
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
85
 
data/Rakefile CHANGED
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "bundler/gem_tasks"
4
- require "rspec/core/rake_task"
4
+ require "rake/testtask"
5
5
 
6
- RSpec::Core::RakeTask.new(:spec)
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
7
11
 
8
12
  require "rubocop/rake_task"
9
13
 
10
14
  RuboCop::RakeTask.new
11
15
 
12
- task default: %i[spec rubocop]
16
+ task default: %i[test rubocop]
data/acidic_job.gemspec CHANGED
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = "Idempotent operations for Rails apps, built on top of ActiveJob."
13
13
  spec.homepage = "https://github.com/fractaledmind/acidic_job"
14
14
  spec.license = "MIT"
15
- spec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = "https://github.com/fractaledmind/acidic_job"
@@ -27,8 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
28
  spec.require_paths = ["lib"]
29
29
 
30
- # Uncomment to register a new dependency of your gem
31
- # spec.add_dependency "example-gem", "~> 1.0"
30
+ spec.add_dependency "activesupport"
32
31
 
33
32
  # For more information and examples about making a new gem, checkout our
34
33
  # guide at: https://bundler.io/guides/creating_gem.html
data/bin/console CHANGED
@@ -3,6 +3,7 @@
3
3
 
4
4
  require "bundler/setup"
5
5
  require "acidic_job"
6
+ require_relative "../test/setup"
6
7
 
7
8
  # You can add fixtures and/or initialization code here to make experimenting
8
9
  # with your gem easier. You can also use a different console, if you like.
data/lib/acidic_job.rb CHANGED
@@ -1,8 +1,190 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "acidic_job/version"
4
+ require_relative "acidic_job/no_op"
5
+ require_relative "acidic_job/recovery_point"
6
+ require_relative "acidic_job/response"
7
+ require "active_support/concern"
4
8
 
9
+ # rubocop:disable Metrics/ModuleLength, Style/Documentation, Metrics/AbcSize, Metrics/MethodLength
5
10
  module AcidicJob
6
- class Error < StandardError; end
7
- # Your code goes here...
11
+ class IdempotencyKeyRequired < StandardError; end
12
+
13
+ class MissingRequiredAttribute < StandardError; end
14
+
15
+ class IdempotencyKeyTooShort < StandardError; end
16
+
17
+ class MismatchedIdempotencyKeyAndJobArguments < StandardError; end
18
+
19
+ class LockedIdempotencyKey < StandardError; end
20
+
21
+ class UnknownRecoveryPoint < StandardError; end
22
+
23
+ class UnknownAtomicPhaseType < StandardError; end
24
+
25
+ class SerializedTransactionConflict < StandardError; end
26
+
27
+ extend ActiveSupport::Concern
28
+
29
+ included do
30
+ attr_reader :key
31
+
32
+ # discard_on MismatchedIdempotencyKeyAndJobArguments
33
+ # discard_on UnknownRecoveryPoint
34
+ # discard_on UnknownAtomicPhaseType
35
+ # discard_on MissingRequiredAttribute
36
+ # retry_on LockedIdempotencyKey
37
+ # retry_on ActiveRecord::SerializationFailure
38
+ end
39
+
40
+ # Number of seconds passed which we consider a held idempotency key lock to be
41
+ # defunct and eligible to be locked again by a different API call. We try to
42
+ # unlock keys on our various failure conditions, but software is buggy, and
43
+ # this might not happen 100% of the time, so this is a hedge against it.
44
+ IDEMPOTENCY_KEY_LOCK_TIMEOUT = 90
45
+
46
+ # To try and enforce some level of required randomness in an idempotency key,
47
+ # we require a minimum length. This of course is a poor approximate, and in
48
+ # real life you might want to consider trying to measure actual entropy with
49
+ # something like the Shannon entropy equation.
50
+ IDEMPOTENCY_KEY_MIN_LENGTH = 20
51
+
52
+ # &block
53
+ def idempotently(with:)
54
+ # set accessors for each argument passed in to ensure they are available
55
+ # to the step methods the job will have written
56
+ define_accessors_for_passed_arguments(with)
57
+
58
+ # execute the block to gather the info on what phases are defined for this job
59
+ defined_steps = yield
60
+
61
+ # convert the array of steps into a hash of recovery_points and callable actions
62
+ phases = define_atomic_phases(defined_steps)
63
+
64
+ # find or create an AcidicJobKey record to store all information about this job
65
+ # side-effect: will set the @key instance variable
66
+ ensure_idempotency_key_record(job_id, with, defined_steps.first)
67
+
68
+ # if the key record is already marked as finished, immediately return its result
69
+ return @key.succeeded? if @key.finished?
70
+
71
+ # otherwise, we will enter a loop to process each required step of the job
72
+ 100.times do
73
+ # our `phases` hash uses Symbols for keys
74
+ recovery_point = @key.recovery_point.to_sym
75
+
76
+ case recovery_point
77
+ when :FINISHED
78
+ break
79
+ else
80
+ raise UnknownRecoveryPoint unless phases.key? recovery_point
81
+
82
+ atomic_phase @key, phases[recovery_point]
83
+ end
84
+ end
85
+
86
+ # the loop will break once the job is finished, so simply report the status
87
+ @key.succeeded?
88
+ end
89
+
90
+ def step(method_name)
91
+ @_steps ||= []
92
+ @_steps << method_name
93
+ @_steps
94
+ end
95
+
96
+ private
97
+
98
+ def atomic_phase(key, proc = nil, &block)
99
+ error = false
100
+ phase_callable = (proc || block)
101
+
102
+ begin
103
+ key.with_lock do
104
+ phase_result = phase_callable.call
105
+
106
+ phase_result.call(key: key)
107
+ end
108
+ rescue StandardError => e
109
+ error = e
110
+ raise e
111
+ ensure
112
+ # If we're leaving under an error condition, try to unlock the idempotency
113
+ # key right away so that another request can try again.
114
+ begin
115
+ key.update_columns(locked_at: nil, error_object: error) if error.present?
116
+ rescue StandardError => e
117
+ # We're already inside an error condition, so swallow any additional
118
+ # errors from here and just send them to logs.
119
+ puts "Failed to unlock key #{key.id} because of #{e}."
120
+ end
121
+ end
122
+ end
123
+
124
+ def ensure_idempotency_key_record(key_val, job_args, first_step)
125
+ # isolation_level = case ActiveRecord::Base.connection.adapter_name.downcase.to_sym
126
+ # when :sqlite
127
+ # :read_uncommitted
128
+ # else # :nocov:
129
+ # :serializable # :nocov:
130
+ # end
131
+ isolation_level = :read_uncommitted
132
+
133
+ ActiveRecord::Base.transaction(isolation: isolation_level) do
134
+ @key = AcidicJobKey.find_by(idempotency_key: key_val)
135
+
136
+ if @key
137
+ # Programs enqueuing multiple jobs with different parameters but the
138
+ # same idempotency key is a bug.
139
+ raise MismatchedIdempotencyKeyAndJobArguments if @key.job_args != job_args.as_json
140
+
141
+ # Only acquire a lock if the key is unlocked or its lock has expired
142
+ # because the original job was long enough ago.
143
+ raise LockedIdempotencyKey if @key.locked_at && @key.locked_at > Time.current - IDEMPOTENCY_KEY_LOCK_TIMEOUT
144
+
145
+ # Lock the key and update latest run unless the job is already
146
+ # finished.
147
+ @key.update!(last_run_at: Time.current, locked_at: Time.current) unless @key.finished?
148
+ else
149
+ @key = AcidicJobKey.create!(
150
+ idempotency_key: key_val,
151
+ locked_at: Time.current,
152
+ last_run_at: Time.current,
153
+ recovery_point: first_step,
154
+ job_name: self.class.name,
155
+ job_args: job_args.as_json
156
+ )
157
+ end
158
+ end
159
+ end
160
+
161
+ def define_accessors_for_passed_arguments(passed_arguments)
162
+ passed_arguments.each do |accessor, value|
163
+ # the reader method may already be defined
164
+ self.class.attr_reader accessor unless respond_to?(accessor)
165
+ # but we should always update the value to match the current value
166
+ instance_variable_set("@#{accessor}", value)
167
+ end
168
+
169
+ true
170
+ end
171
+
172
+ def define_atomic_phases(defined_steps)
173
+ defined_steps << :FINISHED
174
+
175
+ {}.tap do |phases|
176
+ defined_steps.each_cons(2).map do |enter_method, exit_method|
177
+ phases[enter_method] = lambda do
178
+ method(enter_method).call
179
+
180
+ if exit_method == :FINISHED
181
+ Response.new
182
+ else
183
+ RecoveryPoint.new(exit_method)
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
8
189
  end
190
+ # rubocop:enable Metrics/ModuleLength, Style/Documentation, Metrics/AbcSize, Metrics/MethodLength
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Represents an action to perform a no-op. One possible option for a return
4
+ # from an #atomic_phase block.
5
+ class NoOp
6
+ def call(_key)
7
+ # no-op
8
+ end
9
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Represents an action to set a new recovery point. One possible option for a
4
+ # return from an #atomic_phase block.
5
+ class RecoveryPoint
6
+ attr_accessor :name
7
+
8
+ def initialize(name)
9
+ self.name = name
10
+ end
11
+
12
+ def call(key:)
13
+ key.update(recovery_point: name)
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Represents an action to set a new API response (which will be stored onto an
4
+ # idempotency key). One possible option for a return from an #atomic_phase
5
+ # block.
6
+ class Response
7
+ def call(key:)
8
+ key.update!(
9
+ locked_at: nil,
10
+ recovery_point: :FINISHED
11
+ )
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.5"
5
5
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acidic_job
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - fractaledmind
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-06-20 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2021-07-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  description: Idempotent operations for Rails apps, built on top of ActiveJob.
14
28
  email:
15
29
  - stephen.margheim@gmail.com
@@ -17,11 +31,12 @@ executables: []
17
31
  extensions: []
18
32
  extra_rdoc_files: []
19
33
  files:
20
- - ".circleci/config.yml"
34
+ - ".github/workflows/main.yml"
21
35
  - ".gitignore"
22
- - ".rspec"
23
36
  - ".rubocop.yml"
37
+ - ".ruby_version"
24
38
  - Gemfile
39
+ - Gemfile.lock
25
40
  - LICENSE
26
41
  - README.md
27
42
  - Rakefile
@@ -29,6 +44,9 @@ files:
29
44
  - bin/console
30
45
  - bin/setup
31
46
  - lib/acidic_job.rb
47
+ - lib/acidic_job/no_op.rb
48
+ - lib/acidic_job/recovery_point.rb
49
+ - lib/acidic_job/response.rb
32
50
  - lib/acidic_job/version.rb
33
51
  homepage: https://github.com/fractaledmind/acidic_job
34
52
  licenses:
@@ -45,7 +63,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
45
63
  requirements:
46
64
  - - ">="
47
65
  - !ruby/object:Gem::Version
48
- version: 2.4.0
66
+ version: 2.7.0
49
67
  required_rubygems_version: !ruby/object:Gem::Requirement
50
68
  requirements:
51
69
  - - ">="
data/.circleci/config.yml DELETED
@@ -1,13 +0,0 @@
1
- version: 2.1
2
- jobs:
3
- build:
4
- docker:
5
- - image: ruby:2.7.1
6
- steps:
7
- - checkout
8
- - run:
9
- name: Run the default task
10
- command: |
11
- gem install bundler -v 2.2.5
12
- bundle install
13
- bundle exec rake
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper