u-case 1.0.0.rc1

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.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "micro/case"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'micro/attributes'
4
+
5
+ require 'micro/case/version'
6
+ require 'micro/case/error'
7
+ require 'micro/case/result'
8
+ require 'micro/case/base'
9
+ require 'micro/case/safe'
10
+ require 'micro/case/strict'
11
+ require 'micro/case/flow/reducer'
12
+ require 'micro/case/flow'
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Case
5
+ class Base
6
+ include Micro::Attributes.without(:strict_initialize)
7
+
8
+ def self.to_proc
9
+ Proc.new { |arg| call(arg) }
10
+ end
11
+
12
+ def self.>>(use_case)
13
+ Flow[self, use_case]
14
+ end
15
+
16
+ def self.&(use_case)
17
+ Flow::Safe[self, use_case]
18
+ end
19
+
20
+ def self.call(options = {})
21
+ new(options).call
22
+ end
23
+
24
+ def self.__new__(result, arg)
25
+ instance = new(arg)
26
+ instance.__set_result__(result)
27
+ instance
28
+ end
29
+
30
+ def self.__failure_type(arg, type)
31
+ return type if type != :error
32
+
33
+ case arg
34
+ when Exception then :exception
35
+ when Symbol then arg
36
+ else type
37
+ end
38
+ end
39
+
40
+ def call!
41
+ raise NotImplementedError
42
+ end
43
+
44
+ def call
45
+ __call
46
+ end
47
+
48
+ def __set_result__(result)
49
+ raise Error::InvalidResultInstance unless result.is_a?(Result)
50
+ raise Error::ResultIsAlreadyDefined if @__result
51
+ @__result = result
52
+ end
53
+
54
+ private
55
+
56
+ def __call
57
+ result = call!
58
+ return result if result.is_a?(Result)
59
+ raise Error::UnexpectedResult.new(self.class)
60
+ end
61
+
62
+ def __get_result__
63
+ @__result ||= Result.new
64
+ end
65
+
66
+ def Success(arg = :ok)
67
+ value, type = block_given? ? [yield, arg] : [arg, :ok]
68
+ __get_result__.__set__(true, value, type, nil)
69
+ end
70
+
71
+ def Failure(arg = :error)
72
+ value = block_given? ? yield : arg
73
+ type = self.class.__failure_type(value, block_given? ? arg : :error)
74
+ __get_result__.__set__(false, value, type, self)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Case
5
+ module Error
6
+ class UnexpectedResult < TypeError
7
+ MESSAGE = '#call! must return an instance of Micro::Case::Result'.freeze
8
+
9
+ def initialize(klass); super(klass.name + MESSAGE); end
10
+ end
11
+
12
+ ResultIsAlreadyDefined = ArgumentError.new('result is already defined'.freeze)
13
+
14
+ InvalidResultType = TypeError.new('type must be a Symbol'.freeze)
15
+ InvalidResultInstance = ArgumentError.new('argument must be an instance of Micro::Case::Result'.freeze)
16
+
17
+ InvalidUseCase = TypeError.new('use case must be a kind or an instance of Micro::Case::Base'.freeze)
18
+ InvalidUseCases = ArgumentError.new('argument must be a collection of `Micro::Case::Base` classes'.freeze)
19
+
20
+ UndefinedFlow = ArgumentError.new("This class hasn't declared its flow. Please, use the `flow()` macro to define one.".freeze)
21
+
22
+ class InvalidAccessToTheUseCaseObject < StandardError
23
+ MSG = 'only a failure result can access its use case object'.freeze
24
+
25
+ def initialize(message = MSG); super; end
26
+ end
27
+
28
+ module ByWrongUsage
29
+ def self.check(exception)
30
+ exception.is_a?(Error::UnexpectedResult) || exception.is_a?(ArgumentError)
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Case
5
+ module Flow
6
+ module ClassMethods
7
+ def __flow__
8
+ @__flow
9
+ end
10
+
11
+ def flow(*args)
12
+ @__flow= flow_reducer.build(args)
13
+ end
14
+
15
+ def call(options = {})
16
+ new(options).call
17
+ end
18
+ end
19
+
20
+ CONSTRUCTOR = <<-RUBY
21
+ def initialize(options)
22
+ @options = options
23
+ flow= self.class.__flow__
24
+ raise Error::UndefinedFlow unless flow
25
+ end
26
+ RUBY
27
+
28
+ private_constant :ClassMethods, :CONSTRUCTOR
29
+
30
+ def self.included(base)
31
+ def base.flow_reducer; Reducer; end
32
+ base.extend(ClassMethods)
33
+ base.class_eval(CONSTRUCTOR)
34
+ end
35
+
36
+ def self.[](*args)
37
+ Reducer.build(args)
38
+ end
39
+
40
+ def call
41
+ self.class.__flow__.call(@options)
42
+ end
43
+
44
+ module Safe
45
+ def self.included(base)
46
+ base.send(:include, Micro::Case::Flow)
47
+ def base.flow_reducer; SafeReducer; end
48
+ end
49
+
50
+ def self.[](*args)
51
+ SafeReducer.build(args)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Case
5
+ module Flow
6
+ class Reducer
7
+ attr_reader :use_cases
8
+
9
+ def self.map_use_cases(arg)
10
+ return arg.use_cases if arg.is_a?(Reducer)
11
+ return arg.__flow__.use_cases if arg.is_a?(Class) && arg < Micro::Case::Flow
12
+ Array(arg)
13
+ end
14
+
15
+ def self.build(args)
16
+ use_cases = Array(args).flat_map { |arg| map_use_cases(arg) }
17
+
18
+ raise Error::InvalidUseCases if use_cases.any? { |klass| !(klass < ::Micro::Case::Base) }
19
+
20
+ new(use_cases)
21
+ end
22
+
23
+ def initialize(use_cases)
24
+ @use_cases = use_cases
25
+ end
26
+
27
+ def call(arg = {})
28
+ @use_cases.reduce(initial_result(arg)) do |result, use_case|
29
+ break result if result.failure?
30
+ use_case.__new__(result, result.value).call
31
+ end
32
+ end
33
+
34
+ def >>(arg)
35
+ self.class.build(use_cases + self.class.map_use_cases(arg))
36
+ end
37
+
38
+ def &(arg)
39
+ raise NoMethodError, "undefined method `&' for #{self.inspect}. Please, use the method `>>' to avoid this error."
40
+ end
41
+
42
+ def to_proc
43
+ Proc.new { |arg| call(arg) }
44
+ end
45
+
46
+ private
47
+
48
+ def initial_result(arg)
49
+ return arg.call if arg_to_call?(arg)
50
+ return arg if arg.is_a?(Micro::Case::Result)
51
+ result = Micro::Case::Result.new
52
+ result.__set__(true, arg, :ok, nil)
53
+ end
54
+
55
+ def arg_to_call?(arg)
56
+ return true if arg.is_a?(Micro::Case::Base) || arg.is_a?(Reducer)
57
+ return true if arg.is_a?(Class) && (arg < Micro::Case::Base || arg < Micro::Case::Flow)
58
+ return false
59
+ end
60
+ end
61
+
62
+ class SafeReducer < Reducer
63
+ def call(arg = {})
64
+ @use_cases.reduce(initial_result(arg)) do |result, use_case|
65
+ break result if result.failure?
66
+ use_case_result(use_case, result)
67
+ end
68
+ end
69
+
70
+ alias_method :&, :>>
71
+
72
+ def >>(arg)
73
+ raise NoMethodError, "undefined method `>>' for #{self.inspect}. Please, use the method `&' to avoid this error."
74
+ end
75
+
76
+ private
77
+
78
+ def use_case_result(use_case, result)
79
+ begin
80
+ instance = use_case.__new__(result, result.value)
81
+ instance.call
82
+ rescue => exception
83
+ raise exception if Error::ByWrongUsage.check(exception)
84
+ result.__set__(false, exception, :exception, instance)
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Case
5
+ class Result
6
+ attr_reader :value, :type
7
+
8
+ def __set__(is_success, value, type, use_case)
9
+ raise Error::InvalidResultType unless type.is_a?(Symbol)
10
+ raise Error::InvalidUseCase if !is_success && !is_an_use_case?(use_case)
11
+
12
+ @success, @value, @type, @use_case = is_success, value, type, use_case
13
+
14
+ self
15
+ end
16
+
17
+ def success?
18
+ @success
19
+ end
20
+
21
+ def failure?
22
+ !success?
23
+ end
24
+
25
+ def use_case
26
+ return @use_case if failure?
27
+
28
+ raise Error::InvalidAccessToTheUseCaseObject
29
+ end
30
+
31
+ def on_success(arg = nil)
32
+ self.tap { yield(value) if success_type?(arg) }
33
+ end
34
+
35
+ def on_failure(arg = nil)
36
+ self.tap{ yield(value, @use_case) if failure_type?(arg) }
37
+ end
38
+
39
+ private
40
+
41
+ def success_type?(arg)
42
+ success? && (arg.nil? || arg == type)
43
+ end
44
+
45
+ def failure_type?(arg)
46
+ failure? && (arg.nil? || arg == type)
47
+ end
48
+
49
+ def is_an_use_case?(arg)
50
+ (arg.is_a?(Class) && arg < Case::Base) || arg.is_a?(Case::Base)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Case
5
+ class Safe < Case::Base
6
+ def call
7
+ super
8
+ rescue => exception
9
+ raise exception if Error::ByWrongUsage.check(exception)
10
+ Failure(exception)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Case
5
+ class Strict < Case::Base
6
+ include Micro::Attributes::Features::StrictInitialize
7
+
8
+ class Safe < Case::Safe
9
+ include Micro::Attributes::Features::StrictInitialize
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ module Case
5
+ VERSION = '1.0.0.rc1'.freeze
6
+ end
7
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'micro/case'
4
+
5
+ module Micro
6
+ module Case
7
+ class Base
8
+ include Micro::Attributes::Features::ActiveModelValidations
9
+
10
+ def call
11
+ return Failure(:validation_error) { self.errors } unless valid?
12
+
13
+ __call
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'micro/case'
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'micro/case/with_validation'
data/test.sh ADDED
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+
3
+ bundle
4
+
5
+ rm Gemfile.lock
6
+
7
+ source $(dirname $0)/.travis.sh
8
+
9
+ rm Gemfile.lock
10
+
11
+ bundle