u-case 5.6.0 → 5.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5539d3b5cac24a50728c4dd8285e2f36c4a155f344a5f95763b8b02afaa64505
4
- data.tar.gz: 794905f57057a6f198200bbd2b5c3c958440b3fa3154366a636025553a084553
3
+ metadata.gz: bcab602e98bfb71b2bb2439dd162b3401098bfa41b8234ad39a1a7dd6dad5e6a
4
+ data.tar.gz: 4d975264e714cb751d595bc945915ac1273d486c0dd5ee6ce156076cbe858ba2
5
5
  SHA512:
6
- metadata.gz: fc924d76d4e1dbc1dc1a2567688a881a17b726b74210ad57277af2fdd7456e02358501d2f34171d0a027227f5e99a2853aad82f64bca2007e18fefc518e3e622
7
- data.tar.gz: e20f832a0b48456a653e48710c2444c8649dce2a68477c4299b856b23939074d01457215e6c3290c1e417cd38724318289f27055505d1b2865b90ca4c15fee0d
6
+ metadata.gz: 3f5107a4329d04aab99e0458fa5784705c78cc648fdaee53be172b306501f824ccf3af5f8c70629b2ad39289745cb5f79ebb39fa95958cc1820d20913f65111a
7
+ data.tar.gz: 29be61f84a1e4cbdb42f13bbcf72a8f4e3eaf7618300c0f169461adfc21b12a2ad9215e651bc6a9f366fc3658987d19ff3feadfec61609ff3e3d4860e6b7585d
data/CHANGELOG.md CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  > **Note:** This gem was originally published as `u-service` (versions 0.1.0 – 1.0.0) and renamed to `u-case` starting with `u-case 1.0.0` on 2019-09-15.
9
9
 
