flow 0.3.0 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/flow.rb +3 -3
- data/lib/flow/concerns/transaction_wrapper.rb +27 -0
- data/lib/flow/custom_matchers.rb +5 -0
- data/lib/flow/custom_matchers/define_argument.rb +19 -0
- data/lib/flow/custom_matchers/define_option.rb +26 -0
- data/lib/flow/custom_matchers/use_operations.rb +23 -0
- data/lib/flow/flow/callbacks.rb +13 -0
- data/lib/flow/flow/core.rb +24 -0
- data/lib/flow/flow/ebb.rb +28 -0
- data/lib/flow/flow/errors/state_invalid.rb +7 -0
- data/lib/flow/flow/flux.rb +55 -0
- data/lib/flow/flow/operations.rb +27 -0
- data/lib/flow/flow/revert.rb +18 -0
- data/lib/flow/flow/status.rb +28 -0
- data/lib/flow/flow/transactions.rb +14 -0
- data/lib/flow/flow/trigger.rb +38 -0
- data/lib/flow/flow_base.rb +23 -37
- data/lib/flow/operation/callbacks.rb +19 -0
- data/lib/flow/operation/error_handler.rb +24 -0
- data/lib/flow/operation/execute.rb +25 -12
- data/lib/flow/operation/failures.rb +65 -0
- data/lib/flow/operation/rewind.rb +24 -0
- data/lib/flow/operation/status.rb +28 -0
- data/lib/flow/operation/transactions.rb +14 -0
- data/lib/flow/operation_base.rb +17 -0
- data/lib/flow/shoulda_matcher_helper.rb +7 -0
- data/lib/flow/spec_helper.rb +4 -0
- data/lib/flow/state/arguments.rb +1 -1
- data/lib/flow/state/attributes.rb +1 -2
- data/lib/flow/state/callbacks.rb +1 -1
- data/lib/flow/state/core.rb +1 -1
- data/lib/flow/state/options.rb +4 -2
- data/lib/flow/state/string.rb +2 -6
- data/lib/flow/state_base.rb +2 -0
- data/lib/flow/version.rb +1 -1
- data/lib/generators/flow/USAGE +11 -0
- data/lib/generators/flow/application_flow/USAGE +9 -0
- data/lib/generators/flow/application_flow/application_flow_generator.rb +15 -0
- data/lib/generators/flow/application_flow/templates/application_flow.rb +3 -0
- data/lib/generators/flow/application_operation/USAGE +9 -0
- data/lib/generators/flow/application_operation/application_operation_generator.rb +15 -0
- data/lib/generators/flow/application_operation/templates/application_operation.rb +3 -0
- data/lib/generators/flow/application_state/USAGE +9 -0
- data/lib/generators/flow/application_state/application_state_generator.rb +15 -0
- data/lib/generators/flow/application_state/templates/application_state.rb +3 -0
- data/lib/generators/flow/flow_generator.rb +18 -0
- data/lib/generators/flow/install/USAGE +13 -0
- data/lib/generators/flow/install/install_generator.rb +13 -0
- data/lib/generators/flow/operation/USAGE +9 -0
- data/lib/generators/flow/operation/operation_generator.rb +15 -0
- data/lib/generators/flow/operation/templates/operation.rb.erb +7 -0
- data/lib/generators/flow/state/USAGE +9 -0
- data/lib/generators/flow/state/state_generator.rb +15 -0
- data/lib/generators/flow/state/templates/state.rb.erb +8 -0
- data/lib/generators/flow/templates/flow.rb.erb +5 -0
- data/lib/generators/rspec/application_flow/USAGE +8 -0
- data/lib/generators/rspec/application_flow/application_flow_generator.rb +13 -0
- data/lib/generators/rspec/application_flow/templates/application_flow_spec.rb +7 -0
- data/lib/generators/rspec/application_operation/USAGE +8 -0
- data/lib/generators/rspec/application_operation/application_operation_generator.rb +13 -0
- data/lib/generators/rspec/application_operation/templates/application_operation_spec.rb +11 -0
- data/lib/generators/rspec/application_state/USAGE +8 -0
- data/lib/generators/rspec/application_state/application_state_generator.rb +13 -0
- data/lib/generators/rspec/application_state/templates/application_state_spec.rb +7 -0
- data/lib/generators/rspec/flow/USAGE +8 -0
- data/lib/generators/rspec/flow/flow_generator.rb +13 -0
- data/lib/generators/rspec/flow/templates/flow_spec.rb.erb +14 -0
- data/lib/generators/rspec/operation/USAGE +8 -0
- data/lib/generators/rspec/operation/operation_generator.rb +13 -0
- data/lib/generators/rspec/operation/templates/operation_spec.rb.erb +24 -0
- data/lib/generators/rspec/state/USAGE +8 -0
- data/lib/generators/rspec/state/state_generator.rb +13 -0
- data/lib/generators/rspec/state/templates/state_spec.rb.erb +18 -0
- metadata +93 -4
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Callbacks provide an extensible mechanism for hooking into a Operation.
|
4
|
+
module Operation
|
5
|
+
module Callbacks
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
include ActiveSupport::Callbacks
|
10
|
+
define_callbacks :failure, :execute, :behavior, :rewind, :undo
|
11
|
+
end
|
12
|
+
|
13
|
+
class_methods do
|
14
|
+
def on_failure(*filters, &block)
|
15
|
+
set_callback(:failure, :before, *filters, &block)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An unhandled error during execution is exceptional, and handlers unable to rescue an error cause a failure instead.
|
4
|
+
module Operation
|
5
|
+
module ErrorHandler
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
private
|
10
|
+
|
11
|
+
def handle_error(error_class, problem: error_class.name.demodulize.underscore, with: nil, &block)
|
12
|
+
failure problem
|
13
|
+
|
14
|
+
rescue_from(error_class) { |exception| fail!(problem.to_sym, exception: exception) }
|
15
|
+
|
16
|
+
if with.present?
|
17
|
+
rescue_from(error_class, with: with)
|
18
|
+
elsif block_given?
|
19
|
+
rescue_from(error_class, &block)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -1,27 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
3
|
+
# Operations *must* define the `#behavior` that occurs when `#execute` is called.
|
4
4
|
module Operation
|
5
5
|
module Execute
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
included do
|
9
|
+
include ActiveSupport::Rescuable
|
10
|
+
|
11
|
+
attr_reader :operation_failure
|
12
|
+
|
13
|
+
set_callback :execute, :around, ->(_, block) { surveil(:execute) { block.call } }
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute!
|
17
|
+
run_callbacks(:execute) do
|
18
|
+
run_callbacks(:behavior) { behavior }
|
11
19
|
end
|
20
|
+
|
21
|
+
self
|
22
|
+
rescue StandardError => exception
|
23
|
+
rescue_with_handler(exception) || raise
|
24
|
+
|
25
|
+
self
|
12
26
|
end
|
13
27
|
|
14
28
|
def execute
|
15
|
-
|
16
|
-
|
29
|
+
execute!
|
30
|
+
rescue Operation::Failures::OperationFailure => exception
|
31
|
+
@operation_failure = exception
|
17
32
|
|
18
|
-
|
19
|
-
|
20
|
-
include Technologic
|
33
|
+
self
|
34
|
+
end
|
21
35
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
36
|
+
def behavior
|
37
|
+
# abstract method which should be defined by descendants with the functionality of the given operation
|
25
38
|
end
|
26
39
|
end
|
27
40
|
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# When `#execute` is unsuccessful, expected problems are *failures* and unexpected problems are *Exceptions*.
|
4
|
+
module Operation
|
5
|
+
module Failures
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :_failures, instance_writer: false, default: []
|
10
|
+
|
11
|
+
delegate :_failures, to: :class
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
def inherited(base)
|
16
|
+
base._failures = _failures.dup
|
17
|
+
super
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def failure(problem)
|
23
|
+
problem = problem.to_s.to_sym
|
24
|
+
warn(:problem_already_defined) if _failures.include? problem
|
25
|
+
|
26
|
+
_failures << problem
|
27
|
+
define_callbacks problem
|
28
|
+
define_on_failure_for_problem(problem)
|
29
|
+
define_fail_method_for_problem(problem)
|
30
|
+
end
|
31
|
+
|
32
|
+
def define_on_failure_for_problem(problem)
|
33
|
+
on_failure_name = "on_#{problem}_failure".to_sym
|
34
|
+
return if respond_to?(on_failure_name)
|
35
|
+
|
36
|
+
define_singleton_method(on_failure_name) { |*filters, &block| set_callback(problem, :before, *filters, &block) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def define_fail_method_for_problem(problem)
|
40
|
+
problem_failure_name = "#{problem}_failure!".to_sym
|
41
|
+
return if method_defined?(problem_failure_name)
|
42
|
+
|
43
|
+
define_method(problem_failure_name) { |**details| fail!(problem, **details) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def fail!(problem, **details)
|
48
|
+
run_callbacks(problem) do
|
49
|
+
run_callbacks(:failure) do
|
50
|
+
error! OperationFailure.new(problem, **details), **details
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class OperationFailure < StandardError
|
56
|
+
attr_reader :problem, :details
|
57
|
+
|
58
|
+
def initialize(problem = nil, **details)
|
59
|
+
super(problem)
|
60
|
+
@problem = problem
|
61
|
+
@details = OpenStruct.new(details)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# When something goes wrong in Flow, `#rewind` is called on all executed Operations to `#undo` their behavior.
|
4
|
+
module Operation
|
5
|
+
module Rewind
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
set_callback :rewind, :around, ->(_, block) { surveil(:rewind) { block.call } }
|
10
|
+
end
|
11
|
+
|
12
|
+
def rewind
|
13
|
+
run_callbacks(:rewind) do
|
14
|
+
run_callbacks(:undo) { undo }
|
15
|
+
end
|
16
|
+
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def undo
|
21
|
+
# abstract method which should be defined by descendants to undo the functionality of the `#behavior` method
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The Operation status is used by the Flow calling it to determine what to do next; continue on or stop and rollback.
|
4
|
+
module Operation
|
5
|
+
module Status
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
set_callback(:execute, :before) { self.was_executed = true }
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_accessor :was_executed
|
14
|
+
end
|
15
|
+
|
16
|
+
def executed?
|
17
|
+
was_executed.present?
|
18
|
+
end
|
19
|
+
|
20
|
+
def failed?
|
21
|
+
operation_failure.present?
|
22
|
+
end
|
23
|
+
|
24
|
+
def success?
|
25
|
+
executed? && !failed?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# It's best practice to have Operations which modify several persisted objects to use a transaction.
|
4
|
+
module Operation
|
5
|
+
module Transactions
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
class_methods do
|
9
|
+
def callbacks_for_transaction
|
10
|
+
%i[behavior undo].freeze
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/flow/operation_base.rb
CHANGED
@@ -1,10 +1,27 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "flow/errors/state_invalid"
|
4
|
+
|
5
|
+
require_relative "operation/callbacks"
|
3
6
|
require_relative "operation/core"
|
7
|
+
require_relative "operation/error_handler"
|
4
8
|
require_relative "operation/execute"
|
9
|
+
require_relative "operation/failures"
|
10
|
+
require_relative "operation/rewind"
|
11
|
+
require_relative "operation/status"
|
12
|
+
require_relative "operation/transactions"
|
5
13
|
|
6
14
|
# Operations are service objects which are executed with a state.
|
7
15
|
class OperationBase
|
16
|
+
include ShortCircuIt
|
17
|
+
include Technologic
|
18
|
+
include TransactionWrapper
|
19
|
+
include Operation::Callbacks
|
8
20
|
include Operation::Core
|
21
|
+
include Operation::ErrorHandler
|
9
22
|
include Operation::Execute
|
23
|
+
include Operation::Failures
|
24
|
+
include Operation::Rewind
|
25
|
+
include Operation::Status
|
26
|
+
include Operation::Transactions
|
10
27
|
end
|
data/lib/flow/state/arguments.rb
CHANGED
@@ -17,14 +17,13 @@ module State
|
|
17
17
|
super
|
18
18
|
end
|
19
19
|
|
20
|
-
|
20
|
+
private
|
21
21
|
|
22
22
|
def define_attribute(attribute)
|
23
23
|
_attributes << attribute
|
24
24
|
|
25
25
|
attr_accessor attribute
|
26
26
|
define_attribute_methods attribute
|
27
|
-
protected "#{attribute}=".to_sym
|
28
27
|
end
|
29
28
|
end
|
30
29
|
end
|
data/lib/flow/state/callbacks.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
3
|
+
# Callbacks provide an extensible mechanism for hooking into a State.
|
4
4
|
module State
|
5
5
|
module Callbacks
|
6
6
|
extend ActiveSupport::Concern
|
data/lib/flow/state/core.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# Accepts input representing the arguments and
|
3
|
+
# Accepts input representing the arguments and options which define the initial state.
|
4
4
|
module State
|
5
5
|
module Core
|
6
6
|
extend ActiveSupport::Concern
|
data/lib/flow/state/options.rb
CHANGED
@@ -9,7 +9,9 @@ module State
|
|
9
9
|
class_attribute :_options, instance_writer: false, default: {}
|
10
10
|
|
11
11
|
set_callback :initialize, :after do
|
12
|
-
_options.each
|
12
|
+
_options.each do |option, info|
|
13
|
+
__send__("#{option}=".to_sym, info.default_value.dup) if public_send(option).nil?
|
14
|
+
end
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
@@ -20,7 +22,7 @@ module State
|
|
20
22
|
super
|
21
23
|
end
|
22
24
|
|
23
|
-
|
25
|
+
private
|
24
26
|
|
25
27
|
def option(option, default: nil, &block)
|
26
28
|
_options[option] = Option.new(default: default, &block)
|
data/lib/flow/state/string.rb
CHANGED
@@ -5,8 +5,6 @@ module State
|
|
5
5
|
module String
|
6
6
|
extend ActiveSupport::Concern
|
7
7
|
|
8
|
-
delegate :name, to: :class, prefix: true
|
9
|
-
|
10
8
|
def to_s
|
11
9
|
string_for(__method__)
|
12
10
|
end
|
@@ -15,16 +13,14 @@ module State
|
|
15
13
|
string_for(__method__)
|
16
14
|
end
|
17
15
|
|
18
|
-
|
16
|
+
private
|
19
17
|
|
20
18
|
def stringable_attributes
|
21
19
|
self.class._attributes
|
22
20
|
end
|
23
21
|
|
24
|
-
private
|
25
|
-
|
26
22
|
def string_for(method)
|
27
|
-
"#<#{
|
23
|
+
"#<#{self.class.name} #{attribute_string(method)}>"
|
28
24
|
end
|
29
25
|
|
30
26
|
def attribute_string(method)
|
data/lib/flow/state_base.rb
CHANGED
data/lib/flow/version.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flow
|
4
|
+
module Generators
|
5
|
+
class ApplicationFlowGenerator < Rails::Generators::Base
|
6
|
+
source_root File.expand_path("templates", __dir__)
|
7
|
+
|
8
|
+
hook_for :test_framework
|
9
|
+
|
10
|
+
def create_application_flow
|
11
|
+
template "application_flow.rb", File.join("app/flows/application_flow.rb")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|