yaso 1.0.0pre

Sign up to get free protection for your applications and to get access to all the features.
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