u-case 2.1.0 → 2.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2cc6d19966687eef5e783d907074f36cb5b27492cea1e5e9bfadeaa68a799e69
4
- data.tar.gz: cb63d747bdeaf71604fc52f7adecd80a8f2071c2087222ad9c1423fd29e381bb
3
+ metadata.gz: d5b58d2e19bca34b4337e348d4d498b21ee12134082f897122c957ab79e19fa7
4
+ data.tar.gz: 6cb9e76227b0dc96d92de0c7ae1abdfe4bce2f840f0dced7ce1cae24908c0efb
5
5
  SHA512:
6
- metadata.gz: aab4e530a1a74c9a3900cf4347a1d8f01b9dea9551565943c46b5e6367aedf3c25ba0e504fdcffd691a9276a1a902242d46489f4df4dfed02b761b46da06db1c
7
- data.tar.gz: b409f247130404652073cd18edaf4e69746ed4a50091ee48f936e964e7993bf45e93738b8cde19aa5fee6eddcae5fb7722f5ac23639bede336dec1303491b644
6
+ metadata.gz: ea7dd10cd004dc3ef7e3fbfe062afc6e7a09e172b9fca1c5d2f2300c6d876b19eeb4935d06c8650010b14ca318486226fe08ec5c9fa57a9570870caf9b470bb2
7
+ data.tar.gz: 5742702a5c64d2ed8a32cb83aaa6ee8fb5cb550b7144eba248cf058e340f47751528e38214991f1c92c50370b4a6240f7e29a963d872ffe78219d2d91fa5637e
@@ -1 +1 @@
1
- ruby 2.6.3
1
+ ruby 2.6.5
data/.travis.sh CHANGED
@@ -11,3 +11,8 @@ if [[ ! $ruby_v =~ '2.2.0' ]]; then
11
11
  ACTIVEMODEL_VERSION='5.2' bundle update
12
12
  ACTIVEMODEL_VERSION='5.2' bundle exec rake test
13
13
  fi
14
+
15
+ if [[ $ruby_v =~ '2.5.' ]] || [[ $ruby_v =~ '2.6.' ]] || [[ $ruby_v =~ '2.7.' ]]; then
16
+ ACTIVEMODEL_VERSION='6.0' bundle update
17
+ ACTIVEMODEL_VERSION='6.0' bundle exec rake test
18
+ fi
@@ -9,6 +9,7 @@ rvm:
9
9
  - 2.4.0
10
10
  - 2.5.0
11
11
  - 2.6.0
12
+ - 2.7.0
12
13
 
13
14
  cache: bundler
14
15
 
data/Gemfile CHANGED
@@ -7,6 +7,7 @@ activemodel_version = ENV.fetch('ACTIVEMODEL_VERSION', '6.1.0')
7
7
  activemodel = case activemodel_version
8
8
  when '3.2' then '3.2.22'
9
9
  when '5.2' then '5.2.3'
10
+ when '6.0' then '6.0.2'
10
11
  end
11
12
 
12
13
  if activemodel_version < '6.1.0'
@@ -17,7 +18,19 @@ end
17
18
  group :test do
18
19
  gem 'minitest', activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
19
20
  gem 'simplecov', require: false
20
- gem 'minitest-reporters', require: false
21
+ end
22
+
23
+ pry_byebug_version =
24
+ case RUBY_VERSION
25
+ when /\A2.2/ then '3.6'
26
+ when /\A2.3/ then '3.7'
27
+ else '3.9'
28
+ end
29
+
30
+ group :development, :test do
31
+ gem 'awesome_print', '~> 1.8'
32
+
33
+ gem 'pry-byebug', "~> #{pry_byebug_version}"
21
34
  end
22
35
 
23
36
  # Specify your gem's dependencies in u-case.gemspec