10
+ ## [5.7.0] - 2026-05-25
11
+ ### Added
12
+ - Pattern matching support on `Micro::Case::Result` via `#deconstruct` and `#deconstruct_keys` (closes #146). Purely additive — no existing API is changed or removed. `#deconstruct` returns `[status, type, data]` where `status` is `:success` or `:failure`, so array patterns like `in [:failure, :invalid_attributes, { invalid_attributes: errors }]` can use the status as a discriminant — mirroring how libraries with separate `Success`/`Failure` classes are pattern-matched, even though `Micro::Case::Result` is a single class. `#deconstruct_keys` exposes `:type`, `:data`, `:result` (alias of `:data` that matches the `Success(result: …)` creation-site vocabulary), `:use_case` and `:transitions` on every result; `:success` is present only on success results and `:failure` only on failure results, and both carry the result `type` symbol as their value so `in { failure: :invalid_attributes }` works. `#deconstruct_keys` honors Ruby's `keys` argument and only computes the requested entries (relevant for `:transitions`, which allocates a duped array).
13
+ - READMEs (EN + pt-BR) document the new pattern under the `Micro::Case::Result` section, including the key table, the `data:` / `result:` alias note, and the intentional shape difference between `#deconstruct` (`[status, type, data]`, used by pattern matching) and `#to_ary` (`[data, type]`, unchanged, used by multi-assignment).
14
+
10
15
  ## [5.6.0] - 2026-05-24
11
16
  ### Added
12
17
  - `Micro::Cases.flow(transaction: true, steps: [...])` and `Micro::Cases.safe_flow(transaction: true, steps: [...])` to wrap an entire flow in an `ActiveRecord::Base.transaction`. Any step that returns a failure (or, in `safe_flow`, raises) triggers an `ActiveRecord::Rollback`. The same kwargs are accepted by the class-level macro: `class MyCase < Micro::Case; flow(transaction: true, steps: [...]); end` (closes #44).
@@ -495,6 +500,7 @@ First release under the `u-case` name (renamed from `u-service`).
495
500
  - `Micro::Service::Result` with `Success`/`Failure` factories and helper methods for returning typed results from services.
496
501
  - Runtime dependency on `u-attributes` for service input declaration.
497
502
 
503
+ [5.7.0]: https://github.com/serradura/u-case/compare/v5.6.0...v5.7.0
498
504
  [5.6.0]: https://github.com/serradura/u-case/compare/v5.5.0...v5.6.0
499
505
  [5.5.0]: https://github.com/serradura/u-case/compare/v5.4.0...v5.5.0
500
506
  [5.4.0]: https://github.com/serradura/u-case/compare/v5.3.1...v5.4.0
data/README.md CHANGED
@@ -46,6 +46,7 @@ unreleased| https://github.com/serradura/u-case/blob/main/README.md
46
46
  - [How to use the result hooks?](#how-to-use-the-result-hooks)
47
47
  - [Why the hook usage without a defined type exposes the result itself?](#why-the-hook-usage-without-a-defined-type-exposes-the-result-itself)
48
48
  - [Using decomposition to access the result data and type](#using-decomposition-to-access-the-result-data-and-type)
49
+ - [Using pattern matching to destructure a result](#using-pattern-matching-to-destructure-a-result)
49
50
  - [What happens if a result hook was declared multiple times?](#what-happens-if-a-result-hook-was-declared-multiple-times)
50
51
  - [How to use the `Micro::Case::Result#then` method?](#how-to-use-the-microcaseresultthen-method)
51
52
  - [What does happens when a `Micro::Case::Result#then` receives a block?](#what-does-happens-when-a-microcaseresultthen-receives-a-block)
@@ -502,6 +503,54 @@ Double
502
503
 
503
504
  [⬆️ Back to Top](#table-of-contents-)
504
505
 
506
+ ##### Using pattern matching to destructure a result
507
+
508
+ `Micro::Case::Result` implements [`deconstruct`](https://docs.ruby-lang.org/en/3.4/syntax/pattern_matching_rdoc.html) and [`deconstruct_keys`](https://docs.ruby-lang.org/en/3.4/syntax/pattern_matching_rdoc.html), so Ruby's `case`/`in` pattern matching works out of the box (requires Ruby `>= 2.7`).
509
+
510
+ ```ruby
511
+ result = Divide.call(a: 10, b: 2)
512
+
513
+ case result
514
+ in { success: _, data: { number: Numeric => number } }
515
+ puts "got #{number}"
516
+ in { failure: :invalid_attributes, data: { invalid_attributes: errors } }
517
+ warn "bad input: #{errors.keys.join(", ")}"
518
+ in { failure: :exception, data: { exception: } }
519
+ warn "boom: #{exception.message}"
520
+ end
521
+ ```
522
+
523
+ The hash patterns expose these keys:
524
+
525
+ | Key | Present on | Value |
526
+ | --------------- | ----------------- | ------------------------------------------------------- |
527
+ | `success:` | success only | the result `type` (e.g. `:ok`) |
528
+ | `failure:` | failure only | the result `type` (e.g. `:invalid_attributes`) |
529
+ | `type:` | always | the result `type` |
530
+ | `data:` | always | the result `data` hash |
531
+ | `result:` | always | alias of `data:` (matches the `Success(result: …)` keyword used at the creation site) |
532
+ | `use_case:` | always | the use case instance that produced the result |
533
+ | `transitions:` | always | the result `transitions` array |
534
+
535
+ > **Note:** On the **reader** side, `Result#data` is also accessible as `Result#value` (existing alias). On the **pattern-matching** side, the `data:` key is also accessible as `result:` — both refer to the same payload.
536
+
537
+ `Result#deconstruct` returns a three-element array `[status, type, data]` where `status` is `:success` or `:failure`, so array patterns can use the status as a discriminant — mirroring how libraries with separate `Success`/`Failure` classes are pattern-matched, even though `Micro::Case::Result` is a single class:
538
+
539
+ ```ruby
540
+ case result
541
+ in [:success, :ok, { number: Integer => n }]
542
+ n
543
+ in [:failure, :invalid_attributes, { invalid_attributes: errors }]
544
+ # ...
545
+ in [:failure, :exception, { exception: }]
546
+ # ...
547
+ end
548
+ ```
549
+
550
+ > **Note:** `Result#to_ary` is unchanged and still returns `[data, type]` (used by multi-assignment, e.g. `data, type = result`). Ruby's pattern matching uses `#deconstruct`, so the two hooks intentionally return different shapes.
551
+
552
+ [⬆️ Back to Top](#table-of-contents-)
553
+
505
554
  #### What happens if a result hook was declared multiple times?
506
555
 
507
556
  Answer: The hook always will be triggered if it matches the result type.
data/README.pt-BR.md CHANGED
@@ -44,6 +44,7 @@ unreleased| https://github.com/serradura/u-case/blob/main/README.md
44
44
  - [Como utilizar os hooks dos resultados?](#como-utilizar-os-hooks-dos-resultados)
45
45
  - [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)
46
46
  - [Usando decomposição para acessar os dados e tipo do resultado](#usando-decomposição-para-acessar-os-dados-e-tipo-do-resultado)
47
+ - [Usando pattern matching para desestruturar um resultado](#usando-pattern-matching-para-desestruturar-um-resultado)
47
48
  - [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)
48
49
  - [Como usar o método `Micro::Case::Result#then`?](#como-usar-o-método-microcaseresultthen)
49
50
  - [O que acontece quando um `Micro::Case::Result#then` recebe um bloco?](#o-que-acontece-quando-um-microcaseresultthen-recebe-um-bloco)
@@ -501,6 +502,54 @@ Double
501
502
 
502
503
  [⬆️ Voltar para o índice](#índice-)
503
504
 
505
+ ##### Usando pattern matching para desestruturar um resultado
506
+
507
+ `Micro::Case::Result` implementa [`deconstruct`](https://docs.ruby-lang.org/en/3.4/syntax/pattern_matching_rdoc.html) e [`deconstruct_keys`](https://docs.ruby-lang.org/en/3.4/syntax/pattern_matching_rdoc.html), então o pattern matching do Ruby (`case`/`in`) funciona de forma nativa (requer Ruby `>= 2.7`).
508
+
509
+ ```ruby
510
+ result = Divide.call(a: 10, b: 2)
511
+
512
+ case result
513
+ in { success: _, data: { number: Numeric => number } }
514
+ puts "deu #{number}"
515
+ in { failure: :invalid_attributes, data: { invalid_attributes: errors } }
516
+ warn "entrada inválida: #{errors.keys.join(", ")}"
517
+ in { failure: :exception, data: { exception: } }
518
+ warn "boom: #{exception.message}"
519
+ end
520
+ ```
521
+
522
+ Os hash patterns expõem essas chaves:
523
+
524
+ | Chave | Presente em | Valor |
525
+ | --------------- | ----------------- | --------------------------------------------------------------------------------------- |
526
+ | `success:` | apenas em sucesso | o `type` do resultado (ex.: `:ok`) |
527
+ | `failure:` | apenas em falha | o `type` do resultado (ex.: `:invalid_attributes`) |
528
+ | `type:` | sempre | o `type` do resultado |
529
+ | `data:` | sempre | o hash de `data` do resultado |
530
+ | `result:` | sempre | apelido de `data:` (combina com a keyword `result:` usada em `Success(result: …)`) |
531
+ | `use_case:` | sempre | a instância de caso de uso que produziu o resultado |
532
+ | `transitions:` | sempre | o array de `transitions` do resultado |
533
+
534
+ > **Nota:** No lado de **leitura**, `Result#data` também é acessível como `Result#value` (apelido existente). No lado de **pattern matching**, a chave `data:` também é acessível como `result:` — ambas se referem ao mesmo payload.
535
+
536
+ `Result#deconstruct` retorna um array de três elementos `[status, type, data]`, onde `status` é `:success` ou `:failure`. Isso permite que array patterns usem o status como discriminante — espelhando como bibliotecas com classes `Success`/`Failure` separadas fazem pattern matching, mesmo que `Micro::Case::Result` seja uma classe única:
537
+
538
+ ```ruby
539
+ case result
540
+ in [:success, :ok, { number: Integer => n }]
541
+ n
542
+ in [:failure, :invalid_attributes, { invalid_attributes: errors }]
543
+ # ...
544
+ in [:failure, :exception, { exception: }]
545
+ # ...
546
+ end
547
+ ```
548
+
549
+ > **Nota:** `Result#to_ary` permanece inalterado e ainda retorna `[data, type]` (usado pela atribuição múltipla, ex.: `data, type = result`). O pattern matching do Ruby usa `#deconstruct`, então os dois hooks intencionalmente retornam shapes diferentes.
550
+
551
+ [⬆️ Voltar para o índice](#índice-)
552
+
504
553
  #### O que acontece se um hook de resultado for declarado múltiplas vezes?
505
554
 
506
555
  Resposta: Se o tipo do resultado for identificado o hook será sempre executado.
@@ -50,6 +50,32 @@ module Micro
50
50
  @__success ? :success : :failure
51
51
  end
52
52
 
53
+ def deconstruct
54
+ [@__success ? :success : :failure, type, data]
55
+ end
56
+
57
+ def deconstruct_keys(keys)
58
+ if keys.nil?
59
+ hash = { type: type, data: data, result: data, use_case: use_case, transitions: transitions }
60
+ hash[@__success ? :success : :failure] = type
61
+ return hash
62
+ end
63
+
64
+ hash = {}
65
+ keys.each do |key|
66
+ case key
67
+ when :type then hash[:type] = type
68
+ when :data then hash[:data] = data
69
+ when :result then hash[:result] = data
70
+ when :use_case then hash[:use_case] = use_case
71
+ when :transitions then hash[:transitions] = transitions
72
+ when :success then hash[:success] = type if @__success
73
+ when :failure then hash[:failure] = type if !@__success
74
+ end
75
+ end
76
+ hash
77
+ end
78
+
53
79
  def [](key)
54
80
  data[key]
55
81
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Micro
4
4
  class Case
5
- VERSION = '5.6.0'.freeze
5
+ VERSION = '5.7.0'.freeze
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: u-case
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.6.0
4
+ version: 5.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura