u-case 2.1.1 → 2.5.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: a0411016e16662d422a9f6b641ae91a516225e877f5cd8951058b479ea1b413d
4
- data.tar.gz: 586af968d98bf2add7a9882a894db2ea4e6ada5eb3c3e31eae39a1d09e2410bf
3
+ metadata.gz: 349609a8d927ab7fd8edb1eb96141dff2f78f9957f31f2b88a8975239fc0de87
4
+ data.tar.gz: 4b6293023f49f28dd8283656a0d423f7d24abbfb7f010a90ba4d0dd17f577d22
5
5
  SHA512:
6
- metadata.gz: ab3231e87cca1c25cc5148f922292177239738da22a7f1528bd4cdc56a601028c4a4e9eec0660e00c1cfc0a126746c53f37db159002639d9226f22cec931b812
7
- data.tar.gz: 308404bc8b913d4ef84cd59e31fecd18b6bfe5ba51c6724d4a8880525c6058d01bf7b669676a843f2ff47754a7db9e643abcbbd53f50b178d72efce6c979eb40
6
+ metadata.gz: 0553e560e40f71d0e8284ebbbbf7a51326d8c9bc9de6ad0de76db526466a94f633e2b038563868a2c93234800b7bdacc30a99ba57f1624ce08463fdc012d165d
7
+ data.tar.gz: 2866ca201676827b2ffc82877b1615e7ecbb6fc6e993df17007c6446b348a850266faf162a0953954ee6adfb0d35a88c303bee385d2fb52a9c228f45513655db
@@ -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,66 @@
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
+ - [`Micro::Case::Safe::Flow`](#microcasesafeflow)
44
+ - [`Micro::Case::Result#on_exception`](#microcaseresulton_exception)
45
+ - [`u-case/with_activemodel_validation` - How to validate use case attributes?](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes)
46
+ - [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)
47
+ - [Kind::Validator](#kindvalidator)
48
+ - [Benchmarks](#benchmarks)
49
+ - [`Micro::Case`](#microcase)
50
+ - [Best overall](#best-overall)
51
+ - [Success results](#success-results)
52
+ - [Failure results](#failure-results)
53
+ - [`Micro::Case::Flow`](#microcaseflow)
54
+ - [Comparisons](#comparisons)
55
+ - [Examples](#examples)
56
+ - [1️⃣ Rails App (API)](#1️⃣-rails-app-api)
57
+ - [2️⃣ CLI calculator](#2️⃣-cli-calculator)
58
+ - [3️⃣ Users creation](#3️⃣-users-creation)
59
+ - [4️⃣ Rescuing exception inside of the use cases](#4️⃣-rescuing-exception-inside-of-the-use-cases)
60
+ - [Development](#development)
61
+ - [Contributing](#contributing)
62
+ - [License](#license)
63
+ - [Code of Conduct](#code-of-conduct)
52
64
 
53
65
  ## Required Ruby version
54
66
 
@@ -56,8 +68,15 @@ The main project goals are:
56
68
 
57
69
  ## Dependencies
58
70
 
59
- This project depends on [Micro::Attribute](https://github.com/serradura/u-attributes) gem.
60
- It is used to define the use case attributes.
71
+ 1. [`kind`](https://github.com/serradura/kind) gem.
72
+
73
+ A simple type system (at runtime) for Ruby.
74
+
75
+ 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.
76
+ 2. [`u-attributes`](https://github.com/serradura/u-attributes) gem.
77
+
78
+ This gem allows defining read-only attributes, that is, your objects will have only getters to access their attributes data.
79
+ It is used to define the use case attributes.
61
80
 
62
81
  ## Installation
63
82
 
@@ -139,6 +158,7 @@ A `Micro::Case::Result` stores the use cases output data. These are their main m
139
158
  - `#type` a Symbol which gives meaning for the result, this is useful to declare different types of failures or success.
140
159
  - `#on_success` or `#on_failure` are hook methods that help you define the application flow.
141
160
  - `#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).
161
+ - `#then` allows if the current result is a success, the `then` method will allow to applying a new use case for its value.
142
162
 
143
163
  [⬆️ Back to Top](#table-of-contents-)
144
164
 
@@ -409,6 +429,48 @@ accum # 24
409
429
  result.value * 4 == accum # true
410
430
  ```
411
431
 
432
+ #### How to use the `Micro::Case::Result#then` method?
433
+
434
+ ```ruby
435
+ class ForbidNegativeNumber < Micro::Case
436
+ attribute :number
437
+
438
+ def call!
439
+ return Success { attributes } if number >= 0
440
+
441
+ Failure { attributes }
442
+ end
443
+ end
444
+
445
+ class Add3 < Micro::Case
446
+ attribute :number
447
+
448
+ def call!
449
+ Success { { number: number + 3 } }
450
+ end
451
+ end
452
+
453
+ result1 =
454
+ ForbidNegativeNumber
455
+ .call(number: -1)
456
+ .then(Add3)
457
+
458
+ result1.type # :error
459
+ result1.value # {'number' => -1}
460
+ result1.failure? # true
461
+
462
+ # ---
463
+
464
+ result2 =
465
+ ForbidNegativeNumber
466
+ .call(number: 1)
467
+ .then(Add3)
468
+
469
+ result2.type # :ok
470
+ result2.value # {'number' => 4}
471
+ result2.success? # true
472
+ ```
473
+
412
474
  [⬆️ Back to Top](#table-of-contents-)
413
475
 
414
476
  ### `Micro::Case::Flow` - How to compose use cases?
@@ -592,13 +654,150 @@ Note: You can blend any of the [available syntaxes/approaches](#how-to-create-a-
592
654
 
593
655
  [⬆️ Back to Top](#table-of-contents-)
594
656
 
595
- #### Is it possible a flow accumulates its input and merges each success result to use as the argument of their use cases?
657
+ #### Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?
658
+
659
+ Answer: Yes, it is! Look at the example below to understand how the data accumulation works inside of the flow execution.
660
+
661
+ ```ruby
662
+ module Users
663
+ class Find < Micro::Case
664
+ attribute :email
665
+
666
+ def call!
667
+ user = User.find_by(email: email)
668
+
669
+ return Success { { user: user } } if user
670
+
671
+ Failure(:user_not_found)
672
+ end
673
+ end
674
+ end
675
+
676
+ module Users
677
+ class ValidatePassword < Micro::Case::Strict
678
+ attributes :user, :password
679
+
680
+ def call!
681
+ return Failure(:user_must_be_persisted) if user.new_record?
682
+ return Failure(:wrong_password) if user.wrong_password?(password)
683
+
684
+ return Success { attributes(:user) }
685
+ end
686
+ end
687
+ end
688
+
689
+ module Users
690
+ Authenticate = Micro::Case::Flow([
691
+ Find,
692
+ ValidatePassword
693
+ ])
694
+ end
695
+
696
+ Users::Authenticate
697
+ .call(email: 'somebody@test.com', password: 'password')
698
+ .on_success { |result| sign_in(result[:user]) }
699
+ .on_failure(:wrong_password) { |result| render status: 401 }
700
+ .on_failure(:user_not_found) { |result| render status: 404 }
701
+ ```
702
+
703
+ First, lets see the attribute of each use case:
704
+
705
+ ```ruby
706
+ class Users::Find < Micro::Case
707
+ attribute :email
708
+ end
709
+
710
+ class Users::ValidatePassword < Micro::Case
711
+ attributes :user, :password
712
+ end
713
+ ```
714
+
715
+ As you can see the `Users::ValidatePassword` expects a user as its input. So, how does it receives the user?
716
+ It receives the user from the `Users::Find` success result!
717
+
718
+ And this, is the power of use cases composition because the output
719
+ of one flow will compose the input of the next use case in the flow!
720
+
721
+ > input **>>** process **>>** output
596
722
 
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.
723
+ > **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
724
 
599
725
  [⬆️ Back to Top](#table-of-contents-)
600
726
 
601
- #### Is it possible to declare a flow using the use case itself?
727
+ #### How to understand what is happening during a flow execution?
728
+
729
+ Use `Micro::Case::Result#transitions`!
730
+
731
+ 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.
732
+
733
+ ```ruby
734
+ user_authenticated =
735
+ Users::Authenticate.call(email: 'rodrigo@test.com', password: user_password)
736
+
737
+ user_authenticated.transitions
738
+ [
739
+ {
740
+ :use_case => {
741
+ :class => Users::Find,
742
+ :attributes => { :email => "rodrigo@test.com" }
743
+ },
744
+ :success => {
745
+ :type => :ok,
746
+ :value => {
747
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
748
+ }
749
+ },
750
+ :accessible_attributes => [ :email, :password ]
751
+ },
752
+ {
753
+ :use_case => {
754
+ :class => Users::ValidatePassword,
755
+ :attributes => {
756
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
757
+ :password => "123456"
758
+ }
759
+ },
760
+ :success => {
761
+ :type => :ok,
762
+ :value => {
763
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
764
+ }
765
+ },
766
+ :accessible_attributes => [ :email, :password, :user ]
767
+ }
768
+ ]
769
+ ```
770
+
771
+ The example above shows the output generated by the `Micro::Case::Result#transitions`.
772
+ 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.
773
+
774
+ 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).
775
+
776
+ > **Note:** The [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) increments the `Micro::Case::Result#transitions`.
777
+
778
+ PS: Use the `Micro::Case::Result.disable_transition_tracking` global feature toggle to disable this feature (use once) and increase the use cases' performance.
779
+
780
+ ##### `Micro::Case::Result#transitions` schema
781
+ ```ruby
782
+ [
783
+ {
784
+ use_case: {
785
+ class: <Micro::Case>,# Use case which was executed
786
+ attributes: <Hash> # (Input) The use case's attributes
787
+ },
788
+ [success:, failure:] => { # (Output)
789
+ type: <Symbol>, # Result type. Defaults:
790
+ # Success = :ok, Failure = :error/:exception
791
+ value: <Hash> # The data returned by the use case
792
+ },
793
+ accessible_attributes: <Array>, # Properties that can be accessed by the use case's attributes,
794
+ # starting with Hash used to invoke it and which are incremented
795
+ # with each result value of the flow's use cases.
796
+ }
797
+ ]
798
+ ```
799
+
800
+ #### Is it possible to declare a flow which includes the use case itself?
602
801
 
603
802
  Answer: Yes, it is! You can use the `self.call!` macro. e.g:
604
803
 
@@ -620,16 +819,15 @@ class ConvertNumberToText < Micro::Case
620
819
  end
621
820
 
622
821
  class Double < Micro::Case
822
+ flow ConvertTextToNumber,
823
+ self.call!,
824
+ ConvertNumberToText
825
+
623
826
  attribute :number
624
827
 
625
828
  def call!
626
829
  Success { { number: number * 2 } }
627
830
  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
831
  end
634
832
 
635
833
  result = Double.call(text: '4')
@@ -712,7 +910,9 @@ end
712
910
  # Examples: https://github.com/serradura/u-case/blob/5a85fc238b63811a32737493dc6c59965f92491d/test/micro/case/safe_test.rb#L95-L123
713
911
  ```
714
912
 
715
- **Flows:**
913
+ [⬆️ Back to Top](#table-of-contents-)
914
+
915
+ #### `Micro::Case::Safe::Flow`
716
916
 
717
917
  As the safe use cases, safe flows can intercept an exception in any of its steps. These are the ways to define one:
718
918
 
@@ -746,7 +946,6 @@ module Users
746
946
  end
747
947
  end
748
948
 
749
-
750
949
  # !------------------------------------------ ! #
751
950
  # ! Deprecated: Micro::Case::Safe::Flow mixin ! #
752
951
  # !-------------------------------------------! #
@@ -767,11 +966,59 @@ end
767
966
 
768
967
  [⬆️ Back to Top](#table-of-contents-)
769
968
 
770
- ### `u-case/with_validation` - How to validate use case attributes?
969
+ #### `Micro::Case::Result#on_exception`
970
+
971
+ In functional programming errors/exceptions are handled as regular data, the idea is to transform the output even when it happens an unexpected behavior. For many, [exceptions are very similar to the GOTO statement](https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why), jumping the application flow to paths which could be difficult to figure out how things work in a system.
972
+
973
+ To address this the `Micro::Case::Result` has a special hook `#on_exception` to helping you to handle the control flow in the case of exceptions.
974
+
975
+ > **Note** this feature will work better if you use it with a `Micro::Case::Safe` use case/flow.
976
+
977
+ How does it work?
978
+
979
+ ```ruby
980
+ class Divide < Micro::Case::Safe
981
+ attributes :a, :b
982
+
983
+ def call!
984
+ Success(division: a / b)
985
+ end
986
+ end
987
+
988
+ Divide
989
+ .call(a: 2, b: 0)
990
+ .on_success { |result| puts result[:division] }
991
+ .on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
992
+ .on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
993
+ .on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
994
+
995
+ # Output:
996
+ # -------
997
+ # Can't divide a number by 0
998
+ # Oh no, something went wrong!
999
+
1000
+ Divide.
1001
+ .call(a: 2, b: '2').
1002
+ .on_success { |result| puts result[:division] }
1003
+ .on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
1004
+ .on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
1005
+ .on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
1006
+
1007
+ # Output:
1008
+ # -------
1009
+ # Please, use only numeric attributes.
1010
+ # Oh no, something went wrong!
1011
+ ```
1012
+
1013
+ As you can see, this hook has the same behavior of `result.on_failure(:exception)`, but, the ideia here is to have a better communication in the code, making an explicit reference when some failure happened because of an exception.
1014
+
1015
+ [⬆️ Back to Top](#table-of-contents-)
1016
+
1017
+ ### `u-case/with_activemodel_validation` - How to validate use case attributes?
771
1018
 
772
1019
  **Requirement:**
773
1020
 
774
- To do this your application must have the [activemodel >= 3.2](https://rubygems.org/gems/activemodel) as a dependency.
1021
+ To do this your application must have the [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) as a dependency.
775
1022
 
776
1023
  ```ruby
777
1024
  #
@@ -795,10 +1042,10 @@ end
795
1042
  # your use cases on validation errors, you can use:
796
1043
 
797
1044
  # In some file. e.g: A Rails initializer
798
- require 'u-case/with_validation' # or require 'micro/case/with_validation'
1045
+ require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
799
1046
 
800
1047
  # In the Gemfile
801
- gem 'u-case', require: 'u-case/with_validation'
1048
+ gem 'u-case', require: 'u-case/with_activemodel_validation'
802
1049
 
803
1050
  # Using this approach, you can rewrite the previous example with less code. e.g:
804
1051
 
@@ -823,7 +1070,7 @@ end
823
1070
  Answer: Yes, it is. To do this, you only need to use the `disable_auto_validation` macro. e.g:
824
1071
 
825
1072
  ```ruby
826
- require 'u-case/with_validation'
1073
+ require 'u-case/with_activemodel_validation'
827
1074
 
828
1075
  class Multiply < Micro::Case
829
1076
  disable_auto_validation
@@ -845,6 +1092,31 @@ Multiply.call(a: 2, b: 'a')
845
1092
 
846
1093
  [⬆️ Back to Top](#table-of-contents-)
847
1094
 
1095
+ #### Kind::Validator
1096
+
1097
+ 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).
1098
+
1099
+ The example below shows how to validate the attributes data types.
1100
+
1101
+ ```ruby
1102
+ class Todo::List::AddItem < Micro::Case
1103
+ attributes :user, :params
1104
+
1105
+ validates :user, kind: User
1106
+ validates :params, kind: ActionController::Parameters
1107
+
1108
+ def call!
1109
+ todo_params = Todo::Params.to_save(params)
1110
+
1111
+ todo = user.todos.create(todo_params)
1112
+
1113
+ Success { { todo: todo} }
1114
+ rescue ActionController::ParameterMissing => e
1115
+ Failure(:parameter_missing) { { message: e.message } }
1116
+ end
1117
+ end
1118
+ ```
1119
+
848
1120
  ## Benchmarks
849
1121
 
850
1122
  ### `Micro::Case`
@@ -853,25 +1125,25 @@ Multiply.call(a: 2, b: 'a')
853
1125
 
854
1126
  The table below contains the average between the [Success results](#success-results) and [Failure results](#failure-results) benchmarks.
855
1127
 
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 |
1128
+ | Gem / Abstraction | Iterations per second | Comparison |
1129
+ | ---------------------- | --------------------: | ----------------: |
1130
+ | **Micro::Case** | 116629.7 | _**The Fastest**_ |
1131
+ | Dry::Monads | 101796.3 | 1.14x slower |
1132
+ | Interactor | 21230.5 | 5.49x slower |
1133
+ | Trailblazer::Operation | 16466.6 | 7.08x slower |
1134
+ | Dry::Transaction | 5069.5 | 23.00x slower |
863
1135
 
864
1136
  ---
865
1137
 
866
1138
  #### Success results
867
1139
 
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 |
1140
+ | Gem / Abstraction | Iterations per second | Comparison |
1141
+ | ----------------- | --------------------: | ----------------: |
1142
+ | Dry::Monads | 139352.5 | _**The Fastest**_ |
1143
+ | **Micro::Case** | 124749.4 | 1.12x slower |
1144
+ | Interactor | 28974.4 | 4.81x slower |
1145
+ | Trailblazer::Operation | 17275.6 | 8.07x slower |
1146
+ | Dry::Transaction | 5571.7 | 25.01x slower |
875
1147
 
876
1148
  <details>
877
1149
  <summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
@@ -911,13 +1183,13 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success
911
1183
 
912
1184
  #### Failure results
913
1185
 
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 |
1186
+ | Gem / Abstraction | Iterations per second | Comparison |
1187
+ | ----------------- | --------------------: | ----------------: |
1188
+ | **Micro::Case** | 108510.0 | _**The Fastest**_ |
1189
+ | Dry::Monads | 64240.1 | 1.69x slower |
1190
+ | Trailblazer::Operation | 15657.7 | 6.93x slower |
1191
+ | Interactor | 13486.7 | 8.05x slower |
1192
+ | Dry::Transaction | 4567.3 | 23.76x slower |
921
1193
 
922
1194
  <details>
923
1195
  <summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
@@ -960,10 +1232,10 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure
960
1232
  ### `Micro::Case::Flow`
961
1233
 
962
1234
  | 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 |
1235
+ | ------------------ | ----------------: | ----------------: |
1236
+ | Micro::Case::Flow | _**The Fastest**_ | _**The Fastest**_ |
1237
+ | Micro::Case::Safe::Flow | 0x slower | 0x slower |
1238
+ | Interactor::Organizer | 1.47x slower | 5.51x slower |
967
1239
 
968
1240
  \* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
969
1241
 
@@ -1022,13 +1294,27 @@ Check it out implementations of the same use case with different gems/abstractio
1022
1294
 
1023
1295
  ## Examples
1024
1296
 
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)
1297
+ ### 1️⃣ Rails App (API)
1298
+
1299
+ > 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.
1300
+ >
1301
+ > Link: https://github.com/serradura/from-fat-controllers-to-use-cases
1302
+
1303
+ ### 2️⃣ CLI calculator
1304
+
1305
+ > Rake tasks to demonstrate how to handle user data, and how to use different failure types to control the program flow.
1306
+ >
1307
+ > Link: https://github.com/serradura/u-case/tree/master/examples/calculator
1308
+
1309
+ ### 3️⃣ Users creation
1310
+
1311
+ > An example of a use case flow that define steps to sanitize, validate, and persist its input data.
1312
+ >
1313
+ > Link: https://github.com/serradura/u-case/blob/master/examples/users_creation.rb
1027
1314
 
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)
1315
+ ### 4️⃣ Rescuing exception inside of the use cases
1030
1316
 
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.
1317
+ > Link: https://github.com/serradura/u-case/blob/master/examples/rescuing_exceptions.rb
1032
1318
 
1033
1319
  [⬆️ Back to Top](#table-of-contents-)
1034
1320