yaso 1.0.0pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +29 -0
- data/.ruby-version +1 -0
- data/.simplecov +8 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +82 -0
- data/LICENSE.txt +21 -0
- data/README.md +40 -0
- data/Rakefile +12 -0
- data/benchmark/Gemfile +12 -0
- data/benchmark/index.rb +27 -0
- data/benchmark/shared/active_interaction_service.rb +4 -0
- data/benchmark/shared/callable_step.rb +7 -0
- data/benchmark/shared/decouplio_service.rb +4 -0
- data/benchmark/shared/interactor_service.rb +9 -0
- data/benchmark/shared/pure_service.rb +13 -0
- data/benchmark/shared/trailblazer_service.rb +4 -0
- data/benchmark/shared/yaso_service.rb +4 -0
- data/benchmark/step/active_interaction.rb +89 -0
- data/benchmark/step/benchmark.rb +31 -0
- data/benchmark/step/decouplio.rb +77 -0
- data/benchmark/step/interactor.rb +75 -0
- data/benchmark/step/pure.rb +73 -0
- data/benchmark/step/trailblazer.rb +84 -0
- data/benchmark/step/yaso.rb +67 -0
- data/lefthook.yml +5 -0
- data/lib/yaso/context.rb +36 -0
- data/lib/yaso/errors.rb +23 -0
- data/lib/yaso/invokable.rb +29 -0
- data/lib/yaso/logic/base.rb +29 -0
- data/lib/yaso/logic/classic.rb +56 -0
- data/lib/yaso/logic/failure.rb +12 -0
- data/lib/yaso/logic/pass.rb +12 -0
- data/lib/yaso/logic/step.rb +17 -0
- data/lib/yaso/logic/step_builder.rb +67 -0
- data/lib/yaso/logic/switch.rb +19 -0
- data/lib/yaso/logic/wrap.rb +22 -0
- data/lib/yaso/logic.rb +14 -0
- data/lib/yaso/service.rb +19 -0
- data/lib/yaso/stepable.rb +24 -0
- data/lib/yaso/version.rb +5 -0
- data/lib/yaso.rb +11 -0
- metadata +252 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class TrailblazerStepsService < TrailblazerService
|
4
|
+
step :one
|
5
|
+
step :two
|
6
|
+
step :three
|
7
|
+
step :four
|
8
|
+
step :five
|
9
|
+
step :six
|
10
|
+
step :seven
|
11
|
+
step :eight
|
12
|
+
step :nine
|
13
|
+
step :ten
|
14
|
+
|
15
|
+
def one(ctx, **)
|
16
|
+
ctx[:one] = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def two(ctx, **)
|
20
|
+
ctx[:two] = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def three(ctx, **)
|
24
|
+
ctx[:three] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def four(ctx, **)
|
28
|
+
ctx[:four] = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def five(ctx, **)
|
32
|
+
ctx[:five] = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def six(ctx, **)
|
36
|
+
ctx[:six] = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def seven(ctx, **)
|
40
|
+
ctx[:seven] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def eight(ctx, **)
|
44
|
+
ctx[:eight] = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def nine(ctx, **)
|
48
|
+
ctx[:nine] = true
|
49
|
+
end
|
50
|
+
|
51
|
+
def ten(ctx, **)
|
52
|
+
ctx[:ten] = true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
module TrailblazerMacro
|
57
|
+
# rubocop:disable Naming/MethodName
|
58
|
+
def self.CallableStep(key:, value:)
|
59
|
+
id = :"CallableStep.#{key}"
|
60
|
+
step = lambda do |ctx, **|
|
61
|
+
ctx[key] = value
|
62
|
+
return true if ctx[key]
|
63
|
+
|
64
|
+
ctx[:"result.#{id}"] = Trailblazer::Operation::Result.new(false, {})
|
65
|
+
false
|
66
|
+
end
|
67
|
+
task = Trailblazer::Activity::TaskBuilder::Binary(step)
|
68
|
+
{ task: task, id: id }
|
69
|
+
end
|
70
|
+
# rubocop:enable Naming/MethodName
|
71
|
+
end
|
72
|
+
|
73
|
+
class TrailblazerCallablesService < TrailblazerService
|
74
|
+
step TrailblazerMacro::CallableStep(key: :one, value: true)
|
75
|
+
step TrailblazerMacro::CallableStep(key: :two, value: true)
|
76
|
+
step TrailblazerMacro::CallableStep(key: :three, value: true)
|
77
|
+
step TrailblazerMacro::CallableStep(key: :four, value: true)
|
78
|
+
step TrailblazerMacro::CallableStep(key: :five, value: true)
|
79
|
+
step TrailblazerMacro::CallableStep(key: :six, value: true)
|
80
|
+
step TrailblazerMacro::CallableStep(key: :seven, value: true)
|
81
|
+
step TrailblazerMacro::CallableStep(key: :eight, value: true)
|
82
|
+
step TrailblazerMacro::CallableStep(key: :nine, value: true)
|
83
|
+
step TrailblazerMacro::CallableStep(key: :ten, value: true)
|
84
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class YasoStepsService < YasoService
|
4
|
+
step :one
|
5
|
+
step :two
|
6
|
+
step :three
|
7
|
+
step :four
|
8
|
+
step :five
|
9
|
+
step :six
|
10
|
+
step :seven
|
11
|
+
step :eight
|
12
|
+
step :nine
|
13
|
+
step :ten
|
14
|
+
|
15
|
+
def one(ctx, **)
|
16
|
+
ctx[:one] = true
|
17
|
+
end
|
18
|
+
|
19
|
+
def two(ctx, **)
|
20
|
+
ctx[:two] = true
|
21
|
+
end
|
22
|
+
|
23
|
+
def three(ctx, **)
|
24
|
+
ctx[:three] = true
|
25
|
+
end
|
26
|
+
|
27
|
+
def four(ctx, **)
|
28
|
+
ctx[:four] = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def five(ctx, **)
|
32
|
+
ctx[:five] = true
|
33
|
+
end
|
34
|
+
|
35
|
+
def six(ctx, **)
|
36
|
+
ctx[:six] = true
|
37
|
+
end
|
38
|
+
|
39
|
+
def seven(ctx, **)
|
40
|
+
ctx[:seven] = true
|
41
|
+
end
|
42
|
+
|
43
|
+
def eight(ctx, **)
|
44
|
+
ctx[:eight] = true
|
45
|
+
end
|
46
|
+
|
47
|
+
def nine(ctx, **)
|
48
|
+
ctx[:nine] = true
|
49
|
+
end
|
50
|
+
|
51
|
+
def ten(ctx, **)
|
52
|
+
ctx[:ten] = true
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class YasoCallablesService < YasoService
|
57
|
+
step CallableStep, key: :one, value: true
|
58
|
+
step CallableStep, key: :two, value: true
|
59
|
+
step CallableStep, key: :three, value: true
|
60
|
+
step CallableStep, key: :four, value: true
|
61
|
+
step CallableStep, key: :five, value: true
|
62
|
+
step CallableStep, key: :six, value: true
|
63
|
+
step CallableStep, key: :seven, value: true
|
64
|
+
step CallableStep, key: :eight, value: true
|
65
|
+
step CallableStep, key: :nine, value: true
|
66
|
+
step CallableStep, key: :ten, value: true
|
67
|
+
end
|
data/lefthook.yml
ADDED
data/lib/yaso/context.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
class Context
|
5
|
+
attr_writer :success
|
6
|
+
|
7
|
+
def initialize(kwargs)
|
8
|
+
@success = true
|
9
|
+
@data = kwargs
|
10
|
+
end
|
11
|
+
|
12
|
+
def [](key)
|
13
|
+
@data[key]
|
14
|
+
end
|
15
|
+
|
16
|
+
def []=(key, value)
|
17
|
+
@data[key] = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_h
|
21
|
+
@data.dup
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_h!
|
25
|
+
@data
|
26
|
+
end
|
27
|
+
|
28
|
+
def success?
|
29
|
+
@success
|
30
|
+
end
|
31
|
+
|
32
|
+
def failure?
|
33
|
+
!@success
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/yaso/errors.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
class Error < StandardError; end
|
5
|
+
|
6
|
+
class StepIsNotImplementedError < Error
|
7
|
+
def initialize(klass, step)
|
8
|
+
super("#{klass}##{step} step is not implemented")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class InvalidFirstStepError < Error
|
13
|
+
def initialize(category)
|
14
|
+
super("#{category.to_s.capitalize} cannot be the first step")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class UnhandledSwitchCaseError < Error
|
19
|
+
def initialize(klass)
|
20
|
+
super("Unhandled switch case in #{klass}")
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
class Invokable
|
5
|
+
METHOD = :method
|
6
|
+
CALLABLE = :callable
|
7
|
+
YASO = :yaso
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def call(object, options: {}, **)
|
11
|
+
type = object_type(object)
|
12
|
+
invokable = case type
|
13
|
+
when YASO then ->(context, _) { object.call(context.clone).success? }
|
14
|
+
when CALLABLE then ->(context, _, &block) { object.call(context, **options, &block) }
|
15
|
+
else ->(context, instance, &block) { instance.send(object, context, **context.to_h!, &block) }
|
16
|
+
end
|
17
|
+
[type, invokable]
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def object_type(object)
|
23
|
+
return Invokable::METHOD unless object.is_a?(Class)
|
24
|
+
|
25
|
+
object < ::Yaso::Service ? Invokable::YASO : Invokable::CALLABLE
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
module Logic
|
5
|
+
class Base
|
6
|
+
attr_reader :name, :on_success, :on_failure
|
7
|
+
|
8
|
+
def initialize(name:, invokable:, **options)
|
9
|
+
@name = name
|
10
|
+
@invokable = invokable
|
11
|
+
@fast = options[:fast]
|
12
|
+
@on_success = options[:on_success]
|
13
|
+
@on_failure = options[:on_failure]
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(_, _)
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_next_step(step)
|
21
|
+
@next_step = step unless @fast == true || @fast == :success
|
22
|
+
end
|
23
|
+
|
24
|
+
def add_failure(failure)
|
25
|
+
@failure = failure unless @fast == true || @fast == :failure
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
module Logic
|
5
|
+
class Classic
|
6
|
+
def self.call(klass, steps)
|
7
|
+
new(klass, steps).call
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(klass, steps)
|
11
|
+
@klass = klass
|
12
|
+
@steps = steps
|
13
|
+
@step_builder = StepBuilder.new(klass)
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
logicals.each_with_index { |step, i| step.is_a?(Failure) ? link_failure(step, i) : link_step(step, i) }
|
18
|
+
logicals.first
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def logicals
|
24
|
+
@logicals ||= @steps.map { |step| @step_builder.call(**step) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def link_step(step, index)
|
28
|
+
next_step = logicals[index.next..-1].detect { |next_node| !next_node.is_a?(Failure) }
|
29
|
+
step.add_next_step(step.on_success ? find_step(step.on_success) : next_step)
|
30
|
+
step.add_failure(next_step) if step.is_a?(Pass)
|
31
|
+
step.add_failure(find_step(step.on_failure)) if step.on_failure
|
32
|
+
end
|
33
|
+
|
34
|
+
def link_failure(failure, index)
|
35
|
+
link_previous_steps(failure, index)
|
36
|
+
failure.add_next_step(find_step(failure.on_success)) if failure.on_success
|
37
|
+
failure.add_failure(find_step(failure.on_failure)) if failure.on_failure
|
38
|
+
end
|
39
|
+
|
40
|
+
def link_previous_steps(failure, index)
|
41
|
+
logicals[0...index].reverse_each do |previous_step|
|
42
|
+
previous_step.add_failure(failure) unless previous_step.on_failure || previous_step.is_a?(Pass)
|
43
|
+
next unless previous_step.is_a?(Failure)
|
44
|
+
|
45
|
+
previous_step.add_next_step(failure) unless previous_step.on_success
|
46
|
+
break
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_step(name)
|
51
|
+
@steps_by_name ||= logicals.group_by(&:name).transform_values(&:first)
|
52
|
+
@steps_by_name[name] || raise(StepIsNotImplementedError.new(@klass, name))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
module Logic
|
5
|
+
class Step < Base
|
6
|
+
def call(context, instance)
|
7
|
+
context.success = true
|
8
|
+
if @invokable.call(context, instance)
|
9
|
+
@next_step
|
10
|
+
else
|
11
|
+
context.success = false
|
12
|
+
@failure
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
module Logic
|
5
|
+
class StepBuilder
|
6
|
+
CATEGORIES = {
|
7
|
+
step: Step,
|
8
|
+
pass: Pass,
|
9
|
+
failure: Failure,
|
10
|
+
wrap: Wrap,
|
11
|
+
switch: Switch
|
12
|
+
}.freeze
|
13
|
+
|
14
|
+
def initialize(klass)
|
15
|
+
@klass = klass
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(object:, category:, block:, **opts)
|
19
|
+
invokable_type, invokable = Invokable.call(object, **opts)
|
20
|
+
logic_class = CATEGORIES[category]
|
21
|
+
if invokable_type == Invokable::METHOD
|
22
|
+
opts[:name] = logic_class == Switch ? build_switch(object, **opts, &block) : build_method(object, &block)
|
23
|
+
end
|
24
|
+
opts[:wrapper] = build_wrapper(&block) if logic_class == Wrap
|
25
|
+
logic_class.new(invokable: invokable, **opts)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def build_switch(object, options:, **, &block)
|
31
|
+
if block.nil?
|
32
|
+
key = options[:key]
|
33
|
+
cases = options[:cases]
|
34
|
+
block = ->(ctx, **) { cases[ctx[key]] }
|
35
|
+
end
|
36
|
+
build_method(object, &block)
|
37
|
+
end
|
38
|
+
|
39
|
+
def build_method(name, &block)
|
40
|
+
return name if @klass.method_defined?(name) || @klass.private_method_defined?(name)
|
41
|
+
raise StepIsNotImplementedError.new(@klass, name) unless block
|
42
|
+
|
43
|
+
@klass.define_method(name, &block)
|
44
|
+
@klass.instance_eval { private name }
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_wrapper(&block)
|
48
|
+
wrapper_class = Class.new { extend Stepable }
|
49
|
+
wrapper_class.instance_exec(&block)
|
50
|
+
build_wrapper_call(wrapper_class, @klass)
|
51
|
+
wrapper_class
|
52
|
+
end
|
53
|
+
|
54
|
+
def build_wrapper_call(wrapper_class, service_class)
|
55
|
+
wrapper_class.define_singleton_method(:call) do |context, instance|
|
56
|
+
unless @entry
|
57
|
+
@entry = Logic::Classic.call(service_class, steps)
|
58
|
+
clear_steps!
|
59
|
+
end
|
60
|
+
step = @entry
|
61
|
+
step = step.call(context, instance) while step
|
62
|
+
context
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
module Logic
|
5
|
+
class Switch < Base
|
6
|
+
def call(context, instance)
|
7
|
+
context.success = true
|
8
|
+
switch_case = @invokable.call(context, instance) || raise(UnhandledSwitchCaseError, instance.class)
|
9
|
+
|
10
|
+
if Invokable.call(switch_case).last.call(context, instance)
|
11
|
+
@next_step
|
12
|
+
else
|
13
|
+
context.success = false
|
14
|
+
@failure
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
module Logic
|
5
|
+
class Wrap < Base
|
6
|
+
def initialize(wrapper:, **options)
|
7
|
+
super(**options)
|
8
|
+
@wrapper = wrapper
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(context, instance)
|
12
|
+
context.success = true
|
13
|
+
if @invokable.call(context, instance) { @wrapper.call(context, instance).success? }
|
14
|
+
@next_step
|
15
|
+
else
|
16
|
+
context.success = false
|
17
|
+
@failure
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/yaso/logic.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'logic/base'
|
4
|
+
require_relative 'logic/step'
|
5
|
+
require_relative 'logic/pass'
|
6
|
+
require_relative 'logic/failure'
|
7
|
+
require_relative 'logic/wrap'
|
8
|
+
require_relative 'logic/switch'
|
9
|
+
require_relative 'logic/step_builder'
|
10
|
+
require_relative 'logic/classic'
|
11
|
+
|
12
|
+
module Yaso
|
13
|
+
module Logic; end
|
14
|
+
end
|
data/lib/yaso/service.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
class Service
|
5
|
+
extend Stepable
|
6
|
+
|
7
|
+
def self.call(context = {})
|
8
|
+
context = context.is_a?(Context) ? context : Context.new(context)
|
9
|
+
unless @entry
|
10
|
+
@entry = Logic::Classic.call(self, steps)
|
11
|
+
clear_steps!
|
12
|
+
end
|
13
|
+
step = @entry
|
14
|
+
instance = new
|
15
|
+
step = step.call(context, instance) while step
|
16
|
+
context
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Yaso
|
4
|
+
module Stepable
|
5
|
+
def steps
|
6
|
+
@steps ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def clear_steps!
|
10
|
+
remove_instance_variable(:@steps)
|
11
|
+
end
|
12
|
+
|
13
|
+
%i[step pass failure wrap switch].each do |category|
|
14
|
+
define_method(category) do |object, options = {}, &block|
|
15
|
+
raise InvalidFirstStepError, category if category == :failure && steps.empty?
|
16
|
+
|
17
|
+
steps << {
|
18
|
+
object: object, category: category, fast: options.delete(:fast), on_success: options.delete(:on_success),
|
19
|
+
on_failure: options.delete(:on_failure), options: options, block: block, name: options.delete(:name)
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/yaso/version.rb
ADDED
data/lib/yaso.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'yaso/version'
|
4
|
+
require_relative 'yaso/errors'
|
5
|
+
require_relative 'yaso/context'
|
6
|
+
require_relative 'yaso/invokable'
|
7
|
+
require_relative 'yaso/stepable'
|
8
|
+
require_relative 'yaso/logic'
|
9
|
+
require_relative 'yaso/service'
|
10
|
+
|
11
|
+
module Yaso; end
|