flow 0.9.3 → 0.10.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/README.md +177 -245
- data/lib/flow.rb +2 -0
- data/lib/flow/concerns/transaction_wrapper.rb +10 -17
- data/lib/flow/custom_matchers.rb +5 -3
- data/lib/flow/custom_matchers/define_failure.rb +21 -0
- data/lib/flow/custom_matchers/define_output.rb +34 -0
- data/lib/flow/custom_matchers/handle_error.rb +32 -0
- data/lib/flow/custom_matchers/have_on_state.rb +54 -0
- data/lib/flow/custom_matchers/use_operations.rb +13 -11
- data/lib/flow/custom_matchers/wrap_in_transaction.rb +60 -0
- data/lib/flow/flow/callbacks.rb +1 -1
- data/lib/flow/flow/core.rb +1 -0
- data/lib/flow/flow/flux.rb +0 -2
- data/lib/flow/flow/status.rb +0 -4
- data/lib/flow/flow/transactions.rb +2 -2
- data/lib/flow/flow/trigger.rb +1 -1
- data/lib/flow/flow_base.rb +13 -15
- data/lib/flow/operation/accessors.rb +66 -0
- data/lib/flow/operation/callbacks.rb +12 -10
- data/lib/flow/operation/core.rb +10 -8
- data/lib/flow/operation/error_handler.rb +18 -12
- data/lib/flow/operation/errors/already_executed.rb +5 -3
- data/lib/flow/operation/errors/already_rewound.rb +5 -3
- data/lib/flow/operation/execute.rb +28 -26
- data/lib/flow/operation/failures.rb +48 -42
- data/lib/flow/operation/status.rb +18 -21
- data/lib/flow/operation/transactions.rb +8 -6
- data/lib/flow/operation_base.rb +15 -13
- data/lib/flow/rspec_configuration.rb +5 -0
- data/lib/flow/spec_helper.rb +3 -0
- data/lib/flow/state/errors/not_validated.rb +9 -0
- data/lib/flow/state/output.rb +59 -0
- data/lib/flow/state/status.rb +22 -0
- data/lib/flow/state_base.rb +9 -16
- data/lib/flow/version.rb +1 -1
- data/lib/generators/flow/application_flow/templates/application_flow.rb +1 -1
- data/lib/generators/flow/application_operation/templates/application_operation.rb +1 -1
- data/lib/generators/flow/application_state/templates/application_state.rb +1 -1
- data/lib/generators/flow/operation/USAGE +1 -1
- data/lib/generators/flow/operation/templates/operation.rb.erb +0 -5
- data/lib/generators/flow/state/USAGE +1 -1
- data/lib/generators/flow/state/templates/state.rb.erb +2 -1
- data/lib/generators/rspec/application_flow/templates/application_flow_spec.rb +1 -1
- data/lib/generators/rspec/application_operation/templates/application_operation_spec.rb +1 -9
- data/lib/generators/rspec/application_state/templates/application_state_spec.rb +1 -1
- data/lib/generators/rspec/flow/templates/flow_spec.rb.erb +0 -8
- data/lib/generators/rspec/operation/templates/operation_spec.rb.erb +5 -11
- data/lib/generators/rspec/state/templates/state_spec.rb.erb +8 -10
- metadata +39 -52
- data/lib/flow/custom_matchers/define_argument.rb +0 -19
- data/lib/flow/custom_matchers/define_attribute.rb +0 -19
- data/lib/flow/custom_matchers/define_option.rb +0 -26
- data/lib/flow/flow/ebb.rb +0 -28
- data/lib/flow/flow/revert.rb +0 -18
- data/lib/flow/operation/rewind.rb +0 -25
- data/lib/flow/state/arguments.rb +0 -30
- data/lib/flow/state/attributes.rb +0 -31
- data/lib/flow/state/callbacks.rb +0 -13
- data/lib/flow/state/core.rb +0 -14
- data/lib/flow/state/options.rb +0 -45
- data/lib/flow/state/string.rb +0 -34
data/lib/flow.rb
CHANGED
@@ -1,26 +1,19 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# A callback driven approach to wrap business logic within database transaction.
|
4
|
-
module
|
5
|
-
|
4
|
+
module Flow
|
5
|
+
module TransactionWrapper
|
6
|
+
extend ActiveSupport::Concern
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
private
|
13
|
-
|
14
|
-
def wrap_in_transaction(only: nil, except: nil)
|
15
|
-
whitelist = Array.wrap(only).map(&:to_sym)
|
16
|
-
blacklist = Array.wrap(except).map(&:to_sym)
|
8
|
+
class_methods do
|
9
|
+
def transaction_provider
|
10
|
+
ActiveRecord::Base
|
11
|
+
end
|
17
12
|
|
18
|
-
|
19
|
-
callbacks_to_wrap &= whitelist if whitelist.present?
|
20
|
-
callbacks_to_wrap -= blacklist if blacklist.present?
|
13
|
+
private
|
21
14
|
|
22
|
-
|
23
|
-
set_callback
|
15
|
+
def wrap_in_transaction
|
16
|
+
set_callback callback_name, :around, ->(_, block) { self.class.transaction_provider.transaction { block.call } }
|
24
17
|
end
|
25
18
|
end
|
26
19
|
end
|
data/lib/flow/custom_matchers.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "custom_matchers/
|
4
|
-
require_relative "custom_matchers/
|
5
|
-
require_relative "custom_matchers/
|
3
|
+
require_relative "custom_matchers/define_failure"
|
4
|
+
require_relative "custom_matchers/define_output"
|
5
|
+
require_relative "custom_matchers/handle_error"
|
6
6
|
require_relative "custom_matchers/use_operations"
|
7
|
+
require_relative "custom_matchers/wrap_in_transaction"
|
8
|
+
require_relative "custom_matchers/have_on_state"
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RSpec matcher that tests usage of `ApplicationOperation.failure`
|
4
|
+
#
|
5
|
+
# class ExampleOperation
|
6
|
+
# failure :foo
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# RSpec.describe ExampleOperation, type: :operation do
|
10
|
+
# subject { described_class.new(**input) }
|
11
|
+
#
|
12
|
+
# let(:input) { {} }
|
13
|
+
#
|
14
|
+
# it { is_expected.to define_failure :foo }
|
15
|
+
# end
|
16
|
+
|
17
|
+
RSpec::Matchers.define :define_failure do |problem|
|
18
|
+
match { |operation| expect(operation._failures).to include problem.to_sym }
|
19
|
+
description { "defines failure #{problem}" }
|
20
|
+
failure_message { |operation| "expected #{operation.class.name} to define failure #{problem}" }
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RSpec matcher that tests usage of `ApplicationState.output`
|
4
|
+
#
|
5
|
+
# class ExampleState
|
6
|
+
# output :foo
|
7
|
+
# output :bar, default: :baz
|
8
|
+
# output(:gaz) { :haz }
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# RSpec.describe ExampleState, type: :state do
|
12
|
+
# subject { described_class.new(**input) }
|
13
|
+
#
|
14
|
+
# let(:input) { {} }
|
15
|
+
#
|
16
|
+
# it { is_expected.to define_output :foo }
|
17
|
+
# it { is_expected.to define_output :bar, default: :baz }
|
18
|
+
# it { is_expected.to define_output :gaz, default: :haz }
|
19
|
+
# end
|
20
|
+
|
21
|
+
RSpec::Matchers.define :define_output do |output, default: nil|
|
22
|
+
match do |state|
|
23
|
+
expect(state._defaults[output]&.value).to eq default
|
24
|
+
expect(state._outputs).to include output
|
25
|
+
end
|
26
|
+
description { "define output" }
|
27
|
+
failure_message { "expected #{described_class} to define output #{output} #{for_default(default)}" }
|
28
|
+
|
29
|
+
def for_default(default)
|
30
|
+
return "without a default value" if default.nil?
|
31
|
+
|
32
|
+
"with default value #{default}"
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RSpec matcher that tests usage of `ApplicationOperation.handle_error`
|
4
|
+
#
|
5
|
+
# class ExampleFlow
|
6
|
+
# operations OperationOne, OperationTwo
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# RSpec.describe ExampleFlow, type: :flow do
|
10
|
+
# subject { described_class.new(**input) }
|
11
|
+
#
|
12
|
+
# let(:input) { {} }
|
13
|
+
#
|
14
|
+
# it { is_expected.to use_operations OperationOne, OperationTwo }
|
15
|
+
# end
|
16
|
+
|
17
|
+
RSpec::Matchers.define :handle_error do |error_class, problem: error_class.name.demodulize.underscore, with: nil|
|
18
|
+
match do |operation|
|
19
|
+
handlers = operation.rescue_handlers.select { |handler| handler[0] == error_class.to_s }
|
20
|
+
|
21
|
+
expect(handlers).to be_present
|
22
|
+
|
23
|
+
if with.present?
|
24
|
+
expect(handlers.find { |handler| (with == :a_block) ? handler[1].is_a?(Proc) : handler[1] == with }).to be_present
|
25
|
+
end
|
26
|
+
|
27
|
+
expect(operation).to define_failure problem
|
28
|
+
end
|
29
|
+
|
30
|
+
description { "handles error #{error_class}" }
|
31
|
+
failure_message { |flow| "expected #{flow.class.name} to handle error #{error_class}" }
|
32
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RSpec matcher for making assertions on the state of a Flow or Operation after it has been run.
|
4
|
+
#
|
5
|
+
# class ExampleOperation
|
6
|
+
# def behavior
|
7
|
+
# state.foo = "some data"
|
8
|
+
# state.bar = "some other data"
|
9
|
+
# end
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
# class ExampleFlow
|
13
|
+
# operations [ ExampleOperation ]
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# RSpec.describe ExampleOperation, type: :operation do
|
17
|
+
# subject { operation.execute }
|
18
|
+
#
|
19
|
+
# it { is_expected.to have_on_state foo: "some data" }
|
20
|
+
# it { is_expected.to have_on_state foo: instance_of(String) }
|
21
|
+
# it { is_expected.to have_on_state foo: "some data", bar: "some other data" }
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# RSpec.describe ExampleFlow, type: :operation do
|
25
|
+
# subject { flow.trigger }
|
26
|
+
#
|
27
|
+
# it { is_expected.to have_on_state foo: "some data" }
|
28
|
+
# it { is_expected.to have_on_state foo: instance_of(String) }
|
29
|
+
# it { is_expected.to have_on_state foo: "some data", bar: "some other data" }
|
30
|
+
# end
|
31
|
+
|
32
|
+
module CustomMatchers
|
33
|
+
def have_on_state(expectations)
|
34
|
+
HaveOnState.new(expectations)
|
35
|
+
end
|
36
|
+
|
37
|
+
class HaveOnState
|
38
|
+
include RSpec::Matchers
|
39
|
+
|
40
|
+
def initialize(state_expectations)
|
41
|
+
@state_expectations = state_expectations
|
42
|
+
end
|
43
|
+
|
44
|
+
def matches?(object)
|
45
|
+
@state_expectations.all? do |key, value|
|
46
|
+
expect(object.state.public_send(key)).to match value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def description
|
51
|
+
"have the expected data on state"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -1,23 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# RSpec matcher
|
3
|
+
# RSpec matcher that tests usage of `ApplicationFlow.operations`
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# class ExampleFlow
|
6
|
+
# operations OperationOne, OperationTwo
|
7
|
+
# end
|
6
8
|
#
|
7
|
-
#
|
8
|
-
#
|
9
|
+
# RSpec.describe ExampleFlow, type: :flow do
|
10
|
+
# subject { described_class.new(**input) }
|
9
11
|
#
|
10
|
-
#
|
12
|
+
# let(:input) { {} }
|
11
13
|
#
|
12
|
-
#
|
13
|
-
#
|
14
|
+
# it { is_expected.to use_operations OperationOne, OperationTwo }
|
15
|
+
# end
|
14
16
|
|
15
|
-
RSpec::Matchers.define :use_operations do
|
16
|
-
match { |flow| expect(flow._operations).to eq
|
17
|
+
RSpec::Matchers.define :use_operations do |*operations|
|
18
|
+
match { |flow| expect(flow._operations).to eq operations.flatten }
|
17
19
|
description { "uses operations #{display_operations(operations)}" }
|
18
|
-
failure_message { |flow| "expected #{flow.class.name}
|
20
|
+
failure_message { |flow| "expected #{flow.class.name} to use operations #{display_operations(operations)}" }
|
19
21
|
|
20
22
|
def display_operations(operations)
|
21
|
-
|
23
|
+
operations.flatten.join(", ")
|
22
24
|
end
|
23
25
|
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# RSpec matcher that tests usage of `.wrap_in_transaction` for `ApplicationFlow` or `ApplicationOperation`
|
4
|
+
#
|
5
|
+
# class ExampleFlow
|
6
|
+
# wrap_in_transaction
|
7
|
+
# operations OperationOne, OperationTwo
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# class ExampleState
|
11
|
+
# wrap_in_transaction
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# RSpec.describe ExampleFlow, type: :flow do
|
15
|
+
# subject { described_class.new(**input) }
|
16
|
+
#
|
17
|
+
# let(:input) { {} }
|
18
|
+
#
|
19
|
+
# it { is_expected.to wrap_in_transaction }
|
20
|
+
# end
|
21
|
+
#
|
22
|
+
# RSpec.describe Example, type: :operation do
|
23
|
+
# subject(:operation) { described_class.new(state) }
|
24
|
+
#
|
25
|
+
# let(:state) { example_state_class.new(**state_input) }
|
26
|
+
# let(:example_state_class) { Class.new(ApplicationState) }
|
27
|
+
# let(:state_input) do
|
28
|
+
# {}
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# it { is_expected.to wrap_in_transaction }
|
32
|
+
# end
|
33
|
+
|
34
|
+
# rubocop:disable Metrics/BlockLength
|
35
|
+
RSpec::Matchers.define :wrap_in_transaction do
|
36
|
+
match do |instance|
|
37
|
+
callback_name = instance.class.callback_name
|
38
|
+
original_transaction_count = instance.class.transaction_provider.connection.open_transactions
|
39
|
+
|
40
|
+
allow(instance).to receive(callback_name) do
|
41
|
+
expect(instance.class.transaction_provider.connection.open_transactions).to eq original_transaction_count + 1
|
42
|
+
end
|
43
|
+
|
44
|
+
instance.run_callbacks(callback_name) { instance.public_send(callback_name) }
|
45
|
+
expect(instance).to have_received(callback_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
description do
|
49
|
+
"wrap in a transaction"
|
50
|
+
end
|
51
|
+
|
52
|
+
failure_message do |instance|
|
53
|
+
"expected #{instance.class.name} to wrap in a transaction"
|
54
|
+
end
|
55
|
+
|
56
|
+
def pretty_callbacks(callbacks)
|
57
|
+
callbacks.join(", ")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
# rubocop:enable Metrics/BlockLength
|
data/lib/flow/flow/callbacks.rb
CHANGED
data/lib/flow/flow/core.rb
CHANGED
data/lib/flow/flow/flux.rb
CHANGED
data/lib/flow/flow/status.rb
CHANGED
data/lib/flow/flow/trigger.rb
CHANGED
data/lib/flow/flow_base.rb
CHANGED
@@ -4,26 +4,24 @@ require_relative "flow/errors/state_invalid"
|
|
4
4
|
|
5
5
|
require_relative "flow/callbacks"
|
6
6
|
require_relative "flow/core"
|
7
|
-
require_relative "flow/ebb"
|
8
7
|
require_relative "flow/flux"
|
9
8
|
require_relative "flow/operations"
|
10
|
-
require_relative "flow/revert"
|
11
9
|
require_relative "flow/status"
|
12
10
|
require_relative "flow/transactions"
|
13
11
|
require_relative "flow/trigger"
|
14
12
|
|
15
13
|
# A **Flow** is a collection of procedurally executed **Operations** sharing a common **State**.
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
14
|
+
module Flow
|
15
|
+
class FlowBase
|
16
|
+
include ShortCircuIt
|
17
|
+
include Technologic
|
18
|
+
include Flow::TransactionWrapper
|
19
|
+
include Flow::Callbacks
|
20
|
+
include Flow::Core
|
21
|
+
include Flow::Flux
|
22
|
+
include Flow::Operations
|
23
|
+
include Flow::Status
|
24
|
+
include Flow::Transactions
|
25
|
+
include Flow::Trigger
|
26
|
+
end
|
29
27
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Flow
|
4
|
+
module Operation
|
5
|
+
module Accessors
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
class_attribute :_state_readers, instance_writer: false, default: []
|
10
|
+
class_attribute :_state_writers, instance_writer: false, default: []
|
11
|
+
class_attribute :_state_accessors, instance_writer: false, default: []
|
12
|
+
end
|
13
|
+
|
14
|
+
class_methods do
|
15
|
+
protected
|
16
|
+
|
17
|
+
def state_reader(name)
|
18
|
+
return unless _add_state_reader_tracker(name.to_sym)
|
19
|
+
|
20
|
+
delegate name, to: :state
|
21
|
+
end
|
22
|
+
|
23
|
+
def state_writer(name)
|
24
|
+
return unless _add_state_writer_tracker(name.to_sym)
|
25
|
+
|
26
|
+
delegate("#{name}=", to: :state)
|
27
|
+
end
|
28
|
+
|
29
|
+
def state_accessor(name)
|
30
|
+
state_reader name
|
31
|
+
state_writer name
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def _add_state_reader_tracker(name)
|
37
|
+
return false if _state_readers.include?(name)
|
38
|
+
|
39
|
+
_add_state_accessor_tracker(name) if _state_writers.include?(name)
|
40
|
+
_state_readers << name
|
41
|
+
end
|
42
|
+
|
43
|
+
def _add_state_writer_tracker(name)
|
44
|
+
return false if _state_writers.include?(name)
|
45
|
+
|
46
|
+
_add_state_accessor_tracker(name) if _state_readers.include?(name)
|
47
|
+
_state_writers << name
|
48
|
+
end
|
49
|
+
|
50
|
+
def _add_state_accessor_tracker(name)
|
51
|
+
return if _state_accessors.include?(name)
|
52
|
+
|
53
|
+
_state_accessors << name
|
54
|
+
end
|
55
|
+
|
56
|
+
def inherited(base)
|
57
|
+
base._state_readers = _state_readers.dup
|
58
|
+
base._state_writers = _state_writers.dup
|
59
|
+
base._state_accessors = _state_accessors.dup
|
60
|
+
|
61
|
+
super
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|