u-case 3.0.0.rc3 → 3.0.0.rc8

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.
@@ -0,0 +1,1420 @@
1
+ ![Ruby](https://img.shields.io/badge/ruby-2.2+-ruby.svg?colorA=99004d&colorB=cc0066)
2
+ [![Gem](https://img.shields.io/gem/v/u-case.svg?style=flat-square)](https://rubygems.org/gems/u-case)
3
+ [![Build Status](https://travis-ci.com/serradura/u-case.svg?branch=main)](https://travis-ci.com/serradura/u-case)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/5c3c8ad1b0b943f88efd/maintainability)](https://codeclimate.com/github/serradura/u-case/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/5c3c8ad1b0b943f88efd/test_coverage)](https://codeclimate.com/github/serradura/u-case/test_coverage)
6
+
7
+ <img src="./assets/ucase_logo_v1.png" alt="u-case - Crie simples e poderosos casos de uso como objetos em Ruby.">
8
+
9
+ Crie simples e poderosos casos de uso como objetos em Ruby.
10
+
11
+ Principais objetivos deste projeto:
12
+ 1. Fácil de usar e aprender ( entrada **>>** processamento **>>** saída ).
13
+ 2. Promover imutabilidade (transformar dados ao invés de modificar) e integridade de dados.
14
+ 3. Nada de callbacks (ex: before, after, around) para evitar indireções no código que possam comprometer o estado e entendimento dos fluxos da aplicação.
15
+ 4. Resolver regras de negócio complexas, ao permitir uma composição de casos de uso (criação de fluxos).
16
+ 5. Ser rápido e otimizado (verifique a [seção de benchmarks](#benchmarks)).
17
+
18
+ > **Nota:** Verifique o repo https://github.com/serradura/from-fat-controllers-to-use-cases para ver uma aplicação Ruby on Rails que utiliza está gem para resolver as regras de negócio.
19
+
20
+ ## Documentação <!-- omit in toc -->
21
+
22
+ Versão | Documentação
23
+ --------- | -------------
24
+ 3.0.0.rc8 | https://github.com/serradura/u-case/blob/main/README.md
25
+ 2.6.0 | https://github.com/serradura/u-case/blob/v2.x/README.md
26
+ 1.1.0 | https://github.com/serradura/u-case/blob/v1.x/README.md
27
+
28
+ ## Índice <!-- omit in toc -->
29
+ - [Compatibilidade](#compatibilidade)
30
+ - [Dependências](#dependências)
31
+ - [Instalação](#instalação)
32
+ - [Uso](#uso)
33
+ - [`Micro::Case` - Como definir um caso de uso?](#microcase---como-definir-um-caso-de-uso)
34
+ - [`Micro::Case::Result` - O que é o resultado de um caso de uso?](#microcaseresult---o-que-é-o-resultado-de-um-caso-de-uso)
35
+ - [O que são os tipos de resultados?](#o-que-são-os-tipos-de-resultados)
36
+ - [Como difinir tipos customizados de resultados?](#como-difinir-tipos-customizados-de-resultados)
37
+ - [É possíve definir um tipo sem definit os dados do resultado?](#é-possíve-definir-um-tipo-sem-definit-os-dados-do-resultado)
38
+ - [Como utilizar os hooks dos resultados?](#como-utilizar-os-hooks-dos-resultados)
39
+ - [Por que o hook sem um tipo definido expõe o próprio resultado?](#por-que-o-hook-sem-um-tipo-definido-expõe-o-próprio-resultado)
40
+ - [Usando decomposição para acessar os dados e tipo do resultado](#usando-decomposição-para-acessar-os-dados-e-tipo-do-resultado)
41
+ - [O que acontece se um hook de resultado for declarado múltiplas vezes?](#o-que-acontece-se-um-hook-de-resultado-for-declarado-múltiplas-vezes)
42
+ - [Como usar o método `Micro::Case::Result#then`?](#como-usar-o-método-microcaseresultthen)
43
+ - [O que acontece quando um `Micro::Case::Result#then` recebe um bloco?](#o-que-acontece-quando-um-microcaseresultthen-recebe-um-bloco)
44
+ - [Como fazer injeção de dependência usando este recurso?](#como-fazer-injeção-de-dependência-usando-este-recurso)
45
+ - [`Micro::Cases::Flow` - Como compor casos de uso?](#microcasesflow---como-compor-casos-de-uso)
46
+ - [É possível compor um fluxo com outros fluxos?](#é-possível-compor-um-fluxo-com-outros-fluxos)
47
+ - [É possível que um fluxo acumule sua entrada e mescle cada resultado de sucesso para usar como argumento dos próximos casos de uso?](#é-possível-que-um-fluxo-acumule-sua-entrada-e-mescle-cada-resultado-de-sucesso-para-usar-como-argumento-dos-próximos-casos-de-uso)
48
+ - [Como entender o que aconteceu durante a execução de um flow?](#como-entender-o-que-aconteceu-durante-a-execução-de-um-flow)
49
+ - [`Micro::Case::Result#transitions` schema](#microcaseresulttransitions-schema)
50
+ - [É possível desabilitar o `Micro::Case::Result#transitions`?](#é-possível-desabilitar-o-microcaseresulttransitions)
51
+ - [É possível declarar um fluxo que inclui o próprio caso de uso?](#é-possível-declarar-um-fluxo-que-inclui-o-próprio-caso-de-uso)
52
+ - [`Micro::Case::Strict` - O que é um caso de uso estrito?](#microcasestrict---o-que-é-um-caso-de-uso-estrito)
53
+ - [`Micro::Case::Safe` - Existe algum recurso para lidar automaticamente com exceções dentro de um caso de uso ou fluxo?](#microcasesafe---existe-algum-recurso-para-lidar-automaticamente-com-exceções-dentro-de-um-caso-de-uso-ou-fluxo)
54
+ - [`Micro::Case::Result#on_exception`](#microcaseresulton_exception)
55
+ - [`Micro::Cases::Safe::Flow`](#microcasessafeflow)
56
+ - [`Micro::Case::Result#on_exception`](#microcaseresulton_exception-1)
57
+ - [`u-case/with_activemodel_validation` - Como validar os atributos do caso de uso?](#u-casewith_activemodel_validation---como-validar-os-atributos-do-caso-de-uso)
58
+ - [Se eu habilitei a validação automática, é possível desabilitá-la apenas em casos de uso específicos?](#se-eu-habilitei-a-validação-automática-é-possível-desabilitá-la-apenas-em-casos-de-uso-específicos)
59
+ - [`Kind::Validator`](#kindvalidator)
60
+ - [`Micro::Case.config`](#microcaseconfig)
61
+ - [Benchmarks](#benchmarks)
62
+ - [`Micro::Case` (v3.0.0)](#microcase-v300)
63
+ - [Success results](#success-results)
64
+ - [Failure results](#failure-results)
65
+ - [`Micro::Cases::Flow` (v3.0.0)](#microcasesflow-v300)
66
+ - [Comparações](#comparações)
67
+ - [Exemplos](#exemplos)
68
+ - [1️⃣ Criação de usuários](#1️⃣-criação-de-usuários)
69
+ - [2️⃣ Rails App (API)](#2️⃣-rails-app-api)
70
+ - [3️⃣ CLI calculator](#3️⃣-cli-calculator)
71
+ - [4️⃣ Interceptando exceções dentro dos casos de uso](#4️⃣-interceptando-exceções-dentro-dos-casos-de-uso)
72
+ - [Desenvolvimento](#desenvolvimento)
73
+ - [Contribuindo](#contribuindo)
74
+ - [Licença](#licença)
75
+ - [Código de conduta](#código-de-conduta)
76
+
77
+ ## Compatibilidade
78
+
79
+ | u-case | branch | ruby | activemodel |
80
+ | -------------- | ------- | -------- | ------------- |
81
+ | 3.0.0.rc8 | main | >= 2.2.0 | >= 3.2, < 6.1 |
82
+ | 2.6.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
83
+ | 1.1.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
84
+
85
+ > Nota: O activemodel é uma dependência opcional, esse módulo que [pode ser habilitado](#u-casewith_activemodel_validation---como-validar-os-atributos-do-caso-de-uso) para validar os atributos dos casos de uso.
86
+
87
+ ## Dependências
88
+
89
+ 1. Gem [`kind`](https://github.com/serradura/kind).
90
+
91
+ Sistema de tipos simples (em runtime) para Ruby.
92
+
93
+ É usado para validar os inputs de alguns métodos do u-case, além de expor um validador de tipos através do [`activemodel validation`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations) ([veja como habilitar]((#u-casewith_activemodel_validation---how-to-validate-use-case-attributes))). Por fim, ele também expõe dois verificadores de tipo: [`Kind::Of::Micro::Case`, `Kind::Of::Micro::Case::Result`](https://github.com/serradura/kind#registering-new-custom-type-checker).
94
+ 2. [`u-attributes`](https://github.com/serradura/u-attributes) gem.
95
+
96
+ Essa gem permite definir atributos de leitura (read-only), ou seja, os seus objetos só terão getters para acessar os dados dos seus atributos.
97
+ Ela é usada para definir os atributos dos casos de uso.
98
+
99
+ ## Instalação
100
+
101
+ Adicione essa linha ao Gemfile da sua aplicação:
102
+
103
+ ```ruby
104
+ gem 'u-case', '~> 3.0.0.rc8'
105
+ ```
106
+
107
+ E então execute:
108
+
109
+ $ bundle
110
+
111
+ Ou instale manualmente:
112
+
113
+ $ gem install u-case --pre
114
+
115
+ ## Uso
116
+
117
+ ### `Micro::Case` - Como definir um caso de uso?
118
+
119
+ ```ruby
120
+ class Multiply < Micro::Case
121
+ # 1. Defina o input como atributos
122
+ attributes :a, :b
123
+
124
+ # 2. Defina o método `call!` com a regra de negócio
125
+ def call!
126
+
127
+ # 3. Envolva o resultado do caso de uso com os métodos `Success(result: *)` ou `Failure(result: *)`
128
+ if a.is_a?(Numeric) && b.is_a?(Numeric)
129
+ Success result: { number: a * b }
130
+ else
131
+ Failure result: { message: '`a` and `b` attributes must be numeric' }
132
+ end
133
+ end
134
+ end
135
+
136
+ #===========================#
137
+ # Executando um caso de uso #
138
+ #===========================#
139
+
140
+ # Resultado de sucesso
141
+
142
+ result = Multiply.call(a: 2, b: 2)
143
+
144
+ result.success? # true
145
+ result.data # { number: 4 }
146
+
147
+ # Resultado de falha
148
+
149
+ bad_result = Multiply.call(a: 2, b: '2')
150
+
151
+ bad_result.failure? # true
152
+ bad_result.data # { message: "`a` and `b` attributes must be numeric" }
153
+
154
+ # Nota:
155
+ # ----
156
+ # O resultado de um Micro::Case.call é uma instância de Micro::Case::Result
157
+ ```
158
+
159
+ [⬆️ Voltar para o índice](#índice-)
160
+
161
+ ### `Micro::Case::Result` - O que é o resultado de um caso de uso?
162
+
163
+ Um `Micro::Case::Result` armazena os dados de output de um caso de uso. Esses são seus métodos:
164
+ - `#success?` retorna true se for um resultado de sucesso.
165
+ - `#failure?` retorna true se for um resultado de falha.
166
+ - `#use_case` retorna o caso de uso reponsável pelo resultado. Essa funcionalidade é útil para lidar com falhas em flows (esse tópico será abordado mais a frente).
167
+ - `#type` retorna um Symbol que dá significado ao resultado, isso é útil para declarar diferentes tipos de falha e sucesso.
168
+ - `#data` os dados do resultado (um Hash).
169
+ - `#[]` e `#values_at` são atalhos para acessar as propriedades do `#data`.
170
+ - `#key?` retorna `true` se a chave estiver present no `#data`.
171
+ - `#value?` retorna `true` se o valor estiver present no `#data`.
172
+ - `#slice` retorna um novo hash que inclui apenas as chaves fornecidas. Se as chaves fornecidas não existirem, um hash vazio será retornado.
173
+ - `#on_success` or `#on_failure` são métodos de hooks que te auxiliam a definir o fluxo da aplicação.
174
+ - `#then` este método permite aplicar novos casos de uso ao resultado atual se ele for sucesso. A idia dessa feature é a criação de fluxos dinâmicos.
175
+ - `#transitions` retorna um array com todoas as transformações que um resultado [teve durante um flow](#como-entender-o-que-aconteceu-durante-a-execução-de-um-flow).
176
+
177
+ > **Nota:** por conta de retrocompatibilidade, você pode usar o método `#value` como um alias para o método `#data`.
178
+
179
+ [⬆️ Voltar para o índice](#índice-)
180
+
181
+ #### O que são os tipos de resultados?
182
+
183
+ Todo resultado tem um tipo (type), e estão são os valores padrões:
184
+ - `:ok` quando sucesso
185
+ - `:error` or `:exception` quando falhas
186
+
187
+ ```ruby
188
+ class Divide < Micro::Case
189
+ attributes :a, :b
190
+
191
+ def call!
192
+ if invalid_attributes.empty?
193
+ Success result: { number: a / b }
194
+ else
195
+ Failure result: { invalid_attributes: invalid_attributes }
196
+ end
197
+ rescue => exception
198
+ Failure result: exception
199
+ end
200
+
201
+ private def invalid_attributes
202
+ attributes.select { |_key, value| !value.is_a?(Numeric) }
203
+ end
204
+ end
205
+
206
+ # Resultado de sucesso
207
+
208
+ result = Divide.call(a: 2, b: 2)
209
+
210
+ result.type # :ok
211
+ result.data # { number: 1 }
212
+ result.success? # true
213
+ result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>2}, @a=2, @b=2, @__result=...>
214
+
215
+ # Resultado de falha (type == :error)
216
+
217
+ bad_result = Divide.call(a: 2, b: '2')
218
+
219
+ bad_result.type # :error
220
+ bad_result.data # { invalid_attributes: { "b"=>"2" } }
221
+ bad_result.failure? # true
222
+ bad_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>"2"}, @a=2, @b="2", @__result=...>
223
+
224
+ # Resultado de falha (type == :exception)
225
+
226
+ err_result = Divide.call(a: 2, b: 0)
227
+
228
+ err_result.type # :exception
229
+ err_result.data # { exception: <ZeroDivisionError: divided by 0> }
230
+ err_result.failure? # true
231
+ err_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>0}, @a=2, @b=0, @__result=#<Micro::Case::Result:0x0000 @use_case=#<Divide:0x0000 ...>, @type=:exception, @value=#<ZeroDivisionError: divided by 0>, @success=false>
232
+
233
+ # Nota:
234
+ # ----
235
+ # Toda instância de Exception será envolvida pelo método
236
+ # Failure(result: *) que receberá o tipo `:exception` ao invés de `:error`.
237
+ ```
238
+
239
+ [⬆️ Voltar para o índice](#índice-)
240
+
241
+ #### Como difinir tipos customizados de resultados?
242
+
243
+ Resposta: Use um Symbol com argumento dos métodos `Success()`, `Failure()` e declare o `result:` keyword para definir os dados do resultado.
244
+
245
+ ```ruby
246
+ class Multiply < Micro::Case
247
+ attributes :a, :b
248
+
249
+ def call!
250
+ if a.is_a?(Numeric) && b.is_a?(Numeric)
251
+ Success result: { number: a * b }
252
+ else
253
+ Failure :invalid_data, result: {
254
+ attributes: attributes.reject { |_, input| input.is_a?(Numeric) }
255
+ }
256
+ end
257
+ end
258
+ end
259
+
260
+ # Resultado de sucesso
261
+
262
+ result = Multiply.call(a: 3, b: 2)
263
+
264
+ result.type # :ok
265
+ result.data # { number: 6 }
266
+ result.success? # true
267
+
268
+ # Resultado de falha
269
+
270
+ bad_result = Multiply.call(a: 3, b: '2')
271
+
272
+ bad_result.type # :invalid_data
273
+ bad_result.data # { attributes: {"b"=>"2"} }
274
+ bad_result.failure? # true
275
+ ```
276
+
277
+ [⬆️ Voltar para o índice](#índice-)
278
+
279
+ #### É possíve definir um tipo sem definit os dados do resultado?
280
+
281
+ Resposta: Sim, é possível. Mas isso terá um comportamento especial por conta dos dados do resultado ser um hash com o tipo definido como chave e `true` como o valor.
282
+
283
+ ```ruby
284
+ class Multiply < Micro::Case
285
+ attributes :a, :b
286
+
287
+ def call!
288
+ if a.is_a?(Numeric) && b.is_a?(Numeric)
289
+ Success result: { number: a * b }
290
+ else
291
+ Failure(:invalid_data)
292
+ end
293
+ end
294
+ end
295
+
296
+ result = Multiply.call(a: 2, b: '2')
297
+
298
+ result.failure? # true
299
+ result.data # { :invalid_data => true }
300
+ result.type # :invalid_data
301
+ result.use_case.attributes # {"a"=>2, "b"=>"2"}
302
+
303
+ # Nota:
304
+ # ----
305
+ # Essa funcionalidade será muito útil para lidar com resultados de falha de um Flow
306
+ # (este tópico será coberto em breve).
307
+ ```
308
+
309
+ [⬆️ Voltar para o índice](#índice-)
310
+
311
+ #### Como utilizar os hooks dos resultados?
312
+
313
+ Como [mencionando anteriormente](#microcaseresult---o-que-é-o-resultado-de-um-caso-de-uso), o `Micro::Case::Result` tem dois métodoso para melhorar o controle do fluxo da aplicação. São eles:
314
+ `#on_success`, `on_failure`.
315
+
316
+ Os exemplos abaixo demonstram eles em uso:
317
+
318
+ ```ruby
319
+ class Double < Micro::Case
320
+ attribute :number
321
+
322
+ def call!
323
+ return Failure :invalid, result: { msg: 'number must be a numeric value' } unless number.is_a?(Numeric)
324
+ return Failure :lte_zero, result: { msg: 'number must be greater than 0' } if number <= 0
325
+
326
+ Success result: { number: number * 2 }
327
+ end
328
+ end
329
+
330
+ #================================#
331
+ # Imprimindo o output se sucesso #
332
+ #================================#
333
+
334
+ Double
335
+ .call(number: 3)
336
+ .on_success { |result| p result[:number] }
337
+ .on_failure(:invalid) { |result| raise TypeError, result[:msg] }
338
+ .on_failure(:lte_zero) { |result| raise ArgumentError, result[:msg] }
339
+
340
+ # O output será:
341
+ # 6
342
+
343
+ #===================================#
344
+ # Lançando um erro em caso de falha #
345
+ #===================================#
346
+
347
+ Double
348
+ .call(number: -1)
349
+ .on_success { |result| p result[:number] }
350
+ .on_failure { |_result, use_case| puts "#{use_case.class.name} was the use case responsible for the failure" }
351
+ .on_failure(:invalid) { |result| raise TypeError, result[:msg] }
352
+ .on_failure(:lte_zero) { |result| raise ArgumentError, result[:msg] }
353
+
354
+ # O output será:
355
+ #
356
+ # 1. Imprimirá a mensagem: Double was the use case responsible for the failure
357
+ # 2. Lançará a exception: ArgumentError (the number must be greater than 0)
358
+
359
+ # Nota:
360
+ # ----
361
+ # O caso de uso responsável estará sempre acessível como o segundo argumento do hook
362
+ ```
363
+
364
+ #### Por que o hook sem um tipo definido expõe o próprio resultado?
365
+
366
+ Resposta: Para permitir que você defina o controle de fluxo da aplicação usando alguma estrutura condicional como um `if` ou `case when`.
367
+
368
+ ```ruby
369
+ class Double < Micro::Case
370
+ attribute :number
371
+
372
+ def call!
373
+ return Failure(:invalid) unless number.is_a?(Numeric)
374
+ return Failure :lte_zero, result: attributes(:number) if number <= 0
375
+
376
+ Success result: { number: number * 2 }
377
+ end
378
+ end
379
+
380
+ Double
381
+ .call(number: -1)
382
+ .on_failure do |result, use_case|
383
+ case result.type
384
+ when :invalid then raise TypeError, "number must be a numeric value"
385
+ when :lte_zero then raise ArgumentError, "number `#{result[:number]}` must be greater than 0"
386
+ else raise NotImplementedError
387
+ end
388
+ end
389
+
390
+ # O output será uma exception:
391
+ #
392
+ # ArgumentError (number `-1` must be greater than 0)
393
+ ```
394
+
395
+ > **Nota:** O mesmo que foi feito no exemplo anterior poderá ser feito com o hook `#on_success`!
396
+
397
+ ##### Usando decomposição para acessar os dados e tipo do resultado
398
+
399
+ A sintaxe para decompor um Array pode ser usada na declaração de variáveis e nos argumentos de métodos/blocos.
400
+ Se você não sabia disso, confira a [documentação do Ruby](https://ruby-doc.org/core-2.2.0/doc/syntax/assignment_rdoc.html#label-Array+Decomposition).
401
+
402
+ ```ruby
403
+ # O objeto exposto em hook sem um tipo é um Micro::Case::Result e ele pode ser decomposto. Exemplo:
404
+
405
+ Double
406
+ .call(number: -2)
407
+ .on_failure do |(data, type), use_case|
408
+ case type
409
+ when :invalid then raise TypeError, 'number must be a numeric value'
410
+ when :lte_zero then raise ArgumentError, "number `#{data[:number]}` must be greater than 0"
411
+ else raise NotImplementedError
412
+ end
413
+ end
414
+
415
+ # O output será a exception:
416
+ #
417
+ # ArgumentError (the number `-2` must be greater than 0)
418
+ ```
419
+
420
+ > **Nota:** O que mesmo pode ser feito com o `#on_success` hook!
421
+
422
+ [⬆️ Voltar para o índice](#índice-)
423
+
424
+ #### O que acontece se um hook de resultado for declarado múltiplas vezes?
425
+
426
+ Resposta: Se o tipo do resultado for identificado o hook será sempre executado.
427
+
428
+ ```ruby
429
+ class Double < Micro::Case
430
+ attributes :number
431
+
432
+ def call!
433
+ if number.is_a?(Numeric)
434
+ Success :computed, result: { number: number * 2 }
435
+ else
436
+ Failure :invalid, result: { msg: 'number must be a numeric value' }
437
+ end
438
+ end
439
+ end
440
+
441
+ result = Double.call(number: 3)
442
+ result.data # { number: 6 }
443
+ result[:number] * 4 # 24
444
+
445
+ accum = 0
446
+
447
+ result
448
+ .on_success { |result| accum += result[:number] }
449
+ .on_success { |result| accum += result[:number] }
450
+ .on_success(:computed) { |result| accum += result[:number] }
451
+ .on_success(:computed) { |result| accum += result[:number] }
452
+
453
+ accum # 24
454
+
455
+ result[:number] * 4 == accum # true
456
+ ```
457
+
458
+ #### Como usar o método `Micro::Case::Result#then`?
459
+
460
+ Este método permite você criar fluxos dinâmicos, então, com ele, você pode adicionar novos casos de uso ou fluxos para continuar a transformação de um resultado. por exemplo:
461
+
462
+ ```ruby
463
+ class ForbidNegativeNumber < Micro::Case
464
+ attribute :number
465
+
466
+ def call!
467
+ return Success result: attributes if number >= 0
468
+
469
+ Failure result: attributes
470
+ end
471
+ end
472
+
473
+ class Add3 < Micro::Case
474
+ attribute :number
475
+
476
+ def call!
477
+ Success result: { number: number + 3 }
478
+ end
479
+ end
480
+
481
+ result1 =
482
+ ForbidNegativeNumber
483
+ .call(number: -1)
484
+ .then(Add3)
485
+
486
+ result1.data # {'number' => -1}
487
+ result1.failure? # true
488
+
489
+ # ---
490
+
491
+ result2 =
492
+ ForbidNegativeNumber
493
+ .call(number: 1)
494
+ .then(Add3)
495
+
496
+ result2.data # {'number' => 4}
497
+ result2.success? # true
498
+ ```
499
+
500
+ > **Nota:** este método altera o [`Micro::Case::Result#transitions`](#como-entender-o-que-aconteceu-durante-a-execução-de-um-flow).
501
+
502
+ [⬆️ Voltar para o índice](#índice-)
503
+
504
+ ##### O que acontece quando um `Micro::Case::Result#then` recebe um bloco?
505
+
506
+ Ele passará o próprio resultado (uma instância do `Micro::Case::Result`) como argumento do bloco, e retornará o output do bloco ao invés dele mesmo. e.g:
507
+
508
+ ```ruby
509
+ class Add < Micro::Case
510
+ attributes :a, :b
511
+
512
+ def call!
513
+ if Kind.of?(Numeric, a, b)
514
+ Success result: { sum: a + b }
515
+ else
516
+ Failure(:attributes_arent_numbers)
517
+ end
518
+ end
519
+ end
520
+
521
+ # --
522
+
523
+ success_result =
524
+ Add
525
+ .call(a: 2, b: 2)
526
+ .then { |result| result.success? ? result[:sum] : 0 }
527
+
528
+ puts success_result # 4
529
+
530
+ # --
531
+
532
+ failure_result =
533
+ Add
534
+ .call(a: 2, b: '2')
535
+ .then { |result| result.success? ? result[:sum] : 0 }
536
+
537
+ puts failure_result # 0
538
+ ```
539
+
540
+ [⬆️ Voltar para o índice](#índice-)
541
+
542
+ ##### Como fazer injeção de dependência usando este recurso?
543
+
544
+ Passe um `Hash` como segundo argumento do método `Micro::Case::Result#then`.
545
+
546
+ ```ruby
547
+ Todo::FindAllForUser
548
+ .call(user: current_user, params: params)
549
+ .then(Paginate)
550
+ .then(Serialize::PaginatedRelationAsJson, serializer: Todo::Serializer)
551
+ .on_success { |result| render_json(200, data: result[:todos]) }
552
+ ```
553
+
554
+ [⬆️ Voltar para o índice](#índice-)
555
+
556
+ ### `Micro::Cases::Flow` - Como compor casos de uso?
557
+
558
+ Chamamos de **fluxo** uma composição de casos de uso. A ideia principal desse recurso é usar/reutilizar casos de uso como etapas de um novo caso de uso. Exemplo:
559
+
560
+ ```ruby
561
+ module Steps
562
+ class ConvertTextToNumbers < Micro::Case
563
+ attribute :numbers
564
+
565
+ def call!
566
+ if numbers.all? { |value| String(value) =~ /\d+/ }
567
+ Success result: { numbers: numbers.map(&:to_i) }
568
+ else
569
+ Failure result: { message: 'numbers must contain only numeric types' }
570
+ end
571
+ end
572
+ end
573
+
574
+ class Add2 < Micro::Case::Strict
575
+ attribute :numbers
576
+
577
+ def call!
578
+ Success result: { numbers: numbers.map { |number| number + 2 } }
579
+ end
580
+ end
581
+
582
+ class Double < Micro::Case::Strict
583
+ attribute :numbers
584
+
585
+ def call!
586
+ Success result: { numbers: numbers.map { |number| number * 2 } }
587
+ end
588
+ end
589
+
590
+ class Square < Micro::Case::Strict
591
+ attribute :numbers
592
+
593
+ def call!
594
+ Success result: { numbers: numbers.map { |number| number * number } }
595
+ end
596
+ end
597
+ end
598
+
599
+ #-----------------------------------------#
600
+ # Criando um flow com Micro::Cases.flow() #
601
+ #-----------------------------------------#
602
+
603
+ Add2ToAllNumbers = Micro::Cases.flow([
604
+ Steps::ConvertTextToNumbers,
605
+ Steps::Add2
606
+ ])
607
+
608
+ result = Add2ToAllNumbers.call(numbers: %w[1 1 2 2 3 4])
609
+
610
+ result.success? # true
611
+ result.data # {:numbers => [3, 3, 4, 4, 5, 6]}
612
+
613
+ #--------------------------------#
614
+ # Criando um flow usando classes #
615
+ #--------------------------------#
616
+
617
+ class DoubleAllNumbers < Micro::Case
618
+ flow Steps::ConvertTextToNumbers,
619
+ Steps::Double
620
+ end
621
+
622
+ DoubleAllNumbers.
623
+ call(numbers: %w[1 1 b 2 3 4]).
624
+ on_failure { |result| puts result[:message] } # "numbers must contain only numeric types"
625
+ ```
626
+
627
+ Ao ocorrer uma falha, o caso de uso responsável ficará acessível no resultado. Exemplo:
628
+
629
+ ```ruby
630
+ result = DoubleAllNumbers.call(numbers: %w[1 1 b 2 3 4])
631
+
632
+ result.failure? # true
633
+ result.use_case.is_a?(Steps::ConvertTextToNumbers) # true
634
+
635
+ result.on_failure do |_message, use_case|
636
+ puts "#{use_case.class.name} was the use case responsible for the failure" # Steps::ConvertTextToNumbers was the use case responsible for the failure
637
+ end
638
+ ```
639
+
640
+ [⬆️ Voltar para o índice](#índice-)
641
+
642
+ #### É possível compor um fluxo com outros fluxos?
643
+
644
+ Resposta: Sim, é possível.
645
+
646
+ ```ruby
647
+ module Steps
648
+ class ConvertTextToNumbers < Micro::Case
649
+ attribute :numbers
650
+
651
+ def call!
652
+ if numbers.all? { |value| String(value) =~ /\d+/ }
653
+ Success result: { numbers: numbers.map(&:to_i) }
654
+ else
655
+ Failure result: { message: 'numbers must contain only numeric types' }
656
+ end
657
+ end
658
+ end
659
+
660
+ class Add2 < Micro::Case::Strict
661
+ attribute :numbers
662
+
663
+ def call!
664
+ Success result: { numbers: numbers.map { |number| number + 2 } }
665
+ end
666
+ end
667
+
668
+ class Double < Micro::Case::Strict
669
+ attribute :numbers
670
+
671
+ def call!
672
+ Success result: { numbers: numbers.map { |number| number * 2 } }
673
+ end
674
+ end
675
+
676
+ class Square < Micro::Case::Strict
677
+ attribute :numbers
678
+
679
+ def call!
680
+ Success result: { numbers: numbers.map { |number| number * number } }
681
+ end
682
+ end
683
+ end
684
+
685
+ DoubleAllNumbers =
686
+ Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Double])
687
+
688
+ SquareAllNumbers =
689
+ Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Square])
690
+
691
+ DoubleAllNumbersAndAdd2 =
692
+ Micro::Cases.flow([DoubleAllNumbers, Steps::Add2])
693
+
694
+ SquareAllNumbersAndAdd2 =
695
+ Micro::Cases.flow([SquareAllNumbers, Steps::Add2])
696
+
697
+ SquareAllNumbersAndDouble =
698
+ Micro::Cases.flow([SquareAllNumbersAndAdd2, DoubleAllNumbers])
699
+
700
+ DoubleAllNumbersAndSquareAndAdd2 =
701
+ Micro::Cases.flow([DoubleAllNumbers, SquareAllNumbersAndAdd2])
702
+
703
+ SquareAllNumbersAndDouble
704
+ .call(numbers: %w[1 1 2 2 3 4])
705
+ .on_success { |result| p result[:numbers] } # [6, 6, 12, 12, 22, 36]
706
+
707
+ DoubleAllNumbersAndSquareAndAdd2
708
+ .call(numbers: %w[1 1 2 2 3 4])
709
+ .on_success { |result| p result[:numbers] } # [6, 6, 18, 18, 38, 66]
710
+ ```
711
+
712
+ > **Nota:** Você pode mesclar qualquer [approach](#é-possível-compor-um-fluxo-com-outros-fluxos) para criar flows - [exemplos](https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/cases/flow/blend_test.rb#L5-L35).
713
+
714
+ [⬆️ Voltar para o índice](#índice-)
715
+
716
+ #### É possível que um fluxo acumule sua entrada e mescle cada resultado de sucesso para usar como argumento dos próximos casos de uso?
717
+
718
+ Resposta: Sim, é possível! Veja o exemplo abaixo para entender como funciona o acumulp de dados dentro da execução de um fluxo.
719
+
720
+ ```ruby
721
+ module Users
722
+ class FindByEmail < Micro::Case
723
+ attribute :email
724
+
725
+ def call!
726
+ user = User.find_by(email: email)
727
+
728
+ return Success result: { user: user } if user
729
+
730
+ Failure(:user_not_found)
731
+ end
732
+ end
733
+ end
734
+
735
+ module Users
736
+ class ValidatePassword < Micro::Case::Strict
737
+ attributes :user, :password
738
+
739
+ def call!
740
+ return Failure(:user_must_be_persisted) if user.new_record?
741
+ return Failure(:wrong_password) if user.wrong_password?(password)
742
+
743
+ return Success result: attributes(:user)
744
+ end
745
+ end
746
+ end
747
+
748
+ module Users
749
+ Authenticate = Micro::Cases.flow([
750
+ FindByEmail,
751
+ ValidatePassword
752
+ ])
753
+ end
754
+
755
+ Users::Authenticate
756
+ .call(email: 'somebody@test.com', password: 'password')
757
+ .on_success { |result| sign_in(result[:user]) }
758
+ .on_failure(:wrong_password) { render status: 401 }
759
+ .on_failure(:user_not_found) { render status: 404 }
760
+ ```
761
+
762
+ Primeiro, vamos ver os atributos usados por cada caso de uso:
763
+
764
+ ```ruby
765
+ class Users::FindByEmail < Micro::Case
766
+ attribute :email
767
+ end
768
+
769
+ class Users::ValidatePassword < Micro::Case
770
+ attributes :user, :password
771
+ end
772
+ ```
773
+
774
+ Como você pode ver, `Users::ValidatePassword` espera um usuário como sua entrada. Então, como ele recebe o usuário?
775
+ R: Ele recebe o usuário do resultado de sucesso `Users::FindByEmail`!
776
+
777
+ E este é o poder da composição de casos de uso porque o output de uma etapa irá compor a entrada do próximo caso de uso no fluxo!
778
+
779
+ > input **>>** processamento **>>** output
780
+
781
+ > **Nota:** Verifique esses exemplos de teste [Micro::Cases::Flow](https://github.com/serradura/u-case/blob/c96a3650469da40dc9f83ff678204055b7015d01/test/micro/cases/flow/result_transitions_test.rb) e [Micro::Cases::Safe::Flow](https://github.com/serradura/u-case/blob/c96a3650469da40dc9f83ff678204055b7015d01/test/micro/cases/safe/flow/result_transitions_test.rb) para ver diferentes casos de uso tendo acesso aos dados de um fluxo.
782
+
783
+ [⬆️ Voltar para o índice](#índice-)
784
+
785
+ #### Como entender o que aconteceu durante a execução de um flow?
786
+
787
+ Use `Micro::Case::Result#transitions`!
788
+
789
+ Vamos usar os [exemplos da seção anterior](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-the-next-use-cases) para ilustrar como utilizar essa feature.
790
+
791
+ ```ruby
792
+ user_authenticated =
793
+ Users::Authenticate.call(email: 'rodrigo@test.com', password: user_password)
794
+
795
+ user_authenticated.transitions
796
+ [
797
+ {
798
+ :use_case => {
799
+ :class => Users::FindByEmail,
800
+ :attributes => { :email => "rodrigo@test.com" }
801
+ },
802
+ :success => {
803
+ :type => :ok,
804
+ :result => {
805
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
806
+ }
807
+ },
808
+ :accessible_attributes => [ :email, :password ]
809
+ },
810
+ {
811
+ :use_case => {
812
+ :class => Users::ValidatePassword,
813
+ :attributes => {
814
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
815
+ :password => "123456"
816
+ }
817
+ },
818
+ :success => {
819
+ :type => :ok,
820
+ :result => {
821
+ :user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
822
+ }
823
+ },
824
+ :accessible_attributes => [ :email, :password, :user ]
825
+ }
826
+ ]
827
+ ```
828
+
829
+ O exemplo acima mostra a saída gerada pelas `Micro::Case::Result#transitions`.
830
+ Com ele é possível analisar a ordem de execução dos casos de uso e quais foram os `inputs` fornecidos (`[:attributes]`) e `outputs` (`[:success][:result]`) em toda a execução.
831
+
832
+ E observe a propriedade `accessible_attributes`, ela mostra quais atributos são acessíveis nessa etapa do fluxo. Por exemplo, na última etapa, você pode ver que os atributos `accessible_attributes` aumentaram devido ao [acúmulo de fluxo de dados](#é-possível-que-um-fluxo-acumule-sua-entrada-e-mescle-cada-resultado-de-sucesso-para-usar-como-argumento-dos-próximos-casos-de-uso).
833
+
834
+ > **Nota:** O [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) incrementa o `Micro::Case::Result#transitions`.
835
+
836
+ ##### `Micro::Case::Result#transitions` schema
837
+ ```ruby
838
+ [
839
+ {
840
+ use_case: {
841
+ class: <Micro::Case>,# Caso de uso que será executado
842
+ attributes: <Hash> # (Input) Os atributos do caso de uso
843
+ },
844
+ [success:, failure:] => { # (Output)
845
+ type: <Symbol>, # Tipo do resultado. Padrões:
846
+ # Success = :ok, Failure = :error or :exception
847
+ result: <Hash> # Os dados retornados pelo resultado do use case
848
+ },
849
+ accessible_attributes: <Array>, # Propriedades que podem ser acessadas pelos atributos do caso de uso,
850
+ # começando com Hash usado para invocá-lo e que são incrementados
851
+ # com os valores de resultado de cada caso de uso do fluxo.
852
+ }
853
+ ]
854
+
855
+ ```
856
+
857
+ ##### É possível desabilitar o `Micro::Case::Result#transitions`?
858
+
859
+ Resposta: Sim! Você pode usar o `Micro::Case.config` para fazer isso. [Link para](#microcaseconfig) essa seção.
860
+
861
+ #### É possível declarar um fluxo que inclui o próprio caso de uso?
862
+
863
+ Resposta: Sim! Você pode usar a macro `self` ou `self.call!`. Exemplo:
864
+
865
+ ```ruby
866
+ class ConvertTextToNumber < Micro::Case
867
+ attribute :text
868
+
869
+ def call!
870
+ Success result: { number: text.to_i }
871
+ end
872
+ end
873
+
874
+ class ConvertNumberToText < Micro::Case
875
+ attribute :number
876
+
877
+ def call!
878
+ Success result: { text: number.to_s }
879
+ end
880
+ end
881
+
882
+ class Double < Micro::Case
883
+ flow ConvertTextToNumber,
884
+ self.call!,
885
+ ConvertNumberToText
886
+
887
+ attribute :number
888
+
889
+ def call!
890
+ Success result: { number: number * 2 }
891
+ end
892
+ end
893
+
894
+ result = Double.call(text: '4')
895
+
896
+ result.success? # true
897
+ result[:number] # "8"
898
+ ```
899
+
900
+ > **Note:** Essa funcionalidade pode ser usada com Micro::Case::Safe. Verifique esse teste para ver um example: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe/with_inner_flow_test.rb
901
+
902
+ [⬆️ Voltar para o índice](#índice-)
903
+
904
+ ### `Micro::Case::Strict` - O que é um caso de uso estrito?
905
+
906
+ Resposta: é um tipo de caso de uso que exigirá todas as palavras-chave (atributos) em sua inicialização.
907
+
908
+ ```ruby
909
+ class Double < Micro::Case::Strict
910
+ attribute :numbers
911
+
912
+ def call!
913
+ Success result: { numbers: numbers.map { |number| number * 2 } }
914
+ end
915
+ end
916
+
917
+ Double.call({})
918
+
919
+ # O output será:
920
+ # ArgumentError (missing keyword: :numbers)
921
+ ```
922
+
923
+ [⬆️ Voltar para o índice](#índice-)
924
+
925
+ ### `Micro::Case::Safe` - Existe algum recurso para lidar automaticamente com exceções dentro de um caso de uso ou fluxo?
926
+
927
+ Sim, assim como `Micro::Case::Strict`, o `Micro::Case::Safe` é outro tipo de caso de uso. Ele tem a capacidade de interceptar automaticamente qualquer exceção como um resultado de falha. Exemplo:
928
+
929
+ ```ruby
930
+ require 'logger'
931
+
932
+ AppLogger = Logger.new(STDOUT)
933
+
934
+ class Divide < Micro::Case::Safe
935
+ attributes :a, :b
936
+
937
+ def call!
938
+ if a.is_a?(Integer) && b.is_a?(Integer)
939
+ Success result: { number: a / b}
940
+ else
941
+ Failure(:not_an_integer)
942
+ end
943
+ end
944
+ end
945
+
946
+ result = Divide.call(a: 2, b: 0)
947
+ result.type == :exception # true
948
+ result.data # { exception: #<ZeroDivisionError...> }
949
+ result[:exception].is_a?(ZeroDivisionError) # true
950
+
951
+ result.on_failure(:exception) do |result|
952
+ AppLogger.error(result[:exception].message) # E, [2019-08-21T00:05:44.195506 #9532] ERROR -- : divided by 0
953
+ end
954
+ ```
955
+
956
+ #### `Micro::Case::Result#on_exception`
957
+
958
+ Se você precisar lidar com um erro específico, recomendo o uso de uma instrução case. por exemplo:
959
+
960
+ ```ruby
961
+ result.on_failure(:exception) do |data, use_case|
962
+ case exception = data[:exception]
963
+ when ZeroDivisionError then AppLogger.error(exception.message)
964
+ else AppLogger.debug("#{use_case.class.name} was the use case responsible for the exception")
965
+ end
966
+ end
967
+ ```
968
+
969
+ > **Note:** É possível resgatar uma exceção mesmo quando é um caso de uso seguro. Exemplos: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe_test.rb#L90-L118
970
+
971
+
972
+ [⬆️ Voltar para o índice](#índice-)
973
+
974
+ #### `Micro::Cases::Safe::Flow`
975
+
976
+ Como casos de uso seguros, os fluxos seguros podem interceptar uma exceção em qualquer uma de suas etapas. Estas são as maneiras de definir um:
977
+
978
+ ```ruby
979
+ module Users
980
+ Create = Micro::Cases.safe_flow([
981
+ ProcessParams,
982
+ ValidateParams,
983
+ Persist,
984
+ SendToCRM
985
+ ])
986
+ end
987
+ ```
988
+
989
+ Definindo dentro das classes:
990
+
991
+ ```ruby
992
+ module Users
993
+ class Create < Micro::Case::Safe
994
+ flow ProcessParams,
995
+ ValidateParams,
996
+ Persist,
997
+ SendToCRM
998
+ end
999
+ end
1000
+ ```
1001
+
1002
+ [⬆️ Voltar para o índice](#índice-)
1003
+
1004
+ #### `Micro::Case::Result#on_exception`
1005
+
1006
+ Na programação funcional os erros/exceções são tratados como dados comuns, a ideia é transformar a saída mesmo quando ocorre um comportamento inesperado. Para muitos, [as exceções são muito semelhantes à instrução GOTO](https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why), pulando o fluxo do programa para caminhos que podem ser difíceis de descobrir como as coisas funcionam em um sistema.
1007
+
1008
+ Para resolver isso, o `Micro::Case::Result` tem um hook especial `#on_exception` para ajudá-lo a lidar com o fluxo de controle no caso de exceções.
1009
+
1010
+ > **Note**: essa funcionalidade funcionará melhor se for usada com um flow ou caso de uso `Micro::Case::Safe`.
1011
+
1012
+ **Como ele funciona?**
1013
+
1014
+ ```ruby
1015
+ class Divide < Micro::Case::Safe
1016
+ attributes :a, :b
1017
+
1018
+ def call!
1019
+ Success result: { division: a / b }
1020
+ end
1021
+ end
1022
+
1023
+ Divide
1024
+ .call(a: 2, b: 0)
1025
+ .on_success { |result| puts result[:division] }
1026
+ .on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
1027
+ .on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
1028
+ .on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
1029
+
1030
+ # Output:
1031
+ # -------
1032
+ # Can't divide a number by 0
1033
+ # Oh no, something went wrong!
1034
+
1035
+ Divide.
1036
+ .call(a: 2, b: '2').
1037
+ .on_success { |result| puts result[:division] }
1038
+ .on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
1039
+ .on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
1040
+ .on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
1041
+
1042
+ # Output:
1043
+ # -------
1044
+ # Please, use only numeric attributes.
1045
+ # Oh no, something went wrong!
1046
+ ```
1047
+
1048
+ Como você pode ver, este hook tem o mesmo comportamento de `result.on_failure(:exception)`, mas, a ideia aqui é ter uma melhor comunicação no código, fazendo uma referência explícita quando alguma falha acontecer por causa de uma exceção.
1049
+
1050
+ [⬆️ Voltar para o índice](#índice-)
1051
+
1052
+ ### `u-case/with_activemodel_validation` - Como validar os atributos do caso de uso?
1053
+
1054
+ **Requisitos:**
1055
+
1056
+ Para fazer isso a sua aplicação deverá ter o [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) como dependência.
1057
+
1058
+ Por padrão, se a sua aplicação tiver o ActiveModel como uma dependência, qualquer tipo de caso de uso pode fazer uso dele para validar seus atributos.
1059
+
1060
+ ```ruby
1061
+ class Multiply < Micro::Case
1062
+ attributes :a, :b
1063
+
1064
+ validates :a, :b, presence: true, numericality: true
1065
+
1066
+ def call!
1067
+ return Failure :validation_error, result: { errors: self.errors } if invalid?
1068
+
1069
+ Success result: { number: a * b }
1070
+ end
1071
+ end
1072
+ ```
1073
+
1074
+ Mas se você deseja uma maneira automática de falhar seus casos de uso em erros de validação, você poderá fazer:
1075
+
1076
+ 1. **require 'u-case/with_activemodel_validation'** no Gemfile
1077
+
1078
+ ```ruby
1079
+ gem 'u-case', require: 'u-case/with_activemodel_validation'
1080
+ ```
1081
+
1082
+ 2. Usar o `Micro::Case.config` para habilitar ele. [Link para](#microcaseconfig) essa seção.
1083
+
1084
+ Usando essa abordagem, você pode reescrever o exemplo anterior com menos código. Exemplo:
1085
+
1086
+ ```ruby
1087
+ require 'u-case/with_activemodel_validation'
1088
+
1089
+ class Multiply < Micro::Case
1090
+ attributes :a, :b
1091
+
1092
+ validates :a, :b, presence: true, numericality: true
1093
+
1094
+ def call!
1095
+ Success result: { number: a * b }
1096
+ end
1097
+ end
1098
+ ```
1099
+
1100
+ > **Nota:** Após habilitar o modo de validação, as classes `Micro::Case::Strict` e `Micro::Case::Safe` irão herdar este novo comportamento.
1101
+
1102
+ #### Se eu habilitei a validação automática, é possível desabilitá-la apenas em casos de uso específicos?
1103
+
1104
+ Resposta: Sim, é possível. Para fazer isso, você só precisará usar a macro `disable_auto_validation`. Exemplo:
1105
+
1106
+ ```ruby
1107
+ require 'u-case/with_activemodel_validation'
1108
+
1109
+ class Multiply < Micro::Case
1110
+ disable_auto_validation
1111
+
1112
+ attribute :a
1113
+ attribute :b
1114
+ validates :a, :b, presence: true, numericality: true
1115
+
1116
+ def call!
1117
+ Success result: { number: a * b }
1118
+ end
1119
+ end
1120
+
1121
+ Multiply.call(a: 2, b: 'a')
1122
+
1123
+ # O output será:
1124
+ # TypeError (String can't be coerced into Integer)
1125
+ ```
1126
+
1127
+ [⬆️ Voltar para o índice](#índice-)
1128
+
1129
+ #### `Kind::Validator`
1130
+
1131
+ A [gem kind](https://github.com/serradura/kind) possui um módulo para habilitar a validação do tipo de dados através do [`ActiveModel validations`](https://guides.rubyonrails.org/active_model_basics.html#validations). Então, quando você fizer o require do `'u-case/with_activemodel_validation'`, este módulo também irá fazer o require do [`Kind::Validator`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations).
1132
+
1133
+ O exemplo abaixo mostra como validar os tipos de atributos.
1134
+
1135
+ ```ruby
1136
+ class Todo::List::AddItem < Micro::Case
1137
+ attributes :user, :params
1138
+
1139
+ validates :user, kind: User
1140
+ validates :params, kind: ActionController::Parameters
1141
+
1142
+ def call!
1143
+ todo_params = params.require(:todo).permit(:title, :due_at)
1144
+
1145
+ todo = user.todos.create(todo_params)
1146
+
1147
+ Success result: { todo: todo }
1148
+ rescue ActionController::ParameterMissing => e
1149
+ Failure :parameter_missing, result: { message: e.message }
1150
+ end
1151
+ end
1152
+ ```
1153
+
1154
+ [⬆️ Voltar para o índice](#índice-)
1155
+
1156
+ ## `Micro::Case.config`
1157
+
1158
+ A ideia deste recurso é permitir a configuração de algumas funcionalidades/módulos do `u-case`.
1159
+ Eu recomendo que você use apenas uma vez em sua base de código. Exemplo: Em um inicializador do Rails.
1160
+
1161
+ Você pode ver abaixo todas as configurações disponíveis com seus valores padrão:
1162
+
1163
+ ```ruby
1164
+ Micro::Case.config do |config|
1165
+ # Use ActiveModel para auto-validar os atributos dos seus casos de uso.
1166
+ config.enable_activemodel_validation = false
1167
+
1168
+ # Use para habilitar/desabilitar o `Micro::Case::Results#transitions`.
1169
+ config.enable_transitions = true
1170
+ end
1171
+ ```
1172
+
1173
+ [⬆️ Voltar para o índice](#índice-)
1174
+
1175
+ ## Benchmarks
1176
+
1177
+ ### `Micro::Case` (v3.0.0)
1178
+
1179
+ #### Success results
1180
+
1181
+ | Gem / Abstração | Iterações por segundo | Comparação |
1182
+ | ----------------- | --------------------: | ----------------: |
1183
+ | Dry::Monads | 141730.1 | _**O mais rápido**_ |
1184
+ | **Micro::Case** | 103541.3 | 1.37x slower |
1185
+ | Interactor | 29100.8 | 4.87x slower |
1186
+ | Trailblazer::Operation | 15031.4 | 9.43x slower |
1187
+ | Dry::Transaction | 5674.0 | 24.98x slower |
1188
+
1189
+ <details>
1190
+ <summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
1191
+
1192
+ ```ruby
1193
+ # Warming up --------------------------------------
1194
+ # Interactor 2.915k i/100ms
1195
+ # Trailblazer::Operation 1.543k i/100ms
1196
+ # Dry::Monads 14.288k i/100ms
1197
+ # Dry::Transaction 571.000 i/100ms
1198
+ # Micro::Case 10.418k i/100ms
1199
+ # Micro::Case::Strict 8.296k i/100ms
1200
+ # Micro::Case::Safe 10.254k i/100ms
1201
+
1202
+ # Calculating -------------------------------------
1203
+ # Interactor 29.101k (± 2.1%) i/s - 145.750k in 5.010660s
1204
+ # Trailblazer::Operation 15.031k (± 2.0%) i/s - 75.607k in 5.032071s
1205
+ # Dry::Monads 141.730k (± 3.1%) i/s - 714.400k in 5.045546s
1206
+ # Dry::Transaction 5.674k (± 1.9%) i/s - 28.550k in 5.033564s
1207
+ # Micro::Case 103.541k (± 1.6%) i/s - 520.900k in 5.032077s
1208
+ # Micro::Case::Strict 83.045k (± 2.4%) i/s - 423.096k in 5.098031s
1209
+ # Micro::Case::Safe 101.662k (± 1.5%) i/s - 512.700k in 5.044386s
1210
+
1211
+ # Comparison:
1212
+ # Dry::Monads: 141730.1 i/s
1213
+ # Micro::Case: 103541.3 i/s - 1.37x (± 0.00) slower
1214
+ # Micro::Case::Safe: 101662.2 i/s - 1.39x (± 0.00) slower
1215
+ # Micro::Case::Strict: 83044.6 i/s - 1.71x (± 0.00) slower
1216
+ # Interactor: 29100.8 i/s - 4.87x (± 0.00) slower
1217
+ # Trailblazer::Operation: 15031.4 i/s - 9.43x (± 0.00) slower
1218
+ # Dry::Transaction: 5674.0 i/s - 24.98x (± 0.00) slower
1219
+ ```
1220
+ </details>
1221
+
1222
+ https://github.com/serradura/u-case/blob/main/benchmarks/use_case/with_success_result.rb
1223
+
1224
+ #### Failure results
1225
+
1226
+ | Gem / Abstração | Iterações por segundo | Comparação |
1227
+ | ----------------- | --------------------: | ----------------: |
1228
+ | **Micro::Case** | 98820.8 | _**O mais rápido**_ |
1229
+ | Dry::Monads | 71329.7 | 1.39x slower |
1230
+ | Trailblazer::Operation | 15034.9 | 6.57x slower |
1231
+ | Interactor | 13958.7 | 7.08x slower |
1232
+ | Dry::Transaction | 5067.5 | 19.50x slower |
1233
+
1234
+ <details>
1235
+ <summary>Mostrar o resultado completo do <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a>.</summary>
1236
+
1237
+ ```ruby
1238
+ # Warming up --------------------------------------
1239
+ # Interactor 1.324k i/100ms
1240
+ # Trailblazer::Operation 1.525k i/100ms
1241
+ # Dry::Monads 7.126k i/100ms
1242
+ # Dry::Transaction 499.000 i/100ms
1243
+ # Micro::Case 9.919k i/100ms
1244
+ # Micro::Case::Strict 7.837k i/100ms
1245
+ # Micro::Case::Safe 9.762k i/100ms
1246
+
1247
+ # Calculating -------------------------------------
1248
+ # Interactor 13.959k (± 2.5%) i/s - 70.172k in 5.030240s
1249
+ # Trailblazer::Operation 15.035k (± 2.2%) i/s - 76.250k in 5.074108s
1250
+ # Dry::Monads 71.330k (± 2.4%) i/s - 363.426k in 5.097993s
1251
+ # Dry::Transaction 5.068k (± 1.9%) i/s - 25.449k in 5.023922s
1252
+ # Micro::Case 98.821k (± 2.9%) i/s - 495.950k in 5.023421s
1253
+ # Micro::Case::Strict 79.936k (± 3.1%) i/s - 399.687k in 5.005435s
1254
+ # Micro::Case::Safe 98.695k (± 1.9%) i/s - 497.862k in 5.046246s
1255
+
1256
+ # Comparison:
1257
+ # Micro::Case: 98820.8 i/s
1258
+ # Micro::Case::Safe: 98695.0 i/s - same-ish: difference falls within error
1259
+ # Micro::Case::Strict: 79935.9 i/s - 1.24x (± 0.00) slower
1260
+ # Dry::Monads: 71329.7 i/s - 1.39x (± 0.00) slower
1261
+ # Trailblazer::Operation: 15034.9 i/s - 6.57x (± 0.00) slower
1262
+ # Interactor: 13958.7 i/s - 7.08x (± 0.00) slower
1263
+ # Dry::Transaction: 5067.5 i/s - 19.50x (± 0.00) slower
1264
+ ```
1265
+ </details>
1266
+
1267
+ https://github.com/serradura/u-case/blob/main/benchmarks/use_case/with_failure_result.rb
1268
+
1269
+ ---
1270
+
1271
+ ### `Micro::Cases::Flow` (v3.0.0)
1272
+
1273
+ | Gem / Abstração | [Resultados de sucesso](https://github.com/serradura/u-case/blob/main/benchmarks/flow/with_success_result.rb#L40) | [Resultados de falha](https://github.com/serradura/u-case/blob/main/benchmarks/flow/with_failure_result.rb#L40) |
1274
+ | ------------------------------------------- | ----------------: | ----------------: |
1275
+ | Micro::Case internal flow (private methods) | _**O mais rápido**_ | _**O mais rápido**_ |
1276
+ | Micro::Case internal flow (through lambdas) | 1.03x slower | 1.04x slower |
1277
+ | Micro::Case `then` method | 1.49x slower | 0x slower |
1278
+ | Micro::Cases.flow | 1.53x slower | 1.04x slower |
1279
+ | Micro::Cases.safe_flow | 1.54x slower | 1.04x slower |
1280
+ | Interactor::Organizer | 2.05x slower | 6.27x slower |
1281
+
1282
+ \* As gems `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` estão fora desta análise por não terem esse tipo de funcionalidade.
1283
+
1284
+ <details>
1285
+ <summary><strong>Resultados de sucesso</strong> - Mostrar o resultado completo do benchmark/ips.</summary>
1286
+
1287
+ ```ruby
1288
+ # Warming up --------------------------------------
1289
+ # Interactor::Organizer
1290
+ # 4.837k i/100ms
1291
+ # Micro::Cases.flow([])
1292
+ # 6.755k i/100ms
1293
+ # Micro::Cases::safe_flow([])
1294
+ # 6.809k i/100ms
1295
+ # Micro::Case flow using `then` method
1296
+ # 6.968k i/100ms
1297
+ # Micro::Case flow using private methods
1298
+ # 10.362k i/100ms
1299
+ # Micro::Case flow using private methods through lambdas
1300
+ # 10.258k i/100ms
1301
+
1302
+ # Calculating -------------------------------------
1303
+ # Interactor::Organizer
1304
+ # 50.731k (± 1.6%) i/s - 256.361k in 5.054694s
1305
+ # Micro::Cases.flow([])
1306
+ # 67.757k (± 1.6%) i/s - 344.505k in 5.085681s
1307
+ # Micro::Cases::safe_flow([])
1308
+ # 67.613k (± 1.6%) i/s - 340.450k in 5.036562s
1309
+ # Micro::Case flow using `then` method
1310
+ # 69.483k (± 1.5%) i/s - 348.400k in 5.015351s
1311
+ # Micro::Case flow using private methods
1312
+ # 103.788k (± 1.0%) i/s - 528.462k in 5.092240s
1313
+ # Micro::Case flow using private methods through lambdas
1314
+ # 101.081k (± 1.2%) i/s - 512.900k in 5.074904s
1315
+
1316
+ # Comparison:
1317
+ # Micro::Case flow using private methods: 103787.5 i/s
1318
+ # Micro::Case flow using private methods through lambdas: 101080.6 i/s - 1.03x (± 0.00) slower
1319
+ # Micro::Case flow using `then` method: 69483.3 i/s - 1.49x (± 0.00) slower
1320
+ # Micro::Cases.flow([]): 67757.2 i/s - 1.53x (± 0.00) slower
1321
+ # Micro::Cases::safe_flow([]): 67613.3 i/s - 1.54x (± 0.00) slower
1322
+ # Interactor::Organizer: 50730.8 i/s - 2.05x (± 0.00) slower
1323
+ ```
1324
+ </details>
1325
+
1326
+ <details>
1327
+ <summary><strong>Resultados de falha</strong> - Mostrar o resultado completo do benchmark/ips.</summary>
1328
+
1329
+ ```ruby
1330
+ # Warming up --------------------------------------
1331
+ # Interactor::Organizer
1332
+ # 2.299k i/100ms
1333
+ # Micro::Cases.flow([])
1334
+ # 14.187k i/100ms
1335
+ # Micro::Cases::safe_flow([])
1336
+ # 13.609k i/100ms
1337
+ # Micro::Case flow using `then` method
1338
+ # 14.578k i/100ms
1339
+ # Micro::Case flow using private methods
1340
+ # 14.101k i/100ms
1341
+ # Micro::Case flow using private methods through lambdas
1342
+ # 13.670k i/100ms
1343
+ # Calculating -------------------------------------
1344
+ # Interactor::Organizer
1345
+ # 23.306k (± 2.1%) i/s - 117.249k in 5.033171s
1346
+ # Micro::Cases.flow([])
1347
+ # 140.111k (± 1.6%) i/s - 709.350k in 5.064041s
1348
+ # Micro::Cases::safe_flow([])
1349
+ # 139.927k (± 1.7%) i/s - 707.668k in 5.058971s
1350
+ # Micro::Case flow using `then` method
1351
+ # 146.073k (± 2.0%) i/s - 743.478k in 5.091741s
1352
+ # Micro::Case flow using private methods
1353
+ # 142.092k (± 1.5%) i/s - 719.151k in 5.062298s
1354
+ # Micro::Case flow using private methods through lambdas
1355
+ # 140.791k (± 1.2%) i/s - 710.840k in 5.049584s
1356
+
1357
+ # Comparison:
1358
+ # Micro::Case flow using `then` method: 146073.0 i/s
1359
+ # Micro::Case flow using private methods: 142091.7 i/s - same-ish: difference falls within error
1360
+ # Micro::Case flow using private methods through lambdas: 140791.1 i/s - 1.04x (± 0.00) slower
1361
+ # Micro::Cases.flow([]): 140110.8 i/s - 1.04x (± 0.00) slower
1362
+ # Micro::Cases::safe_flow([]): 139926.6 i/s - 1.04x (± 0.00) slower
1363
+ # Interactor::Organizer: 23305.9 i/s - 6.27x (± 0.00) slower
1364
+ ```
1365
+ </details>
1366
+
1367
+ https://github.com/serradura/u-case/tree/main/benchmarks/flow
1368
+
1369
+ ### Comparações
1370
+
1371
+ Confira as implementações do mesmo caso de uso com diferentes gems/abstrações.
1372
+
1373
+ * [interactor](https://github.com/serradura/u-case/blob/main/comparisons/interactor.rb)
1374
+ * [u-case](https://github.com/serradura/u-case/blob/main/comparisons/u-case.rb)
1375
+
1376
+ [⬆️ Voltar para o índice](#índice-)
1377
+
1378
+ ## Exemplos
1379
+
1380
+ ### 1️⃣ Criação de usuários
1381
+
1382
+ > Um exemplo de fluxo que define etapas para higienizar, validar e persistir seus dados de entrada. Ele tem todas as abordagens possíveis para representar casos de uso com a gem `u-case`.
1383
+ >
1384
+ > Link: https://github.com/serradura/u-case/blob/main/examples/users_creation
1385
+
1386
+ ### 2️⃣ Rails App (API)
1387
+
1388
+ > Este projeto mostra diferentes tipos de arquitetura (uma por commit), e na última, como usar a gem `Micro::Case` para lidar com a lógica de negócios da aplicação.
1389
+ >
1390
+ > Link: https://github.com/serradura/from-fat-controllers-to-use-cases
1391
+
1392
+ ### 3️⃣ CLI calculator
1393
+
1394
+ > Rake tasks para demonstrar como lidar com os dados do usuário e como usar diferentes tipos de falha para controlar o fluxo do programa.
1395
+ >
1396
+ > Link: https://github.com/serradura/u-case/tree/main/examples/calculator
1397
+
1398
+ ### 4️⃣ Interceptando exceções dentro dos casos de uso
1399
+
1400
+ > Link: https://github.com/serradura/u-case/blob/main/examples/rescuing_exceptions.rb
1401
+
1402
+ [⬆️ Voltar para o índice](#índice-)
1403
+
1404
+ ## Desenvolvimento
1405
+
1406
+ Após fazer o checking out do repo, execute `bin/setup` para instalar dependências. Então, execute `./test.sh` para executar os testes. Você pode executar `bin/console` para ter um prompt interativo que permitirá você experimenta-lá.
1407
+
1408
+ Para instalar esta gem em sua máquina local, execute `bundle exec rake install`. Para lançar uma nova versão, atualize o número da versão em `version.rb` e execute` bundle exec rake release`, que criará uma tag git para a versão, enviará git commits e tags e enviará o arquivo `.gem`para [rubygems.org](https://rubygems.org).
1409
+
1410
+ ## Contribuindo
1411
+
1412
+ Reportar bugs e solicitar pull requests são bem-vindos no GitHub em https://github.com/serradura/u-case. Este projeto pretende ser um espaço seguro e acolhedor para colaboração, e espera-se que os colaboradores sigam o código de conduta do [Covenant do Contribuidor](http://contributor-covenant.org).
1413
+
1414
+ ## Licença
1415
+
1416
+ A gem está disponível como código aberto nos termos da [licença MIT](https://opensource.org/licenses/MIT).
1417
+
1418
+ ## Código de conduta
1419
+
1420
+ Espera-se que todos que interagem com o codebase do projeto `Micro::Case`, issue trackers, chat rooms and mailing lists sigam o [código de conduta](https://github.com/serradura/u-case/blob/main/CODE_OF_CONDUCT.md).