data/README.md CHANGED
@@ -1,54 +1,64 @@
1
+ ![Ruby](https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066)
1
2
  [![Gem](https://img.shields.io/gem/v/u-case.svg?style=flat-square)](https://rubygems.org/gems/u-case)
2
3
  [![Build Status](https://travis-ci.com/serradura/u-case.svg?branch=master)](https://travis-ci.com/serradura/u-case)
3
4
  [![Maintainability](https://api.codeclimate.com/v1/badges/5c3c8ad1b0b943f88efd/maintainability)](https://codeclimate.com/github/serradura/u-case/maintainability)
4
5
  [![Test Coverage](https://api.codeclimate.com/v1/badges/5c3c8ad1b0b943f88efd/test_coverage)](https://codeclimate.com/github/serradura/u-case/test_coverage)
5
6
 
6
- μ-case (Micro::Case)
7
- ==========================
7
+ μ-case (Micro::Case) <!-- omit in toc -->
8
+ ====================
8
9
 
9
10
  Create simple and powerful use cases as objects.
10
11
 
11
12
  The main project goals are:
12
- 1. Be simple to use and easy to learn (input **>>** process / transform **>>** output).
13
+ 1. Easy to use and easy to learn (input **>>** process **>>** output).
13
14
  2. Promote referential transparency (transforming instead of modifying) and data integrity.
14
15
  3. No callbacks (e.g: before, after, around).
15
16
  4. Solve complex business logic, by allowing the composition of use cases.
16
17
  5. Be fast and optimized (Check out the [benchmarks](#benchmarks) section).
17
18
 
19
+ > Note: Check out the repo https://github.com/serradura/from-fat-controllers-to-use-cases to see a Rails application that uses this gem to handle its business logic.
20
+
18
21
  ## Table of Contents <!-- omit in toc -->
19
- - [μ-case (Micro::Case)](#μ-case-microcase)
20
- - [Required Ruby version](#required-ruby-version)
21
- - [Dependencies](#dependencies)
22
- - [Installation](#installation)
23
- - [Usage](#usage)
24
- - [Micro::Case - How to define a use case?](#microcase---how-to-define-a-use-case)
25
- - [Micro::Case::Result - What is a use case result?](#microcaseresult---what-is-a-use-case-result)
26
- - [What are the default result types?](#what-are-the-default-result-types)
27
- - [How to define custom result types?](#how-to-define-custom-result-types)
28
- - [Is it possible to define a custom result type without a block?](#is-it-possible-to-define-a-custom-result-type-without-a-block)
29
- - [How to use the result hooks?](#how-to-use-the-result-hooks)
30
- - [Why the failure hook (without a type) exposes a different kind of data?](#why-the-failure-hook-without-a-type-exposes-a-different-kind-of-data)
31
- - [What happens if a result hook was declared multiple times?](#what-happens-if-a-result-hook-was-declared-multiple-times)
32
- - [Micro::Case::Flow - How to compose use cases?](#microcaseflow---how-to-compose-use-cases)
33
- - [Is it possible to compose a use case flow with other ones?](#is-it-possible-to-compose-a-use-case-flow-with-other-ones)
34
- - [Is it possible a flow accumulates its input and merges each success result to use as the argument of their use cases?](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-their-use-cases)
35
- - [Is it possible to declare a flow using the use case itself?](#is-it-possible-to-declare-a-flow-using-the-use-case-itself)
36
- - [Micro::Case::Strict - What is a strict use case?](#microcasestrict---what-is-a-strict-use-case)
37
- - [Micro::Case::Safe - Is there some feature to auto handle exceptions inside of a use case or flow?](#microcasesafe---is-there-some-feature-to-auto-handle-exceptions-inside-of-a-use-case-or-flow)
38
- - [u-case/with_validation - How to validate use case attributes?](#u-casewith_validation---how-to-validate-use-case-attributes)
39
- - [If I enabled the auto validation, is it possible to disable it only in specific use case classes?](#if-i-enabled-the-auto-validation-is-it-possible-to-disable-it-only-in-specific-use-case-classes)
40
- - [Benchmarks](#benchmarks)
41
- - [Micro::Case](#microcase)
42
- - [Best overall](#best-overall)
43
- - [Success results](#success-results)
44
- - [Failure results](#failure-results)
45
- - [Micro::Case::Flow](#microcaseflow)
46
- - [Comparisons](#comparisons)
47
- - [Examples](#examples)
48
- - [Development](#development)
49
- - [Contributing](#contributing)
50
- - [License](#license)
51
- - [Code of Conduct](#code-of-conduct)
22
+ - [Required Ruby version](#required-ruby-version)
23
+ - [Dependencies](#dependencies)
24
+ - [Installation](#installation)
25
+ - [Usage](#usage)
26
+ - [`Micro::Case` - How to define a use case?](#microcase---how-to-define-a-use-case)
27
+ - [`Micro::Case::Result` - What is a use case result?](#microcaseresult---what-is-a-use-case-result)
28
+ - [What are the default result types?](#what-are-the-default-result-types)
29
+ - [How to define custom result types?](#how-to-define-custom-result-types)
30
+ - [Is it possible to define a custom result type without a block?](#is-it-possible-to-define-a-custom-result-type-without-a-block)
31
+ - [How to use the result hooks?](#how-to-use-the-result-hooks)
32
+ - [Why the failure hook (without a type) exposes a different kind of data?](#why-the-failure-hook-without-a-type-exposes-a-different-kind-of-data)
33
+ - [What happens if a result hook was declared multiple times?](#what-happens-if-a-result-hook-was-declared-multiple-times)
34
+ - [How to use the `Micro::Case::Result#then` method?](#how-to-use-the-microcaseresultthen-method)
35
+ - [`Micro::Case::Flow` - How to compose use cases?](#microcaseflow---how-to-compose-use-cases)
36
+ - [Is it possible to compose a use case flow with other ones?](#is-it-possible-to-compose-a-use-case-flow-with-other-ones)
37
+ - [Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-the-next-use-cases)
38
+ - [How to understand what is happening during a flow execution?](#how-to-understand-what-is-happening-during-a-flow-execution)
39
+ - [Micro::Case::Result#transitions schema.](#microcaseresulttransitions-schema)
40
+ - [Is it possible to declare a flow which includes the use case itself?](#is-it-possible-to-declare-a-flow-which-includes-the-use-case-itself)
41
+ - [`Micro::Case::Strict` - What is a strict use case?](#microcasestrict---what-is-a-strict-use-case)
42
+ - [`Micro::Case::Safe` - Is there some feature to auto handle exceptions inside of a use case or flow?](#microcasesafe---is-there-some-feature-to-auto-handle-exceptions-inside-of-a-use-case-or-flow)
43
+ - [`u-case/with_activemodel_validation` - How to validate use case attributes?](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes)
44
+ - [If I enabled the auto validation, is it possible to disable it only in specific use case classes?](#if-i-enabled-the-auto-validation-is-it-possible-to-disable-it-only-in-specific-use-case-classes)
45
+ - [Kind::Validator](#kindvalidator)
46
+ - [Benchmarks](#benchmarks)
47
+ - [`Micro::Case`](#microcase)
48
+ - [Best overall](#best-overall)
49
+ - [Success results](#success-results)
50
+ - [Failure results](#failure-results)
51
+ - [`Micro::Case::Flow`](#microcaseflow)
52
+ - [Comparisons](#comparisons)
53
+ - [Examples](#examples)
54
+ - [1️⃣ Rails App (API)](#1️⃣-rails-app-api)
55
+ - [2️⃣ CLI calculator](#2️⃣-cli-calculator)
56
+ - [3️⃣ Users creation](#3️⃣-users-creation)
57
+ - [4️⃣ Rescuing exception inside of the use cases](#4️⃣-rescuing-exception-inside-of-the-use-cases)
58
+ - [Development](#development)
59
+ - [Contributing](#contributing)
60
+ - [License](#license)
61
+ - [Code of Conduct](#code-of-conduct)
52
62
 
53
63
  ## Required Ruby version
54
64
 
@@ -56,8 +66,15 @@ The main project goals are:
56
66
 
57
67
  ## Dependencies
58
68
 
59
- This project depends on [Micro::Attribute](https://github.com/serradura/u-attributes) gem.
60
- It is used to define the use case attributes.
69
+ 1. [`kind`](https://github.com/serradura/kind) gem.
70
+
71
+ A simple type system (at runtime) for Ruby.
72
+
73
+ Used to validate method inputs, expose `Kind.of.Micro::Case::Result` type checker and its [`activemodel validation`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations) module is auto required by [`u-case/with_activemodel_validation`](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes) mode.
74
+ 2. [`u-attributes`](https://github.com/serradura/u-attributes) gem.
75
+
76
+ This gem allows defining read-only attributes, that is, your objects will have only getters to access their attributes data.
77
+ It is used to define the use case attributes.
61
78
 
62
79
  ## Installation
63
80
 
@@ -139,6 +156,7 @@ A `Micro::Case::Result` stores the use cases output data. These are their main m
139
156
  - `#type` a Symbol which gives meaning for the result, this is useful to declare different types of failures or success.
140
157
  - `#on_success` or `#on_failure` are hook methods that help you define the application flow.
141
158
  - `#use_case` if is a failure result, the use case responsible for it will be accessible through this method. This feature is handy to handle a flow failure (this topic will be covered ahead).
159
+ - `#then` allows if the current result is a success, the `then` method will allow to applying a new use case for its value.
142
160
 
143
161
  [⬆️ Back to Top](#table-of-contents-)
144
162
 
@@ -409,6 +427,48 @@ accum # 24
409
427
  result.value * 4 == accum # true
410
428
  ```
411
429
 
430
+ #### How to use the `Micro::Case::Result#then` method?
431
+
432
+ ```ruby
433
+ class ForbidNegativeNumber < Micro::Case
434
+ attribute :number
435
+
436
+ def call!
437
+ return Success { attributes } if number >= 0
438
+
439
+ Failure { attributes }
440
+ end
441
+ end
442
+
443
+ class Add3 < Micro::Case
444
+ attribute :number
445
+
446
+ def call!
447
+ Success { { number: number + 3 } }
448
+ end
449
+ end
450
+
451
+ result1 =
452
+ ForbidNegativeNumber
453
+ .call(number: -1)
454
+ .then(Add3)
455
+
456
+ result1.type # :error
457
+ result1.value # {'number' => -1}
458
+ result1.failure? # true
459
+
460
+ # ---
461
+
462
+ result2 =
463
+ ForbidNegativeNumber
464
+ .call(number: 1)
465
+ .then(Add3)
466
+
467
+ result2.type # :ok
468
+ result2.value # {'number' => 4}
469
+ result2.success? # true
470
+ ```
471
+
412
472
  [⬆️ Back to Top](#table-of-contents-)
413
473
 
414
474
  ### `Micro::Case::Flow` - How to compose use cases?
@@ -592,13 +652,148 @@ Note: You can blend any of the [available syntaxes/approaches](#how-to-create-a-
592
652
 
593
653
  [⬆️ Back to Top](#table-of-contents-)
594
654
 
595
- #### Is it possible a flow accumulates its input and merges each success result to use as the argument of their use cases?
655
+ #### Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?
656
+
657
+ Answer: Yes, it is! Look at the example below to understand how the data accumulation works inside of the flow execution.
658
+
659
+ ```ruby
660
+ module Users
661
+ class Find < Micro::Case
662
+ attribute :email
663
+
664
+ def call!
665
+ user = User.find_by(email: email)
666
+
667
+ return Success { { user: user } } if user
668
+
669
+ Failure(:user_not_found)
670
+ end
671
+ end
672
+ end
673
+
674
+ module Users
675
+ class ValidatePassword < Micro::Case::Strict
676
+ attributes :user, :password
677
+
678
+ def call!
679
+ return Failure(:user_must_be_persisted) if user.new_record?
680
+ return Failure(:wrong_password) if user.wrong_password?(password)
681
+
682
+ return Success { attributes(:user) }
683
+ end
684
+ end
685
+ end
686
+
687
+ module Users
688
+ Authenticate = Micro::Case::Flow([
689
+ Find,
690
+ ValidatePassword
691
+ ])
692
+ end
693
+
694
+ Users::Authenticate
695
+ .call(email: 'somebody@test.com', password: 'password')
696
+ .on_success { |result| sign_in(result[:user]) }
697
+ .on_failure(:wrong_password) { |result| render status: 401 }
698
+ .on_failure(:user_not_found) { |result| render status: 404 }
699
+ ```
700
+
701
+ First, lets see the attribute of each use case:
596
702
 
597
- Answer: Yes, it is! Check out these test examples [Micro::Case::Flow](https://github.com/serradura/u-case/blob/e0066d8a6e3a9404069dfcb9bf049b854f08a33c/test/micro/case/flow/reducer_test.rb) and [Micro::Case::Safe::Flow](https://github.com/serradura/u-case/blob/e0066d8a6e3a9404069dfcb9bf049b854f08a33c/test/micro/case/safe/flow/reducer_test.rb) to see different use cases sharing their own data.
703
+ ```ruby
704
+ class Users::Find < Micro::Case
705
+ attribute :email
706
+ end
707
+
708
+ class Users::ValidatePassword < Micro::Case
709
+ attributes :user, :password
710
+ end
711
+ ```
712
+
713
+ As you can see the `Users::ValidatePassword` expects a user as its input. So, how it receives the user?
714
+ It receives the user from the `Users::Find` success result!
715
+
716
+ And this, is the power of use cases composition because the output
717
+ of one flow will compose the input of the next use case in the flow!
718
+
719
+ > input **>>** process **>>** output
720
+
721
+ > **Note:** Check out these test examples [Micro::Case::Flow](https://github.com/serradura/u-case/blob/b6d63b0db0caada67d2a6cf5cc5937000c0acf04/test/micro/case/flow/reducer_test.rb) and [Micro::Case::Safe::Flow](https://github.com/serradura/u-case/blob/b1d84b355f2b92d329e10d5d56d8012df1d32681/test/micro/case/safe/flow/reducer_test.rb) to see different use cases sharing their own data.
598
722
 
599
723
  [⬆️ Back to Top](#table-of-contents-)
600
724
 
601
- #### Is it possible to declare a flow using the use case itself?
725
+ #### How to understand what is happening during a flow execution?
726
+
727
+ Use `Micro::Case::Result#transitions`!
728
+
729
+ Let's use the [previous section example](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-the-next-use-cases) to ilustrate how to use this feature.
730
+
731
+ ```ruby
732
+ user_authenticated =
733
+ Users::Authenticate.call(email: 'rodrigo@test.com', password: user_password)
734
+
735
+ user_authenticated.transitions
736
+ [
737
+ {
738
+ :use_case => {
739
+ :class => Users::Find,
740
+ :attributes => { :email => "rodrigo@test.com" }
741
+ },
742
+ :success => {
743
+ :type => :ok,
744
+ :value => {
745
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
746
+ }
747
+ },
748
+ :accessible_attributes => [ :email, :password ]
749
+ },
750
+ {
751
+ :use_case => {
752
+ :class => Users::ValidatePassword,
753
+ :attributes => {
754
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
755
+ :password => "123456"
756
+ }
757
+ },
758
+ :success => {
759
+ :type => :ok,
760
+ :value => {
761
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
762
+ }
763
+ },
764
+ :accessible_attributes => [ :email, :password, :user ]
765
+ }
766
+ ]
767
+ ```
768
+
769
+ The example above shows the output generated by the `Micro::Case::Result#transitions`.
770
+ With it is possible to analyze the use cases execution order and what were the given `inputs` (attributes) and `outputs` (`success.value`) in the entire execution.
771
+
772
+ And look up the `accessible_attributes` property, because it shows whats attributes are accessible in that flow step. For example, in the last step, you can see that the `accessible_attributes` increased because of the [flow data accumulation](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-the-next-use-cases).
773
+
774
+ > **Note:** The [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) increments the `Micro::Case::Result#transitions`.
775
+
776
+ ##### Micro::Case::Result#transitions schema.
777
+ ```ruby
778
+ [
779
+ {
780
+ use_case: {
781
+ class: <Micro::Case>,# Use case which was executed
782
+ attributes: <Hash> # (Input) The use case's attributes
783
+ },
784
+ [success:, failure:] => { # (Output)
785
+ type: <Symbol>, # Result type. Defaults:
786
+ # Success = :ok, Failure = :error/:exception
787
+ value: <Hash> # The data returned by the use case
788
+ },
789
+ accessible_attributes: <Array>, # Properties that can be accessed by the use case's attributes,
790
+ # starting with Hash used to invoke it and which are incremented
791
+ # with each result value of the flow's use cases.
792
+ }
793
+ ]
794
+ ```
795
+
796
+ #### Is it possible to declare a flow which includes the use case itself?
602
797
 
603
798
  Answer: Yes, it is! You can use the `self.call!` macro. e.g:
604
799
 
@@ -620,16 +815,15 @@ class ConvertNumberToText < Micro::Case
620
815
  end
621
816
 
622
817
  class Double < Micro::Case
818
+ flow ConvertTextToNumber,
819
+ self.call!,
820
+ ConvertNumberToText
821
+
623
822
  attribute :number
624
823
 
625
824
  def call!
626
825
  Success { { number: number * 2 } }
627
826
  end
628
-
629
- # NOTE: You need to declare the flow after the definition of the attributes.
630
- flow ConvertTextToNumber,
631
- self.call!,
632
- ConvertNumberToText
633
827
  end
634
828
 
635
829
  result = Double.call(text: '4')
@@ -767,11 +961,11 @@ end
767
961
 
768
962
  [⬆️ Back to Top](#table-of-contents-)
769
963
 
770
- ### `u-case/with_validation` - How to validate use case attributes?
964
+ ### `u-case/with_activemodel_validation` - How to validate use case attributes?
771
965
 
772
966
  **Requirement:**
773
967
 
774
- To do this your application must have the [activemodel >= 3.2](https://rubygems.org/gems/activemodel) as a dependency.
968
+ To do this your application must have the [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) as a dependency.
775
969
 
776
970
  ```ruby
777
971
  #
@@ -795,10 +989,10 @@ end
795
989
  # your use cases on validation errors, you can use:
796
990
 
797
991
  # In some file. e.g: A Rails initializer
798
- require 'u-case/with_validation' # or require 'micro/case/with_validation'
992
+ require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
799
993
 
800
994
  # In the Gemfile
801
- gem 'u-case', require: 'u-case/with_validation'
995
+ gem 'u-case', require: 'u-case/with_activemodel_validation'
802
996
 
803
997
  # Using this approach, you can rewrite the previous example with less code. e.g:
804
998
 
@@ -823,7 +1017,7 @@ end
823
1017
  Answer: Yes, it is. To do this, you only need to use the `disable_auto_validation` macro. e.g:
824
1018
 
825
1019
  ```ruby
826
- require 'u-case/with_validation'
1020
+ require 'u-case/with_activemodel_validation'
827
1021
 
828
1022
  class Multiply < Micro::Case
829
1023
  disable_auto_validation
@@ -845,6 +1039,31 @@ Multiply.call(a: 2, b: 'a')
845
1039
 
846
1040
  [⬆️ Back to Top](#table-of-contents-)
847
1041
 
1042
+ #### Kind::Validator
1043
+
1044
+ The [kind gem](https://github.com/serradura/kind) has a module to enable the validation of data type through [`ActiveModel validations`](https://guides.rubyonrails.org/active_model_basics.html#validations). So, when you require the `'u-case/with_activemodel_validation'`, this module will require the [`Kind::Validator`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations).
1045
+
1046
+ The example below shows how to validate the attributes data types.
1047
+
1048
+ ```ruby
1049
+ class Todo::List::AddItem < Micro::Case
1050
+ attributes :user, :params
1051
+
1052
+ validates :user, kind: User
1053
+ validates :params, kind: ActionController::Parameters
1054
+
1055
+ def call!
1056
+ todo_params = Todo::Params.to_save(params)
1057
+
1058
+ todo = user.todos.create(todo_params)
1059
+
1060
+ Success { { todo: todo} }
1061
+ rescue ActionController::ParameterMissing => e
1062
+ Failure(:parameter_missing) { { message: e.message } }
1063
+ end
1064
+ end
1065
+ ```
1066
+
848
1067
  ## Benchmarks
849
1068
 
850
1069
  ### `Micro::Case`
@@ -853,25 +1072,25 @@ Multiply.call(a: 2, b: 'a')
853
1072
 
854
1073
  The table below contains the average between the [Success results](#success-results) and [Failure results](#failure-results) benchmarks.
855
1074
 
856
- | Gem / Abstraction | Iterations per second | Comparison |
857
- | ---------------------- | --------------------: | ---------------: |
858
- | **Micro::Case** | 116629.7 | _**The Faster**_ |
859
- | Dry::Monads | 101796.3 | 1.14x slower |
860
- | Interactor | 21230.5 | 5.49x slower |
861
- | Trailblazer::Operation | 16466.6 | 7.08x slower |
862
- | Dry::Transaction | 5069.5 | 23.00x slower |
1075
+ | Gem / Abstraction | Iterations per second | Comparison |
1076
+ | ---------------------- | --------------------: | ----------------: |
1077
+ | **Micro::Case** | 116629.7 | _**The Fastest**_ |
1078
+ | Dry::Monads | 101796.3 | 1.14x slower |
1079
+ | Interactor | 21230.5 | 5.49x slower |
1080
+ | Trailblazer::Operation | 16466.6 | 7.08x slower |
1081
+ | Dry::Transaction | 5069.5 | 23.00x slower |
863
1082
 
864
1083
  ---
865
1084
 
866
1085
  #### Success results
867
1086
 
868
- | Gem / Abstraction | Iterations per second | Comparison |
869
- | ----------------- | --------------------: | ---------------: |
870
- | Dry::Monads | 139352.5 | _**The Faster**_ |
871
- | **Micro::Case** | 124749.4 | 1.12x slower |
872
- | Interactor | 28974.4 | 4.81x slower |
873
- | Trailblazer::Operation | 17275.6 | 8.07x slower |
874
- | Dry::Transaction | 5571.7 | 25.01x slower |
1087
+ | Gem / Abstraction | Iterations per second | Comparison |
1088
+ | ----------------- | --------------------: | ----------------: |
1089
+ | Dry::Monads | 139352.5 | _**The Fastest**_ |
1090
+ | **Micro::Case** | 124749.4 | 1.12x slower |
1091
+ | Interactor | 28974.4 | 4.81x slower |
1092
+ | Trailblazer::Operation | 17275.6 | 8.07x slower |
1093
+ | Dry::Transaction | 5571.7 | 25.01x slower |
875
1094
 
876
1095
  <details>
877
1096
  <summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
@@ -911,13 +1130,13 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success
911
1130
 
912
1131
  #### Failure results
913
1132
 
914
- | Gem / Abstraction | Iterations per second | Comparison |
915
- | ----------------- | --------------------: | ---------------: |
916
- | **Micro::Case** | 108510.0 | _**The Faster**_ |
917
- | Dry::Monads | 64240.1 | 1.69x slower |
918
- | Trailblazer::Operation | 15657.7 | 6.93x slower |
919
- | Interactor | 13486.7 | 8.05x slower |
920
- | Dry::Transaction | 4567.3 | 23.76x slower |
1133
+ | Gem / Abstraction | Iterations per second | Comparison |
1134
+ | ----------------- | --------------------: | ----------------: |
1135
+ | **Micro::Case** | 108510.0 | _**The Fastest**_ |
1136
+ | Dry::Monads | 64240.1 | 1.69x slower |
1137
+ | Trailblazer::Operation | 15657.7 | 6.93x slower |
1138
+ | Interactor | 13486.7 | 8.05x slower |
1139
+ | Dry::Transaction | 4567.3 | 23.76x slower |
921
1140
 
922
1141
  <details>
923
1142
  <summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
@@ -960,10 +1179,10 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure
960
1179
  ### `Micro::Case::Flow`
961
1180
 
962
1181
  | Gems / Abstraction | [Success results](https://github.com/serradura/u-case/blob/master/benchmarks/flow/with_success_result.rb#L40) | [Failure results](https://github.com/serradura/u-case/blob/master/benchmarks/flow/with_failure_result.rb#L40) |
963
- | ------------------ | ---------------: | ---------------: |
964
- | Micro::Case::Flow | _**The Faster**_ | _**The Faster**_ |
965
- | Micro::Case::Safe::Flow | 0x slower | 0x slower |
966
- | Interactor::Organizer | 1.47x slower | 5.51x slower |
1182
+ | ------------------ | ----------------: | ----------------: |
1183
+ | Micro::Case::Flow | _**The Fastest**_ | _**The Fastest**_ |
1184
+ | Micro::Case::Safe::Flow | 0x slower | 0x slower |
1185
+ | Interactor::Organizer | 1.47x slower | 5.51x slower |
967
1186
 
968
1187
  \* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
969
1188
 
@@ -1022,13 +1241,27 @@ Check it out implementations of the same use case with different gems/abstractio
1022
1241
 
1023
1242
  ## Examples
1024
1243
 
1025
- 1. [Rescuing an exception inside of use cases](https://github.com/serradura/u-case/blob/master/examples/rescuing_exceptions.rb)
1026
- 2. [Users creation](https://github.com/serradura/u-case/blob/master/examples/users_creation.rb)
1244
+ ### 1️⃣ Rails App (API)
1245
+
1246
+ > This project shows different kinds of architecture (one per commit), and in the last one, how to use the Micro::Case gem to handle the application business logic.
1247
+ >
1248
+ > Link: https://github.com/serradura/from-fat-controllers-to-use-cases
1249
+
1250
+ ### 2️⃣ CLI calculator
1251
+
1252
+ > Rake tasks to demonstrate how to handle user data, and how to use different failure types to control the program flow.
1253
+ >
1254
+ > Link: https://github.com/serradura/u-case/tree/master/examples/calculator
1255
+
1256
+ ### 3️⃣ Users creation
1257
+
1258
+ > An example of a use case flow that define steps to sanitize, validate, and persist its input data.
1259
+ >
1260
+ > Link: https://github.com/serradura/u-case/blob/master/examples/users_creation.rb
1027
1261
 
1028
- An example of flow in how to define steps to sanitize, validate, and persist some input data.
1029
- 3. [CLI calculator](https://github.com/serradura/u-case/tree/master/examples/calculator)
1262
+ ### 4️⃣ Rescuing exception inside of the use cases
1030
1263
 
1031
- A more complex example which use rake tasks to demonstrate how to handle user data, and how to use different failures type to control the program flow.
1264
+ > Link: https://github.com/serradura/u-case/blob/master/examples/rescuing_exceptions.rb
1032
1265
 
1033
1266
  [⬆️ Back to Top](#table-of-contents-)
1034
1267
 
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'kind'
3
4
  require 'micro/attributes'
4
- # frozen_string_literal: true
5
5
 
6
6
  module Micro
7
7
  class Case
8
8
  require 'micro/case/version'
9
+ require 'micro/case/utils'
9
10
  require 'micro/case/result'
10
11
  require 'micro/case/error'
11
12
  require 'micro/case/safe'
@@ -42,23 +43,15 @@ module Micro
42
43
  instance
43
44
  end
44
45
 
45
- def self.__get_flow__
46
- @__flow
47
- end
48
-
49
- private_class_method def self.__set_flow__(reducer, args)
50
- def self.use_cases; __get_flow__.use_cases; end
51
-
52
- self.class_eval('def use_cases; self.class.use_cases; end')
53
-
54
- reducer.build(args)
55
- end
46
+ def self.__call_and_set_transition__(result, arg)
47
+ if arg.respond_to?(:keys)
48
+ result.__set_transitions_accessible_attributes__(arg.keys)
49
+ end
56
50
 
57
- def self.flow(*args)
58
- @__flow ||= __set_flow__(Flow::Reducer, args)
51
+ __new__(result, arg).call
59
52
  end
60
53
 
61
- def self.call!
54
+ def self.__call!
62
55
  return const_get(:Flow_Step) if const_defined?(:Flow_Step)
63
56
 
64
57
  const_set(:Flow_Step, Class.new(self) do
@@ -68,9 +61,51 @@ module Micro
68
61
  end)
69
62
  end
70
63
 
64
+ def self.call!
65
+ self
66
+ end
67
+
68
+ def self.__flow_reducer
69
+ Flow::Reducer
70
+ end
71
+
72
+ def self.__flow_get
73
+ return @__flow if defined?(@__flow)
74
+ end
75
+
76
+ private_class_method def self.__flow_use_cases
77
+ return @__flow_use_cases if defined?(@__flow_use_cases)
78
+ end
79
+
80
+ private_class_method def self.__flow_use_cases_get
81
+ Array(__flow_use_cases)
82
+ .map { |use_case| use_case == self ? self.__call! : use_case }
83
+ end
84
+
85
+ private_class_method def self.__flow_use_cases_set(args)
86
+ @__flow_use_cases = args
87
+ end
88
+
89
+ private_class_method def self.__flow_set(args)
90
+ return if __flow_get
91
+
92
+ def self.use_cases; __flow_get.use_cases; end
93
+
94
+ self.class_eval('def use_cases; self.class.use_cases; end')
95
+
96
+ @__flow = __flow_reducer.build(args)
97
+ end
98
+
99
+ def self.__flow_set!
100
+ __flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
101
+ end
102
+
103
+ def self.flow(*args)
104
+ __flow_use_cases_set(args)
105
+ end
106
+
71
107
  def initialize(input)
72
- @__input = input
73
- self.attributes = input
108
+ __setup_use_case(input)
74
109
  end
75
110
 
76
111
  def call!
@@ -83,15 +118,23 @@ module Micro
83
118
 
84
119
  def __set_result__(result)
85
120
  raise Error::InvalidResultInstance unless result.is_a?(Result)
86
- raise Error::ResultIsAlreadyDefined if @__result
121
+ raise Error::ResultIsAlreadyDefined if defined?(@__result)
87
122
 
88
123
  @__result = result
89
124
  end
90
125
 
91
126
  private
92
127
 
128
+ def __setup_use_case(input)
129
+ self.class.__flow_set!
130
+
131
+ @__input = input
132
+
133
+ self.attributes = input
134
+ end
135
+
93
136
  def __call
94
- return self.class.__get_flow__.call(@__input) if self.class.__get_flow__
137
+ return __call_use_case_flow if __call_use_case_flow?
95
138
 
96
139
  __call_use_case
97
140
  end
@@ -104,21 +147,25 @@ module Micro
104
147
  raise Error::UnexpectedResult.new(self.class)
105
148
  end
106
149
 
150
+ def __call_use_case_flow?
151
+ self.class.__flow_get
152
+ end
153
+
154
+ def __call_use_case_flow
155
+ self.class.__flow_get.call(@__input)
156
+ end
157
+
107
158
  def Success(arg = :ok)
108
159
  value, type = block_given? ? [yield, arg] : [arg, :ok]
109
160
 
110
- __get_result__.__set__(true, value, type, nil)
161
+ __get_result_with(true, value, type)
111
162
  end
112
163
 
113
164
  def Failure(arg = :error)
114
165
  value = block_given? ? yield : arg
115
166
  type = __map_failure_type(value, block_given? ? arg : :error)
116
167
 
117
- __get_result__.__set__(false, value, type, self)
118
- end
119
-
120
- def __get_result__
121
- @__result ||= Result.new
168
+ __get_result_with(false, value, type)
122
169
  end
123
170
 
124
171
  def __map_failure_type(arg, type)
@@ -128,5 +175,13 @@ module Micro
128
175
 
129
176
  type
130
177
  end
178
+
179
+ def __get_result__
180
+ @__result ||= Result.new
181
+ end
182
+
183
+ def __get_result_with(is_success, value, type)
184
+ __get_result__.__set__(is_success, value, type, self)
185
+ end
131
186
  end
132
187
  end
@@ -9,20 +9,36 @@ module Micro
9
9
  def initialize(klass); super(klass.name + MESSAGE); end
10
10
  end
11
11
 
12
- ResultIsAlreadyDefined = ArgumentError.new('result is already defined'.freeze)
12
+ class ResultIsAlreadyDefined < ArgumentError
13
+ def initialize; super('result is already defined'.freeze); end
14
+ end
13
15
 
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
+ class InvalidResultType < TypeError
17
+ def initialize; super('type must be a Symbol'.freeze); end
18
+ end
16
19
 
17
- InvalidUseCase = TypeError.new('use case must be a kind or an instance of Micro::Case'.freeze)
18
- InvalidUseCases = ArgumentError.new('argument must be a collection of `Micro::Case` classes'.freeze)
20
+ class InvalidResultInstance < ArgumentError
21
+ def initialize; super('argument must be an instance of Micro::Case::Result'.freeze); end
22
+ end
19
23
 
20
- UndefinedFlow = ArgumentError.new("This class hasn't declared its flow. Please, use the `flow()` macro to define one.".freeze)
24
+ class InvalidUseCase < TypeError
25
+ def initialize; super('use case must be a kind or an instance of Micro::Case'.freeze); end
26
+ end
21
27
 
22
- class InvalidAccessToTheUseCaseObject < StandardError
23
- MSG = 'only a failure result can access its use case object'.freeze
28
+ class InvalidUseCases < ArgumentError
29
+ def initialize; super('argument must be a collection of `Micro::Case` classes'.freeze); end
30
+ end
24
31
 
25
- def initialize(message = MSG); super; end
32
+ class InvalidInvocationOfTheThenMethod < StandardError
33
+ def initialize; super('Invalid invocation of the Micro::Case::Result#then method'); end
34
+ end
35
+
36
+ class UndefinedFlow < ArgumentError
37
+ def initialize; super("This class hasn't declared its flow. Please, use the `flow()` macro to define one.".freeze); end
38
+ end
39
+
40
+ class InvalidAccessToTheUseCaseObject < StandardError
41
+ def initialize; super('only a failure result can access its use case object'.freeze); end
26
42
  end
27
43
 
28
44
  module ByWrongUsage
@@ -5,7 +5,7 @@ module Micro
5
5
  module Flow
6
6
  module ClassMethods
7
7
  def __flow__
8
- @__flow
8
+ return @__flow if defined?(@__flow)
9
9
  end
10
10
 
11
11
  def flow(*args)
@@ -23,19 +23,18 @@ module Micro
23
23
 
24
24
  def initialize(use_cases)
25
25
  @use_cases = use_cases
26
+ @first_use_case = use_cases[0]
27
+ @next_use_cases = use_cases[1..-1]
26
28
  end
27
29
 
28
30
  def call(arg = {})
29
31
  memo = arg.is_a?(Hash) ? arg.dup : {}
30
32
 
31
- @use_cases.reduce(initial_result(arg)) do |result, use_case|
32
- break result if result.failure?
33
+ first_result = first_use_case_result(arg)
33
34
 
34
- value = result.value
35
- input = value.is_a?(Hash) ? memo.tap { |data| data.merge!(value) } : value
35
+ return first_result if @next_use_cases.empty?
36
36
 
37
- use_case_result(use_case, result, input)
38
- end
37
+ next_use_cases_result(first_result, memo)
39
38
  end
40
39
 
41
40
  def >>(arg)
@@ -52,16 +51,8 @@ module Micro
52
51
 
53
52
  private
54
53
 
55
- def use_case_result(use_case, result, input)
56
- use_case.__new__(result, input).call
57
- end
58
-
59
- def initial_result(arg)
60
- return arg.call if arg_to_call?(arg)
61
- return arg if arg.is_a?(Micro::Case::Result)
62
-
63
- result = ::Micro::Case::Result.new
64
- result.__set__(true, arg, :ok, nil)
54
+ def is_a_result?(arg)
55
+ arg.is_a?(Micro::Case::Result)
65
56
  end
66
57
 
67
58
  def arg_to_call?(arg)
@@ -69,6 +60,44 @@ module Micro
69
60
  return true if arg.is_a?(Class) && (arg < ::Micro::Case || arg < ::Micro::Case::Flow)
70
61
  return false
71
62
  end
63
+
64
+ def call_arg(arg)
65
+ output = arg.call
66
+
67
+ is_a_result?(output) ? output.value : output
68
+ end
69
+
70
+ def first_use_case_input(arg)
71
+ return call_arg(arg) if arg_to_call?(arg)
72
+ return arg.value if is_a_result?(arg)
73
+
74
+ arg
75
+ end
76
+
77
+ def first_use_case_result(arg)
78
+ input = first_use_case_input(arg)
79
+
80
+ result = ::Micro::Case::Result.new
81
+
82
+ @first_use_case.__call_and_set_transition__(result, input)
83
+ end
84
+
85
+ def next_use_case_result(use_case, result, input)
86
+ use_case.__new__(result, input).call
87
+ end
88
+
89
+ def next_use_cases_result(first_result, memo)
90
+ @next_use_cases.reduce(first_result) do |result, use_case|
91
+ break result if result.failure?
92
+
93
+ value = result.value
94
+ input = value.is_a?(Hash) ? memo.tap { |data| data.merge!(value) } : value
95
+
96
+ result.__set_transitions_accessible_attributes__(memo.keys)
97
+
98
+ next_use_case_result(use_case, result, input)
99
+ end
100
+ end
72
101
  end
73
102
  end
74
103
  end
@@ -1,8 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'set'
4
+
3
5
  module Micro
4
6
  class Case
5
7
  class Result
8
+ Kind::Types.add(self)
9
+
10
+ @@transition_tracking_disabled = false
11
+
12
+ def self.disable_transition_tracking
13
+ @@transition_tracking_disabled = true
14
+ end
15
+
6
16
  class Data
7
17
  attr_reader :value, :type
8
18
 
@@ -17,12 +27,19 @@ module Micro
17
27
 
18
28
  attr_reader :value, :type
19
29
 
30
+ def initialize
31
+ @__transitions__ = {}
32
+ @__transitions_accessible_attributes__ = Set.new
33
+ end
34
+
20
35
  def __set__(is_success, value, type, use_case)
21
36
  raise Error::InvalidResultType unless type.is_a?(Symbol)
22
- raise Error::InvalidUseCase if !is_success && !is_a_use_case?(use_case)
37
+ raise Error::InvalidUseCase if !is_a_use_case?(use_case)
23
38
 
24
39
  @success, @value, @type, @use_case = is_success, value, type, use_case
25
40
 
41
+ __set_transition__ unless @@transition_tracking_disabled
42
+
26
43
  self
27
44
  end
28
45
 
@@ -52,8 +69,47 @@ module Micro
52
69
  self.tap { yield(data, @use_case) }
53
70
  end
54
71
 
72
+ def then(arg = nil, &block)
73
+ can_yield_self = respond_to?(:yield_self)
74
+
75
+ if block
76
+ raise Error::InvalidInvocationOfTheThenMethod if arg
77
+ raise NotImplementedError if !can_yield_self
78
+
79
+ yield_self(&block)
80
+ else
81
+ return yield_self if !arg && can_yield_self
82
+
83
+ raise Error::InvalidInvocationOfTheThenMethod if !is_a_use_case?(arg)
84
+
85
+ return self if failure?
86
+
87
+ arg.__call_and_set_transition__(self, self.value)
88
+ end
89
+ end
90
+
91
+ def transitions
92
+ return [] if @__transitions__.empty?
93
+
94
+ @__transitions__.map { |_use_case, transition| transition }
95
+ end
96
+
97
+ def __set_transitions_accessible_attributes__(attribute_names)
98
+ return if @@transition_tracking_disabled
99
+
100
+ __set_transitions_accessible_attributes__!(
101
+ attribute_names.map!(&:to_sym)
102
+ )
103
+ end
104
+
55
105
  private
56
106
 
107
+ def __set_transitions_accessible_attributes__!(attribute_names)
108
+ @__transitions_accessible_attributes__.merge(
109
+ attribute_names
110
+ )
111
+ end
112
+
57
113
  def success_type?(expected_type)
58
114
  success? && (expected_type.nil? || expected_type == type)
59
115
  end
@@ -65,6 +121,22 @@ module Micro
65
121
  def is_a_use_case?(arg)
66
122
  (arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
67
123
  end
124
+
125
+ def __set_transition__
126
+ use_case_class = @use_case.class
127
+ use_case_attributes = Utils.symbolize_keys(@use_case.attributes)
128
+
129
+ __set_transitions_accessible_attributes__!(use_case_attributes.keys)
130
+
131
+ result = @success ? :success : :failure
132
+ transition = {
133
+ use_case: { class: use_case_class, attributes: use_case_attributes },
134
+ result => { type: @type, value: @value },
135
+ accessible_attributes: @__transitions_accessible_attributes__.to_a
136
+ }
137
+
138
+ @__transitions__[use_case_class] = transition
139
+ end
68
140
  end
69
141
  end
70
142
  end
@@ -4,7 +4,7 @@ module Micro
4
4
  class Case
5
5
  class Safe < ::Micro::Case
6
6
  def call
7
- super
7
+ __call
8
8
  rescue => exception
9
9
  raise exception if Error::ByWrongUsage.check(exception)
10
10
 
@@ -19,7 +19,7 @@ module Micro
19
19
 
20
20
  private
21
21
 
22
- def use_case_result(use_case, result, input)
22
+ def next_use_case_result(use_case, result, input)
23
23
  begin
24
24
  instance = use_case.__new__(result, input)
25
25
  instance.call
@@ -36,8 +36,8 @@ module Micro
36
36
  Flow::Reducer.build(Array(args))
37
37
  end
38
38
 
39
- def self.flow(*args)
40
- @__flow ||= __set_flow__(Flow::Reducer, args)
39
+ def self.__flow_reducer
40
+ Flow::Reducer
41
41
  end
42
42
  end
43
43
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Micro
4
+ class Case
5
+ module Utils
6
+ def self.symbolize_keys(hash)
7
+ if Kind.of.Hash(hash).respond_to?(:transform_keys)
8
+ hash.transform_keys { |key| key.to_sym rescue key }
9
+ else
10
+ hash.each_with_object({}) do |(k, v), memo|
11
+ key = k.to_sym rescue k
12
+
13
+ memo[key] = v
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Micro
4
4
  class Case
5
- VERSION = '2.1.0'.freeze
5
+ VERSION = '2.4.0'.freeze
6
6
  end
7
7
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'micro/case'
4
+
5
+ module Micro
6
+ class Case
7
+ include Micro::Attributes::Features::ActiveModelValidations
8
+
9
+ def self.auto_validation_disabled?
10
+ return @disable_auto_validation if defined?(@disable_auto_validation)
11
+ end
12
+
13
+ def self.disable_auto_validation
14
+ @disable_auto_validation = true
15
+ end
16
+
17
+ def initialize(input)
18
+ __setup_use_case(input)
19
+
20
+ run_validations! if respond_to?(:run_validations!, true)
21
+ end
22
+
23
+ private
24
+
25
+ def __call_use_case
26
+ return failure_by_validation_error(self) if !self.class.auto_validation_disabled? && invalid?
27
+
28
+ result = call!
29
+
30
+ return result if result.is_a?(Result)
31
+
32
+ raise Error::UnexpectedResult.new(self.class)
33
+ end
34
+
35
+ def failure_by_validation_error(object)
36
+ errors = object.respond_to?(:errors) ? object.errors : object
37
+
38
+ Failure(:validation_error) { { errors: errors } }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kind/active_model/validation'
4
+
5
+ require 'micro/case/with_activemodel_validation'
@@ -1,3 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'micro/case/with_validation'
3
+ warn 'Deprecation: "u-case/with_validation" will be deprecated in the next major release.' \
4
+ 'Please use "u-case/with_activemodel_validation" instead of it.'
5
+
6
+ require 'u-case/with_activemodel_validation'
@@ -14,20 +14,8 @@ Gem::Specification.new do |spec|
14
14
  spec.homepage = 'https://github.com/serradura/u-case'
15
15
  spec.license = 'MIT'
16
16
 
17
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
- # to allow pushing to a single host or delete this section to allow pushing to any host.
19
- if spec.respond_to?(:metadata)
20
- # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
21
-
22
- # spec.metadata["homepage_uri"] = spec.homepage
23
- # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
24
- # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
25
- else
26
- raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
27
- end
17
+ raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.' unless spec.respond_to?(:metadata)
28
18
 
29
- # Specify which files should be added to the gem when it is released.
30
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
31
19
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
32
20
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|assets|benchmarks|comparisons|examples)/}) }
33
21
  end
@@ -37,8 +25,9 @@ Gem::Specification.new do |spec|
37
25
 
38
26
  spec.required_ruby_version = '>= 2.2.0'
39
27
 
28
+ spec.add_runtime_dependency 'kind', '~> 3.0'
40
29
  spec.add_runtime_dependency 'u-attributes', '~> 1.1'
41
30
 
42
31
  spec.add_development_dependency 'bundler'
43
- spec.add_development_dependency 'rake', '~> 10.0'
32
+ spec.add_development_dependency 'rake', '~> 13.0'
44
33
  end
metadata CHANGED
@@ -1,15 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: u-case
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-12-12 00:00:00.000000000 Z
11
+ date: 2020-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: kind
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: u-attributes
15
29
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +58,14 @@ dependencies:
44
58
  requirements:
45
59
  - - "~>"
46
60
  - !ruby/object:Gem::Version
47
- version: '10.0'
61
+ version: '13.0'
48
62
  type: :development
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - "~>"
53
67
  - !ruby/object:Gem::Version
54
- version: '10.0'
68
+ version: '13.0'
55
69
  description: Create simple and powerful use cases as objects.
56
70
  email:
57
71
  - rodrigo.serradura@gmail.com
@@ -78,9 +92,11 @@ files:
78
92
  - lib/micro/case/safe.rb
79
93
  - lib/micro/case/safe/flow.rb
80
94
  - lib/micro/case/strict.rb
95
+ - lib/micro/case/utils.rb
81
96
  - lib/micro/case/version.rb
82
- - lib/micro/case/with_validation.rb
97
+ - lib/micro/case/with_activemodel_validation.rb
83
98
  - lib/u-case.rb
99
+ - lib/u-case/with_activemodel_validation.rb
84
100
  - lib/u-case/with_validation.rb
85
101
  - test.sh
86
102
  - u-case.gemspec
@@ -103,7 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
119
  - !ruby/object:Gem::Version
104
120
  version: '0'
105
121
  requirements: []
106
- rubygems_version: 3.0.3
122
+ rubygems_version: 3.0.6
107
123
  signing_key:
108
124
  specification_version: 4
109
125
  summary: Create simple and powerful use cases as objects.
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'micro/case'
4
-
5
- module Micro
6
- class Case
7
- include Micro::Attributes::Features::ActiveModelValidations
8
-
9
- def self.auto_validation_disabled?
10
- @disable_auto_validation
11
- end
12
-
13
- def self.disable_auto_validation
14
- @disable_auto_validation = true
15
- end
16
-
17
- def initialize(input)
18
- @__input = input
19
- self.attributes = input
20
- run_validations! if respond_to?(:run_validations!, true)
21
- end
22
-
23
- def call
24
- return failure_by_validation_error(self) if !self.class.auto_validation_disabled? && invalid?
25
-
26
- __call
27
- end
28
-
29
- private def failure_by_validation_error(object)
30
- errors = object.respond_to?(:errors) ? object.errors : object
31
-
32
- Failure(:validation_error) { { errors: errors } }
33
- end
34
- end
35
- end