u-case 1.0.0.rc1

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