flow_core 0.0.2 → 0.0.3

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: 9a450593fd3daee1db9bcb92693d4329b0f6153f4d4c09997a409742925e38df
4
- data.tar.gz: 326838fdb6df30560b532e93cf65d67f7059599106aa47a9db46cb6859d6c7de
3
+ metadata.gz: d31b83d470547f95bbe973b6376c596a2d2ebe7c5d15efe569452a4342cfe5b8
4
+ data.tar.gz: 1b7ae085f8487d2d6765cb946435b5f83c1d32bbea26e625303ee8941ec2efa1
5
5
  SHA512:
6
- metadata.gz: 454c961586288c08a59192a3c26bc393e58b11a20f5e91daf198e83222210b661dc4fb4c2ff6aa3679914778b45cf9ddfa292cfefed90e29b3037c1abf6e32db
7
- data.tar.gz: 59a6189a9b1d787473d68f4e2bbcd38c7e40ec52ea336a8b6efc82899b7e9f6c271f0aeea076b22ff14d1d06114352d4d48e9d2193c6e805e29552bfadcfd6b3
6
+ metadata.gz: 1765159897d571c89c05b3cca8f3d30229a13e6511a3797f8bfda0620540ac97940132bcc019a80fd371b1db8680bba91cca27927c841a54e0c4f48f88ad80ca
7
+ data.tar.gz: c6d354ddecbdda0596132ad83d56fce39bcbb9ab05a7a642e4c20bfa557ef792a0932156d6be195d8a66ff7e6143fc076a46a166e62be6cc77e33152a888d1de
data/README.md CHANGED
@@ -131,6 +131,8 @@ Some notable:
131
131
  - Require app task finished first (if bind)
132
132
  - `terminated` Task killed by instance (e.g Instance cancelled) or other race condition task
133
133
 
134
+ ![Life cycle of workflow instance](doc/assets/life_cycle_of_workflow_instance.png)
135
+
134
136
  ### FlowKit
135
137
 
136
138
  Because FlowCore only care about essentials of workflow engine,
@@ -156,7 +158,8 @@ and you shall have fully control and without any unnecessary abstraction.
156
158
 
157
159
  - Document
158
160
  - 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.
161
+ - Activity-based to Petri-net mapping that will possible to integrate to existing visual editors (with some modification),
162
+ see <https://www.researchgate.net/figure/The-mapping-between-BPMN-and-Petri-nets_tbl2_221250389> for understanding.
160
163
  - More efficient and powerful workflow definition checking
161
164
  - Grammar and naming correction (I'm not English native-speaker)
162
165
 
@@ -5,8 +5,8 @@ module FlowCore
5
5
  self.table_name = "flow_core_instances"
6
6
 
7
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
8
+ workflow_id stage activated_at finished_at canceled_at terminated_at terminate_reason
9
+ created_at updated_at
10
10
  ].freeze
11
11
 
12
12
  belongs_to :workflow, class_name: "FlowCore::Workflow"
@@ -32,14 +32,14 @@ module FlowCore
32
32
  end
33
33
 
34
34
  def errored?
35
- errored_at.present?
35
+ tasks.where.not(errored_at: nil).exists?
36
36
  end
37
37
 
38
- def suspended?
39
- suspended_at.present?
38
+ def can_cancel?
39
+ created?
40
40
  end
41
41
 
42
- def can_active?
42
+ def can_activate?
43
43
  created?
44
44
  end
45
45
 
@@ -47,69 +47,75 @@ module FlowCore
47
47
  activated?
48
48
  end
49
49
 
50
- def active
51
- return false unless can_active?
50
+ def can_terminate?
51
+ true
52
+ end
53
+
54
+ def cancel
55
+ return false unless can_cancel?
56
+
57
+ with_transaction_returning_status do
58
+ update! stage: :canceled, canceled_at: Time.zone.now
59
+ workflow.on_instance_cancel(self)
60
+
61
+ true
62
+ end
63
+ end
64
+
65
+ def activate
66
+ return false unless can_activate?
52
67
 
53
- transaction do
68
+ with_transaction_returning_status do
54
69
  tokens.create! place: workflow.start_place
55
70
  update! stage: :activated, activated_at: Time.zone.now
56
- end
71
+ workflow.on_instance_activate(self)
57
72
 
58
- true
73
+ true
74
+ end
59
75
  end
60
76
 
61
77
  def finish
62
78
  return false unless can_finish?
63
79
 
