nayati 0.1.0
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 +79 -0
- data/Rakefile +32 -0
- data/app/models/nayati/application_record.rb +5 -0
- data/app/models/nayati/master_operation.rb +46 -0
- data/app/models/nayati/master_workflow.rb +67 -0
- data/app/models/nayati/name_based_constantable.rb +30 -0
- data/app/models/nayati/operation.rb +29 -0
- data/app/models/nayati/operation_configuration.rb +73 -0
- data/app/models/nayati/operation_implementer_base.rb +21 -0
- data/app/models/nayati/workflow.rb +170 -0
- data/config/operations.yml +3 -0
- data/db/migrate/20200127144219_create_workflow.rb +16 -0
- data/db/migrate/20200127144220_create_operation.rb +19 -0
- data/lib/generators/nayati/install/install_generator.rb +30 -0
- data/lib/generators/nayati/install/templates/create_operation.rb +19 -0
- data/lib/generators/nayati/install/templates/create_workflow.rb +16 -0
- data/lib/generators/nayati/install/templates/operations.yml +3 -0
- data/lib/generators/nayati/operation/operation_generator.rb +46 -0
- data/lib/generators/nayati/operation/templates/operation_template.txt +20 -0
- data/lib/generators/nayati/workflow/templates/nayati_service_template.txt +20 -0
- data/lib/generators/nayati/workflow/workflow_generator.rb +39 -0
- data/lib/nayati.rb +5 -0
- data/lib/nayati/engine.rb +12 -0
- data/lib/nayati/version.rb +3 -0
- data/lib/nayati/workflow_results/base.rb +18 -0
- data/lib/nayati/workflow_runner.rb +34 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +36 -0
- data/spec/dummy/bin/update +31 -0
- data/spec/dummy/bin/yarn +11 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +19 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/database.yml +24 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +61 -0
- data/spec/dummy/config/environments/production.rb +94 -0
- data/spec/dummy/config/environments/test.rb +46 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/assets.rb +14 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +25 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/operations.yml +3 -0
- data/spec/dummy/config/puma.rb +34 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/config/storage.yml +34 -0
- data/spec/dummy/db/migrate/20200202111127_create_workflow.rb +16 -0
- data/spec/dummy/db/migrate/20200202111128_create_operation.rb +19 -0
- data/spec/dummy/db/schema.rb +36 -0
- data/spec/dummy/log/development.log +56 -0
- data/spec/dummy/package.json +5 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/operations_factory.rb +6 -0
- data/spec/factories/workflow_factory.rb +7 -0
- data/spec/lib/decider/workflow_runner_spec.rb +59 -0
- data/spec/lib/decider_spec.rb +4 -0
- data/spec/models/decider/master_operation_spec.rb +26 -0
- data/spec/models/decider/master_workflow_spec.rb +28 -0
- data/spec/models/decider/operation_spec.rb +48 -0
- data/spec/models/decider/workflow_spec.rb +159 -0
- data/spec/rails_helper.rb +57 -0
- data/spec/spec_helper.rb +120 -0
- metadata +259 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0bbc6f00148eadad1b96f8e72de1c6c6d2b69572
|
4
|
+
data.tar.gz: 232650a7d477b49b88bf0ed33e275d7282d622bc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ef068a11d3264a28b870a818ad9bd1791e2aca115662ebfe920e21ff05cf79a7ce56e856a3e60e8ab51995a950c5a4318578cfaee9325ba8eaed7480494291fb
|
7
|
+
data.tar.gz: 551b829e305bd72fb39deddf7793b1f7effcf840a8725c7d84f237a6fe33840162cc6612435d486d56e50c09d4ddea068e5f41105c4d7712ca9820430d1afac8
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 Tanmay Tupe
|
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,79 @@
|
|
1
|
+
# Nayati
|
2
|
+
Nayati is a Rails engine that helps in creating a clean and maintainable multi tenant Rails application. Creating multitenant application with Nayati allows you to have models that do just database talking and business logic gets handled by a layer I like to call 'Operation layer'. The sequence in which these operations get executed for a tenant is put in database.
|
3
|
+
|
4
|
+
## Installation
|
5
|
+
Add this line to your application's Gemfile:
|
6
|
+
|
7
|
+
```ruby
|
8
|
+
gem 'nayati'
|
9
|
+
```
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
```bash
|
13
|
+
$ bundle
|
14
|
+
```
|
15
|
+
|
16
|
+
Or install it yourself as:
|
17
|
+
```bash
|
18
|
+
$ gem install nayati
|
19
|
+
```
|
20
|
+
First run the install generator
|
21
|
+
```bash
|
22
|
+
$ rails generator nayati:install
|
23
|
+
```
|
24
|
+
|
25
|
+
## How to use
|
26
|
+
Nayati is meant to help in handling functional changes across tenants in your application.
|
27
|
+
Lets say you have a attendance system. Your company installs devices which have fingerprint scanning functionality. Your clients vary from
|
28
|
+
scools, gym, private companies. Now lets say you have 5 'operations' that can happen
|
29
|
+
in this workflow viz marking_attendace, notify_parents, give_extra_marks_for_consistency, notify_sitting_position, late_marking, notify_missing_registration
|
30
|
+
notify_number_of_late_marks
|
31
|
+
|
32
|
+
After installing, first generate a workflow e.g. attendance_management in example above.
|
33
|
+
|
34
|
+
```bash
|
35
|
+
$ rails generate nayati:workflow attendance_management
|
36
|
+
```
|
37
|
+
|
38
|
+
This will create a service class AttendanceManagementNayatiService which you are supposed to call in controller.
|
39
|
+
|
40
|
+
Service classes do following tasks
|
41
|
+
1. Find which workflow is supposed to run based on information passed from controller.
|
42
|
+
2. Create context that will be passed to every operation.
|
43
|
+
3. Return result object. By convention, this will return instance Nayati::WorkflowResults::Base object. You can have your own result object. Just create a class in app/models/nayati/workflow_results/#{workflow_name}.rb and nayati will pass instance of this class to operation classes.
|
44
|
+
|
45
|
+
Next we will create our operation classes. RESPONSIBILITIES OF A SINGLE OPERATION CLASS SHOULD NOT CHANGE ACROSS TENANTS. Rather this is how I define operation class. It is that piece of functionality which does not change across tenant. to create an operation class belonging to a workflow run
|
46
|
+
|
47
|
+
```bash
|
48
|
+
$ rails generate nayati:operation attendance_management marking_attendance
|
49
|
+
```
|
50
|
+
|
51
|
+
Next we will configure a workflow for a tenant. Lets say we are going to identify this workflow by name 'tenant_1_attendance_marking'. This tenant is a school and needs only marking_attendance and late_marking piece of functionality and if marking_attendance fails, you are supposed to nottify to register first. Nayati comes with a method to configure a workflow in database.
|
52
|
+
|
53
|
+
workflow_hash corresponding to sequence mentioned in above paragraph would be
|
54
|
+
workflow_sequence_hash = {
|
55
|
+
name: 'marking_attendance',
|
56
|
+
workflow_identifier: 'tenant_1_attendance_marking',
|
57
|
+
initial_operation_name: 'marking_attendance',
|
58
|
+
operations: [
|
59
|
+
{ name: 'marking_attendance', after_failure_operation_name: 'notify_missing_registration', after_success_operation_name: 'late_marking' },
|
60
|
+
{ name: 'notify_missing_registration'},
|
61
|
+
{ name: 'late_marking'}
|
62
|
+
]
|
63
|
+
}
|
64
|
+
|
65
|
+
You can put this workflow in database by calling
|
66
|
+
```ruby
|
67
|
+
Nayati::Workflow.create_or_update_from_workflow_json(workflow_sequence_hash)
|
68
|
+
```
|
69
|
+
|
70
|
+
Next we will have a look at operation class
|
71
|
+
nayati:operation will generate operation class in app/nayati_operations/{workflow_name}/ directory which will automatically be initialized with operation_context that you created in nayati service class and result object when you call the 'call' method on service class.
|
72
|
+
|
73
|
+
By default nayati will first call to_fail? method of a operation class. If this method returns true, nayati will call perform_failure and next operation will be after_failure operation configured in database. If this method returns false, nayati will call perform method and next operation will be after_success operation configured in database. Nayati will stop when there is no operation to run.
|
74
|
+
|
75
|
+
## Sample
|
76
|
+
|
77
|
+
You can find a sample multitenant application here.
|
78
|
+
## License
|
79
|
+
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,32 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'Nayati'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.md')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: :test
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module Nayati
|
2
|
+
class MasterOperation
|
3
|
+
attr_reader :name
|
4
|
+
def initialize(master_workflow, operation_name)
|
5
|
+
@master_workflow = master_workflow
|
6
|
+
@name = operation_name
|
7
|
+
@edit_errors = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def edit(new_name)
|
11
|
+
@new_name = ::Nayati::NameBasedConstantable.name_as_namespace(new_name.to_s)
|
12
|
+
@is_name_changed = true unless @new_name == @name
|
13
|
+
@valid_edit_called = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def exists?
|
17
|
+
@master_workflow.operation_names.include?(@name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def save
|
21
|
+
OperationConfiguration.add_operation_unless_present(@master_workflow.name, @name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid_edit?
|
25
|
+
validate_name_for_edit if @is_name_changed
|
26
|
+
@edit_errors.empty?
|
27
|
+
@valid_edit_called = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def update
|
31
|
+
raise 'Please check valid edit first' unless @valid_edit_called
|
32
|
+
OperationConfiguration.update_operation(@master_workflow.name, @name, @new_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def edit_error_message
|
36
|
+
@edit_errors.join(', ')
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def validate_name_for_edit
|
42
|
+
@edit_errors << 'New name is not valid' unless @new_name.present?
|
43
|
+
@edit_errors << 'New name already exists' if @master_workflow.operation_names.include?(@new_name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module Nayati
|
2
|
+
class MasterWorkflow
|
3
|
+
attr_reader :name
|
4
|
+
def initialize(name)
|
5
|
+
@name = name
|
6
|
+
@errors = []
|
7
|
+
@operation_addition_errors = []
|
8
|
+
@name_as_sym = @name.to_sym
|
9
|
+
end
|
10
|
+
|
11
|
+
def operation_names
|
12
|
+
Nayati::OperationConfiguration.operation_names(self.name)
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_operation(operation_name)
|
16
|
+
Nayati::OperationConfiguration.add_operation_unless_present(self.name, operation_name)
|
17
|
+
end
|
18
|
+
|
19
|
+
def save!
|
20
|
+
Nayati::OperationConfiguration.add_workflow_unless_present(self.name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def exists?
|
24
|
+
Nayati::OperationConfiguration.workflow_names.include?(@name_as_sym)
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
validate_name
|
29
|
+
@errors.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def valid_to_add_operation?(operation_name)
|
33
|
+
validate_operation_uniqueness(operation_name)
|
34
|
+
@operation_addition_errors.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
def all_workflow_names
|
39
|
+
Nayati::OperationConfiguration.workflow_names
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
def error_message
|
45
|
+
@errors.join(', ')
|
46
|
+
end
|
47
|
+
|
48
|
+
def operation_addition_error_message
|
49
|
+
@operation_addition_errors.join(', ')
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def validate_operation_uniqueness(operation_name)
|
55
|
+
@operation_addition_errors << 'Already exists' if operation_exists?(operation_name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def operation_exists?(operation_name)
|
59
|
+
operation_namespace = ::Nayati::NameBasedConstantable.name_as_namespace(operation_name.to_s)
|
60
|
+
operation_names.include?(operation_namespace)
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_name
|
64
|
+
@errors << 'Name already exists' if Nayati::OperationConfiguration.workflow_exists?(self.name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Nayati
|
2
|
+
module NameBasedConstantable
|
3
|
+
def camelcased_name
|
4
|
+
return "" if name.nil?
|
5
|
+
Nayati::NameBasedConstantable.name_as_constant(self.name)
|
6
|
+
end
|
7
|
+
|
8
|
+
def namespaced_name
|
9
|
+
return "" if name.nil?
|
10
|
+
Nayati::NameBasedConstantable.name_as_namespace(self.name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def underscored_name
|
14
|
+
return "" if name.nil?
|
15
|
+
Nayati::NameBasedConstantable.underscored_name(name)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.name_as_constant(name)
|
19
|
+
name.to_s.split(' ').join('_').camelcase
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.name_as_namespace(name)
|
23
|
+
name.to_s.downcase.split(' ').join('_')
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.underscored_name(name)
|
27
|
+
name.to_s.underscore.split(' ').join('_')
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Nayati
|
2
|
+
class Operation < ApplicationRecord
|
3
|
+
include NameBasedConstantable
|
4
|
+
|
5
|
+
belongs_to :workflow
|
6
|
+
belongs_to :after_success_operation, class_name: "Operation", foreign_key: :after_success_operation_id, required: false
|
7
|
+
belongs_to :after_failure_operation, class_name: "Operation", foreign_key: :after_failure_operation_id, required: false
|
8
|
+
before_save :format_name
|
9
|
+
|
10
|
+
|
11
|
+
validates :name, presence: true
|
12
|
+
|
13
|
+
def operation_implementer_klass_name
|
14
|
+
"#{workflow.camelcased_name}NayatiWorkflow::#{self.camelcased_name}NayatiOperation"
|
15
|
+
end
|
16
|
+
|
17
|
+
def build_implementer(operation_context, result_object)
|
18
|
+
operation_implementer_klass_name.constantize.new(operation_context, result_object)
|
19
|
+
end
|
20
|
+
|
21
|
+
def details_hash
|
22
|
+
{ name: self.name, after_success_operation: after_success_operation.try(:name), after_failure_operation: after_failure_operation.try(:name)}
|
23
|
+
end
|
24
|
+
|
25
|
+
def format_name
|
26
|
+
self.name = namespaced_name
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Nayati
|
2
|
+
module OperationConfiguration
|
3
|
+
class << self
|
4
|
+
def configuration
|
5
|
+
@configuration ||= YAML.load(File.open(yaml_file_path)) || {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def operation_names(workflow_name)
|
9
|
+
wf_name = ::Nayati::NameBasedConstantable.name_as_namespace(workflow_name.to_s)
|
10
|
+
(configuration[wf_name.to_sym] || []).map { |operation_conf| operation_conf.try(:[], :name) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_workflow_unless_present(workflow_name)
|
14
|
+
wf_namespace = ::Nayati::NameBasedConstantable.name_as_namespace(workflow_name.to_s)
|
15
|
+
unless workflow_names.include?(wf_namespace.to_sym)
|
16
|
+
configuration[wf_namespace.to_sym] = []
|
17
|
+
write_configuration
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def add_operation_unless_present(workflow_name, operation_name)
|
22
|
+
wf_namespace = ::Nayati::NameBasedConstantable.name_as_namespace(workflow_name.to_s)
|
23
|
+
op_namespace = ::Nayati::NameBasedConstantable.name_as_namespace(operation_name.to_s)
|
24
|
+
|
25
|
+
add_workflow_unless_present(wf_namespace)
|
26
|
+
|
27
|
+
unless operation_names(wf_namespace).include?(op_namespace)
|
28
|
+
configuration[wf_namespace.to_sym] << { name: op_namespace }
|
29
|
+
write_configuration
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def workflow_exists?(workflow_name)
|
34
|
+
wf_namespace = ::Nayati::NameBasedConstantable.name_as_namespace(workflow_name.to_s)
|
35
|
+
workflow_names.include?(wf_namespace.to_sym)
|
36
|
+
end
|
37
|
+
|
38
|
+
def yaml_file_path
|
39
|
+
"#{Rails.root}/config/operations.yml"
|
40
|
+
end
|
41
|
+
|
42
|
+
def write_configuration
|
43
|
+
File.open(yaml_file_path, 'w') { |f| f.write configuration.to_yaml }
|
44
|
+
end
|
45
|
+
|
46
|
+
def workflow_names
|
47
|
+
configuration.keys
|
48
|
+
end
|
49
|
+
|
50
|
+
def update_operation(workflow_name, old_name, new_name)
|
51
|
+
op_conf = operation_configuration(workflow_name, old_name)
|
52
|
+
|
53
|
+
op_conf[:name] = ::Nayati::NameBasedConstantable.name_as_namespace(new_name.to_s)
|
54
|
+
write_configuration
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def operation_configuration(workflow_name, operation_name)
|
60
|
+
wf_namespace = ::Nayati::NameBasedConstantable.name_as_namespace(workflow_name.to_s)
|
61
|
+
op_namespace = ::Nayati::NameBasedConstantable.name_as_namespace(operation_name.to_s)
|
62
|
+
|
63
|
+
wf_conf = workflow_configuration(wf_namespace)
|
64
|
+
wf_conf.detect { |operation| operation[:name] == op_namespace }
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def workflow_configuration(workflow_namespace)
|
69
|
+
configuration[workflow_namespace.to_sym]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Nayati
|
2
|
+
class OperationImplementerBase
|
3
|
+
attr_accessor :operation_context
|
4
|
+
def initialize(operation_context)
|
5
|
+
@operation_context = operation_context
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_fail?
|
9
|
+
puts "#{__callee__} called on #{self.class.name}"
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def perform_failure
|
14
|
+
puts "#{__callee__} called on #{self.class.name}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
puts "#{__callee__} called on #{self.class.name}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'pry'
|
2
|
+
require 'nayati/workflow_results/base'
|
3
|
+
module Nayati
|
4
|
+
class Workflow < ApplicationRecord
|
5
|
+
include NameBasedConstantable
|
6
|
+
|
7
|
+
attr_accessor :context, :initial_operation_name
|
8
|
+
|
9
|
+
has_many :operations
|
10
|
+
after_initialize :set_initial_creation_flag
|
11
|
+
belongs_to :initial_operation, class_name: 'Operation', optional: true
|
12
|
+
after_create :create_initial_operation
|
13
|
+
|
14
|
+
validates :name, presence: true
|
15
|
+
validates :identifier, uniqueness: true
|
16
|
+
validates :initial_operation, presence: true, unless: :initial_creation_flag_set?
|
17
|
+
validates :initial_operation_name, presence: { message: 'Please set initial_operation_name'}, if: :initial_creation_flag_set?
|
18
|
+
|
19
|
+
def name_space
|
20
|
+
name.parameterize.underscore
|
21
|
+
end
|
22
|
+
|
23
|
+
def klass_name
|
24
|
+
name_space.camelcase + 'Workflow'
|
25
|
+
end
|
26
|
+
|
27
|
+
def klass
|
28
|
+
raise ContextNotSetError.new unless context.present?
|
29
|
+
klass_name.constantize.new(context)
|
30
|
+
end
|
31
|
+
|
32
|
+
def specific_result_obj_klass_constant_name
|
33
|
+
"Nayati::WorkflowResults::#{klass_name}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def result_obj
|
37
|
+
result_class = begin
|
38
|
+
Module.const_get(specific_result_obj_klass_constant_name)
|
39
|
+
rescue NameError
|
40
|
+
::Nayati::WorkflowResults::Base
|
41
|
+
end
|
42
|
+
result_class.new
|
43
|
+
end
|
44
|
+
|
45
|
+
def entire_workflow_as_hash
|
46
|
+
{
|
47
|
+
name: self.name,
|
48
|
+
initial_operation_name: self.initial_operation.try(:name),
|
49
|
+
workflow_identifier: self.identifier,
|
50
|
+
operations: operations.map { |operation| operation.details_hash }
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
class << self
|
55
|
+
def name_selection_options
|
56
|
+
Nayati::OperationConfiguration.workflow_names.map { |name| [name, name] }
|
57
|
+
end
|
58
|
+
|
59
|
+
# Workflow json has following structure
|
60
|
+
# {
|
61
|
+
# name: 'workflow_name'
|
62
|
+
# initial_operation_name: 'operation_name',
|
63
|
+
# workflow_identifier: 'workflow_identifier',
|
64
|
+
# operations: [
|
65
|
+
# { name: 'operation_name', after_failure_operation_name: 'afo_name', after_success_operation_name: 'aso_name' },
|
66
|
+
# .
|
67
|
+
# .
|
68
|
+
# .
|
69
|
+
|
70
|
+
# ]
|
71
|
+
# }
|
72
|
+
|
73
|
+
def create_or_update_from_workflow_json(workflow_hash)
|
74
|
+
ActiveRecord::Base.transaction do
|
75
|
+
workflow_name = workflow_hash[:name]
|
76
|
+
workflow_identifier = workflow_hash[:workflow_identifier]
|
77
|
+
initial_operation_name = workflow_hash[:initial_operation_name]
|
78
|
+
|
79
|
+
wf = find_or_initialize_by(name: workflow_name.to_s, identifier: workflow_identifier)
|
80
|
+
wf.initial_operation_name = initial_operation_name
|
81
|
+
wf.save!
|
82
|
+
|
83
|
+
initial_operation_record = wf.operations.find_or_initialize_by(name: initial_operation_name)
|
84
|
+
initial_operation_record.save!
|
85
|
+
|
86
|
+
workflow_hash[:operations].each do |operation_hash|
|
87
|
+
operation_name = operation_hash[:name].to_s
|
88
|
+
operation_record = wf.operations.find_or_initialize_by(name: operation_name)
|
89
|
+
|
90
|
+
after_failure_operation_name = operation_hash[:after_failure_operation_name].to_s
|
91
|
+
if after_failure_operation_name.present?
|
92
|
+
after_failure_operation_record = wf.operations.find_or_initialize_by(name: after_failure_operation_name)
|
93
|
+
after_failure_operation_record.save!
|
94
|
+
operation_record.after_failure_operation = after_failure_operation_record
|
95
|
+
end
|
96
|
+
|
97
|
+
after_success_operation_name = operation_hash[:after_success_operation_name].to_s
|
98
|
+
if after_success_operation_name.present?
|
99
|
+
after_success_operation_record = wf.operations.find_or_initialize_by(name: after_success_operation_name)
|
100
|
+
after_success_operation_record.save!
|
101
|
+
operation_record.after_success_operation = after_success_operation_record
|
102
|
+
end
|
103
|
+
|
104
|
+
operation_record.save!
|
105
|
+
end
|
106
|
+
# puts "---------------- printing operation sequences ----------------"
|
107
|
+
# wf.print_sequences
|
108
|
+
# puts "-----------------------------------------------------------------"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def configuration
|
116
|
+
@configuration ||= OperationConfiguration.new
|
117
|
+
end
|
118
|
+
|
119
|
+
def set_initial_creation_flag
|
120
|
+
@creation_flag = true
|
121
|
+
end
|
122
|
+
|
123
|
+
def initial_creation_flag_set?
|
124
|
+
@creation_flag
|
125
|
+
end
|
126
|
+
|
127
|
+
def create_initial_operation
|
128
|
+
op = self.operations.new(name: initial_operation_name)
|
129
|
+
op.save
|
130
|
+
self.initial_operation = op
|
131
|
+
self.save
|
132
|
+
end
|
133
|
+
|
134
|
+
def assign_from_creation_params(params)
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# class Master
|
140
|
+
# attr_reader :name, :name_as_namespace, :operations
|
141
|
+
# def initialize(workflow_name)
|
142
|
+
# @name = @workflow_name
|
143
|
+
# @operations = operations_from_configuration(workflow_name)
|
144
|
+
# @name_as_namespace = Nayati::NameBasedConstantable.name_as_namespace(workflow_name)
|
145
|
+
# end
|
146
|
+
|
147
|
+
class << self
|
148
|
+
def assign_from_creation_params(params)
|
149
|
+
wf = self.new(name: params[:name])
|
150
|
+
wf.initial_operation_name = params[:initial_operation_name]
|
151
|
+
wf
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# private
|
156
|
+
|
157
|
+
# def operations_from_configuration(workflow_name)
|
158
|
+
# configuration[@name_as_namespace]
|
159
|
+
# end
|
160
|
+
|
161
|
+
# def configuration
|
162
|
+
# @configuration ||= ( YAML.load(File.open("#{Rails.root}/config/operations.yml")) || {} )
|
163
|
+
# end
|
164
|
+
# end
|
165
|
+
|
166
|
+
class ContextNotSetError < StandardError
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|