acidic_job 1.0.0.pre6 → 1.0.0.pre9

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: 9247c761078c570dfbacac2542bc8b7417c816bb9928f975bf7b92722a29f6a4
4
- data.tar.gz: 8d36bb7517db0493d37eaf44aee901339a5d8d77411cad0e55fefe2d940137c6
3
+ metadata.gz: 3971aa5af368952620c483730c9a8a28efd9e01bffecc38d015d322e165bdffe
4
+ data.tar.gz: a6c7374d7e0f2121a0fc92b988c28cee29d488f32506dd84e9e84c15b0117eda
5
5
  SHA512:
6
- metadata.gz: 47697f8b833d7061bacb2b6eaf1e6c15a9d6fec700db0891f881b8f945cf271e1c37adba9cde4572aed5d0e272767a690c172ece0937dbc212a90bc0dd02e8e5
7
- data.tar.gz: cda551ac7a76a63199f03b19e7787ba0bfae414253b1e71b098b8304705458d0c8f21b1755f9f10c15b651d55b849469284754d6e585d13159ee4c28a17ed723
6
+ metadata.gz: 7a890818e15da5fb4f5990f9753545d2e33c2bfb6d6e2a9793a42efeed34fcb6dbef076b18111980b0bfe32fe58f490f5fad0d70640b8d6624c8ae0210b4dd8a
7
+ data.tar.gz: b988408714e535184d553982a8220e32c83e7551ea129a7a5c001f1e83462d03d64f86e7df4112fed3af51f1a8a197f38c587fd01a9dc0b4d77ea8051a43aee0
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- acidic_job (1.0.0.pre6)
4
+ acidic_job (1.0.0.pre9)
5
5
  activerecord (>= 6.1.0)
6
6
  activesupport
7
7
 
data/README.md CHANGED
@@ -78,7 +78,7 @@ class RideCreateJob < ActiveJob::Base
78
78
  def perform(user_id, ride_params)
79
79
  user = User.find(user_id)
80
80
 
81
- with_acidity given: { user: user, params: ride_params, ride: nil } do
81
+ with_acidity providing: { user: user, params: ride_params, ride: nil } do
82
82
  step :create_ride_and_audit_record
83
83
  step :create_stripe_charge
84
84
  step :send_receipt
@@ -99,20 +99,20 @@ class RideCreateJob < ActiveJob::Base
99
99
  end
100
100
  ```
101
101
 
102
- `with_acidity` takes only the `given:` 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!
102
+ `with_acidity` takes only the `providing:` 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!
103
103
 
104
104
  Now, each execution of this job will find or create an `AcidicJob::Run` record, which we leverage to wrap every step in a database transaction. Moreover, this database record allows `acidic_job` to ensure that if your job fails on step 3, when it retries, it will simply jump right back to trying to execute the method defined for the 3rd step, and won't even execute the first two step methods. This means your step methods only need to be idempotent on failure, not on success, since they will never be run again if they succeed.
105
105
 
106
106
  ### Persisted Attributes
107
107
 
108
- Any objects passed to the `given` option on the `with_acidity` method are not just made available to each of your step methods, they are made available across retries. This means that you can set an attribute in step 1, access it in step 2, have step 2 fail, have the job retry, jump directly back to step 2 on retry, and have that object still accessible. This is done by serializing all objects to a field on the `AcidicJob::Run` and manually providing getters and setters that sync with the database record.
108
+ Any objects passed to the `providing` option on the `with_acidity` method are not just made available to each of your step methods, they are made available across retries. This means that you can set an attribute in step 1, access it in step 2, have step 2 fail, have the job retry, jump directly back to step 2 on retry, and have that object still accessible. This is done by serializing all objects to a field on the `AcidicJob::Run` and manually providing getters and setters that sync with the database record.
109
109
 
110
110
  ```ruby
111
111
  class RideCreateJob < ActiveJob::Base
112
112
  include AcidicJob
113
113
 
114
114
  def perform(ride_params)