64
- transaction do
80
+ with_transaction_returning_status do
65
81
  update! stage: :finished, finished_at: Time.zone.now
66
82
 
67
83
  tasks.where(stage: %i[created enabled]).find_each do |task|
68
84
  task.terminate! reason: "Instance finished"
69
85
  end
70
86
  tokens.where(stage: %i[free locked]).find_each(&:terminate!)
71
- end
72
-
73
- true
74
- end
87
+ workflow.on_instance_finish(self)
75
88
 
76
- def active!
77
- active || raise(FlowCore::InvalidTransition, "Can't active Instance##{id}")
89
+ true
90
+ end
78
91
  end
79
92
 
80
- def finish!
81
- finish || raise(FlowCore::InvalidTransition, "Can't finish Instance##{id}")
82
- end
93
+ def terminate(reason:)
94
+ return unless can_terminate?
83
95
 
84
- def error!
85
- return if errored?
96
+ with_transaction_returning_status do
97
+ tasks.enabled.each { |task| task.terminate! reason: "Instance terminated" }
98
+ update! stage: :terminated, terminated_at: Time.zone.now, terminate_reason: reason
99
+ workflow.on_instance_terminate(self)
86
100
 
87
- update! errored_at: Time.zone.now
101
+ true
102
+ end
88
103
  end
89
104
 
90
- def rescue!
91
- return unless errored?
92
- return unless tasks.errored.any?
93
-
94
- update! errored_at: nil, rescued_at: Time.zone.now
105
+ def cancel!
106
+ cancel || raise(FlowCore::InvalidTransition, "Can't cancel Instance##{id}")
95
107
  end
96
108
 
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
109
+ def activate!
110
+ activate || raise(FlowCore::InvalidTransition, "Can't activate Instance##{id}")
104
111
  end
105
112
 
106
- def resume!
107
- return unless suspended?
113
+ def finish!
114
+ finish || raise(FlowCore::InvalidTransition, "Can't finish Instance##{id}")
115
+ end
108
116
 
109
- transaction do
110
- tasks.enabled.each(&:resume!)
111
- update! suspended_at: nil, resumed_at: Time.zone.now
112
- end
117
+ def terminate!(reason:)
118
+ terminate(reason: reason) || raise(FlowCore::InvalidTransition, "Can't terminate Instance##{id}")
113
119
  end
114
120
  end
115
121
  end
@@ -7,7 +7,7 @@ module FlowCore
7
7
  belongs_to :workflow, class_name: "FlowCore::Workflow"
8
8
  belongs_to :transition, class_name: "FlowCore::Transition"
9
9
 
10
- belongs_to :instance, class_name: "FlowCore::Instance"
10
+ belongs_to :instance, class_name: "FlowCore::Instance", autosave: true
11
11
  belongs_to :created_by_token, class_name: "FlowCore::Token", optional: true
12
12
 
13
13
  belongs_to :executable, polymorphic: true, optional: true
@@ -80,20 +80,21 @@ module FlowCore
80
80
  def enable
81
81
  return false unless can_enable?
82
82
 
83
- transaction do
83
+ with_transaction_returning_status do
84
84
  input_free_tokens.each(&:lock!)
85
85
  update! stage: :enabled, enabled_at: Time.zone.now
86
86
 
87
- transition.on_task_enabled(self)
88
- end
87
+ transition.on_task_enable(self)
88
+ workflow.on_instance_task_enable(self)
89
89
 
90
- true
90
+ true
91
+ end
91
92
  end
92
93
 
93
94
  def finish
94
95
  return false unless can_finish?
95
96
 
96
- transaction do
97
+ with_transaction_returning_status do
97
98
  # terminate other racing tasks
98
99
  instance.tasks.enabled.where(created_by_token: created_by_token).find_each do |task|
99
100
  task.terminate! reason: "Same origin task #{id} finished"
@@ -102,80 +103,106 @@ module FlowCore
102
103
  input_locked_tokens.each { |token| token.consume! by: self }
103
104
  update! stage: :finished, finished_at: Time.zone.now
104
105
 
105
- transition.on_task_finished(self)
106
- end
106
+ transition.on_task_finish(self)
107
+ workflow.on_instance_task_finish(self)
107
108
 
108
- create_output_token!
109
+ true
110
+ end
109
111
 
110
- true
112
+ create_output_token
111
113
  end
112
114
 
113
115
  def terminate(reason:)
114
116
  return false unless can_terminate?
