u-case 2.2.0 → 2.6.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: 3ba53faeaf465b491f94a686da7fc2537e9c50108b18162d11c473246e6a531e
4
- data.tar.gz: 6b91c842ad57b917678649f4003f34ade8459a70c56938d2e226a4d8322e855d
3
+ metadata.gz: de7b8e442e0b578b478d9d63f05901c9023884f3f2b15a1819228af36fe7afc5
4
+ data.tar.gz: 50e9128500306350a56102b93ca8bd171c75ecae24899c6157a7955e845221a3
5
5
  SHA512:
6
- metadata.gz: 8d4790abfb49211a69d9874900e83a0237d5fe0f0b288102e9bcff33fd932db4192e66ea2e5814990da2d5e92b5499dd93173257433639e2248463f2b3c991e8
7
- data.tar.gz: e2bd4e73018961d931a76298dacf8000836d26f2331f975b777bc0371dcfe84d2064a51ef12636f31eaf22986a76b97f99e7cbe16b653a1bd1923ae5f8f4f125
6
+ metadata.gz: 93fcec72232ac2d1ce479cdc2efa02d7b4eddf0d3b57e2c93346ecb6359a0f9ccba33f7851b2115c813ef53f79f9276984864f172c341be23be8c11120e933fe
7
+ data.tar.gz: 9993e5cdcbe6f1e864c32a9db7f637c1cdb839bd838d1823e64b2646b704b079d9f83915af65e364abe8edd66034952202909a42833a634d04aeac11eee384bd
@@ -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,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,43 +19,49 @@ 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)](#%ce%bc-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
- - [Micro::Case::Flow - How to compose use cases?](#microcaseflow---how-to-compose-use-cases)
35
- - [Is it possible to compose a use case flow with other ones?](#is-it-possible-to-compose-a-use-case-flow-with-other-ones)
36
- - [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)
37
- - [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)
38
- - [Micro::Case::Strict - What is a strict use case?](#microcasestrict---what-is-a-strict-use-case)
39
- - [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)
40
- - [u-case/with_validation - How to validate use case attributes?](#u-casewithvalidation---how-to-validate-use-case-attributes)
41
- - [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)
42
- - [Benchmarks](#benchmarks)
43
- - [Micro::Case](#microcase)
44
- - [Best overall](#best-overall)
45
- - [Success results](#success-results)
46
- - [Failure results](#failure-results)
47
- - [Micro::Case::Flow](#microcaseflow)
48
- - [Comparisons](#comparisons)
49
- - [Examples](#examples)
50
- - [1️⃣ Rails App (API)](#1%ef%b8%8f%e2%83%a3-rails-app-api)
51
- - [2️⃣ CLI calculator](#2%ef%b8%8f%e2%83%a3-cli-calculator)
52
- - [3️⃣ Users creation](#3%ef%b8%8f%e2%83%a3-users-creation)
53
- - [4️⃣ Rescuing exception inside of the use cases](#4%ef%b8%8f%e2%83%a3-rescuing-exception-inside-of-the-use-cases)
54
- - [Development](#development)
55
- - [Contributing](#contributing)
56
- - [License](#license)
57
- - [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
+ - [How to make attributes data injection using this feature?](#how-to-make-attributes-data-injection-using-this-feature)
36
+ - [`Micro::Case::Flow` - How to compose use cases?](#microcaseflow---how-to-compose-use-cases)
37
+ - [Is it possible to compose a use case flow with other ones?](#is-it-possible-to-compose-a-use-case-flow-with-other-ones)
38
+ - [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)
39
+ - [How to understand what is happening during a flow execution?](#how-to-understand-what-is-happening-during-a-flow-execution)
40
+ - [`Micro::Case::Result#transitions` schema](#microcaseresulttransitions-schema)
41
+ - [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)
42
+ - [`Micro::Case::Strict` - What is a strict use case?](#microcasestrict---what-is-a-strict-use-case)
43
+ - [`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)
44
+ - [`Micro::Case::Safe::Flow`](#microcasesafeflow)
45
+ - [`Micro::Case::Result#on_exception`](#microcaseresulton_exception)
46
+ - [`u-case/with_activemodel_validation` - How to validate use case attributes?](#u-casewith_activemodel_validation---how-to-validate-use-case-attributes)
47
+ - [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)
48
+ - [`Kind::Validator`](#kindvalidator)
49
+ - [Benchmarks](#benchmarks)
50
+ - [`Micro::Case`](#microcase)
51
+ - [Best overall](#best-overall)
52
+ - [Success results](#success-results)
53
+ - [Failure results](#failure-results)
54
+ - [`Micro::Case::Flow`](#microcaseflow)
55
+ - [Comparisons](#comparisons)
56
+ - [Examples](#examples)
57
+ - [1️⃣ Rails App (API)](#1️⃣-rails-app-api)
58
+ - [2️⃣ CLI calculator](#2️⃣-cli-calculator)
59
+ - [3️⃣ Users creation](#3️⃣-users-creation)
60
+ - [4️⃣ Rescuing exception inside of the use cases](#4️⃣-rescuing-exception-inside-of-the-use-cases)
61
+ - [Development](#development)
62
+ - [Contributing](#contributing)
63
+ - [License](#license)
64
+ - [Code of Conduct](#code-of-conduct)
58
65
 
59
66
  ## Required Ruby version
60
67
 
@@ -62,8 +69,15 @@ The main project goals are:
62
69
 
63
70
  ## Dependencies
64
71
 
65
- This project depends on [Micro::Attribute](https://github.com/serradura/u-attributes) gem.
66
- It is used to define the use case attributes.
72
+ 1. [`kind`](https://github.com/serradura/kind) gem.
73
+
74
+ A simple type system (at runtime) for Ruby.
75
+
76
+ 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.
77
+ 2. [`u-attributes`](https://github.com/serradura/u-attributes) gem.
78
+
79
+ This gem allows defining read-only attributes, that is, your objects will have only getters to access their attributes data.
80
+ It is used to define the use case attributes.
67
81
 
68
82
  ## Installation
69
83
 
@@ -145,6 +159,7 @@ A `Micro::Case::Result` stores the use cases output data. These are their main m
145
159
  - `#type` a Symbol which gives meaning for the result, this is useful to declare different types of failures or success.
146
160
  - `#on_success` or `#on_failure` are hook methods that help you define the application flow.
147
161
  - `#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).
162
+ - `#then` allows if the current result is a success, the `then` method will allow to applying a new use case for its value.
148
163
 
149
164
  [⬆️ Back to Top](#table-of-contents-)
150
165
 
@@ -415,6 +430,65 @@ accum # 24
415
430
  result.value * 4 == accum # true
416
431
  ```
417
432
 
433
+ #### How to use the `Micro::Case::Result#then` method?
434
+
435
+ This method allows you to create dynamic flows, so, with it,
436
+ you can add new use cases or flows to continue the result transformation. e.g:
437
+
438
+ ```ruby
439
+ class ForbidNegativeNumber < Micro::Case
440
+ attribute :number
441
+
442
+ def call!
443
+ return Success { attributes } if number >= 0
444
+
445
+ Failure { attributes }
446
+ end
447
+ end
448
+
449
+ class Add3 < Micro::Case
450
+ attribute :number
451
+
452
+ def call!
453
+ Success { { number: number + 3 } }
454
+ end
455
+ end
456
+
457
+ result1 =
458
+ ForbidNegativeNumber
459
+ .call(number: -1)
460
+ .then(Add3)
461
+
462
+ result1.value # {'number' => -1}
463
+ result1.failure? # true
464
+
465
+ # ---
466
+
467
+ result2 =
468
+ ForbidNegativeNumber
469
+ .call(number: 1)
470
+ .then(Add3)
471
+
472
+ result2.value # {'number' => 4}
473
+ result2.success? # true
474
+ ```
475
+
476
+ > **Note:** this method changes the [`Micro::Case::Result#transitions`](#how-to-understand-what-is-happening-during-a-flow-execution).
477
+
478
+ [⬆️ Back to Top](#table-of-contents-)
479
+
480
+ ##### How to make attributes data injection using this feature?
481
+
482
+ Pass a Hash as the second argument of the `Micro::Case::Result#then` method.
483
+
484
+ ```ruby
485
+ Todo::FindAllForUser
486
+ .call(user: current_user, params: params)
487
+ .then(Paginate)
488
+ .then(Serialize::PaginatedRelationAsJson, serializer: Todo::Serializer)
489
+ .on_success { |result| render_json(200, data: result[:todos]) }
490
+ ```
491
+
418
492
  [⬆️ Back to Top](#table-of-contents-)
419
493
 
420
494
  ### `Micro::Case::Flow` - How to compose use cases?
@@ -598,12 +672,149 @@ Note: You can blend any of the [available syntaxes/approaches](#how-to-create-a-
598
672
 
599
673
  [⬆️ Back to Top](#table-of-contents-)
600
674
 
601
- #### Is it possible a flow accumulates its input and merges each success result to use as the argument of their use cases?
675
+ #### Is it possible a flow accumulates its input and merges each success result to use as the argument of the next use cases?
676
+
677
+ Answer: Yes, it is! Look at the example below to understand how the data accumulation works inside of the flow execution.
678
+
679
+ ```ruby
680
+ module Users
681
+ class Find < Micro::Case
682
+ attribute :email
683
+
684
+ def call!
685
+ user = User.find_by(email: email)
686
+
687
+ return Success { { user: user } } if user
688
+
689
+ Failure(:user_not_found)
690
+ end
691
+ end
692
+ end
693
+
694
+ module Users
695
+ class ValidatePassword < Micro::Case::Strict
696
+ attributes :user, :password
697
+
698
+ def call!
699
+ return Failure(:user_must_be_persisted) if user.new_record?
700
+ return Failure(:wrong_password) if user.wrong_password?(password)
701
+
702
+ return Success { attributes(:user) }
703
+ end
704
+ end
705
+ end
706
+
707
+ module Users
708
+ Authenticate = Micro::Case::Flow([
709
+ Find,
710
+ ValidatePassword
711
+ ])
712
+ end
713
+
714
+ Users::Authenticate
715
+ .call(email: 'somebody@test.com', password: 'password')
716
+ .on_success { |result| sign_in(result[:user]) }
717
+ .on_failure(:wrong_password) { |result| render status: 401 }
718
+ .on_failure(:user_not_found) { |result| render status: 404 }
719
+ ```
602
720
 
603
- 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.
721
+ First, lets see the attribute of each use case:
722
+
723
+ ```ruby
724
+ class Users::Find < Micro::Case
725
+ attribute :email
726
+ end
727
+
728
+ class Users::ValidatePassword < Micro::Case
729
+ attributes :user, :password
730
+ end
731
+ ```
732
+
733
+ As you can see the `Users::ValidatePassword` expects a user as its input. So, how does it receives the user?
734
+ It receives the user from the `Users::Find` success result!
735
+
736
+ And this, is the power of use cases composition because the output
737
+ of one flow will compose the input of the next use case in the flow!
738
+
739
+ > input **>>** process **>>** output
740
+
741
+ > **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.
604
742
 
605
743
  [⬆️ Back to Top](#table-of-contents-)
606
744
 
745
+ #### How to understand what is happening during a flow execution?
746
+
747
+ Use `Micro::Case::Result#transitions`!
748
+
749
+ 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.
750
+
751
+ ```ruby
752
+ user_authenticated =
753
+ Users::Authenticate.call(email: 'rodrigo@test.com', password: user_password)
754
+
755
+ user_authenticated.transitions
756
+ [
757
+ {
758
+ :use_case => {
759
+ :class => Users::Find,
760
+ :attributes => { :email => "rodrigo@test.com" }
761
+ },
762
+ :success => {
763
+ :type => :ok,
764
+ :value => {
765
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
766
+ }
767
+ },
768
+ :accessible_attributes => [ :email, :password ]
769
+ },
770
+ {
771
+ :use_case => {
772
+ :class => Users::ValidatePassword,
773
+ :attributes => {
774
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
775
+ :password => "123456"
776
+ }
777
+ },
778
+ :success => {
779
+ :type => :ok,
780
+ :value => {
781
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
782
+ }
783
+ },
784
+ :accessible_attributes => [ :email, :password, :user ]
785
+ }
786
+ ]
787
+ ```
788
+
789
+ The example above shows the output generated by the `Micro::Case::Result#transitions`.
790
+ 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.
791
+
792
+ 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).
793
+
794
+ > **Note:** The [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) increments the `Micro::Case::Result#transitions`.
795
+
796
+ PS: Use the `Micro::Case::Result.disable_transition_tracking` global feature toggle to disable this feature (use once) and increase the use cases' performance.
797
+
798
+ ##### `Micro::Case::Result#transitions` schema
799
+ ```ruby
800
+ [
801
+ {
802
+ use_case: {
803
+ class: <Micro::Case>,# Use case which was executed
804
+ attributes: <Hash> # (Input) The use case's attributes
805
+ },
806
+ [success:, failure:] => { # (Output)
807
+ type: <Symbol>, # Result type. Defaults:
808
+ # Success = :ok, Failure = :error/:exception
809
+ value: <Hash> # The data returned by the use case
810
+ },
811
+ accessible_attributes: <Array>, # Properties that can be accessed by the use case's attributes,
812
+ # starting with Hash used to invoke it and which are incremented
813
+ # with each result value of the flow's use cases.
814
+ }
815
+ ]
816
+ ```
817
+
607
818
  #### Is it possible to declare a flow which includes the use case itself?
608
819
 
609
820
  Answer: Yes, it is! You can use the `self.call!` macro. e.g:
@@ -717,7 +928,9 @@ end
717
928
  # Examples: https://github.com/serradura/u-case/blob/5a85fc238b63811a32737493dc6c59965f92491d/test/micro/case/safe_test.rb#L95-L123
718
929
  ```
719
930
 
720
- **Flows:**
931
+ [⬆️ Back to Top](#table-of-contents-)
932
+
933
+ #### `Micro::Case::Safe::Flow`
721
934
 
722
935
  As the safe use cases, safe flows can intercept an exception in any of its steps. These are the ways to define one:
723
936
 
@@ -751,7 +964,6 @@ module Users
751
964
  end
752
965
  end
753
966
 
754
-
755
967
  # !------------------------------------------ ! #
756
968
  # ! Deprecated: Micro::Case::Safe::Flow mixin ! #
757
969
  # !-------------------------------------------! #
@@ -772,11 +984,59 @@ end
772
984
 
773
985
  [⬆️ Back to Top](#table-of-contents-)
774
986
 
775
- ### `u-case/with_validation` - How to validate use case attributes?
987
+ #### `Micro::Case::Result#on_exception`
988
+
989
+ 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.
990
+
991
+ 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.
992
+
993
+ > **Note**: this feature will work better if you use it with a `Micro::Case::Safe` use case/flow.
994
+
995
+ How does it work?
996
+
997
+ ```ruby
998
+ class Divide < Micro::Case::Safe
999
+ attributes :a, :b
1000
+
1001
+ def call!
1002
+ Success(division: a / b)
1003
+ end
1004
+ end
1005
+
1006
+ Divide
1007
+ .call(a: 2, b: 0)
1008
+ .on_success { |result| puts result[:division] }
1009
+ .on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
1010
+ .on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
1011
+ .on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
1012
+
1013
+ # Output:
1014
+ # -------
1015
+ # Can't divide a number by 0
1016
+ # Oh no, something went wrong!
1017
+
1018
+ Divide.
1019
+ .call(a: 2, b: '2').
1020
+ .on_success { |result| puts result[:division] }
1021
+ .on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
1022
+ .on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
1023
+ .on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
1024
+
1025
+ # Output:
1026
+ # -------
1027
+ # Please, use only numeric attributes.
1028
+ # Oh no, something went wrong!
1029
+ ```
1030
+
1031
+ 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.
1032
+
1033
+ [⬆️ Back to Top](#table-of-contents-)
1034
+
1035
+ ### `u-case/with_activemodel_validation` - How to validate use case attributes?
776
1036
 
777
1037
  **Requirement:**
778
1038
 
779
- To do this your application must have the [activemodel >= 3.2](https://rubygems.org/gems/activemodel) as a dependency.
1039
+ To do this your application must have the [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) as a dependency.
780
1040
 
781
1041
  ```ruby
782
1042
  #
@@ -800,10 +1060,10 @@ end
800
1060
  # your use cases on validation errors, you can use:
801
1061
 
802
1062
  # In some file. e.g: A Rails initializer
803
- require 'u-case/with_validation' # or require 'micro/case/with_validation'
1063
+ require 'u-case/with_activemodel_validation' # or require 'micro/case/with_validation'
804
1064
 
805
1065
  # In the Gemfile
806
- gem 'u-case', require: 'u-case/with_validation'
1066
+ gem 'u-case', require: 'u-case/with_activemodel_validation'
807
1067
 
808
1068
  # Using this approach, you can rewrite the previous example with less code. e.g:
809
1069
 
@@ -828,7 +1088,7 @@ end
828
1088
  Answer: Yes, it is. To do this, you only need to use the `disable_auto_validation` macro. e.g:
829
1089
 
830
1090
  ```ruby
831
- require 'u-case/with_validation'
1091
+ require 'u-case/with_activemodel_validation'
832
1092
 
833
1093
  class Multiply < Micro::Case
834
1094
  disable_auto_validation
@@ -850,6 +1110,31 @@ Multiply.call(a: 2, b: 'a')
850
1110
 
851
1111
  [⬆️ Back to Top](#table-of-contents-)
852
1112
 
1113
+ #### `Kind::Validator`
1114
+
1115
+ 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).
1116
+
1117
+ The example below shows how to validate the attributes data types.
1118
+
1119
+ ```ruby
1120
+ class Todo::List::AddItem < Micro::Case
1121
+ attributes :user, :params
1122
+
1123
+ validates :user, kind: User
1124
+ validates :params, kind: ActionController::Parameters
1125
+
1126
+ def call!
1127
+ todo_params = Todo::Params.to_save(params)
1128
+
1129
+ todo = user.todos.create(todo_params)
1130
+
1131
+ Success { { todo: todo} }
1132
+ rescue ActionController::ParameterMissing => e
1133
+ Failure(:parameter_missing) { { message: e.message } }
1134
+ end
1135
+ end
1136
+ ```
1137
+
853
1138
  ## Benchmarks
854
1139
 
855
1140
  ### `Micro::Case`
@@ -858,25 +1143,25 @@ Multiply.call(a: 2, b: 'a')
858
1143
 
859
1144
  The table below contains the average between the [Success results](#success-results) and [Failure results](#failure-results) benchmarks.
860
1145
 
861
- | Gem / Abstraction | Iterations per second | Comparison |
862
- | ---------------------- | --------------------: | ---------------: |
863
- | **Micro::Case** | 116629.7 | _**The Faster**_ |
864
- | Dry::Monads | 101796.3 | 1.14x slower |
865
- | Interactor | 21230.5 | 5.49x slower |
866
- | Trailblazer::Operation | 16466.6 | 7.08x slower |
867
- | Dry::Transaction | 5069.5 | 23.00x slower |
1146
+ | Gem / Abstraction | Iterations per second | Comparison |
1147
+ | ---------------------- | --------------------: | ----------------: |
1148
+ | **Micro::Case** | 116629.7 | _**The Fastest**_ |
1149
+ | Dry::Monads | 101796.3 | 1.14x slower |
1150
+ | Interactor | 21230.5 | 5.49x slower |
1151
+ | Trailblazer::Operation | 16466.6 | 7.08x slower |
1152
+ | Dry::Transaction | 5069.5 | 23.00x slower |
868
1153
 
869
1154
  ---
870
1155
 
871
1156
  #### Success results
872
1157
 
873
- | Gem / Abstraction | Iterations per second | Comparison |
874
- | ----------------- | --------------------: | ---------------: |
875
- | Dry::Monads | 139352.5 | _**The Faster**_ |
876
- | **Micro::Case** | 124749.4 | 1.12x slower |
877
- | Interactor | 28974.4 | 4.81x slower |
878
- | Trailblazer::Operation | 17275.6 | 8.07x slower |
879
- | Dry::Transaction | 5571.7 | 25.01x slower |
1158
+ | Gem / Abstraction | Iterations per second | Comparison |
1159
+ | ----------------- | --------------------: | ----------------: |
1160
+ | Dry::Monads | 139352.5 | _**The Fastest**_ |
1161
+ | **Micro::Case** | 124749.4 | 1.12x slower |
1162
+ | Interactor | 28974.4 | 4.81x slower |
1163
+ | Trailblazer::Operation | 17275.6 | 8.07x slower |
1164
+ | Dry::Transaction | 5571.7 | 25.01x slower |
880
1165
 
881
1166
  <details>
882
1167
  <summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
@@ -916,13 +1201,13 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_success
916
1201
 
917
1202
  #### Failure results
918
1203
 
919
- | Gem / Abstraction | Iterations per second | Comparison |
920
- | ----------------- | --------------------: | ---------------: |
921
- | **Micro::Case** | 108510.0 | _**The Faster**_ |
922
- | Dry::Monads | 64240.1 | 1.69x slower |
923
- | Trailblazer::Operation | 15657.7 | 6.93x slower |
924
- | Interactor | 13486.7 | 8.05x slower |
925
- | Dry::Transaction | 4567.3 | 23.76x slower |
1204
+ | Gem / Abstraction | Iterations per second | Comparison |
1205
+ | ----------------- | --------------------: | ----------------: |
1206
+ | **Micro::Case** | 108510.0 | _**The Fastest**_ |
1207
+ | Dry::Monads | 64240.1 | 1.69x slower |
1208
+ | Trailblazer::Operation | 15657.7 | 6.93x slower |
1209
+ | Interactor | 13486.7 | 8.05x slower |
1210
+ | Dry::Transaction | 4567.3 | 23.76x slower |
926
1211
 
927
1212
  <details>
928
1213
  <summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
@@ -965,10 +1250,10 @@ https://github.com/serradura/u-case/blob/master/benchmarks/use_case/with_failure
965
1250
  ### `Micro::Case::Flow`
966
1251
 
967
1252
  | 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) |
968
- | ------------------ | ---------------: | ---------------: |
969
- | Micro::Case::Flow | _**The Faster**_ | _**The Faster**_ |
970
- | Micro::Case::Safe::Flow | 0x slower | 0x slower |
971
- | Interactor::Organizer | 1.47x slower | 5.51x slower |
1253
+ | ------------------ | ----------------: | ----------------: |
1254
+ | Micro::Case::Flow | _**The Fastest**_ | _**The Fastest**_ |
1255
+ | Micro::Case::Safe::Flow | 0x slower | 0x slower |
1256
+ | Interactor::Organizer | 1.47x slower | 5.51x slower |
972
1257
 
973
1258
  \* The `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` are out of this analysis because all of them doesn't have this kind of feature.
974
1259
 
@@ -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,37 +43,60 @@ module Micro
42
43
  instance
43
44
  end
44
45
 
46
+ def self.__call_and_set_transition__(result, arg)
47
+ input =
48
+ arg.is_a?(Hash) ? result.__set_transitions_accessible_attributes__(arg) : arg
49
+
50
+ __new__(result, input).call
51
+ end
52
+
53
+ FLOW_STEP = 'Flow_Step'.freeze
54
+
55
+ private_constant :FLOW_STEP
56
+
45
57
  def self.__call!
46
- return const_get(:Flow_Step) if const_defined?(:Flow_Step)
58
+ return const_get(FLOW_STEP) if const_defined?(FLOW_STEP, false)
47
59
 
48
- const_set(:Flow_Step, Class.new(self) do
49
- private def __call
50
- __call_use_case
51
- end
52
- end)
60
+ class_eval("class #{FLOW_STEP} < #{self.name}; private def __call; __call_use_case; end; end")
53
61
  end
54
62
 
55
63
  def self.call!
56
64
  self
57
65
  end
58
66
 
67
+ def self.inherited(subclass)
68
+ subclass.attributes(self.attributes_data({}))
69
+ subclass.extend ::Micro::Attributes.const_get('Macros::ForSubclasses'.freeze)
70
+
71
+ if self.send(:__flow_use_cases) && !subclass.name.to_s.end_with?(FLOW_STEP)
72
+ raise "Wooo, you can't do this! Inherits from a use case which has an inner flow violates "\
73
+ "one of the project principles: Solve complex business logic, by allowing the composition of use cases. "\
74
+ "Instead of doing this, declare a new class/constant with the steps needed.\n\n"\
75
+ "Related issue: https://github.com/serradura/u-case/issues/19\n"
76
+ end
77
+ end
78
+
59
79
  def self.__flow_reducer
60
80
  Flow::Reducer
61
81
  end
62
82
 
63
83
  def self.__flow_get
64
- @__flow
84
+ return @__flow if defined?(@__flow)
65
85
  end
66
86
 
67
- private_class_method def self.__flow_use_cases_set(args)
68
- @__flow_use_cases = args
87
+ private_class_method def self.__flow_use_cases
88
+ return @__flow_use_cases if defined?(@__flow_use_cases)
69
89
  end
70
90
 
71
91
  private_class_method def self.__flow_use_cases_get
72
- Array(@__flow_use_cases)
92
+ Array(__flow_use_cases)
73
93
  .map { |use_case| use_case == self ? self.__call! : use_case }
74
94
  end
75
95
 
96
+ private_class_method def self.__flow_use_cases_set(args)
97
+ @__flow_use_cases = args
98
+ end
99
+
76
100
  private_class_method def self.__flow_set(args)
77
101
  return if __flow_get
78
102
 
@@ -84,7 +108,7 @@ module Micro
84
108
  end
85
109
 
86
110
  def self.__flow_set!
87
- __flow_set(__flow_use_cases_get) if !__flow_get && @__flow_use_cases
111
+ __flow_set(__flow_use_cases_get) if !__flow_get && __flow_use_cases
88
112
  end
89
113
 
90
114
  def self.flow(*args)
@@ -105,7 +129,7 @@ module Micro
105
129
 
106
130
  def __set_result__(result)
107
131
  raise Error::InvalidResultInstance unless result.is_a?(Result)
108
- raise Error::ResultIsAlreadyDefined if @__result
132
+ raise Error::ResultIsAlreadyDefined if defined?(@__result)
109
133
 
110
134
  @__result = result
111
135
  end
@@ -145,18 +169,14 @@ module Micro
145
169
  def Success(arg = :ok)
146
170
  value, type = block_given? ? [yield, arg] : [arg, :ok]
147
171
 
148
- __get_result__.__set__(true, value, type, nil)
172
+ __get_result_with(true, value, type)
149
173
  end
150
174
 
151
175
  def Failure(arg = :error)
152
176
  value = block_given? ? yield : arg
153
177
  type = __map_failure_type(value, block_given? ? arg : :error)
154
178
 
155
- __get_result__.__set__(false, value, type, self)
156
- end
157
-
158
- def __get_result__
159
- @__result ||= Result.new
179
+ __get_result_with(false, value, type)
160
180
  end
161
181
 
162
182
  def __map_failure_type(arg, type)
@@ -166,5 +186,13 @@ module Micro
166
186
 
167
187
  type
168
188
  end
189
+
190
+ def __get_result__
191
+ @__result ||= Result.new
192
+ end
193
+
194
+ def __get_result_with(is_success, value, type)
195
+ __get_result__.__set__(is_success, value, type, self)
196
+ end
169
197
  end
170
198
  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)
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__ = {}
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
 
@@ -41,7 +58,9 @@ module Micro
41
58
  end
42
59
 
43
60
  def on_success(expected_type = nil)
44
- self.tap { yield(value) if success_type?(expected_type) }
61
+ yield(value) if success_type?(expected_type)
62
+
63
+ self
45
64
  end
46
65
 
47
66
  def on_failure(expected_type = nil)
@@ -49,11 +68,65 @@ module Micro
49
68
 
50
69
  data = expected_type.nil? ? Data.new(value, type).tap(&:freeze) : value
51
70
 
52
- self.tap { yield(data, @use_case) }
71
+ yield(data, @use_case)
72
+
73
+ self
74
+ end
75
+
76
+ def on_exception(expected_exception = nil)
77
+ return self unless failure_type?(:exception)
78
+
79
+ if !expected_exception || (Kind.is(Exception, expected_exception) && value.is_a?(expected_exception))
80
+ yield(value, @use_case)
81
+ end
82
+
83
+ self
84
+ end
85
+
86
+ def then(arg = nil, attributes = nil, &block)
87
+ can_yield_self = respond_to?(:yield_self)
88
+
89
+ if block
90
+ raise Error::InvalidInvocationOfTheThenMethod if arg
91
+ raise NotImplementedError if !can_yield_self
92
+
93
+ yield_self(&block)
94
+ else
95
+ return yield_self if !arg && can_yield_self
96
+
97
+ raise Error::InvalidInvocationOfTheThenMethod if !is_a_use_case?(arg)
98
+
99
+ return self if failure?
100
+
101
+ input = attributes.is_a?(Hash) ? self.value.merge(attributes) : self.value
102
+
103
+ arg.__call_and_set_transition__(self, input)
104
+ end
105
+ end
106
+
107
+ def transitions
108
+ @__transitions__.clone
109
+ end
110
+
111
+ def __set_transitions_accessible_attributes__(attributes_data)
112
+ return attributes_data if @@transition_tracking_disabled
113
+
114
+ __set_transitions_accessible_attributes__!(attributes_data)
53
115
  end
54
116
 
55
117
  private
56
118
 
119
+ def __set_transitions_accessible_attributes__!(attributes_data)
120
+ attributes = Utils.symbolize_hash_keys(attributes_data)
121
+
122
+ __update_transitions_accessible_attributes__(attributes)
123
+ end
124
+
125
+ def __update_transitions_accessible_attributes__(attributes)
126
+ @__transitions_accessible_attributes__.merge!(attributes)
127
+ @__transitions_accessible_attributes__
128
+ end
129
+
57
130
  def success_type?(expected_type)
58
131
  success? && (expected_type.nil? || expected_type == type)
59
132
  end
@@ -65,6 +138,21 @@ module Micro
65
138
  def is_a_use_case?(arg)
66
139
  (arg.is_a?(Class) && arg < ::Micro::Case) || arg.is_a?(::Micro::Case)
67
140
  end
141
+
142
+ def __set_transition__
143
+ use_case_class = @use_case.class
144
+ use_case_attributes = Utils.symbolize_hash_keys(@use_case.attributes)
145
+
146
+ __update_transitions_accessible_attributes__(use_case_attributes)
147
+
148
+ result = @success ? :success : :failure
149
+
150
+ @__transitions__ << {
151
+ use_case: { class: use_case_class, attributes: use_case_attributes },
152
+ result => { type: @type, value: @value },
153
+ accessible_attributes: @__transitions_accessible_attributes__.keys
154
+ }
155
+ end
68
156
  end
69
157
  end
70
158
  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_hash_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.2.0'.freeze
5
+ VERSION = '2.6.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
@@ -22,12 +22,14 @@ module Micro
22
22
 
23
23
  private
24
24
 
25
- def __call
26
- return __call_use_case_flow if __call_use_case_flow?
27
-
25
+ def __call_use_case
28
26
  return failure_by_validation_error(self) if !self.class.auto_validation_disabled? && invalid?
29
27
 
30
- __call_use_case
28
+ result = call!
29
+
30
+ return result if result.is_a?(Result)
31
+
32
+ raise Error::UnexpectedResult.new(self.class)
31
33
  end
32
34
 
33
35
  def failure_by_validation_error(object)
@@ -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.2.0
4
+ version: 2.6.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-07-07 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.