u-case 2.3.1 → 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: 9f5a28fb237f74e009225d20bcc65115e6d8159818a3cfdbfcb7e23c3726aef4
4
- data.tar.gz: fa34ab50f7b2d0df65ba75f4b59734e0326d8431652823ad35e406d243801853
3
+ metadata.gz: d5b58d2e19bca34b4337e348d4d498b21ee12134082f897122c957ab79e19fa7
4
+ data.tar.gz: 6cb9e76227b0dc96d92de0c7ae1abdfe4bce2f840f0dced7ce1cae24908c0efb
5
5
  SHA512:
6
- metadata.gz: cd2a5fd871fd561ab98d96297e474f3ea251cdee866d5b14cc69e5e04bf654b98c3b2f75ef55d4c774f0d91ca6297a89746c923a29cd1fd1985e5761db6848b3
7
- data.tar.gz: 12c467840bab805b562ae9de9fe6c80ccda0129672989d6b14f844e916fd270f0f5f9c2f213fed508c439e6d17142712f2013475bbc6f0f9a22066ab0aeb84ef
6
+ metadata.gz: ea7dd10cd004dc3ef7e3fbfe062afc6e7a09e172b9fca1c5d2f2300c6d876b19eeb4935d06c8650010b14ca318486226fe08ec5c9fa57a9570870caf9b470bb2
7
+ data.tar.gz: 5742702a5c64d2ed8a32cb83aaa6ee8fb5cb550b7144eba248cf058e340f47751528e38214991f1c92c50370b4a6240f7e29a963d872ffe78219d2d91fa5637e
data/Gemfile CHANGED
@@ -18,7 +18,19 @@ end
18
18
  group :test do
19
19
  gem 'minitest', activemodel_version < '4.1' ? '~> 4.2' : '~> 5.0'
20
20
  gem 'simplecov', require: false
21
- 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}"
22
34
  end
23
35
 
24
36
  # Specify your gem's dependencies in u-case.gemspec
data/README.md CHANGED
@@ -1,15 +1,16 @@
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.
@@ -18,44 +19,46 @@ The main project goals are:
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.
19
20
 
20
21
  ## Table of Contents <!-- omit in toc -->
