opera 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/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +11 -0
- data/Gemfile.lock +65 -0
- data/LICENSE.txt +21 -0
- data/README.md +828 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/opera.rb +7 -0
- data/lib/opera/errors.rb +6 -0
- data/lib/opera/operation.rb +20 -0
- data/lib/opera/operation/README.md +786 -0
- data/lib/opera/operation/base.rb +60 -0
- data/lib/opera/operation/builder.rb +50 -0
- data/lib/opera/operation/config.rb +31 -0
- data/lib/opera/operation/executor.rb +80 -0
- data/lib/opera/operation/instructions/executors/benchmark.rb +19 -0
- data/lib/opera/operation/instructions/executors/operation.rb +25 -0
- data/lib/opera/operation/instructions/executors/operations.rb +58 -0
- data/lib/opera/operation/instructions/executors/step.rb +21 -0
- data/lib/opera/operation/instructions/executors/success.rb +20 -0
- data/lib/opera/operation/instructions/executors/transaction.rb +33 -0
- data/lib/opera/operation/instructions/executors/validate.rb +24 -0
- data/lib/opera/operation/result.rb +72 -0
- data/lib/opera/version.rb +3 -0
- data/opera.gemspec +28 -0
- metadata +90 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
class Base
|
6
|
+
extend Gem::Deprecate
|
7
|
+
include Opera::Operation::Builder
|
8
|
+
|
9
|
+
attr_accessor :context
|
10
|
+
attr_reader :params, :dependencies, :result
|
11
|
+
|
12
|
+
def initialize(params: {}, dependencies: {})
|
13
|
+
@context = {}
|
14
|
+
@finished = false
|
15
|
+
@result = Result.new
|
16
|
+
@params = params.freeze
|
17
|
+
@dependencies = dependencies.freeze
|
18
|
+
end
|
19
|
+
|
20
|
+
def config
|
21
|
+
self.class.config
|
22
|
+
end
|
23
|
+
|
24
|
+
def finish
|
25
|
+
finish!
|
26
|
+
end
|
27
|
+
|
28
|
+
deprecate :finish, :finish!, 2019, 6
|
29
|
+
|
30
|
+
def finish!
|
31
|
+
@finished = true
|
32
|
+
end
|
33
|
+
|
34
|
+
def finished?
|
35
|
+
@finished
|
36
|
+
end
|
37
|
+
|
38
|
+
class << self
|
39
|
+
def call(args = {})
|
40
|
+
operation = new(params: args.fetch(:params, {}), dependencies: args.fetch(:dependencies, {}))
|
41
|
+
executor = Executor.new(operation)
|
42
|
+
executor.evaluate_instructions(instructions)
|
43
|
+
executor.result
|
44
|
+
end
|
45
|
+
|
46
|
+
def config
|
47
|
+
@config ||= Config.new
|
48
|
+
end
|
49
|
+
|
50
|
+
def configure
|
51
|
+
yield config
|
52
|
+
end
|
53
|
+
|
54
|
+
def reporter
|
55
|
+
config.reporter
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
module Builder
|
6
|
+
INSTRUCTIONS = %I[validate transaction benchmark step success operation operations].freeze
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
def instructions
|
14
|
+
@instructions ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
INSTRUCTIONS.each do |instruction|
|
18
|
+
define_method instruction do |method = nil, &blk|
|
19
|
+
instructions.concat(InnerBuilder.new.send(instruction, method, &blk))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class InnerBuilder
|
25
|
+
attr_reader :instructions
|
26
|
+
|
27
|
+
def initialize(&block)
|
28
|
+
@instructions = []
|
29
|
+
instance_eval(&block) if block_given?
|
30
|
+
end
|
31
|
+
|
32
|
+
INSTRUCTIONS.each do |instruction|
|
33
|
+
define_method instruction do |method = nil, &blk|
|
34
|
+
instructions << if !blk.nil?
|
35
|
+
{
|
36
|
+
kind: instruction,
|
37
|
+
instructions: InnerBuilder.new(&blk).instructions
|
38
|
+
}
|
39
|
+
else
|
40
|
+
{
|
41
|
+
kind: instruction,
|
42
|
+
method: method
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
class Config
|
6
|
+
attr_accessor :transaction_class, :transaction_method, :reporter
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@transaction_class = self.class.transaction_class
|
10
|
+
@transaction_method = self.class.transaction_method || :transaction
|
11
|
+
@reporter = custom_reporter || self.class.reporter
|
12
|
+
end
|
13
|
+
|
14
|
+
def configure
|
15
|
+
yield self
|
16
|
+
end
|
17
|
+
|
18
|
+
def custom_reporter
|
19
|
+
Rails.application.config.x.reporter.presence if defined?(Rails)
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
attr_accessor :transaction_class, :transaction_method, :reporter
|
24
|
+
|
25
|
+
def configure
|
26
|
+
yield self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
class Executor
|
6
|
+
attr_reader :operation
|
7
|
+
|
8
|
+
def initialize(operation)
|
9
|
+
@operation = operation
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(instruction)
|
13
|
+
instructions = instruction[:instructions]
|
14
|
+
|
15
|
+
if instructions
|
16
|
+
evaluate_instructions(instructions)
|
17
|
+
else
|
18
|
+
evaluate_instruction(instruction)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def evaluate_instructions(instructions = [])
|
23
|
+
instruction_copy = Marshal.load(Marshal.dump(instructions))
|
24
|
+
|
25
|
+
while instruction_copy.any?
|
26
|
+
instruction = instruction_copy.shift
|
27
|
+
evaluate_instruction(instruction)
|
28
|
+
break if break_condition
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
33
|
+
def evaluate_instruction(instruction)
|
34
|
+
case instruction[:kind]
|
35
|
+
when :step
|
36
|
+
Instructions::Executors::Step.new(operation).call(instruction)
|
37
|
+
when :operation
|
38
|
+
Instructions::Executors::Operation.new(operation).call(instruction)
|
39
|
+
when :operations
|
40
|
+
Instructions::Executors::Operations.new(operation).call(instruction)
|
41
|
+
when :success
|
42
|
+
Instructions::Executors::Success.new(operation).call(instruction)
|
43
|
+
when :validate
|
44
|
+
Instructions::Executors::Validate.new(operation).call(instruction)
|
45
|
+
when :transaction
|
46
|
+
Instructions::Executors::Transaction.new(operation).call(instruction)
|
47
|
+
when :benchmark
|
48
|
+
Instructions::Executors::Benchmark.new(operation).call(instruction)
|
49
|
+
else
|
50
|
+
raise(UnknownInstructionError, "Unknown instruction #{instruction[:kind]}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity
|
54
|
+
|
55
|
+
def result
|
56
|
+
operation.result
|
57
|
+
end
|
58
|
+
|
59
|
+
def config
|
60
|
+
operation.config
|
61
|
+
end
|
62
|
+
|
63
|
+
def context
|
64
|
+
operation.context
|
65
|
+
end
|
66
|
+
|
67
|
+
def reporter
|
68
|
+
config.reporter
|
69
|
+
end
|
70
|
+
|
71
|
+
def break_condition
|
72
|
+
operation.finished? || result.failure?
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_instruction_output(instruction, output = {})
|
76
|
+
context["#{instruction[:method]}_output".to_sym] = output
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
module Instructions
|
6
|
+
module Executors
|
7
|
+
class Benchmark < Executor
|
8
|
+
def call(instruction)
|
9
|
+
benchmark = ::Benchmark.measure do
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
result.add_information(real: benchmark.real, total: benchmark.total)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
module Instructions
|
6
|
+
module Executors
|
7
|
+
class Operation < Executor
|
8
|
+
def call(instruction)
|
9
|
+
instruction[:kind] = :step
|
10
|
+
operation_result = super
|
11
|
+
|
12
|
+
if operation_result.success?
|
13
|
+
add_instruction_output(instruction, operation_result.output)
|
14
|
+
execution = result.executions.pop
|
15
|
+
result.executions << { execution => operation_result.executions }
|
16
|
+
else
|
17
|
+
result.add_errors(operation_result.errors)
|
18
|
+
result.add_exceptions(operation_result.exceptions)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
module Instructions
|
6
|
+
module Executors
|
7
|
+
class Operations < Executor
|
8
|
+
class WrongOperationsResultError < Opera::Error; end
|
9
|
+
|
10
|
+
# rubocop:disable Metrics/MethodLength
|
11
|
+
def call(instruction)
|
12
|
+
instruction[:kind] = :step
|
13
|
+
operations_results = super
|
14
|
+
|
15
|
+
return if result.exceptions.any?
|
16
|
+
|
17
|
+
case operations_results
|
18
|
+
when Array
|
19
|
+
operations_results.each do |operation_result|
|
20
|
+
raise_error unless operation_result.is_a?(Opera::Operation::Result)
|
21
|
+
end
|
22
|
+
|
23
|
+
failures = operations_results.select(&:failure?)
|
24
|
+
|
25
|
+
if failures.any?
|
26
|
+
add_failures(failures)
|
27
|
+
else
|
28
|
+
add_results(instruction, operations_results)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise_error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
# rubocop:enable Metrics/MethodLength
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def add_failures(failures)
|
39
|
+
failures.each do |failure|
|
40
|
+
result.add_errors(failure.errors)
|
41
|
+
result.add_exceptions(failure.exceptions)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def add_results(instruction, results)
|
46
|
+
add_instruction_output(instruction, results.map(&:output))
|
47
|
+
execution = result.executions.pop
|
48
|
+
result.executions << { execution => results.map(&:executions) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def raise_error
|
52
|
+
raise WrongOperationsResultError, 'Have to return array of Opera::Operation::Result'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
module Instructions
|
6
|
+
module Executors
|
7
|
+
class Step < Executor
|
8
|
+
def call(instruction)
|
9
|
+
method = instruction[:method]
|
10
|
+
|
11
|
+
operation.result.add_execution(method)
|
12
|
+
operation.send(method)
|
13
|
+
rescue StandardError => exception
|
14
|
+
reporter&.error(exception)
|
15
|
+
operation.result.add_exception(method, exception.message, classname: operation.class.name)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
module Instructions
|
6
|
+
module Executors
|
7
|
+
class Success < Executor
|
8
|
+
def call(instruction)
|
9
|
+
instruction[:kind] = :step
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def break_condition
|
14
|
+
operation.finished?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
module Instructions
|
6
|
+
module Executors
|
7
|
+
class Transaction < Executor
|
8
|
+
class RollbackTransactionError < Opera::Error; end
|
9
|
+
|
10
|
+
def call(instruction)
|
11
|
+
transaction_class.send(transaction_method) do
|
12
|
+
super
|
13
|
+
|
14
|
+
return if !operation.finished? && result.success?
|
15
|
+
|
16
|
+
raise(RollbackTransactionError)
|
17
|
+
end
|
18
|
+
rescue RollbackTransactionError
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def transaction_class
|
23
|
+
config.transaction_class
|
24
|
+
end
|
25
|
+
|
26
|
+
def transaction_method
|
27
|
+
config.transaction_method
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
module Instructions
|
6
|
+
module Executors
|
7
|
+
class Validate < Executor
|
8
|
+
def break_condition
|
9
|
+
operation.finished?
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def evaluate_instruction(instruction)
|
15
|
+
instruction[:kind] = :step
|
16
|
+
dry_result = super
|
17
|
+
add_instruction_output(instruction, dry_result.output)
|
18
|
+
result.add_errors(dry_result.errors) unless dry_result.success?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Opera
|
4
|
+
module Operation
|
5
|
+
class Result
|
6
|
+
attr_reader :errors, # Acumulator of errors in validation + steps
|
7
|
+
:exceptions, # Acumulator of exceptions in steps
|
8
|
+
:information, # Temporal object to store related information
|
9
|
+
:executions # Stacktrace or Pipe of the methods evaludated
|
10
|
+
|
11
|
+
attr_accessor :output # Final object returned if success?
|
12
|
+
|
13
|
+
def initialize(output: nil, errors: {})
|
14
|
+
@errors = errors
|
15
|
+
@exceptions = {}
|
16
|
+
@information = {}
|
17
|
+
@executions = []
|
18
|
+
@output = output
|
19
|
+
end
|
20
|
+
|
21
|
+
def failure?
|
22
|
+
errors.any? || exceptions.any?
|
23
|
+
end
|
24
|
+
|
25
|
+
def success?
|
26
|
+
!failure?
|
27
|
+
end
|
28
|
+
|
29
|
+
# rubocop:disable Metrics/MethodLength
|
30
|
+
def add_error(field, message)
|
31
|
+
@errors[field] ||= []
|
32
|
+
if message.is_a?(Hash)
|
33
|
+
if @errors[field].first&.is_a?(Hash)
|
34
|
+
@errors[field].first.merge!(message)
|
35
|
+
else
|
36
|
+
@errors[field].push(message)
|
37
|
+
end
|
38
|
+
else
|
39
|
+
@errors[field].concat(Array(message))
|
40
|
+
end
|
41
|
+
@errors[field].uniq!
|
42
|
+
end
|
43
|
+
# rubocop:enable Metrics/MethodLength
|
44
|
+
|
45
|
+
def add_errors(errors)
|
46
|
+
errors.each_pair do |key, value|
|
47
|
+
add_error(key, value)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_exception(method, message, classname: nil)
|
52
|
+
key = [classname, Array(method).first].compact.join('#')
|
53
|
+
@exceptions[key] ||= []
|
54
|
+
@exceptions[key].push(message)
|
55
|
+
end
|
56
|
+
|
57
|
+
def add_exceptions(exceptions)
|
58
|
+
exceptions.each_pair do |key, value|
|
59
|
+
add_exception(key, value)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def add_information(hash)
|
64
|
+
@information.merge!(hash)
|
65
|
+
end
|
66
|
+
|
67
|
+
def add_execution(step)
|
68
|
+
@executions << step
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|