flow_core 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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