21
- - [μ-case (Micro::Case)](#μ-case-microcase)
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 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)
38
- - [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)
39
- - [Micro::Case::Strict - What is a strict use case?](#microcasestrict---what-is-a-strict-use-case)
40
- - [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)
41
- - [u-case/with_validation - How to validate use case attributes?](#u-casewith_validation---how-to-validate-use-case-attributes)
42
- - [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)
43
- - [Benchmarks](#benchmarks)
44
- - [Micro::Case](#microcase)
45
- - [Best overall](#best-overall)
46
- - [Success results](#success-results)
47
- - [Failure results](#failure-results)
48
- - [Micro::Case::Flow](#microcaseflow)
49
- - [Comparisons](#comparisons)
50
- - [Examples](#examples)
51
- - [1️⃣ Rails App (API)](#1️⃣-rails-app-api)
52
- - [2️⃣ CLI calculator](#2️⃣-cli-calculator)
53
- - [3️⃣ Users creation](#3️⃣-users-creation)
54
- - [4️⃣ Rescuing exception inside of the use cases](#4️⃣-rescuing-exception-inside-of-the-use-cases)
55
- - [Development](#development)
56
- - [Contributing](#contributing)
57
- - [License](#license)
58
- - [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)
59
62
 
60
63
  ## Required Ruby version
61
64
 
@@ -63,8 +66,15 @@ The main project goals are:
63
66
 
64
67
  ## Dependencies
65
68
 
66
- This project depends on [Micro::Attribute](https://github.com/serradura/u-attributes) gem.
67
- 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.
68
78
 
69
79
  ## Installation
70
80
 
@@ -642,12 +652,147 @@ Note: You can blend any of the [available syntaxes/approaches](#how-to-create-a-
642
652
 
643
653
  [⬆️ Back to Top](#table-of-contents-)
644
654
 
645
- #### 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:
646
702
 
647
- 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.
648
722
 
649
723
  [⬆️ Back to Top](#table-of-contents-)
650
724
 
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
+
651
796
  #### Is it possible to declare a flow which includes the use case itself?
652
797
 
653
798
  Answer: Yes, it is! You can use the `self.call!` macro. e.g:
@@ -816,11 +961,11 @@ end
816
961
 
817
962
  [⬆️ Back to Top](#table-of-contents-)
818
963
 
819
- ### `u-case/with_validation` - How to validate use case attributes?
964
+ ### `u-case/with_activemodel_validation` - How to validate use case attributes?
820
965
 
821
966
  **Requirement:**
822
967
 
823
- 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.
824
969
 
825
970
  ```ruby
826
971
  #
@@ -844,10 +989,10 @@ end
844
989
  # your use cases on validation errors, you can use:
845
990
 
846
991
  # In some file. e.g: A Rails initializer
847
- require 'u-case/with_validation' # or require 'micro/case/with_validation'
992
+ require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
848
993
 
849
994
  # In the Gemfile
850
- gem 'u-case', require: 'u-case/with_validation'
995
+ gem 'u-case', require: 'u-case/with_activemodel_validation'
851
996
 
852
997
  # Using this approach, you can rewrite the previous example with less code. e.g:
853
998
 
@@ -872,7 +1017,7 @@ end
872
1017
  Answer: Yes, it is. To do this, you only need to use the `disable_auto_validation` macro. e.g:
873
1018
 
874
1019
  ```ruby
875
- require 'u-case/with_validation'
1020
+ require 'u-case/with_activemodel_validation'
876
1021
 
877
1022
  class Multiply < Micro::Case
878
1023
  disable_auto_validation
@@ -894,6 +1039,31 @@ Multiply.call(a: 2, b: 'a')
894
1039
 
895
1040
  [⬆️ Back to Top](#table-of-contents-)
896
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
+
897
1067
  ## Benchmarks
898
1068
 
899
1069
  ### `Micro::Case`
@@ -902,25 +1072,25 @@ Multiply.call(a: 2, b: 'a')
902
1072
 
903
1073
  The table below contains the average between the [Success results](#success-results) and [Failure results](#failure-results) benchmarks.
904
1074
 
905
- | Gem / Abstraction | Iterations per second | Comparison |
906
- | ---------------------- | --------------------: | ---------------: |
907
- | **Micro::Case** | 116629.7 | _**The Faster**_ |
908
- | Dry::Monads | 101796.3 | 1.14x slower |
909
- | Interactor | 21230.5 | 5.49x slower |
910
- | Trailblazer::Operation | 16466.6 | 7.08x slower |
911
- | 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 |
912
1082
 
913
1083
  ---
914
1084
 
915
1085
  #### Success results
916
1086
 
917
- | Gem / Abstraction | Iterations per second | Comparison |
918
- | ----------------- | --------------------: | ---------------: |
919
- | Dry::Monads | 139352.5 | _**The Faster**_ |
920
- | **Micro::Case** | 124749.4 | 1.12x slower |
921
- | Interactor | 28974.4 | 4.81x slower |
922
- | Trailblazer::Operation | 17275.6 | 8.07x slower |
923
- | 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 |
924
1094
 
925
1095
  <details>
926
1096
  <summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
@@ -960,13 +1130,13 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success
960
1130
 
961
1131
  #### Failure results
962
1132
 
963
- | Gem / Abstraction | Iterations per second | Comparison |
964
- | ----------------- | --------------------: | ---------------: |
965
- | **Micro::Case** | 108510.0 | _**The Faster**_ |
966
- | Dry::Monads | 64240.1 | 1.69x slower |
967
- | Trailblazer::Operation | 15657.7 | 6.93x slower |
968
- | Interactor | 13486.7 | 8.05x slower |
969
- | 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 |
970
1140
 
971
1141
  <details>
972
1142
  <summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
@@ -1009,10 +1179,10 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure
1009
1179
  ### `Micro::Case::Flow`
1010
1180
 
1011
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) |
1012
- | ------------------ | ---------------: | ---------------: |
1013
- | Micro::Case::Flow | _**The Faster**_ | _**The Faster**_ |
1014
- | Micro::Case::Safe::Flow | 0x slower | 0x slower |
1015
- | 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 |
1016
1186
 
1017
1187
  \* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
1018
1188
 
@@ -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,6 +43,14 @@ module Micro
42
43
  instance
43
44
  end
44
45
 
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
50
+
51
+ __new__(result, arg).call
52
+ end
53
+
45
54
  def self.__call!
46
55
  return const_get(:Flow_Step) if const_defined?(:Flow_Step)
47
56
 
@@ -61,18 +70,22 @@ module Micro
61
70
  end
62
71
 
63
72
  def self.__flow_get
64
- @__flow
73
+ return @__flow if defined?(@__flow)
65
74
  end
66
75
 
67
- private_class_method def self.__flow_use_cases_set(args)
68
- @__flow_use_cases = args
76
+ private_class_method def self.__flow_use_cases
77
+ return @__flow_use_cases if defined?(@__flow_use_cases)
69
78
  end
70
79
 
71
80
  private_class_method def self.__flow_use_cases_get
72
- Array(@__flow_use_cases)
81
+ Array(__flow_use_cases)
73
82
  .map { |use_case| use_case == self ? self.__call! : use_case }
74
83
  end
75
84
 
85
+ private_class_method def self.__flow_use_cases_set(args)
86
+ @__flow_use_cases = args
87
+ end
88
+
76
89
  private_class_method def self.__flow_set(args)
77
90
  return if __flow_get
78
91
 
@@ -84,7 +97,7 @@ module Micro
84
97
  end
85
98
 
86
99
  def self.__flow_set!
87
- __flow_set(__flow_use_cases_get) if !__flow_get && @__flow_use_cases
100
+ __flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
88
101
  end
89
102
 
90
103
  def self.flow(*args)
@@ -105,7 +118,7 @@ module Micro
105
118
 
106
119
  def __set_result__(result)
107
120
  raise Error::InvalidResultInstance unless result.is_a?(Result)
108
- raise Error::ResultIsAlreadyDefined if @__result
121
+ raise Error::ResultIsAlreadyDefined if defined?(@__result)
109
122
 
110
123
  @__result = result
111
124
  end
@@ -145,18 +158,14 @@ module Micro
145
158
  def Success(arg = :ok)
146
159
  value, type = block_given? ? [yield, arg] : [arg, :ok]
147
160
 
148
- __get_result__.__set__(true, value, type, nil)
161
+ __get_result_with(true, value, type)
149
162
  end
150
163
 
151
164
  def Failure(arg = :error)
152
165
  value = block_given? ? yield : arg
153
166
  type = __map_failure_type(value, block_given? ? arg : :error)
154
167
 
155
- __get_result__.__set__(false, value, type, self)
156
- end
157
-
158
- def __get_result__
159
- @__result ||= Result.new
168
+ __get_result_with(false, value, type)
160
169
  end
161
170
 
162
171
  def __map_failure_type(arg, type)
@@ -166,5 +175,13 @@ module Micro
166
175
 
167
176
  type
168
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
169
186
  end
170
187
  end
@@ -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
 
@@ -67,12 +84,32 @@ module Micro
67
84
 
68
85
  return self if failure?
69
86
 
70
- arg.call(self.value)
87
+ arg.__call_and_set_transition__(self, self.value)
71
88
  end
72
89
  end
73
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
+
74
105
  private
75
106
 
107
+ def __set_transitions_accessible_attributes__!(attribute_names)
108
+ @__transitions_accessible_attributes__.merge(
109
+ attribute_names
110
+ )
111
+ end
112
+
76
113
  def success_type?(expected_type)
77
114
  success? && (expected_type.nil? || expected_type == type)
78
115
  end
@@ -84,6 +121,22 @@ module Micro
84
121
  def is_a_use_case?(arg)
85
122
  (arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
86
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
87
140
  end
88
141
  end
89
142
  end
@@ -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
@@ -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.3.1'.freeze
5
+ VERSION = '2.4.0'.freeze
6
6
  end
7
7
  end
@@ -7,7 +7,7 @@ module Micro
7
7
  include Micro::Attributes::Features::ActiveModelValidations
8
8
 
9
9
  def self.auto_validation_disabled?
10
- @disable_auto_validation
10
+ return @disable_auto_validation if defined?(@disable_auto_validation)
11
11
  end
12
12
 
13
13
  def self.disable_auto_validation
@@ -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'
@@ -25,8 +25,9 @@ Gem::Specification.new do |spec|
25
25
 
26
26
  spec.required_ruby_version = '>= 2.2.0'
27
27
 
28
+ spec.add_runtime_dependency 'kind', '~> 3.0'
28
29
  spec.add_runtime_dependency 'u-attributes', '~> 1.1'
29
30
 
30
31
  spec.add_development_dependency 'bundler'
31
- spec.add_development_dependency 'rake', '~> 10.0'
32
+ spec.add_development_dependency 'rake', '~> 13.0'
32
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.3.1
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-29 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