115
117
 
116
- transaction do
118
+ with_transaction_returning_status do
117
119
  update! stage: :terminated, terminated_at: Time.zone.now, terminate_reason: reason
118
- transition.on_task_terminated(self)
119
- end
120
-
121
- true
122
- end
120
+ transition.on_task_terminate(self)
121
+ workflow.on_instance_task_terminate(self)
123
122
 
124
- def enable!
125
- enable || raise(FlowCore::InvalidTransition, "Can't enable Task##{id}")
126
- end
127
-
128
- def finish!
129
- finish || raise(FlowCore::InvalidTransition, "Can't finish Task##{id}")
130
- end
131
-
132
- def terminate!(reason:)
133
- terminate(reason: reason) || raise(FlowCore::InvalidTransition, "Can't terminate Task##{id}")
123
+ true
124
+ end
134
125
  end
135
126
 
136
- def error!(error)
137
- transaction do
138
- update! errored_at: Time.zone.now, error_reason: error.message
139
- transition.on_task_errored(self, error)
127
+ def error(error)
128
+ update! errored_at: Time.zone.now, error_reason: error.message
129
+ instance.update! errored_at: Time.zone.now
130
+ transition.on_task_errored(self, error)
131
+ workflow.on_instance_task_errored(self, error)
140
132
 
141
- instance.error!
142
- end
133
+ true
143
134
  end
144
135
 
145
- def rescue!
136
+ def rescue
146
137
  return unless errored?
147
138
 
148
- transaction do
139
+ with_transaction_returning_status do
149
140
  update! errored_at: nil, rescued_at: Time.zone.now
150
- transition.on_task_rescued(self)
141
+ instance.update! errored_at: nil, rescued_at: Time.zone.now
142
+ transition.on_task_rescue(self)
143
+ workflow.on_instance_task_rescue(self)
151
144
 
152
- instance.rescue!
145
+ true
153
146
  end
154
147
  end
155
148
 
156
- def suspend!
157
- transaction do
149
+ def suspend
150
+ with_transaction_returning_status do
158
151
  update! suspended_at: Time.zone.now
159
- transition.on_task_suspended(self)
152
+ transition.on_task_suspend(self)
153
+ workflow.on_instance_task_suspend(self)
154
+
155
+ true
160
156
  end
161
157
  end
162
158
 
163
- def resume!
164
- transaction do
159
+ def resume
160
+ with_transaction_returning_status do
165
161
  update! suspended_at: nil, resumed_at: Time.zone.now
166
- transition.on_task_resumed(self)
162
+ transition.on_task_resume(self)
163
+ workflow.on_instance_task_resume(self)
164
+
165
+ true
167
166
  end
168
167
  end
169
168
 
170
- def create_output_token!
169
+ def create_output_token
171
170
  return if output_token_created
172
171
 
173
- transaction do
172
+ with_transaction_returning_status do
174
173
  transition.create_tokens_for_output(task: self)
175
174
  update! output_token_created: true
175
+
176
+ true
176
177
  end
177
178
  end
178
179
 
180
+ def enable!
181
+ enable || raise(FlowCore::InvalidTransition, "Can't enable Task##{id}")
182
+ end
183
+
184
+ def finish!
185
+ finish || raise(FlowCore::InvalidTransition, "Can't finish Task##{id}")
186
+ end
187
+
188
+ def terminate!(reason:)
189
+ terminate(reason: reason) || raise(FlowCore::InvalidTransition, "Can't terminate Task##{id}")
190
+ end
191
+
192
+ alias error! error
193
+
194
+ def rescue!
195
+ self.rescue || raise(FlowCore::InvalidTransition, "Can't rescue Task##{id}")
196
+ end
197
+
198
+ def suspend!
199
+ suspend || raise(FlowCore::InvalidTransition, "Can't suspend Task##{id}")
200
+ end
201
+
202
+ def resume!
203
+ resume || raise(FlowCore::InvalidTransition, "Can't resume Task##{id}")
204
+ end
205
+
179
206
  private
180
207
 
181
208
  def input_tokens
@@ -76,7 +76,9 @@ module FlowCore
76
76
  end
77
77
 
78
78
  if candidate_arcs.empty?
79
- trigger&.on_error(task, FlowCore::NoNewTokenCreated.new)
79
+ # TODO: find a better way
80
+ on_task_errored task, FlowCore::NoNewTokenCreated.new
81
+ return
80
82
  end
