u-case 2.6.0 → 3.0.0.rc5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1390 @@
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=master)](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.rc5 | https://github.com/serradura/u-case/blob/master/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️⃣ Rails App (API)](#1️⃣-rails-app-api)
69
+ - [2️⃣ CLI calculator](#2️⃣-cli-calculator)
70
+ - [3️⃣ Criação de usuários](#3️⃣-criação-de-usuários)
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.rc5 | 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.rc5'
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 | 139037.7 | _**O mais rápido**_ |
1184
+ | **Micro::Case** | 101497.3 | 1.37x slower |
1185
+ | Interactor | 30694.2 | 4.53x slower |
1186
+ | Trailblazer::Operation | 14580.8 | 9.54x slower |
1187
+ | Dry::Transaction | 5728.0 | 24.27x 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 3.056k i/100ms
1195
+ # Trailblazer::Operation 1.480k i/100ms
1196
+ # Dry::Monads 14.316k i/100ms
1197
+ # Dry::Transaction 576.000 i/100ms
1198
+ # Micro::Case 10.388k i/100ms
1199
+ # Micro::Case::Strict 8.223k i/100ms
1200
+ # Micro::Case::Safe 10.057k i/100ms
1201
+
1202
+ # Calculating -------------------------------------
1203
+ # Interactor 30.694k (± 2.3%) i/s - 155.856k in 5.080475s
1204
+ # Trailblazer::Operation 14.581k (± 3.9%) i/s - 74.000k in 5.083091s
1205
+ # Dry::Monads 139.038k (± 3.0%) i/s - 701.484k in 5.049921s
1206
+ # Dry::Transaction 5.728k (± 3.6%) i/s - 28.800k in 5.034599s
1207
+ # Micro::Case 100.712k (± 3.4%) i/s - 509.012k in 5.060139s
1208
+ # Micro::Case::Strict 81.513k (± 3.4%) i/s - 411.150k in 5.049962s
1209
+ # Micro::Case::Safe 101.497k (± 3.1%) i/s - 512.907k in 5.058463s
1210
+
1211
+ # Comparison:
1212
+ # Dry::Monads: 139037.7 i/s
1213
+ # Micro::Case::Safe: 101497.3 i/s - 1.37x (± 0.00) slower
1214
+ # Micro::Case: 100711.6 i/s - 1.38x (± 0.00) slower
1215
+ # Micro::Case::Strict: 81512.9 i/s - 1.71x (± 0.00) slower
1216
+ # Interactor: 30694.2 i/s - 4.53x (± 0.00) slower
1217
+ # Trailblazer::Operation: 14580.8 i/s - 9.54x (± 0.00) slower
1218
+ # Dry::Transaction: 5728.0 i/s - 24.27x (± 0.00) slower
1219
+ ```
1220
+ </details>
1221
+
1222
+ https://github.com/serradura/u-case/blob/master/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** | 94619.6 | _**O mais rápido**_ |
1229
+ | Dry::Monads | 70250.6 | 1.35x slower |
1230
+ | Trailblazer::Operation | 14786.1 | 6.40x slower |
1231
+ | Interactor | 13770.0 | 6.87x slower |
1232
+ | Dry::Transaction | 4994.4 | 18.95x 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.408k i/100ms
1240
+ # Trailblazer::Operation 1.492k i/100ms
1241
+ # Dry::Monads 7.224k i/100ms
1242
+ # Dry::Transaction 501.000 i/100ms
1243
+ # Micro::Case 9.664k i/100ms
1244
+ # Micro::Case::Strict 7.823k i/100ms
1245
+ # Micro::Case::Safe 9.464k i/100ms
1246
+
1247
+ # Calculating -------------------------------------
1248
+ # Interactor 13.770k (± 4.3%) i/s - 68.992k in 5.020330s
1249
+ # Trailblazer::Operation 14.786k (± 5.3%) i/s - 74.600k in 5.064700s
1250
+ # Dry::Monads 70.251k (± 6.7%) i/s - 353.976k in 5.063010s
1251
+ # Dry::Transaction 4.994k (± 4.0%) i/s - 25.050k in 5.023997s
1252
+ # Micro::Case 94.620k (± 3.8%) i/s - 473.536k in 5.012483s
1253
+ # Micro::Case::Strict 76.059k (± 3.0%) i/s - 383.327k in 5.044482s
1254
+ # Micro::Case::Safe 91.719k (± 5.6%) i/s - 463.736k in 5.072552s
1255
+
1256
+ # Comparison:
1257
+ # Micro::Case: 94619.6 i/s
1258
+ # Micro::Case::Safe: 91719.4 i/s - same-ish: difference falls within error
1259
+ # Micro::Case::Strict: 76058.7 i/s - 1.24x (± 0.00) slower
1260
+ # Dry::Monads: 70250.6 i/s - 1.35x (± 0.00) slower
1261
+ # Trailblazer::Operation: 14786.1 i/s - 6.40x (± 0.00) slower
1262
+ # Interactor: 13770.0 i/s - 6.87x (± 0.00) slower
1263
+ # Dry::Transaction: 4994.4 i/s - 18.95x (± 0.00) slower
1264
+ ```
1265
+ </details>
1266
+
1267
+ https://github.com/serradura/u-case/blob/master/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/master/benchmarks/flow/with_success_result.rb#L40) | [Resultados de falha](https://github.com/serradura/u-case/blob/master/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 `then` method | 1.48x slower | 0x slower |
1277
+ | Micro::Cases.flow | 1.62x slower | 1.16x slower |
1278
+ | Micro::Cases.safe_flow | 1.64x slower | 1.16x slower |
1279
+ | Interactor::Organizer | 1.95x slower | 6.17x slower |
1280
+
1281
+ \* As gems `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` estão fora desta análise por não terem esse tipo de funcionalidade.
1282
+
1283
+ <details>
1284
+ <summary><strong>Resultados de sucesso</strong> - Mostrar o resultado completo do benchmark/ips.</summary>
1285
+
1286
+ ```ruby
1287
+ # Warming up --------------------------------------
1288
+ # Interactor::Organizer 5.219k i/100ms
1289
+ # Micro::Cases.flow([]) 6.451k i/100ms
1290
+ # Micro::Cases::safe_flow([]) 6.421k i/100ms
1291
+ # Micro::Case flow using `then` method 7.139k i/100ms
1292
+ # Micro::Case flow using private methods 10.355k i/100ms
1293
+
1294
+ # Calculating -------------------------------------
1295
+ # Interactor::Organizer 52.959k (± 1.7%) i/s - 266.169k in 5.027332s
1296
+ # Micro::Cases.flow([]) 63.947k (± 1.7%) i/s - 322.550k in 5.045597s
1297
+ # Micro::Cases::safe_flow([]) 63.047k (± 3.1%) i/s - 321.050k in 5.097228s
1298
+ # Micro::Case flow using `then` method 69.644k (± 4.0%) i/s - 349.811k in 5.031120s
1299
+ # Micro::Case flow using private methods 103.297k (± 1.4%) i/s - 517.750k in 5.013254s
1300
+
1301
+ # Comparison:
1302
+ # Micro::Case flow using private methods: 103297.4 i/s
1303
+ # Micro::Case flow using `then` method: 69644.0 i/s - 1.48x (± 0.00) slower
1304
+ # Micro::Cases.flow([]): 63946.7 i/s - 1.62x (± 0.00) slower
1305
+ # Micro::Cases::safe_flow([]): 63047.2 i/s - 1.64x (± 0.00) slower
1306
+ # Interactor::Organizer: 52958.9 i/s - 1.95x (± 0.00) slower
1307
+ ```
1308
+ </details>
1309
+
1310
+ <details>
1311
+ <summary><strong>Resultados de falha</strong> - Mostrar o resultado completo do benchmark/ips.</summary>
1312
+
1313
+ ```ruby
1314
+ # Warming up --------------------------------------
1315
+ # Interactor::Organizer 2.381k i/100ms
1316
+ # Micro::Cases.flow([]) 12.003k i/100ms
1317
+ # Micro::Cases::safe_flow([]) 12.771k i/100ms
1318
+ # Micro::Case flow using `then` method 15.085k i/100ms
1319
+ # Micro::Case flow using private methods 14.254k i/100ms
1320
+
1321
+ # Calculating -------------------------------------
1322
+ # Interactor::Organizer 23.579k (± 3.2%) i/s - 119.050k in 5.054410s
1323
+ # Micro::Cases.flow([]) 124.072k (± 3.4%) i/s - 624.156k in 5.036618s
1324
+ # Micro::Cases::safe_flow([]) 124.894k (± 3.6%) i/s - 625.779k in 5.017494s
1325
+ # Micro::Case flow using `then` method 145.370k (± 4.8%) i/s - 739.165k in 5.096972s
1326
+ # Micro::Case flow using private methods 139.753k (± 5.6%) i/s - 698.446k in 5.015207s
1327
+
1328
+ # Comparison:
1329
+ # Micro::Case flow using `then` method: 145369.7 i/s
1330
+ # Micro::Case flow using private methods: 139753.4 i/s - same-ish: difference falls within error
1331
+ # Micro::Cases::safe_flow([]): 124893.7 i/s - 1.16x (± 0.00) slower
1332
+ # Micro::Cases.flow([]): 124071.8 i/s - 1.17x (± 0.00) slower
1333
+ # Interactor::Organizer: 23578.7 i/s - 6.17x (± 0.00) slower
1334
+ ```
1335
+ </details>
1336
+
1337
+ https://github.com/serradura/u-case/tree/master/benchmarks/flow
1338
+
1339
+ ### Comparações
1340
+
1341
+ Confira as implementações do mesmo caso de uso com diferentes gems/abstrações.
1342
+
1343
+ * [interactor](https://github.com/serradura/u-case/blob/master/comparisons/interactor.rb)
1344
+ * [u-case](https://github.com/serradura/u-case/blob/master/comparisons/u-case.rb)
1345
+
1346
+ [⬆️ Voltar para o índice](#índice-)
1347
+
1348
+ ## Exemplos
1349
+
1350
+ ### 1️⃣ Rails App (API)
1351
+
1352
+ > 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.
1353
+ >
1354
+ > Link: https://github.com/serradura/from-fat-controllers-to-use-cases
1355
+
1356
+ ### 2️⃣ CLI calculator
1357
+
1358
+ > 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.
1359
+ >
1360
+ > Link: https://github.com/serradura/u-case/tree/master/examples/calculator
1361
+
1362
+ ### 3️⃣ Criação de usuários
1363
+
1364
+ > Um exemplo de fluxo de caso de uso que define etapas para higienizar, validar e persistir seus dados de entrada.
1365
+ >
1366
+ > Link: https://github.com/serradura/u-case/blob/master/examples/users_creation.rb
1367
+
1368
+ ### 4️⃣ Interceptando exceções dentro dos casos de uso
1369
+
1370
+ > Link: https://github.com/serradura/u-case/blob/master/examples/rescuing_exceptions.rb
1371
+
1372
+ [⬆️ Voltar para o índice](#índice-)
1373
+
1374
+ ## Desenvolvimento
1375
+
1376
+ 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á.
1377
+
1378
+ 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).
1379
+
1380
+ ## Contribuindo
1381
+
1382
+ 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).
1383
+
1384
+ ## Licença
1385
+
1386
+ A gem está disponível como código aberto nos termos da [licença MIT](https://opensource.org/licenses/MIT).
1387
+
1388
+ ## Código de conduta
1389
+
1390
+ 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/master/CODE_OF_CONDUCT.md).