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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +255 -0
  4. data/Rakefile +29 -0
  5. data/app/models/flow_core/application_record.rb +7 -0
  6. data/app/models/flow_core/arc.rb +43 -0
  7. data/app/models/flow_core/arc_guard.rb +18 -0
  8. data/app/models/flow_core/end_place.rb +17 -0
  9. data/app/models/flow_core/instance.rb +115 -0
  10. data/app/models/flow_core/place.rb +63 -0
  11. data/app/models/flow_core/start_place.rb +17 -0
  12. data/app/models/flow_core/task.rb +197 -0
  13. data/app/models/flow_core/token.rb +105 -0
  14. data/app/models/flow_core/transition.rb +143 -0
  15. data/app/models/flow_core/transition_callback.rb +24 -0
  16. data/app/models/flow_core/transition_trigger.rb +24 -0
  17. data/app/models/flow_core/workflow.rb +131 -0
  18. data/config/routes.rb +4 -0
  19. data/db/migrate/20200130200532_create_flow_core_tables.rb +151 -0
  20. data/lib/flow_core.rb +25 -0
  21. data/lib/flow_core/arc_guardable.rb +15 -0
  22. data/lib/flow_core/definition.rb +17 -0
  23. data/lib/flow_core/definition/callback.rb +33 -0
  24. data/lib/flow_core/definition/guard.rb +33 -0
  25. data/lib/flow_core/definition/net.rb +111 -0
  26. data/lib/flow_core/definition/place.rb +33 -0
  27. data/lib/flow_core/definition/transition.rb +107 -0
  28. data/lib/flow_core/definition/trigger.rb +33 -0
  29. data/lib/flow_core/engine.rb +6 -0
  30. data/lib/flow_core/errors.rb +14 -0
  31. data/lib/flow_core/locale/en.yml +26 -0
  32. data/lib/flow_core/task_executable.rb +34 -0
  33. data/lib/flow_core/transition_callbackable.rb +33 -0
  34. data/lib/flow_core/transition_triggerable.rb +27 -0
  35. data/lib/flow_core/version.rb +5 -0
  36. data/lib/flow_core/violations.rb +253 -0
  37. data/lib/tasks/flow_core_tasks.rake +6 -0
  38. metadata +123 -0
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowCore
4
+ class TransitionCallback < FlowCore::ApplicationRecord
5
+ self.table_name = "flow_core_transition_callbacks"
6
+
7
+ belongs_to :workflow, class_name: "FlowCore::Workflow"
8
+ belongs_to :transition, class_name: "FlowCore::Transition"
9
+
10
+ before_validation do
11
+ self.workflow ||= transition&.workflow
12
+ end
13
+
14
+ def configurable?
15
+ false
16
+ end
17
+
18
+ def type_key
19
+ self.class.to_s.split("::").last.underscore
20
+ end
21
+
22
+ include FlowCore::TransitionCallbackable
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowCore
4
+ class TransitionTrigger < FlowCore::ApplicationRecord
5
+ self.table_name = "flow_core_transition_triggers"
6
+
7
+ belongs_to :workflow, class_name: "FlowCore::Workflow"
8
+ belongs_to :transition, class_name: "FlowCore::Transition"
9
+
10
+ before_validation do
11
+ self.workflow ||= transition&.workflow
12
+ end
13
+
14
+ def configurable?
15
+ false
16
+ end
17
+
18
+ def type_key
19
+ self.class.to_s.split("::").last.underscore
20
+ end
21
+
22
+ include FlowCore::TransitionTriggerable
23
+ end
24
+ end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowCore
4
+ class Workflow < FlowCore::ApplicationRecord
5
+ self.table_name = "flow_core_workflows"
6
+
7
+ FORBIDDEN_ATTRIBUTES = %i[verified verified_at created_at updated_at].freeze
8
+
9
+ has_many :instances, class_name: "FlowCore::Instance", dependent: :destroy
10
+
11
+ has_many :arcs, class_name: "FlowCore::Arc", dependent: :delete_all
12
+ has_many :places, class_name: "FlowCore::Place", dependent: :delete_all
13
+ has_many :transitions, class_name: "FlowCore::Transition", dependent: :delete_all
14
+
15
+ has_one :start_place, class_name: "FlowCore::StartPlace", dependent: :delete
16
+ has_one :end_place, class_name: "FlowCore::EndPlace", dependent: :delete
17
+
18
+ def create_instance!(attributes = {})
19
+ unless verified?
20
+ raise FlowCore::UnverifiedWorkflow, "Workflow##{id} didn't do soundness check yet."
21
+ end
22
+
23
+ instances.create! attributes.with_indifferent_access.except(FlowCore::Instance::FORBIDDEN_ATTRIBUTES)
24
+ end
25
+
26
+ def invalid?
27
+ !verified?
28
+ end
29
+
30
+ def verify?
31
+ violations.clear
32
+
33
+ unless start_place
34
+ violations.add(:start_place, :presence)
35
+ end
36
+ unless end_place
37
+ violations.add(:end_place, :presence)
38
+ end
39
+
40
+ return false unless start_place && end_place
41
+
42
+ # TODO: Naive implementation for now, Help wanted!
43
+
44
+ rgl = to_rgl
45
+
46
+ start_place_code = "P_#{start_place.id}"
47
+ end_place_code = "P_#{end_place.id}"
48
+
49
+ unless rgl.path?(start_place_code, end_place_code)
50
+ violations.add :end_place, :unreachable
51
+ end
52
+
53
+ places.find_each do |p|
54
+ next if p == start_place
55
+ next if p == end_place
56
+
57
+ place_code = "P_#{p.id}"
58
+
59
+ unless rgl.path?(start_place_code, place_code)
60
+ violations.add p, :unreachable
61
+ end
62
+
63
+ unless rgl.path?(place_code, end_place_code)
64
+ violations.add p, :impassable
65
+ end
66
+ end
67
+ transitions.includes(:trigger).find_each do |t|
68
+ transition_code = "T_#{t.id}"
69
+
70
+ unless rgl.path?(start_place_code, transition_code)
71
+ violations.add t, :unreachable
72
+ end
73
+
74
+ unless rgl.path?(transition_code, end_place_code)
75
+ violations.add t, :impassable
76
+ end
77
+
78
+ t.verify violations: violations
79
+ end
80
+
81
+ violations.empty?
82
+ end
83
+
84
+ def violations
85
+ @violations ||= FlowCore::Violations.new
86
+ end
87
+
88
+ def verify_status
89
+ if verified_at.blank?
90
+ :unverified
91
+ elsif verified?
92
+ :verified
93
+ else
94
+ :invalid
95
+ end
96
+ end
97
+
98
+ def verify!
99
+ update! verified: verify?, verified_at: Time.zone.now
100
+ violations.empty?
101
+ end
102
+
103
+ def reset_workflow_verification!
104
+ update! verified: false, verified_at: nil
105
+ end
106
+
107
+ private
108
+
109
+ def to_rgl
110
+ graph = RGL::DirectedAdjacencyGraph.new
111
+
112
+ places.find_each do |p|
113
+ graph.add_vertex "P_#{p.id}"
114
+ end
115
+
116
+ transitions.find_each do |t|
117
+ graph.add_vertex "T_#{t.id}"
118
+ end
119
+
120
+ arcs.find_each do |arc|
121
+ if arc.in?
122
+ graph.add_edge "P_#{arc.place_id}", "T_#{arc.transition_id}"
123
+ else
124
+ graph.add_edge "T_#{arc.transition_id}", "P_#{arc.place_id}"
125
+ end
126
+ end
127
+
128
+ graph
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ Rails.application.routes.draw do
4
+ end
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateFlowCoreTables < ActiveRecord::Migration[6.0]
4
+ def change
5
+ create_table :flow_core_workflows do |t|
6
+ t.string :name, null: false
7
+ t.string :tag
8
+
9
+ t.integer :verified, null: false, default: false
10
+ t.datetime :verified_at
11
+
12
+ t.string :type
13
+
14
+ t.timestamps
15
+ end
16
+
17
+ create_table :flow_core_places do |t|
18
+ t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
19
+
20
+ t.string :name
21
+ t.string :tag
22
+
23
+ t.string :type
24
+
25
+ t.timestamps
26
+ end
27
+
28
+ create_table :flow_core_transitions do |t|
29
+ t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
30
+
31
+ t.string :name
32
+ t.string :tag
33
+
34
+ t.timestamps
35
+ end
36
+
37
+ create_table :flow_core_transition_callbacks do |t|
38
+ t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
39
+ t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
40
+
41
+ t.string :configuration
42
+ t.string :type
43
+
44
+ t.timestamps
45
+ end
46
+
47
+ create_table :flow_core_transition_triggers do |t|
48
+ t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
49
+ t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
50
+
51
+ t.text :configuration
52
+ t.string :type
53
+
54
+ t.timestamps
55
+ end
56
+
57
+ create_table :flow_core_arcs do |t|
58
+ t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
59
+ t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
60
+ t.references :place, null: false, foreign_key: { to_table: :flow_core_places }
61
+
62
+ t.integer :direction, null: false, default: 0, comment: "0-in, 1-out"
63
+
64
+ t.timestamps
65
+ end
66
+
67
+ create_table :flow_core_arc_guards do |t|
68
+ t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
69
+ t.references :arc, null: false, foreign_key: { to_table: :flow_core_arcs }
70
+
71
+ t.text :configuration
72
+ t.string :type
73
+
74
+ t.timestamps
75
+ end
76
+
77
+ create_table :flow_core_instances do |t|
78
+ t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
79
+
80
+ t.string :tag, index: { unique: true }
81
+
82
+ t.integer :stage, default: 0, comment: "0-created, 1-activated, 2-canceled, 3-finished, 4-terminated"
83
+ t.datetime :activated_at
84
+ t.datetime :finished_at
85
+ t.datetime :canceled_at
86
+ t.datetime :terminated_at
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
95
+
96
+ t.text :payload
97
+
98
+ t.timestamps
99
+ end
100
+
101
+ create_table :flow_core_tokens do |t|
102
+ t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
103
+ t.references :instance, null: false, foreign_key: { to_table: :flow_core_instances }
104
+ t.references :place, null: false, foreign_key: { to_table: :flow_core_places }
105
+
106
+ t.integer :stage, default: 0, comment: "0-free, 1-locked, 11-consumed, 12-terminated"
107
+ t.datetime :locked_at
108
+ t.datetime :consumed_at
109
+ t.datetime :terminated_at
110
+
111
+ t.references :created_by_task, foreign_key: { to_table: :flow_core_tasks }
112
+ t.references :consumed_by_task, foreign_key: { to_table: :flow_core_tasks }
113
+
114
+ t.boolean :task_created, null: false, default: false
115
+
116
+ t.timestamps
117
+ end
118
+
119
+ create_table :flow_core_tasks do |t|
120
+ t.references :workflow, null: false, foreign_key: { to_table: :flow_core_workflows }
121
+ t.references :instance, null: false, foreign_key: { to_table: :flow_core_instances }
122
+ t.references :transition, null: false, foreign_key: { to_table: :flow_core_transitions }
123
+
124
+ t.string :tag, index: { unique: true }
125
+
126
+ t.references :created_by_token, foreign_key: { to_table: :flow_core_tokens }
127
+
128
+ t.integer :stage, default: 0, comment: "0-created, 1-enabled, 11-finished, 12-terminated"
129
+ t.datetime :enabled_at
130
+ t.datetime :finished_at
131
+ t.datetime :terminated_at
132
+
133
+ t.string :terminate_reason
134
+
135
+ t.datetime :errored_at
136
+ t.datetime :rescued_at
137
+ t.string :error_reason
138
+
139
+ t.datetime :suspended_at
140
+ t.datetime :resumed_at
141
+
142
+ t.boolean :output_token_created, null: false, default: false
143
+
144
+ t.references :executable, polymorphic: true
145
+
146
+ t.text :payload
147
+
148
+ t.timestamps
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rgl/adjacency"
4
+ require "rgl/dijkstra"
5
+ require "rgl/topsort"
6
+ require "rgl/traversal"
7
+ require "rgl/path"
8
+
9
+ require "flow_core/engine"
10
+
11
+ require "flow_core/errors"
12
+ require "flow_core/arc_guardable"
13
+ require "flow_core/transition_triggerable"
14
+ require "flow_core/transition_callbackable"
15
+ require "flow_core/task_executable"
16
+
17
+ require "flow_core/definition"
18
+ require "flow_core/violations"
19
+
20
+ ActiveSupport.on_load(:i18n) do
21
+ I18n.load_path << File.expand_path("flow_core/locale/en.yml", __dir__)
22
+ end
23
+
24
+ module FlowCore
25
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowCore
4
+ module ArcGuardable
5
+ extend ActiveSupport::Concern
6
+
7
+ def permit?(_task)
8
+ raise NotImplementedError
9
+ end
10
+
11
+ def description
12
+ raise NotImplementedError
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "flow_core/definition/place"
4
+ require "flow_core/definition/transition"
5
+ require "flow_core/definition/net"
6
+ require "flow_core/definition/trigger"
7
+ require "flow_core/definition/guard"
8
+ require "flow_core/definition/callback"
9
+
10
+ module FlowCore::Definition
11
+ class << self
12
+ def build(attributes = {}, &block)
13
+ FlowCore::Definition::Net.new(attributes, &block)
14
+ end
15
+ alias new build
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowCore::Definition
4
+ class Callback
5
+ def initialize(attributes)
6
+ constant_or_klass = attributes.is_a?(Hash) ? attributes.delete(:type) : attributes
7
+ @klass =
8
+ if constant_or_klass.is_a? String
9
+ constant_or_klass.safe_constantize
10
+ else
11
+ constant_or_klass
12
+ end
13
+ unless @klass && @klass < FlowCore::TransitionCallback
14
+ raise TypeError, "First argument expect `FlowCore::TransitionCallback` subclass or its constant name - #{constant_or_klass}"
15
+ end
16
+
17
+ @configuration = attributes.is_a?(Hash) ? attributes : nil
18
+ end
19
+
20
+ def compile
21
+ if @configuration&.any?
22
+ {
23
+ type: @klass.to_s,
24
+ configuration: @configuration
25
+ }
26
+ else
27
+ {
28
+ type: @klass.to_s
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FlowCore::Definition
4
+ class Guard
5
+ def initialize(attributes)
6
+ constant_or_klass = attributes.is_a?(Hash) ? attributes.delete(:type) : attributes
7
+ @klass =
8
+ if constant_or_klass.is_a? String
9
+ constant_or_klass.safe_constantize
10
+ else
11
+ constant_or_klass
12
+ end
13
+ unless @klass && @klass < FlowCore::ArcGuard
14
+ raise TypeError, "First argument expect `FlowCore::TransitionTrigger` subclass or its constant name - #{constant_or_klass}"
15
+ end
16
+
17
+ @configuration = attributes.is_a?(Hash) ? attributes : nil
18
+ end
19
+
20
+ def compile
21
+ if @configuration&.any?
22
+ {
23
+ type: @klass.to_s,
24
+ configuration: @configuration
25
+ }
26
+ else
27
+ {
28
+ type: @klass.to_s
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end