81
83
 
82
84
  candidate_arcs.each do |arc|
@@ -84,43 +86,38 @@ module FlowCore
84
86
  end
85
87
  end
86
88
 
87
- def on_task_created(task)
88
- trigger&.on_task_created(task)
89
+ def on_task_enable(task)
90
+ trigger&.on_task_enable(task)
89
91
  callbacks.each { |callback| callback.call task }
90
92
  end
91
93
 
92
- def on_task_enabled(task)
93
- trigger&.on_task_enabled(task)
94
+ def on_task_finish(task)
95
+ trigger&.on_task_finish(task)
94
96
  callbacks.each { |callback| callback.call task }
95
97
  end
96
98
 
97
- def on_task_finished(task)
98
- trigger&.on_task_finished(task)
99
- callbacks.each { |callback| callback.call task }
100
- end
101
-
102
- def on_task_terminated(task)
103
- trigger&.on_task_terminated(task)
99
+ def on_task_terminate(task)
100
+ trigger&.on_task_terminate(task)
104
101
  callbacks.each { |callback| callback.call task }
105
102
  end
106
103
 
107
104
  def on_task_errored(task, error)
108
105
  trigger&.on_task_errored(task, error)
109
- callbacks.each { |callback| callback.call task }
106
+ callbacks.each { |callback| callback.call task, error }
110
107
  end
111
108
 
112
- def on_task_rescued(task)
113
- trigger&.on_task_rescued(task)
109
+ def on_task_rescue(task)
110
+ trigger&.on_task_rescue(task)
114
111
  callbacks.each { |callback| callback.call task }
115
112
  end
116
113
 
117
- def on_task_suspended(task)
118
- trigger&.on_task_suspended(task)
114
+ def on_task_suspend(task)
115
+ trigger&.on_task_suspend(task)
119
116
  callbacks.each { |callback| callback.call task }
120
117
  end
121
118
 
122
- def on_task_resumed(task)
123
- trigger&.on_task_resumed(task)
119
+ def on_task_resume(task)
120
+ trigger&.on_task_resume(task)
124
121
  callbacks.each { |callback| callback.call task }
125
122
  end
126
123
 
@@ -15,12 +15,34 @@ module FlowCore
15
15
  has_one :start_place, class_name: "FlowCore::StartPlace", dependent: :delete
16
16
  has_one :end_place, class_name: "FlowCore::EndPlace", dependent: :delete
17
17
 
18
- def create_instance!(attributes = {})
19
- unless verified?
18
+ class_attribute :force_workflow_verified_on_create_instance, default: false
19
+
20
+ include FlowCore::WorkflowCallbacks
21
+
22
+ def build_instance(attributes = {})
23
+ if force_workflow_verified_on_create_instance && invalid?
20
24
  raise FlowCore::UnverifiedWorkflow, "Workflow##{id} didn't do soundness check yet."
21
25
  end
22
26
 
23
- instances.create! attributes.with_indifferent_access.except(FlowCore::Instance::FORBIDDEN_ATTRIBUTES)
27
+ instances.build attributes.with_indifferent_access.except(FlowCore::Instance::FORBIDDEN_ATTRIBUTES)
28
+ end
29
+
30
+ def create_instance(attributes = {})
31
+ instance = build_instance(attributes)
32
+ return unless instance
33
+
34
+ instance.save
35
+ instance
36
+ end
37
+
38
+ def create_instance!(attributes = {})
39
+ instance = build_instance(attributes)
40
+ unless instance
41
+ raise RecordNotSaved.new("Failed to create workflow instance", instance)
42
+ end
43
+
44
+ instance.save!
45
+ instance
24
46
  end
25
47
 
26
48
  def invalid?
@@ -85,13 +85,7 @@ class CreateFlowCoreTables < ActiveRecord::Migration[6.0]
85
85
  t.datetime :canceled_at
86
86
  t.datetime :terminated_at
87
87
 
88
- t.string :terminated_reason
89
-
90
- t.datetime :errored_at
91
- t.datetime :rescued_at
92
-
93
- t.datetime :suspended_at
94
- t.datetime :resumed_at
88
+ t.string :terminate_reason
95
89
 
96
90
  t.text :payload
97
91
 
@@ -128,8 +122,8 @@ class CreateFlowCoreTables < ActiveRecord::Migration[6.0]
128
122
  t.integer :stage, default: 0, comment: "0-created, 1-enabled, 11-finished, 12-terminated"
