jidoka 0.2.0
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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +125 -0
- data/LICENSE.txt +21 -0
- data/README.md +58 -0
- data/Rakefile +8 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/jidoka.gemspec +34 -0
- data/lib/jidoka/errors.rb +39 -0
- data/lib/jidoka/notifiable.rb +19 -0
- data/lib/jidoka/supervisor/step.rb +31 -0
- data/lib/jidoka/supervisor.rb +94 -0
- data/lib/jidoka/validatable.rb +113 -0
- data/lib/jidoka/version.rb +5 -0
- data/lib/jidoka/worker.rb +239 -0
- data/lib/jidoka.rb +42 -0
- metadata +158 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 856c4e4436542b548e463c61551160030fd4baf16c25c78a0ff1868f55b0193e
|
|
4
|
+
data.tar.gz: '0849949d4d82447b878a348d575bc36bba70c11ed64a987093a482a9627a575e'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 56f3274112e8f9e1afb3ff0c0e9e35c670a2baf4248dd46eced87429e5f3f6286d11c08ab9d79ca8f453d58a29f86763e3baa23800a11168ae94dfad068e159c
|
|
7
|
+
data.tar.gz: 17acb1f17bf45281cc1265a883bf44772270a5c0bfe24e904109dbb0af32f6ae778454652b55a9f2cb1cdb4db82a1ec5bd04360221372178d917c5d51fefe492
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.4.2
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
jidoka (0.2.0)
|
|
5
|
+
activejob (>= 6.0)
|
|
6
|
+
activerecord (>= 6.0)
|
|
7
|
+
activesupport (>= 6.0)
|
|
8
|
+
|
|
9
|
+
GEM
|
|
10
|
+
remote: https://rubygems.org/
|
|
11
|
+
specs:
|
|
12
|
+
activejob (8.1.2)
|
|
13
|
+
activesupport (= 8.1.2)
|
|
14
|
+
globalid (>= 0.3.6)
|
|
15
|
+
activemodel (8.1.2)
|
|
16
|
+
activesupport (= 8.1.2)
|
|
17
|
+
activerecord (8.1.2)
|
|
18
|
+
activemodel (= 8.1.2)
|
|
19
|
+
activesupport (= 8.1.2)
|
|
20
|
+
timeout (>= 0.4.0)
|
|
21
|
+
activesupport (8.1.2)
|
|
22
|
+
base64
|
|
23
|
+
bigdecimal
|
|
24
|
+
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
25
|
+
connection_pool (>= 2.2.5)
|
|
26
|
+
drb
|
|
27
|
+
i18n (>= 1.6, < 2)
|
|
28
|
+
json
|
|
29
|
+
logger (>= 1.4.2)
|
|
30
|
+
minitest (>= 5.1)
|
|
31
|
+
securerandom (>= 0.3)
|
|
32
|
+
tzinfo (~> 2.0, >= 2.0.5)
|
|
33
|
+
uri (>= 0.13.1)
|
|
34
|
+
base64 (0.3.0)
|
|
35
|
+
bigdecimal (4.0.1)
|
|
36
|
+
coderay (1.1.3)
|
|
37
|
+
concurrent-ruby (1.3.6)
|
|
38
|
+
connection_pool (3.0.2)
|
|
39
|
+
diff-lcs (1.6.2)
|
|
40
|
+
drb (2.2.3)
|
|
41
|
+
globalid (1.3.0)
|
|
42
|
+
activesupport (>= 6.1)
|
|
43
|
+
i18n (1.14.8)
|
|
44
|
+
concurrent-ruby (~> 1.0)
|
|
45
|
+
io-console (0.8.2)
|
|
46
|
+
json (2.18.0)
|
|
47
|
+
logger (1.7.0)
|
|
48
|
+
method_source (1.1.0)
|
|
49
|
+
minitest (6.0.1)
|
|
50
|
+
prism (~> 1.5)
|
|
51
|
+
prism (1.9.0)
|
|
52
|
+
pry (0.16.0)
|
|
53
|
+
coderay (~> 1.1)
|
|
54
|
+
method_source (~> 1.0)
|
|
55
|
+
reline (>= 0.6.0)
|
|
56
|
+
rake (13.3.1)
|
|
57
|
+
reline (0.6.3)
|
|
58
|
+
io-console (~> 0.5)
|
|
59
|
+
rspec (3.13.2)
|
|
60
|
+
rspec-core (~> 3.13.0)
|
|
61
|
+
rspec-expectations (~> 3.13.0)
|
|
62
|
+
rspec-mocks (~> 3.13.0)
|
|
63
|
+
rspec-core (3.13.6)
|
|
64
|
+
rspec-support (~> 3.13.0)
|
|
65
|
+
rspec-expectations (3.13.5)
|
|
66
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
67
|
+
rspec-support (~> 3.13.0)
|
|
68
|
+
rspec-mocks (3.13.7)
|
|
69
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
70
|
+
rspec-support (~> 3.13.0)
|
|
71
|
+
rspec-support (3.13.7)
|
|
72
|
+
securerandom (0.4.1)
|
|
73
|
+
sqlite3 (2.9.0-arm64-darwin)
|
|
74
|
+
timeout (0.6.0)
|
|
75
|
+
tzinfo (2.0.6)
|
|
76
|
+
concurrent-ruby (~> 1.0)
|
|
77
|
+
uri (1.1.1)
|
|
78
|
+
|
|
79
|
+
PLATFORMS
|
|
80
|
+
arm64-darwin-23
|
|
81
|
+
|
|
82
|
+
DEPENDENCIES
|
|
83
|
+
jidoka!
|
|
84
|
+
pry
|
|
85
|
+
rake (~> 13.0)
|
|
86
|
+
rspec (~> 3.0)
|
|
87
|
+
sqlite3
|
|
88
|
+
|
|
89
|
+
CHECKSUMS
|
|
90
|
+
activejob (8.1.2) sha256=908dab3713b101859536375819f4156b07bdf4c232cc645e7538adb9e302f825
|
|
91
|
+
activemodel (8.1.2) sha256=e21358c11ce68aed3f9838b7e464977bc007b4446c6e4059781e1d5c03bcf33e
|
|
92
|
+
activerecord (8.1.2) sha256=acfbe0cadfcc50fa208011fe6f4eb01cae682ebae0ef57145ba45380c74bcc44
|
|
93
|
+
activesupport (8.1.2) sha256=88842578ccd0d40f658289b0e8c842acfe9af751afee2e0744a7873f50b6fdae
|
|
94
|
+
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
95
|
+
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
|
96
|
+
coderay (1.1.3) sha256=dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b
|
|
97
|
+
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
98
|
+
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
|
99
|
+
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
100
|
+
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
|
101
|
+
globalid (1.3.0) sha256=05c639ad6eb4594522a0b07983022f04aa7254626ab69445a0e493aa3786ff11
|
|
102
|
+
i18n (1.14.8) sha256=285778639134865c5e0f6269e0b818256017e8cde89993fdfcbfb64d088824a5
|
|
103
|
+
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
|
|
104
|
+
jidoka (0.2.0)
|
|
105
|
+
json (2.18.0) sha256=b10506aee4183f5cf49e0efc48073d7b75843ce3782c68dbeb763351c08fd505
|
|
106
|
+
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
107
|
+
method_source (1.1.0) sha256=181301c9c45b731b4769bc81e8860e72f9161ad7d66dd99103c9ab84f560f5c5
|
|
108
|
+
minitest (6.0.1) sha256=7854c74f48e2e975969062833adc4013f249a4b212f5e7b9d5c040bf838d54bb
|
|
109
|
+
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
110
|
+
pry (0.16.0) sha256=d76c69065698ed1f85e717bd33d7942c38a50868f6b0673c636192b3d1b6054e
|
|
111
|
+
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
|
112
|
+
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
|
113
|
+
rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
|
|
114
|
+
rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
|
|
115
|
+
rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
|
|
116
|
+
rspec-mocks (3.13.7) sha256=0979034e64b1d7a838aaaddf12bf065ea4dc40ef3d4c39f01f93ae2c66c62b1c
|
|
117
|
+
rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
|
|
118
|
+
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
|
|
119
|
+
sqlite3 (2.9.0-arm64-darwin) sha256=a917bd9b84285766ff3300b7d79cd583f5a067594c8c1263e6441618c04a6ed3
|
|
120
|
+
timeout (0.6.0) sha256=6d722ad619f96ee383a0c557ec6eb8c4ecb08af3af62098a0be5057bf00de1af
|
|
121
|
+
tzinfo (2.0.6) sha256=8daf828cc77bcf7d63b0e3bdb6caa47e2272dcfaf4fbfe46f8c3a9df087a829b
|
|
122
|
+
uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6
|
|
123
|
+
|
|
124
|
+
BUNDLED WITH
|
|
125
|
+
4.0.5
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Nate Mortensen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Jidoka
|
|
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/jidoka`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
4
|
+
|
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
Add this line to your application's Gemfile:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
gem 'jidoka'
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
And then execute:
|
|
16
|
+
|
|
17
|
+
$ bundle
|
|
18
|
+
|
|
19
|
+
Or install it yourself as:
|
|
20
|
+
|
|
21
|
+
$ gem install jidoka
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
Add an initializer to configure Jidoka:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
# config/initializers/jidoka.rb
|
|
29
|
+
Jidoka.configure do |config|
|
|
30
|
+
# Inherit from your app's base job to get queues, retries, etc.
|
|
31
|
+
config.parent_job_class = "ApplicationJob"
|
|
32
|
+
|
|
33
|
+
# Hook into your error reporting tool
|
|
34
|
+
config.error_handler = ->(e, context) {
|
|
35
|
+
if defined?(Sentry)
|
|
36
|
+
Sentry.set_context('jidoka', context)
|
|
37
|
+
Sentry.capture_exception(e)
|
|
38
|
+
Sentry::Context.clear!
|
|
39
|
+
else
|
|
40
|
+
Rails.logger.error("Jidoka Error: #{e.message} Context: #{context}")
|
|
41
|
+
end
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Development
|
|
47
|
+
|
|
48
|
+
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.
|
|
49
|
+
|
|
50
|
+
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 tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
51
|
+
|
|
52
|
+
## Contributing
|
|
53
|
+
|
|
54
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/jidoka.
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
|
|
58
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'jidoka'
|
|
6
|
+
|
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
9
|
+
|
|
10
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
11
|
+
# require "pry"
|
|
12
|
+
# Pry.start
|
|
13
|
+
|
|
14
|
+
require 'irb'
|
|
15
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/jidoka.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/jidoka/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "jidoka"
|
|
7
|
+
spec.version = Jidoka::VERSION
|
|
8
|
+
spec.authors = ["Nate Mortensen"]
|
|
9
|
+
spec.summary = "Reversible Command and Orchestrator patterns for Rails."
|
|
10
|
+
spec.description = "Encapsulate complex business logic with automatic rollback, validation, and background processing."
|
|
11
|
+
spec.homepage = "https://github.com/natemortensen/jidoka"
|
|
12
|
+
spec.license = "MIT"
|
|
13
|
+
|
|
14
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
15
|
+
|
|
16
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
17
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
spec.bindir = "exe"
|
|
21
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
22
|
+
spec.require_paths = ["lib"]
|
|
23
|
+
|
|
24
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
25
|
+
|
|
26
|
+
spec.add_dependency "activejob", ">= 6.0"
|
|
27
|
+
spec.add_dependency "activerecord", ">= 6.0"
|
|
28
|
+
spec.add_dependency "activesupport", ">= 6.0"
|
|
29
|
+
|
|
30
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
|
31
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
32
|
+
spec.add_development_dependency "pry"
|
|
33
|
+
spec.add_development_dependency "sqlite3"
|
|
34
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jidoka
|
|
4
|
+
# Raised when rollback (down) fails
|
|
5
|
+
class IrreversibleAction < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when enforce_arguments! validation fails
|
|
8
|
+
class ArgumentClassMismatch < StandardError
|
|
9
|
+
attr_reader :param, :expected, :actual
|
|
10
|
+
|
|
11
|
+
def initialize(argument, expected:, actual:)
|
|
12
|
+
@param = argument
|
|
13
|
+
@expected = expected
|
|
14
|
+
@actual = actual
|
|
15
|
+
super("#{param} was expected to be a(n) #{@expected} but was a(n) #{@actual || 'nil'}")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Raised when business logic conditions are not met (Validation phase)
|
|
20
|
+
class ConditionNotMet < StandardError
|
|
21
|
+
attr_reader :code
|
|
22
|
+
|
|
23
|
+
def initialize(code:, message:)
|
|
24
|
+
@code = code
|
|
25
|
+
super(message)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Raised when execution fails for a known reason (Execution phase)
|
|
30
|
+
class Failure < StandardError
|
|
31
|
+
attr_reader :code, :context
|
|
32
|
+
|
|
33
|
+
def initialize(code:, message:, context: {})
|
|
34
|
+
@code = code
|
|
35
|
+
@context = context
|
|
36
|
+
super(message)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jidoka
|
|
4
|
+
module Notifiable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
def send_notification
|
|
9
|
+
notify(@opts)
|
|
10
|
+
|
|
11
|
+
# These are just notifications so they can silently fail but still report error
|
|
12
|
+
rescue StandardError => e
|
|
13
|
+
capture_error(e)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def notify(**_opts); end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Jidoka
|
|
2
|
+
class Supervisor < Commander
|
|
3
|
+
class Step
|
|
4
|
+
attr_reader :result
|
|
5
|
+
|
|
6
|
+
def initialize(caller)
|
|
7
|
+
@caller = caller
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def up(&block)
|
|
11
|
+
@result = @caller.instance_eval(&block)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def _down
|
|
15
|
+
@caller.instance_exec(@result, &@down) if @down.present?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def down(&block)
|
|
19
|
+
@down = block
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def _notify
|
|
23
|
+
@caller.instance_exec(@result, &@notify) if @notify.present?
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def notify(&block)
|
|
27
|
+
@notify = block
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
require_relative "supervisor/step"
|
|
2
|
+
|
|
3
|
+
module Jidoka
|
|
4
|
+
class Supervisor < Commander
|
|
5
|
+
def initialize(*args)
|
|
6
|
+
super(*args)
|
|
7
|
+
@steps = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def run!
|
|
11
|
+
orchestrate(**@opts)
|
|
12
|
+
rescue StandardError => e
|
|
13
|
+
rollback
|
|
14
|
+
# notice_failure! is called in run! wrapper of super, but we need to ensure
|
|
15
|
+
# we notice it here if we want to log it before re-raising
|
|
16
|
+
send(:notice_failure!, e)
|
|
17
|
+
raise(e)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def rollback
|
|
21
|
+
@steps.reverse_each do |step|
|
|
22
|
+
begin
|
|
23
|
+
step._down
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
report_error(e)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Alias for compatibility
|
|
31
|
+
def down
|
|
32
|
+
rollback
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def notify!
|
|
36
|
+
@steps.each(&:_notify)
|
|
37
|
+
_notify(**@opts)
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
report_error(e)
|
|
40
|
+
raise(e) if defined?(Rails) && Rails.env.test?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def step!(&block)
|
|
46
|
+
step = Step.new(self)
|
|
47
|
+
begin
|
|
48
|
+
step.instance_eval(&block)
|
|
49
|
+
rescue StandardError => e
|
|
50
|
+
@message = e.message
|
|
51
|
+
raise(e)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
@steps << step
|
|
55
|
+
step.result
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def worker_step!(klass, opts = {})
|
|
59
|
+
step! do
|
|
60
|
+
up { klass.run!(**opts.merge(notify: false)) }
|
|
61
|
+
down(&:down) # Calls down on the worker instance returned by up
|
|
62
|
+
notify(&:notify!) unless opts[:notify] == false
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def update_record_step!(record, updates)
|
|
67
|
+
previous_state = updates.to_h do |k, v|
|
|
68
|
+
[
|
|
69
|
+
k,
|
|
70
|
+
k.to_s =~ /_attributes/ ? v.class.new : record.send(k)
|
|
71
|
+
]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
step! do
|
|
75
|
+
up { record.tap { record.update!(updates) } }
|
|
76
|
+
down { record.tap { record.update!(previous_state) } }
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def create_record_step!(&block)
|
|
81
|
+
step! do
|
|
82
|
+
up(&block)
|
|
83
|
+
down do |record|
|
|
84
|
+
record.reload.destroy!
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Abstract method to be implemented by subclass
|
|
90
|
+
def orchestrate(**_opts)
|
|
91
|
+
raise NotImplementedError
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jidoka
|
|
4
|
+
module Validatable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
attr_reader :message, :error
|
|
9
|
+
|
|
10
|
+
##
|
|
11
|
+
# This is a Hash of required argument keys and their expected class
|
|
12
|
+
class_attribute :argument_types, :errors
|
|
13
|
+
|
|
14
|
+
def validate!
|
|
15
|
+
validate_arguments!(@opts)
|
|
16
|
+
prepare(@opts)
|
|
17
|
+
audit(@opts)
|
|
18
|
+
rescue ConditionNotMet, ArgumentClassMismatch, Failure => e
|
|
19
|
+
notice_failure(e)
|
|
20
|
+
raise(e)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def validate
|
|
24
|
+
validate!
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
notice_failure(e)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def notice_failure(e)
|
|
30
|
+
@failure = true
|
|
31
|
+
@error = e
|
|
32
|
+
@message = e.message
|
|
33
|
+
|
|
34
|
+
capture_error(error)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def capture_error(error); end
|
|
38
|
+
|
|
39
|
+
def failure?
|
|
40
|
+
!@failure.nil?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def failed
|
|
44
|
+
yield(self) if failure?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def success?
|
|
48
|
+
!failure?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def succeeded
|
|
52
|
+
yield(self) if success?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def audit(**_opts); end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Raises `ConditionNotMet` with specified error message if block does not return a truthy value.
|
|
59
|
+
# Intended for use with `audit`
|
|
60
|
+
def valid_if(error_key, message: nil)
|
|
61
|
+
raise_condition!(error_key, message: message) unless yield
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Raises `ConditionNotMet` with specified error message if block does not return a falsey value.
|
|
66
|
+
# Intended for use with `audit`
|
|
67
|
+
def valid_unless(error_key, message: nil)
|
|
68
|
+
raise_condition!(error_key, message: message) if yield
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def raise_condition!(key, message: nil)
|
|
72
|
+
raise ConditionNotMet.new(
|
|
73
|
+
message: message || self.class.possible_errors[key],
|
|
74
|
+
code: key
|
|
75
|
+
)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def fail!(key, message: nil)
|
|
79
|
+
raise Failure.new(
|
|
80
|
+
message: message || self.class.possible_errors[key],
|
|
81
|
+
code: key
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Iterate through `argument_types` checking for a class match
|
|
86
|
+
def validate_arguments!(**args)
|
|
87
|
+
return if self.class.argument_types.nil?
|
|
88
|
+
|
|
89
|
+
self.class.argument_types.each do |key, klass|
|
|
90
|
+
case args[key]
|
|
91
|
+
when *Array(klass).map(&:constantize) then nil # Object is okay
|
|
92
|
+
else raise ArgumentClassMismatch.new(key, expected: klass, actual: args[key]&.class&.to_s)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class_methods do
|
|
99
|
+
def enforce_arguments!(obj)
|
|
100
|
+
self.argument_types = obj.freeze
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def set_errors(obj)
|
|
104
|
+
self.errors = (errors || {}).merge(obj)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def possible_errors(with_prefix = false)
|
|
108
|
+
@possible_errors ||= errors || {}
|
|
109
|
+
with_prefix ? @possible_errors.transform_keys { |k| [to_s.underscore, k].join('-') } : @possible_errors
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
module Jidoka
|
|
2
|
+
class Commander < Jidoka.configuration.parent_job_class.constantize
|
|
3
|
+
include ActiveSupport::Rescuable
|
|
4
|
+
|
|
5
|
+
# @return [String] Error message to display to end users
|
|
6
|
+
attr_reader :message, :error
|
|
7
|
+
|
|
8
|
+
class_attribute :argument_types
|
|
9
|
+
|
|
10
|
+
# Shared error messages available to all Commanders
|
|
11
|
+
BASE_ERRORS = {
|
|
12
|
+
invalid_state_transition: 'You cannot transition to this state',
|
|
13
|
+
action_already_performed: 'This action has already been performed'
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
# Default ERRORS hash to be overridden by subclasses
|
|
17
|
+
ERRORS = {}.freeze
|
|
18
|
+
|
|
19
|
+
# -- Class Methods --
|
|
20
|
+
|
|
21
|
+
def self.enforce_arguments!(obj)
|
|
22
|
+
self.argument_types = obj.freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.possible_errors(with_prefix = false)
|
|
26
|
+
@possible_errors ||= self::ERRORS
|
|
27
|
+
with_prefix ? @possible_errors.transform_keys { |k| [to_s.underscore, k].join('-') } : @possible_errors
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.run!(opts = {})
|
|
31
|
+
initialize_and_call!(opts, :validate!, :run!, *include_notify?(opts))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.run(opts = {})
|
|
35
|
+
initialize_and_call!(opts, :validate, :run, *include_notify?(opts)).tap do |result|
|
|
36
|
+
yield(result) if block_given?
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.dry_run!(opts = {})
|
|
41
|
+
initialize_and_call!(opts, :validate!)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.dry_run(opts = {})
|
|
45
|
+
initialize_and_call!(opts, :validate)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.undo!(opts = {})
|
|
49
|
+
initialize_and_call!(opts, :undo!)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.undo(opts = {})
|
|
53
|
+
initialize_and_call!(opts, :undo).tap do |result|
|
|
54
|
+
yield(result) if block_given?
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.include_notify?(opts)
|
|
59
|
+
[nil, true].include?(opts.delete(:notify)) ? %i[notify!] : []
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def self.initialize_and_call!(opts, *methods_to_call)
|
|
63
|
+
instance = new(opts.transform_keys(&:to_sym))
|
|
64
|
+
methods_to_call.each do |m|
|
|
65
|
+
instance.send(m)
|
|
66
|
+
break if instance.failure?
|
|
67
|
+
end
|
|
68
|
+
instance
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# -- Instance Methods --
|
|
72
|
+
|
|
73
|
+
def initialize(args = nil)
|
|
74
|
+
super
|
|
75
|
+
# Handle ActiveJob vs direct instantiation args
|
|
76
|
+
@opts = args || (arguments ? arguments[0] : {})
|
|
77
|
+
@opts = @opts.transform_keys(&:to_sym) if @opts
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def perform(opts = {})
|
|
81
|
+
@opts = opts.transform_keys(&:to_sym)
|
|
82
|
+
validate!
|
|
83
|
+
run!
|
|
84
|
+
notify!
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def prepare(opts); end
|
|
88
|
+
|
|
89
|
+
def validate!
|
|
90
|
+
validate_arguments!(**@opts)
|
|
91
|
+
prepare(**@opts)
|
|
92
|
+
validate_conditions!(**@opts)
|
|
93
|
+
rescue Jidoka::ConditionNotMet, Jidoka::ArgumentClassMismatch, Jidoka::Failure => e
|
|
94
|
+
notice_failure!(e)
|
|
95
|
+
raise(e)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def validate
|
|
99
|
+
validate!
|
|
100
|
+
rescue StandardError => e
|
|
101
|
+
notice_failure!(e)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def run!
|
|
105
|
+
ActiveRecord::Base.transaction { up(**@opts) }
|
|
106
|
+
rescue Jidoka::ConditionNotMet, Jidoka::Failure => e
|
|
107
|
+
notice_failure!(e)
|
|
108
|
+
raise(e)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def run
|
|
112
|
+
run!
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
notice_failure!(e)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def undo!
|
|
118
|
+
prepare_inverse(**@opts) if respond_to?(:prepare_inverse)
|
|
119
|
+
ActiveRecord::Base.transaction { down }
|
|
120
|
+
rescue Jidoka::ConditionNotMet, Jidoka::Failure => e
|
|
121
|
+
notice_failure!(e)
|
|
122
|
+
raise(e)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def undo
|
|
126
|
+
undo!
|
|
127
|
+
rescue StandardError => e
|
|
128
|
+
notice_failure!(e)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def up(_opts = nil)
|
|
132
|
+
raise NotImplementedError
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def down; end
|
|
136
|
+
|
|
137
|
+
def notify!
|
|
138
|
+
_notify(**@opts)
|
|
139
|
+
rescue StandardError => e
|
|
140
|
+
report_error(e)
|
|
141
|
+
# We do not re-raise notification errors by default unless in test
|
|
142
|
+
raise(e) if defined?(Rails) && Rails.env.test?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def _notify(**_opts); end
|
|
146
|
+
|
|
147
|
+
# -- State Helpers --
|
|
148
|
+
|
|
149
|
+
def failure?
|
|
150
|
+
@failure.present?
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def success?
|
|
154
|
+
!failure?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def failed
|
|
158
|
+
yield(self) if failure?
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def success
|
|
162
|
+
yield(self) if success?
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
protected
|
|
166
|
+
|
|
167
|
+
def validate_conditions!(**opts); end
|
|
168
|
+
|
|
169
|
+
def condition!(key, message: nil)
|
|
170
|
+
raise_condition!(key, message: message) unless yield
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def raise_condition!(key, message: nil)
|
|
174
|
+
raise Jidoka::ConditionNotMet.new(
|
|
175
|
+
message: message || self.class.possible_errors[key],
|
|
176
|
+
code: [self.class.to_s.underscore, key].join('-')
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def fail!(key, message: nil, **context)
|
|
181
|
+
raise Jidoka::Failure.new(
|
|
182
|
+
message: message || self.class.possible_errors[key],
|
|
183
|
+
code: [self.class.to_s.underscore, key].join('-'),
|
|
184
|
+
context: context
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def validate_arguments!(**args)
|
|
189
|
+
return if self.class.argument_types.nil?
|
|
190
|
+
|
|
191
|
+
self.class.argument_types.each do |key, klass_name|
|
|
192
|
+
val = args[key]
|
|
193
|
+
expected_classes = Array(klass_name).map(&:constantize)
|
|
194
|
+
|
|
195
|
+
# Check if value matches any of the expected classes
|
|
196
|
+
unless expected_classes.any? { |k| val.is_a?(k) }
|
|
197
|
+
raise Jidoka::ArgumentClassMismatch.new(key, expected: klass_name, actual: val.class.to_s)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Validates usage of AASM state machines if AASM is present
|
|
203
|
+
def aasm_event_condition!(object, event)
|
|
204
|
+
return unless defined?(AASM)
|
|
205
|
+
|
|
206
|
+
matched = object.class.aasm.events.detect { |e| e.name.to_s == event.to_s }
|
|
207
|
+
return if matched.may_fire?(object)
|
|
208
|
+
|
|
209
|
+
if matched.transitions[0].to == object.aasm.current_state
|
|
210
|
+
raise_condition!(
|
|
211
|
+
:action_already_performed,
|
|
212
|
+
message: "This #{object.class.to_s.humanize} is already #{object.try(:status)&.humanize || 'in that state'}"
|
|
213
|
+
)
|
|
214
|
+
else
|
|
215
|
+
raise_condition!(
|
|
216
|
+
:invalid_state_transition,
|
|
217
|
+
message: "Could not #{event} because current status is #{object.try(:status)&.humanize || 'invalid'}"
|
|
218
|
+
)
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def valid_for?(record, context)
|
|
223
|
+
record.valid?(context) || raise(ActiveRecord::RecordInvalid, record)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
private
|
|
227
|
+
|
|
228
|
+
def notice_failure!(e)
|
|
229
|
+
@failure = true
|
|
230
|
+
@error = e
|
|
231
|
+
@message = e.message
|
|
232
|
+
report_error(e)
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def report_error(e)
|
|
236
|
+
Jidoka.configuration.error_handler.call(e, { worker: self.class.name, args: @opts })
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
data/lib/jidoka.rb
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support"
|
|
4
|
+
require "active_job"
|
|
5
|
+
require "active_record"
|
|
6
|
+
require "jidoka/version"
|
|
7
|
+
require "jidoka/errors"
|
|
8
|
+
|
|
9
|
+
module Jidoka
|
|
10
|
+
class Error < StandardError; end
|
|
11
|
+
|
|
12
|
+
class Configuration
|
|
13
|
+
# The parent class for Workers (defaults to ActiveJob::Base)
|
|
14
|
+
attr_accessor :parent_job_class
|
|
15
|
+
# Block to execute when an error occurs (for Sentry/Honeybadger)
|
|
16
|
+
attr_accessor :error_handler
|
|
17
|
+
|
|
18
|
+
def initialize
|
|
19
|
+
@parent_job_class = "ActiveJob::Base"
|
|
20
|
+
@error_handler = ->(error, context = {}) {
|
|
21
|
+
# Default: just log it
|
|
22
|
+
if defined?(Rails)
|
|
23
|
+
Rails.logger.error("[Jidoka] #{error.message}")
|
|
24
|
+
Rails.logger.error(error.backtrace.join("\n"))
|
|
25
|
+
end
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def configuration
|
|
32
|
+
@configuration ||= Configuration.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def configure
|
|
36
|
+
yield(configuration)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
require "jidoka/worker"
|
|
42
|
+
require "jidoka/supervisor"
|
metadata
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: jidoka
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.2.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Nate Mortensen
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-02-12 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activejob
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: activerecord
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '6.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '6.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: activesupport
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '6.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '6.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rspec
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rake
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '13.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '13.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: pry
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - ">="
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: sqlite3
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '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'
|
|
110
|
+
description: Encapsulate complex business logic with automatic rollback, validation,
|
|
111
|
+
and background processing.
|
|
112
|
+
executables: []
|
|
113
|
+
extensions: []
|
|
114
|
+
extra_rdoc_files: []
|
|
115
|
+
files:
|
|
116
|
+
- ".gitignore"
|
|
117
|
+
- ".rspec"
|
|
118
|
+
- ".ruby-version"
|
|
119
|
+
- ".travis.yml"
|
|
120
|
+
- Gemfile
|
|
121
|
+
- Gemfile.lock
|
|
122
|
+
- LICENSE.txt
|
|
123
|
+
- README.md
|
|
124
|
+
- Rakefile
|
|
125
|
+
- bin/console
|
|
126
|
+
- bin/setup
|
|
127
|
+
- jidoka.gemspec
|
|
128
|
+
- lib/jidoka.rb
|
|
129
|
+
- lib/jidoka/errors.rb
|
|
130
|
+
- lib/jidoka/notifiable.rb
|
|
131
|
+
- lib/jidoka/supervisor.rb
|
|
132
|
+
- lib/jidoka/supervisor/step.rb
|
|
133
|
+
- lib/jidoka/validatable.rb
|
|
134
|
+
- lib/jidoka/version.rb
|
|
135
|
+
- lib/jidoka/worker.rb
|
|
136
|
+
homepage: https://github.com/natemortensen/jidoka
|
|
137
|
+
licenses:
|
|
138
|
+
- MIT
|
|
139
|
+
metadata:
|
|
140
|
+
rubygems_mfa_required: 'true'
|
|
141
|
+
rdoc_options: []
|
|
142
|
+
require_paths:
|
|
143
|
+
- lib
|
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
145
|
+
requirements:
|
|
146
|
+
- - ">="
|
|
147
|
+
- !ruby/object:Gem::Version
|
|
148
|
+
version: 3.0.0
|
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
150
|
+
requirements:
|
|
151
|
+
- - ">="
|
|
152
|
+
- !ruby/object:Gem::Version
|
|
153
|
+
version: '0'
|
|
154
|
+
requirements: []
|
|
155
|
+
rubygems_version: 3.6.2
|
|
156
|
+
specification_version: 4
|
|
157
|
+
summary: Reversible Command and Orchestrator patterns for Rails.
|
|
158
|
+
test_files: []
|