flow_core 0.0.2 → 0.0.3

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 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