flow 0.3.0 → 0.9.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 +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
|