flow_core 0.0.1
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/MIT-LICENSE +20 -0
- data/README.md +255 -0
- data/Rakefile +29 -0
- data/app/models/flow_core/application_record.rb +7 -0
- data/app/models/flow_core/arc.rb +43 -0
- data/app/models/flow_core/arc_guard.rb +18 -0
- data/app/models/flow_core/end_place.rb +17 -0
- data/app/models/flow_core/instance.rb +115 -0
- data/app/models/flow_core/place.rb +63 -0
- data/app/models/flow_core/start_place.rb +17 -0
- data/app/models/flow_core/task.rb +197 -0
- data/app/models/flow_core/token.rb +105 -0
- data/app/models/flow_core/transition.rb +143 -0
- data/app/models/flow_core/transition_callback.rb +24 -0
- data/app/models/flow_core/transition_trigger.rb +24 -0
- data/app/models/flow_core/workflow.rb +131 -0
- data/config/routes.rb +4 -0
- data/db/migrate/20200130200532_create_flow_core_tables.rb +151 -0
- data/lib/flow_core.rb +25 -0
- data/lib/flow_core/arc_guardable.rb +15 -0
- data/lib/flow_core/definition.rb +17 -0
- data/lib/flow_core/definition/callback.rb +33 -0
- data/lib/flow_core/definition/guard.rb +33 -0
- data/lib/flow_core/definition/net.rb +111 -0
- data/lib/flow_core/definition/place.rb +33 -0
- data/lib/flow_core/definition/transition.rb +107 -0
- data/lib/flow_core/definition/trigger.rb +33 -0
- data/lib/flow_core/engine.rb +6 -0
- data/lib/flow_core/errors.rb +14 -0
- data/lib/flow_core/locale/en.yml +26 -0
- data/lib/flow_core/task_executable.rb +34 -0
- data/lib/flow_core/transition_callbackable.rb +33 -0
- data/lib/flow_core/transition_triggerable.rb +27 -0
- data/lib/flow_core/version.rb +5 -0
- data/lib/flow_core/violations.rb +253 -0
- data/lib/tasks/flow_core_tasks.rake +6 -0
- metadata +123 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a8930957495dfe27e025aaebc7b0c88b665cd421ddcba835cc4680776279553d
|
4
|
+
data.tar.gz: 02d2f3166cde5e4d90226163d05ba8ffac13a26df81e1221040fbf8914933b5c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 96ce06af9cce87d138115bce55601dd0d3e278192c6052b82546ed1e2fad741876c5f0f01fbe71d37d01b04c60bf8d46e2a3c289f616d25bd5316cdff65a646a
|
7
|
+
data.tar.gz: 6fbc984298817220c0569484d40e7d8136013ad499d62d119fe43bfb6621a00a2056c59c07d1bf6bae6dfa82e91c06c1775a82a0d2f1e87ae093e540f43ec8fa
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2020 jasl
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
Flow Core
|
2
|
+
===
|
3
|
+
|
4
|
+
> FlowCore is ready for open reviewing, but it haven't tested in production yet,
|
5
|
+
> any help are most welcome, breaking change is acceptable.
|
6
|
+
|
7
|
+
A multi purpose, extendable, Workflow-net-based workflow engine for Rails applications.
|
8
|
+
|
9
|
+
FlowCore is an open source Rails engine provides core workflow functionalities,
|
10
|
+
including workflow definition and workflow instance scheduling.
|
11
|
+
Easily making automation (including CI, CD, Data processing, etc.) and BPM applications or help you solve parts which changing frequently.
|
12
|
+
|
13
|
+
## Features
|
14
|
+
|
15
|
+
### Support all databases which based on ActiveRecord
|
16
|
+
|
17
|
+
All persistent data are present as ActiveRecord model and not use any DB-specific feature.
|
18
|
+
|
19
|
+
### Easy to extend & hack
|
20
|
+
|
21
|
+
FlowCore basically followed best practice of Rails engine,
|
22
|
+
you can extend as [Rails Guides](https://guides.rubyonrails.org/engines.html#improving-engine-functionality) suggests.
|
23
|
+
|
24
|
+
Your app-specific workflow triggers, callbacks and guards can be extended via [Single Table Inheritance](https://guides.rubyonrails.org/association_basics.html#single-table-inheritance)
|
25
|
+
|
26
|
+
FlowCore also provides callbacks for triggers (which control behavior of a transition) covered whole task lifecycle.
|
27
|
+
|
28
|
+
### Petri-net based
|
29
|
+
|
30
|
+
[Petri-net](https://en.wikipedia.org/wiki/Petri_net) is a technique for description and analysis of concurrent systems.
|
31
|
+
|
32
|
+
FlowCore choose its special type called [Workflow-net](http://mlwiki.org/index.php/Workflow_Nets) to expressing workflow.
|
33
|
+
|
34
|
+
Compared to more popular activity-based workflow definitions (e.g BPMN),
|
35
|
+
Petri-net has only few rules but could express very complex case.
|
36
|
+
|
37
|
+
Check [workflow patterns](http://workflowpatterns.com) to learn how to use Petri-net expressing workflows.
|
38
|
+
|
39
|
+
### Basic workflow checking.
|
40
|
+
|
41
|
+
A workflow should be verified first before running it.
|
42
|
+
|
43
|
+
FlowCore provides the mechanism to help to prevent unexpected error on instance running
|
44
|
+
|
45
|
+
> This is the hard work and help wanted
|
46
|
+
> Workflow-net has [soundness](http://mlwiki.org/index.php/Workflow_Soundness) checking but I don't know how to implement it
|
47
|
+
|
48
|
+
### Interfaces and abstractions to integrate your business
|
49
|
+
|
50
|
+
FlowCore separate app-world and engine-world using interfaces and abstract classes,
|
51
|
+
basically you no need to know Workflow-net internal works.
|
52
|
+
|
53
|
+
### Runtime error and suspend support
|
54
|
+
|
55
|
+
FlowCore provides necessary columns and event callbacks for runtime error and suspend.
|
56
|
+
|
57
|
+
### A DSL to simplify workflow creation
|
58
|
+
|
59
|
+
FlowCore provides a powerful DSL for creating workflow.
|
60
|
+
|
61
|
+
## Demo
|
62
|
+
|
63
|
+
**You need to install Graphviz first**
|
64
|
+
|
65
|
+
Clone the repository.
|
66
|
+
|
67
|
+
```sh
|
68
|
+
$ git clone https://github.com/rails-engine/flow_core.git
|
69
|
+
```
|
70
|
+
|
71
|
+
Change directory
|
72
|
+
|
73
|
+
```sh
|
74
|
+
$ cd flow_core
|
75
|
+
```
|
76
|
+
|
77
|
+
Run bundler
|
78
|
+
|
79
|
+
```sh
|
80
|
+
$ bundle install
|
81
|
+
```
|
82
|
+
|
83
|
+
Preparing database
|
84
|
+
|
85
|
+
```sh
|
86
|
+
$ bin/rails db:migrate
|
87
|
+
```
|
88
|
+
|
89
|
+
Import sample workflow
|
90
|
+
|
91
|
+
```sh
|
92
|
+
$ bin/rails db:seed
|
93
|
+
```
|
94
|
+
|
95
|
+
Start the Rails server
|
96
|
+
|
97
|
+
```sh
|
98
|
+
$ bin/rails s
|
99
|
+
```
|
100
|
+
|
101
|
+
Open your browser, and visit `http://localhost:3000`
|
102
|
+
|
103
|
+
## Design
|
104
|
+
|
105
|
+
Architecture:
|
106
|
+
|
107
|
+

|
108
|
+
|
109
|
+
Basic design based on [An activity based Workflow Engine for PHP By Tony Marston](https://www.tonymarston.net/php-mysql/workflow.html).
|
110
|
+
|
111
|
+
Some notable:
|
112
|
+
|
113
|
+
- Arc: The line to connecting a Place and a Transition
|
114
|
+
- ArcGuard: The matcher to decide the arc is passable,
|
115
|
+
it's an base class that you can extend it for your own purpose.
|
116
|
+
- Task: A stateful record to present current workflow instance work,
|
117
|
+
and can reference a `TaskExecutable` through Rails polymorphic-reference.
|
118
|
+
It finish means the transition is done and can moving on.
|
119
|
+
- TaskExecutable: An interface for binding App task and FlowCore Task.
|
120
|
+
- TransitionTrigger: It controls the behavior of a Transition,
|
121
|
+
it's an base class that you can extend it for your own purpose,
|
122
|
+
best place for implementing business.
|
123
|
+
- TransitionCallback: It can be registered to a Transition, and be triggered on specified lifecycle(s) of Task
|
124
|
+
|
125
|
+
### Lifecycle of Task
|
126
|
+
|
127
|
+
- `created` Task created by a Petri-net Token
|
128
|
+
- `enabled` Transit to this stage when next transition requirement fulfilled
|
129
|
+
- Best chance to create app task (your custom task for business) in `TransitionTrigger#on_task_enabled`
|
130
|
+
- `finished` Normal ending
|
131
|
+
- Require app task finished first (if bind)
|
132
|
+
- `terminated` Task killed by instance (e.g Instance cancelled) or other race condition task
|
133
|
+
|
134
|
+
### FlowKit
|
135
|
+
|
136
|
+
Because FlowCore only care about essentials of workflow engine,
|
137
|
+
I'm planning a gem based on FlowCore to provides BPM-oriented features, including:
|
138
|
+
|
139
|
+
- Dynamic form
|
140
|
+
- Approval Task with assignment
|
141
|
+
- ExpressionGuard
|
142
|
+
|
143
|
+
### Why "core"
|
144
|
+
|
145
|
+
Because it's not aim to "out-of-box",
|
146
|
+
some gem like Devise giving developer an out-of-box experience, that's awesome,
|
147
|
+
but on the other hand, it also introducing a very complex abstraction that may hard to understanding how it works,
|
148
|
+
especially when you attempting to customize it.
|
149
|
+
|
150
|
+
I believe that the gem is tightly coupled with features that face to end users directly,
|
151
|
+
so having a good customizability and easy to understanding are of the most concern,
|
152
|
+
so I just wanna give you a domain framework that you can build your own that just fitting your need,
|
153
|
+
and you shall have fully control and without any unnecessary abstraction.
|
154
|
+
|
155
|
+
## TODO / Help wanted
|
156
|
+
|
157
|
+
- Document
|
158
|
+
- Test
|
159
|
+
- Activity-based to Petri-net mapping, see <https://www.researchgate.net/figure/The-mapping-between-BPMN-and-Petri-nets_tbl2_221250389> for example.
|
160
|
+
- More efficient and powerful workflow definition checking
|
161
|
+
- Grammar and naming correction (I'm not English native-speaker)
|
162
|
+
|
163
|
+
## Usage
|
164
|
+
|
165
|
+
> WIP
|
166
|
+
|
167
|
+
### Deploy a workflow
|
168
|
+
|
169
|
+
See [test/dummy/db/seeds.rb](test/dummy/db/seeds.rb) to learn the DSL, more complex sample see [test/dummy/app/models/internal_workflow.rb](test/dummy/app/models/internal_workflow.rb)
|
170
|
+
|
171
|
+
### Running a workflow
|
172
|
+
|
173
|
+
`workflow.create_instance!`
|
174
|
+
|
175
|
+
### Implementing an ArcGuard
|
176
|
+
|
177
|
+
[test/dummy/app/models/arc_guards/dentaku.rb](test/dummy/app/models/arc_guards/dentaku.rb) shows an expression guard which using [Dentaku](https://github.com/rubysolo/dentaku)
|
178
|
+
|
179
|
+
### Implementing a TransitionTrigger
|
180
|
+
|
181
|
+
[test/dummy/app/models/transition_triggers/timer.rb](test/dummy/app/models/transition_triggers/timer.rb) shows a delayed trigger which can be used for expires.
|
182
|
+
|
183
|
+
[test/dummy/app/models/transition_triggers/user_task.rb](test/dummy/app/models/transition_triggers/user_task.rb) shows a simple user task with a simple assignment.
|
184
|
+
|
185
|
+
### Implementing a TransitionCallback
|
186
|
+
|
187
|
+
[test/dummy/app/models/transition_callbacks/notification.rb](test/dummy/app/models/transition_callbacks/notification.rb) shows a simple callback that notify the assignee when the task started
|
188
|
+
|
189
|
+
### Implementing a TaskExecutable
|
190
|
+
|
191
|
+
[test/dummy/app/models/user_task.rb](test/dummy/app/models/user_task.rb) shows a sample,
|
192
|
+
[test/dummy/app/models/approval_task.rb](test/dummy/app/models/approval_task.rb) shows how to set payload to task that use for ArcGuard
|
193
|
+
|
194
|
+
### Extending Workflow
|
195
|
+
|
196
|
+
[test/dummy/app/models/internal_workflow.rb](test/dummy/app/models/internal_workflow.rb) shows how to use STI extending Workflow.
|
197
|
+
|
198
|
+
[test/dummy/app/overrides/models/flow_core/workflow_override.rb](test/dummy/app/overrides/models/flow_core/workflow_override.rb) shows how to apply Rails override pattern to extend base model,
|
199
|
+
here I add `to_graphviz` for dummy app visualize workflows.
|
200
|
+
|
201
|
+
## Requirement
|
202
|
+
|
203
|
+
- Rails 6.0+
|
204
|
+
- Ruby 2.5+
|
205
|
+
|
206
|
+
## Installation
|
207
|
+
|
208
|
+
Add this line to your application's Gemfile:
|
209
|
+
|
210
|
+
```ruby
|
211
|
+
gem "flow_core"
|
212
|
+
```
|
213
|
+
|
214
|
+
Or you may want to include the gem directly from GitHub:
|
215
|
+
|
216
|
+
```ruby
|
217
|
+
gem 'flow_core', github: 'rails-engine/flow_core'
|
218
|
+
```
|
219
|
+
|
220
|
+
And then execute:
|
221
|
+
|
222
|
+
```bash
|
223
|
+
$ bundle
|
224
|
+
```
|
225
|
+
|
226
|
+
Or install it yourself as:
|
227
|
+
|
228
|
+
```bash
|
229
|
+
$ gem install flow_core
|
230
|
+
```
|
231
|
+
|
232
|
+
## References
|
233
|
+
|
234
|
+
- [hooopo/petri_flow](https://github.com/hooopo/petri_flow) (my partner's version, we share the same basis)
|
235
|
+
- <http://mlwiki.org/index.php/Petri_Nets>
|
236
|
+
- <https://www.tonymarston.net/php-mysql/workflow.html>
|
237
|
+
- <http://workflowpatterns.com/>
|
238
|
+
|
239
|
+
## Contributing
|
240
|
+
|
241
|
+
Bug report or pull request are welcome.
|
242
|
+
|
243
|
+
### Make a pull request
|
244
|
+
|
245
|
+
1. Fork it
|
246
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
247
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
248
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
249
|
+
5. Create new Pull Request
|
250
|
+
|
251
|
+
Please write unit test with your code if necessary.
|
252
|
+
|
253
|
+
## License
|
254
|
+
|
255
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "rdoc/task"
|
5
|
+
|
6
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
7
|
+
rdoc.rdoc_dir = "rdoc"
|
8
|
+
rdoc.title = "FlowCore"
|
9
|
+
rdoc.options << "--line-numbers"
|
10
|
+
rdoc.rdoc_files.include("README.md")
|
11
|
+
rdoc.rdoc_files.include("lib/**/*.rb")
|
12
|
+
end
|
13
|
+
|
14
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
15
|
+
load "rails/tasks/engine.rake"
|
16
|
+
|
17
|
+
load "rails/tasks/statistics.rake"
|
18
|
+
|
19
|
+
require "bundler/gem_tasks"
|
20
|
+
|
21
|
+
require "rake/testtask"
|
22
|
+
|
23
|
+
Rake::TestTask.new(:test) do |t|
|
24
|
+
t.libs << "test"
|
25
|
+
t.pattern = "test/**/*_test.rb"
|
26
|
+
t.verbose = false
|
27
|
+
end
|
28
|
+
|
29
|
+
task default: :test
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore
|
4
|
+
class Arc < FlowCore::ApplicationRecord
|
5
|
+
self.table_name = "flow_core_arcs"
|
6
|
+
|
7
|
+
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
8
|
+
belongs_to :transition, class_name: "FlowCore::Transition"
|
9
|
+
belongs_to :place, class_name: "FlowCore::Place"
|
10
|
+
|
11
|
+
has_many :guards, class_name: "FlowCore::ArcGuard", dependent: :delete_all
|
12
|
+
|
13
|
+
enum direction: {
|
14
|
+
in: 0,
|
15
|
+
out: 1
|
16
|
+
}
|
17
|
+
|
18
|
+
validates :place,
|
19
|
+
uniqueness: {
|
20
|
+
scope: %i[workflow transition direction]
|
21
|
+
}
|
22
|
+
|
23
|
+
before_destroy :prevent_destroy
|
24
|
+
after_create :reset_workflow_verification
|
25
|
+
after_destroy :reset_workflow_verification
|
26
|
+
|
27
|
+
def can_destroy?
|
28
|
+
workflow.instances.empty?
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def reset_workflow_verification
|
34
|
+
workflow.reset_workflow_verification!
|
35
|
+
end
|
36
|
+
|
37
|
+
def prevent_destroy
|
38
|
+
unless can_destroy?
|
39
|
+
raise FlowCore::ForbiddenOperation, "Found exists instance, destroy transition will lead serious corruption"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore
|
4
|
+
class ArcGuard < FlowCore::ApplicationRecord
|
5
|
+
self.table_name = "flow_core_arc_guards"
|
6
|
+
|
7
|
+
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
8
|
+
belongs_to :arc, class_name: "FlowCore::Arc"
|
9
|
+
|
10
|
+
has_one :transition, through: :arc, class_name: "FlowCore::Transition"
|
11
|
+
|
12
|
+
before_validation do
|
13
|
+
self.workflow ||= arc&.workflow
|
14
|
+
end
|
15
|
+
|
16
|
+
include FlowCore::ArcGuardable
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FlowCore
|
4
|
+
class Instance < FlowCore::ApplicationRecord
|
5
|
+
self.table_name = "flow_core_instances"
|
6
|
+
|
7
|
+
FORBIDDEN_ATTRIBUTES = %i[
|
8
|
+
workflow_id stage activated_at finished_at canceled_at terminated_at terminated_reason
|
9
|
+
errored_at rescued_at suspended_at resumed_at created_at updated_at
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
belongs_to :workflow, class_name: "FlowCore::Workflow"
|
13
|
+
|
14
|
+
has_many :tokens, class_name: "FlowCore::Token", dependent: :delete_all
|
15
|
+
has_many :tasks, class_name: "FlowCore::Task", dependent: :delete_all
|
16
|
+
|
17
|
+
serialize :payload
|
18
|
+
|
19
|
+
enum stage: {
|
20
|
+
created: 0,
|
21
|
+
activated: 1,
|
22
|
+
canceled: 2,
|
23
|
+
finished: 11,
|
24
|
+
terminated: 12
|
25
|
+
}
|
26
|
+
|
27
|
+
scope :errored, -> { where.not(errored_at: nil) }
|
28
|
+
scope :suspended, -> { where.not(suspended_at: nil) }
|
29
|
+
|
30
|
+
after_initialize do
|
31
|
+
self.payload ||= {}
|
32
|
+
end
|
33
|
+
|
34
|
+
def errored?
|
35
|
+
errored_at.present?
|
36
|
+
end
|
37
|
+
|
38
|
+
def suspended?
|
39
|
+
suspended_at.present?
|
40
|
+
end
|
41
|
+
|
42
|
+
def can_active?
|
43
|
+
created?
|
44
|
+
end
|
45
|
+
|
46
|
+
def can_finish?
|
47
|
+
activated?
|
48
|
+
end
|
49
|
+
|
50
|
+
def active
|
51
|
+
return false unless can_active?
|
52
|
+
|
53
|
+
transaction do
|
54
|
+
tokens.create! place: workflow.start_place
|
55
|
+
update! stage: :activated, activated_at: Time.zone.now
|
56
|
+
end
|
57
|
+
|
58
|
+
true
|
59
|
+
end
|
60
|
+
|
61
|
+
def finish
|
62
|
+
return false unless can_finish?
|
63
|
+
|
64
|
+
transaction do
|
65
|
+
update! stage: :finished, finished_at: Time.zone.now
|
66
|
+
|
67
|
+
tasks.where(stage: %i[created enabled]).find_each do |task|
|
68
|
+
task.terminate! reason: "Instance finished"
|
69
|
+
end
|
70
|
+
tokens.where(stage: %i[free locked]).find_each(&:terminate!)
|
71
|
+
end
|
72
|
+
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
def active!
|
77
|
+
active || raise(FlowCore::InvalidTransition, "Can't active Instance##{id}")
|
78
|
+
end
|
79
|
+
|
80
|
+
def finish!
|
81
|
+
finish || raise(FlowCore::InvalidTransition, "Can't finish Instance##{id}")
|
82
|
+
end
|
83
|
+
|
84
|
+
def error!
|
85
|
+
return if errored?
|
86
|
+
|
87
|
+
update! errored_at: Time.zone.now
|
88
|
+
end
|
89
|
+
|
90
|
+
def rescue!
|
91
|
+
return unless errored?
|
92
|
+
return unless tasks.errored.any?
|
93
|
+
|
94
|
+
update! errored_at: nil, rescued_at: Time.zone.now
|
95
|
+
end
|
96
|
+
|
97
|
+
def suspend!
|
98
|
+
return if suspended?
|
99
|
+
|
100
|
+
transaction do
|
101
|
+
tasks.enabled.each(&:suspend!)
|
102
|
+
update! suspended_at: Time.zone.now
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def resume!
|
107
|
+
return unless suspended?
|
108
|
+
|
109
|
+
transaction do
|
110
|
+
tasks.enabled.each(&:resume!)
|
111
|
+
update! suspended_at: nil, resumed_at: Time.zone.now
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|