yaso 1.0.0pre

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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +29 -0
  4. data/.ruby-version +1 -0
  5. data/.simplecov +8 -0
  6. data/Gemfile +5 -0
  7. data/Gemfile.lock +82 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +40 -0
  10. data/Rakefile +12 -0
  11. data/benchmark/Gemfile +12 -0
  12. data/benchmark/index.rb +27 -0
  13. data/benchmark/shared/active_interaction_service.rb +4 -0
  14. data/benchmark/shared/callable_step.rb +7 -0
  15. data/benchmark/shared/decouplio_service.rb +4 -0
  16. data/benchmark/shared/interactor_service.rb +9 -0
  17. data/benchmark/shared/pure_service.rb +13 -0
  18. data/benchmark/shared/trailblazer_service.rb +4 -0
  19. data/benchmark/shared/yaso_service.rb +4 -0
  20. data/benchmark/step/active_interaction.rb +89 -0
  21. data/benchmark/step/benchmark.rb +31 -0
  22. data/benchmark/step/decouplio.rb +77 -0
  23. data/benchmark/step/interactor.rb +75 -0
  24. data/benchmark/step/pure.rb +73 -0
  25. data/benchmark/step/trailblazer.rb +84 -0
  26. data/benchmark/step/yaso.rb +67 -0
  27. data/lefthook.yml +5 -0
  28. data/lib/yaso/context.rb +36 -0
  29. data/lib/yaso/errors.rb +23 -0
  30. data/lib/yaso/invokable.rb +29 -0
  31. data/lib/yaso/logic/base.rb +29 -0
  32. data/lib/yaso/logic/classic.rb +56 -0
  33. data/lib/yaso/logic/failure.rb +12 -0
  34. data/lib/yaso/logic/pass.rb +12 -0
  35. data/lib/yaso/logic/step.rb +17 -0
  36. data/lib/yaso/logic/step_builder.rb +67 -0
  37. data/lib/yaso/logic/switch.rb +19 -0
  38. data/lib/yaso/logic/wrap.rb +22 -0
  39. data/lib/yaso/logic.rb +14 -0
  40. data/lib/yaso/service.rb +19 -0
  41. data/lib/yaso/stepable.rb +24 -0
  42. data/lib/yaso/version.rb +5 -0
  43. data/lib/yaso.rb +11 -0
  44. 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
@@ -0,0 +1,5 @@
1
+ pre-commit:
2
+ parallel: true
3
+ commands:
4
+ rubocop:
5
+ run: bundle exec rubocop
@@ -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
@@ -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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yaso
4
+ module Logic
5
+ class Failure < Base
6
+ def call(context, instance)
7
+ context.success = false
8
+ @invokable.call(context, instance) ? @next_step : @failure
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yaso
4
+ module Logic
5
+ class Pass < Base
6
+ def call(context, instance)
7
+ context.success = true
8
+ @invokable.call(context, instance) ? @next_step : @failure
9
+ end
10
+ end
11
+ end
12
+ 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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Yaso
4
+ VERSION = '1.0.0pre'
5
+ end
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