129
123
  t.datetime :enabled_at
130
124
  t.datetime :finished_at
131
- t.datetime :terminated_at
132
125
 
126
+ t.datetime :terminated_at
133
127
  t.string :terminate_reason
134
128
 
135
129
  t.datetime :errored_at
@@ -13,6 +13,7 @@ require "flow_core/arc_guardable"
13
13
  require "flow_core/transition_triggerable"
14
14
  require "flow_core/transition_callbackable"
15
15
  require "flow_core/task_executable"
16
+ require "flow_core/workflow_callbacks"
16
17
 
17
18
  require "flow_core/definition"
18
19
  require "flow_core/violations"
@@ -5,8 +5,7 @@ module FlowCore
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- has_one :task, as: :executable, required: true, class_name: "FlowCore::Task"
9
- has_one :instance, through: :task, class_name: "FlowCore::Instance"
8
+ has_one :task, as: :executable, class_name: "FlowCore::Task", autosave: true, required: true
10
9
  has_one :transition, -> { readonly }, through: :task, class_name: "FlowCore::Transition"
11
10
 
12
11
  after_save :notify_workflow_task_finished!, if: :implicit_notify_workflow_task_finished
@@ -19,15 +19,15 @@ module FlowCore
19
19
  true
20
20
  end
21
21
 
22
- def _call(_task)
22
+ def _call(_task, *_args)
23
23
  raise NotImplementedError
24
24
  end
25
25
 
26
- def call(task)
26
+ def call(task, *args)
27
27
  return unless on.include? task.stage.to_sym
28
28
  return unless callable? task
29
29
 
30
- _call task
30
+ _call task, *args
31
31
  end
32
32
  end
33
33
  end
@@ -6,22 +6,20 @@ module FlowCore
6
6
 
7
7
  def on_verify(_transition, _violations); end
8
8
 
9
- def on_task_created(_task); end
9
+ def on_task_enable(_task); end
10
10
 
11
- def on_task_enabled(_task); end
11
+ def on_task_finish(_task); end
12
12
 
13
- def on_task_finished(_task); end
13
+ def on_task_terminate(_task); end
14
14
 
15
- def on_task_terminated(_task); end
15
+ def on_task_suspend(_task); end
16
16
 
17
- def on_task_suspended(_task); end
18
-
19
- def on_task_resumed(_task); end
17
+ def on_task_resume(_task); end
20
18
 
21
19
  def on_task_errored(_task, error)
22
20
  raise error
23
21
  end
24
22
 
25
- def on_task_rescued(_task); end
23
+ def on_task_rescue(_task); end
26
24
  end
27
25
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlowCore
4
- VERSION = "0.0.2"
4
+ VERSION = "0.0.3"
5
5
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowCore
4
+ module WorkflowCallbacks
5
+ extend ActiveSupport::Concern
6
+
7
+ def on_instance_cancel(_instance); end
8
+
9
+ def on_instance_activate(_instance); end
10
+
11
+ def on_instance_finish(_instance); end
12
+
13
+ def on_instance_terminate(_instance); end
14
+
15
+ def on_instance_task_enable(_task); end
16
+
17
+ def on_instance_task_finish(_task); end
18
+
19
+ def on_instance_task_terminate(_task); end
20
+
21
+ def on_instance_task_errored(_task, _error); end
22
+
23
+ def on_instance_task_rescue(_task); end
24
+
25
+ def on_instance_task_suspend(_task); end
26
+
27
+ def on_instance_task_resume(_task); end
28
+ end
29
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flow_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 0.0.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - jasl
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-02-18 00:00:00.000000000 Z
11
+ date: 2020-04-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -95,6 +95,7 @@ files:
95
95
  - lib/flow_core/transition_triggerable.rb
96
96
  - lib/flow_core/version.rb
97
97
  - lib/flow_core/violations.rb
98
+ - lib/flow_core/workflow_callbacks.rb
98
99
  - lib/tasks/flow_core_tasks.rake
99
100
  homepage: https://github.com/rails-engine/flow_core
100
101
  licenses:
@@ -115,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
115
116
  - !ruby/object:Gem::Version
116
117
  version: '0'
117
118
  requirements: []
118
- rubygems_version: 3.1.2
119
+ rubygems_version: 3.0.8
119
120
  signing_key:
120
121
  specification_version: 4
121
122
  summary: A multi purpose, extendable, Workflow-net-based workflow engine for Rails