115
- with_acidity given: { ride: nil } do
115
+ with_acidity providing: { ride: nil } do
116
116
  step :create_ride_and_audit_record
117
117
  step :create_stripe_charge
118
118
  step :send_receipt
@@ -148,7 +148,7 @@ class RideCreateJob < ActiveJob::Base
148
148
  def perform(user_id, ride_params)
149
149
  user = User.find(user_id)
150
150
 
151
- with_acidity given: { user: user, params: ride_params, ride: nil } do
151
+ with_acidity providing: { user: user, params: ride_params, ride: nil } do
152
152
  step :create_ride_and_audit_record
153
153
  step :create_stripe_charge
154
154
  step :send_receipt
@@ -176,14 +176,13 @@ One final feature for those of you using Sidekiq Pro: an integrated DSL for Side
176
176
  In my opinion, any commercial software using Sidekiq should get Sidekiq Pro; it is _absolutely_ worth the money. If, however, you are using `acidic_job` in a non-commercial application, you could use the open-source dropin replacement for this functionality: https://github.com/breamware/sidekiq-batch
177
177
 
178
178
  ```ruby
179
- # TODO: write code sample
180
179
  class RideCreateJob < ActiveJob::Base
181
180
  include AcidicJob
182
181
 
183
182
  def perform(user_id, ride_params)
184
183
  user = User.find(user_id)
185
184
 
186
- with_acidity given: { user: user, params: ride_params, ride: nil } do
185
+ with_acidity providing: { user: user, params: ride_params, ride: nil } do
187
186
  step :create_ride_and_audit_record, awaits: [SomeJob]
188
187
  step :create_stripe_charge, args: [1, 2, 3], kwargs: { some: 'thing' }
189
188
  step :send_receipt
@@ -192,6 +191,53 @@ class RideCreateJob < ActiveJob::Base
192
191
  end
193
192
  ```
194
193
 
194
+ ## Testing
195
+
196
+ When testing acidic jobs, you are likely to run into `ActiveRecord::TransactionIsolationError`s:
197
+
198
+ ```
199
+ ActiveRecord::TransactionIsolationError: cannot set transaction isolation in a nested transaction
200
+ ```
201
+
202
+ This error is thrown because by default RSpec and most MiniTest test suites use database transactions to keep the test database clean between tests. The database transaction that is wrapping all of the code executed in your test is run at the standard isolation level, but acidic jobs then try to create another transaction run at a more conservative isolation level. You cannot have a nested transaction that runs at a different isolation level, thus, this error.
203
+
204
+ In order to avoid this error, you need to ensure firstly that your tests that run your acidic jobs are not using a database transaction and secondly that they use some different strategy to keep your test database clean. The [DatabaseCleaner](https://github.com/DatabaseCleaner/database_cleaner) gem is a commonly used tool to manage different strategies for keeping your test database clean. As for which strategy to use, `truncation` and `deletion` are both safe, but their speed varies based on our app's table structure (see https://github.com/DatabaseCleaner/database_cleaner#what-strategy-is-fastest). Either is fine; use whichever is faster for your app.
205
+
206
+ In order to make this test setup simpler, `AcidicJob` provides a `TestCase` class that your MiniTest jobs tests can inherit from. It is simple; it inherits from `ActiveJob::TestCase`, sets `use_transactional_tests` to `false`, and ensures `DatabaseCleaner` is run for each of your tests. Moreover, it ensures that the system's original DatabaseCleaner configuration is maintained, options included, except that any `transaction` strategies for any ORMs are replaced with a `deletion` strategy. It does so by storing whatever the system DatabaseCleaner configuration is at the start of `before_setup` phase in an instance variable and then restores that configuration at the end of `after_teardown` phase. In between, it runs the configuration thru a pipeline that selectively replaces any `transaction` strategies with a corresponding `deletion` strategy, leaving any other configured strategies untouched.
207
+
208
+ For those of you using RSpec, you can require the `acidic_job/rspec_configuration` file, which will configure RSpec in the exact same way I have used in my RSpec projects to allow me to test acidic jobs with either the `deletion` strategy but still have all of my other tests use the fast `transaction` strategy:
209
+
210
+ ```ruby
211
+ require "database_cleaner/active_record"
212
+
213
+ # see https://github.com/DatabaseCleaner/database_cleaner#how-to-use
214
+ RSpec.configure do |config|
215
+ config.use_transactional_fixtures = false
216
+
217
+ config.before(:suite) do
218
+ DatabaseCleaner.clean_with :truncation
219
+
220
+ # Here we are defaulting to :transaction but swapping to deletion for some specs;
221
+ # if your spec or its code-under-test uses
222
+ # nested transactions then specify :transactional e.g.:
223
+ # describe "SomeWorker", :transactional do
224
+ #
225
+ DatabaseCleaner.strategy = :transaction
226
+
227
+ config.before(:context, transactional: true) { DatabaseCleaner.strategy = :deletion }
228
+ config.after(:context, transactional: true) { DatabaseCleaner.strategy = :transaction }
229
+ config.before(:context, type: :system) { DatabaseCleaner.strategy = :deletion }
230
+ config.after(:context, type: :system) { DatabaseCleaner.strategy = :transaction }
231
+ end
232
+
233
+ config.around(:each) do |example|
234
+ DatabaseCleaner.cleaning do
235
+ example.run
236
+ end
237
+ end
238
+ end
239
+ ```
240
+
195
241
  ## Development
196
242
 
197
243
  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.
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec"
4
+ require "database_cleaner/active_record"
5
+
6
+ # see https://github.com/DatabaseCleaner/database_cleaner#how-to-use
7
+ RSpec.configure do |config|
8
+ config.use_transactional_fixtures = false
9
+
10
+ config.before(:suite) do
11
+ DatabaseCleaner.clean_with :truncation
12
+
13
+ # Here we are defaulting to :transaction but swapping to deletion for some specs;
14
+ # if your spec or its code-under-test uses
15
+ # nested transactions then specify :transactional e.g.:
16
+ # describe "SomeWorker", :transactional do
17
+ #
18
+ DatabaseCleaner.strategy = :transaction
19
+
20
+ config.before(:context, transactional: true) { DatabaseCleaner.strategy = :deletion }
21
+ config.after(:context, transactional: true) { DatabaseCleaner.strategy = :transaction }
22
+ config.before(:context, type: :system) { DatabaseCleaner.strategy = :deletion }
23
+ config.after(:context, type: :system) { DatabaseCleaner.strategy = :transaction }
24
+ end
25
+
26
+ config.around(:each) do |example|
27
+ DatabaseCleaner.cleaning do
28
+ example.run
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_job/test_case"
4
+ require "database_cleaner"
5
+
6
+ module AcidicJob
7
+ class TestCase < ActiveJob::TestCase
8
+ self.use_transactional_tests = false if respond_to?(:use_transactional_tests)
9
+
10
+ def before_setup
11
+ @original_cleaners = DatabaseCleaner.cleaners
12
+ DatabaseCleaner.cleaners = transaction_free_cleaners_for(@original_cleaners)
13
+ super
14
+ DatabaseCleaner.start
15
+ end
16
+
17
+ def after_teardown
18
+ DatabaseCleaner.clean
19
+ super
20
+ DatabaseCleaner.cleaners = @original_cleaners
21
+ end
22
+
23
+ private
24
+
25
+ # Ensure that the system's original DatabaseCleaner configuration is maintained, options included,
26
+ # except that any `transaction` strategies for any ORMs are replaced with a `deletion` strategy.
27
+ def transaction_free_cleaners_for(original_cleaners)
28
+ non_transaction_cleaners = original_cleaners.dup.map do |(orm, opts), cleaner|
29
+ [[orm, opts], ensure_no_transaction_strategies_for(cleaner)]
30
+ end.to_h
31
+ DatabaseCleaner::Cleaners.new(non_transaction_cleaners)
32
+ end
33
+
34
+ def ensure_no_transaction_strategies_for(cleaner)
35
+ return cleaner unless strategy_name_for(cleaner) == "transaction"
36
+
37
+ cleaner.strategy = deletion_strategy_for(cleaner)
38
+ cleaner
39
+ end
40
+
41
+ def strategy_name_for(cleaner)
42
+ cleaner # <DatabaseCleaner::Cleaner>
43
+ .strategy # <DatabaseCleaner::ActiveRecord::Truncation>
44
+ .class # DatabaseCleaner::ActiveRecord::Truncation
45
+ .name # "DatabaseCleaner::ActiveRecord::Truncation"
46
+ .rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
47
+ .last # "Truncation"
48
+ .downcase # "truncation"
49
+ end
50
+
51
+ def deletion_strategy_for(cleaner)
52
+ strategy = cleaner.strategy
53
+ strategy_namespace = strategy # <DatabaseCleaner::ActiveRecord::Truncation>
54
+ .class # DatabaseCleaner::ActiveRecord::Truncation
55
+ .name # "DatabaseCleaner::ActiveRecord::Truncation"
56
+ .rpartition("::") # ["DatabaseCleaner::ActiveRecord", "::", "Truncation"]
57
+ .first # "DatabaseCleaner::ActiveRecord"
58
+ deletion_strategy_class_name = [strategy_namespace, "::", "Deletion"].join
59
+ deletion_strategy_class = deletion_strategy_class_name.constantize
60
+ instance_variable_hash = strategy.instance_variables.map do |var|
61
+ [
62
+ var.to_s.remove("@"),
63
+ strategy.instance_variable_get(var)
64
+ ]
65
+ end.to_h
66
+ options = instance_variable_hash.except("db", "connection_class")
67
+
68
+ deletion_strategy_class.new(**options)
69
+ end
70
+ end
71
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AcidicJob
4
- VERSION = "1.0.0.pre6"
4
+ VERSION = "1.0.0.pre9"
5
5
  end
data/lib/acidic_job.rb CHANGED
@@ -59,7 +59,7 @@ module AcidicJob
59
59
  end
60
60
  end
61
61
 
62
- def with_acidity(given: {})
62
+ def with_acidity(providing: {})
63
63
  # execute the block to gather the info on what steps are defined for this job workflow
64
64
  @__acidic_job_steps = []
65
65
  steps = yield || []
@@ -76,7 +76,7 @@ module AcidicJob
76
76
  # TODO: allow idempotency to be defined by args OR job id
77
77
  @__acidic_job_idempotency_key ||= IdempotencyKey.value_for(self, @__acidic_job_args, @__acidic_job_kwargs)
78
78
 
79
- @run = ensure_run_record(@__acidic_job_idempotency_key, workflow, given)
79
+ @run = ensure_run_record(@__acidic_job_idempotency_key, workflow, providing)
80
80
 
81
81
  # begin the workflow
82
82
  process_run(@run)
@@ -84,7 +84,7 @@ module AcidicJob
84
84
 
85
85
  # DEPRECATED
86
86
  def idempotently(with:, &blk)
87
- with_acidity(given: with, &blk)
87
+ with_acidity(providing: with, &blk)
88
88
  end
89
89
 
90
90
  def safely_finish_acidic_job
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.pre6
4
+ version: 1.0.0.pre9
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-10 00:00:00.000000000 Z
11
+ date: 2022-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -84,9 +84,11 @@ files:
84
84
  - lib/acidic_job/idempotency_key.rb
85
85
  - lib/acidic_job/perform_wrapper.rb
86
86
  - lib/acidic_job/recovery_point.rb
87
+ - lib/acidic_job/rspec_configuration.rb
87
88
  - lib/acidic_job/run.rb
88
89
  - lib/acidic_job/staging.rb
89
90
  - lib/acidic_job/step.rb
91
+ - lib/acidic_job/test_case.rb
90
92
  - lib/acidic_job/upgrade_service.rb
91
93
  - lib/acidic_job/version.rb
92
94
  - lib/generators/acidic_job/drop_tables_generator.rb