hubbado-sequence 0.7.0 → 0.8.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: 4c072f343cb764529d76c7babb7824217546cadeaf4cab59c305b40b0b1f7647
4
- data.tar.gz: 15c860841338562d2ba8b39c8d49d1a95959345eec316489878676a0b9f4a3e4
3
+ metadata.gz: bae3a73329cdbf0194b5074fae10bd5e2d4035e773ee70f238ca802a5d0f2ae7
4
+ data.tar.gz: 8ff2d5bc1088333c8480695d411000f7b361efb22e296057c0111f88c0886113
5
5
  SHA512:
6
- metadata.gz: 414d6011c8b468686ba3fe6f93adfa119d16196b25cc3313073463a56673aaf5a51100cb2b21ecd96e471511b4954cff6bbfd7e14806fc2c9370ac7c40a40020
7
- data.tar.gz: df9d8e8d1c8ce2715f7b8582de9ed6a51106f5ff0f6c99caf596d9fb0f8b4ec5dc0818fecf01cb92149aa13f9813c40b7221b4cdcb1604d9a8ef3b0f43e037db
6
+ metadata.gz: 16069e3f557800e6c0c18e2f6b52c5bec482d2d8610d244e2679403879bf81ab742bbcf4da74d63a03fbaa12614103e51ffe53a6889d45b6cc3a73509ca3ecd5
7
+ data.tar.gz: c92c360d5fe1995cf05eefb330ab1949d0870d6ede7fcc308f350e7c23f98118544cda4d218580ba62d12793e7bbf6329af7f2b33a4c0adc6b5d87db2bc85dea
data/CHANGELOG.md CHANGED
@@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## [0.8.0] - Instance calls take only a Ctx; chained pipeline carries the i18n scope
8
+
9
+ ### Changed (breaking)
10
+
11
+ - **`pipeline` rejects anything but a `Ctx`.** A sequencer's instance
12
+ `call` takes a `Ctx`, as `docs/design.md` already stated, and
13
+ `pipeline` now raises `ArgumentError` when it gets keyword arguments or
14
+ a plain Hash. A spec that calls an instance
15
+ (`seq = Seq.new; seq.(params: ...)`) must pass
16
+ `Hubbado::Sequence::Ctx.build(params: ...)`. On a plain Hash, a read of a
17
+ missing ctx key returned `nil` in the spec while it raised `KeyError` in
18
+ production through the class-level `.()`, so specs could not find that
19
+ fault. The class-level `.()` still accepts keyword arguments.
20
+
21
+ ### Fixed
22
+
23
+ - **A chained `pipeline(ctx).step(...).result` carries the sequencer's
24
+ i18n scope.** Only the block form tagged its Result, so a sequencer
25
+ using the chained form got no scope on an instance call, and its
26
+ parent's scope when nested through `p.invoke`. The pipeline now applies
27
+ the scope in both forms. The README states that a failure returned
28
+ without `pipeline` should be built with the `failure` helper.
29
+
7
30
  ## [0.7.0] - Macros::Policy::Check record-less policies; Sequencer i18n_scope applied at boundary
8
31
 
9
32
  ### Changed (breaking)
data/README.md CHANGED
@@ -519,11 +519,18 @@ the same kwargs as `Result.failure` (`code:`, `data:`, `step:`,
519
519
  4. **Humanized code** — `:not_found` → `"Not found"`.
520
520
 
521
521
  The sequencer's scope is applied automatically. Both the `failure(ctx, ...)`
522
- helper *and* the boundary itself (`Sequencer#pipeline` and `Sequencer.()`)
523
- tag the returned `Result` with `i18n_scope` via `Result#with_i18n_scope`.
524
- That means an unscoped failure produced inside a macro, a hand-rolled
525
- step, or anywhere else in the sequencer body picks up the sequencer's
526
- scope when the Result bubbles out — no `failure` call required.
522
+ helper *and* the boundary itself (the result of `pipeline(ctx)`, in block
523
+ and chained form, and `Sequencer.()`) tag the returned `Result` with
524
+ `i18n_scope` via `Result#with_i18n_scope`. That means an unscoped failure
525
+ produced inside a macro, a hand-rolled step, or anywhere else in the
526
+ pipeline picks up the sequencer's scope when the Result bubbles out — no
527
+ `failure` call required.
528
+
529
+ A sequencer that returns a failure without `pipeline` must build it with
530
+ `failure(ctx, ...)`, not `Result.failure(...)`. An instance call (a test,
531
+ or a nested sequencer run through `p.invoke`) reaches the sequencer's
532
+ `call` directly, so only the helper gives that failure the sequencer's
533
+ scope there.
527
534
 
528
535
  ### Defining translations for a sequencer
529
536
 
@@ -628,6 +635,13 @@ as substitutes. Tests configure the substitutes for the scenario at hand.
628
635
  Substitutes default to pass-through `Result.success(ctx)` so a test only
629
636
  configures the ones whose return matters.
630
637
 
638
+ An instance call takes a `Ctx`, the same strict ctx that the class-level
639
+ `.()` builds for production, so a test passes one:
640
+ `seq.(Hubbado::Sequence::Ctx.build(params: ..., current_user: ...))`.
641
+ `pipeline` raises `ArgumentError` for keyword arguments or a plain Hash,
642
+ because on a Hash a read of a missing key would return `nil` in the test
643
+ and raise `KeyError` in production.
644
+
631
645
  ### Substituting macros directly
632
646
 
633
647
  ```ruby
@@ -639,7 +653,7 @@ context "Seqs::UpdateUser::Present happy path" do
639
653
  seq.find.succeed_with(user)
640
654
  seq.build_contract.succeed_with(contract)
641
655
 
642
- result = seq.(params: { id: 1 }, current_user: User.new)
656
+ result = seq.(Hubbado::Sequence::Ctx.build(params: { id: 1 }, current_user: User.new))
643
657
 
644
658
  test "Is success" do
645
659
  assert(result.success?)
@@ -662,7 +676,7 @@ context "Seqs::UpdateUser::Present when the user is not found" do
662
676
  seq = Seqs::UpdateUser::Present.new
663
677
  seq.find.fail_with(code: :not_found)
664
678
 
665
- result = seq.(params: { id: 999 }, current_user: User.new)
679
+ result = seq.(Hubbado::Sequence::Ctx.build(params: { id: 999 }, current_user: User.new))
666
680
 
667
681
  test "Fails with :not_found" do
668
682
  assert(result.code == :not_found)
@@ -703,8 +717,10 @@ context "Seqs::UpdateUser happy path" do
703
717
  seq.present.succeed_with(user: user, contract: contract)
704
718
 
705
719
  result = seq.(
706
- params: { user: { email: "new@example.com" } },
707
- current_user: User.new
720
+ Hubbado::Sequence::Ctx.build(
721
+ params: { user: { email: "new@example.com" } },
722
+ current_user: User.new
723
+ )
708
724
  )
709
725
 
710
726
  test "Is success" do
@@ -724,7 +740,7 @@ context "Seqs::UpdateUser when Present denies access" do
724
740
  seq = Seqs::UpdateUser.new
725
741
  seq.present.fail_with(code: :forbidden)
726
742
 
727
- result = seq.(params: { user: {} }, current_user: User.new)
743
+ result = seq.(Hubbado::Sequence::Ctx.build(params: { user: {} }, current_user: User.new))
728
744
 
729
745
  test "Fails" do
730
746
  assert(result.failure?)
@@ -747,7 +763,7 @@ context "Seqs::UpdateUser when Present cannot find the record" do
747
763
  seq = Seqs::UpdateUser.new
748
764
  seq.present.fail_with(code: :not_found)
749
765
 
750
- result = seq.(params: { id: 999, user: {} }, current_user: User.new)
766
+ result = seq.(Hubbado::Sequence::Ctx.build(params: { id: 999, user: {} }, current_user: User.new))
751
767
 
752
768
  test "Fails with :not_found" do
753
769
  assert(result.code == :not_found)
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |s|
3
3
  s.name = "hubbado-sequence"
4
- s.version = "0.7.0"
4
+ s.version = "0.8.0"
5
5
  s.summary = "A small framework for the short sequences of common steps that controller actions usually boil down to"
6
6
  s.description = "A sequencer takes input, runs an ordered sequence of steps, and returns a Result carrying a success-or-failure flag, a structured error, and the working context that was built up during execution. Built with Rails in mind but framework-agnostic."
7
7
 
@@ -4,11 +4,12 @@ module Hubbado
4
4
  # `pipeline(ctx)` helper. Not part of the public API — sequencers reach
5
5
  # it through the helper.
6
6
  class Pipeline
7
- def initialize(ctx, dispatcher:)
7
+ def initialize(ctx, dispatcher:, i18n_scope: nil)
8
8
  @ctx = ctx
9
9
  @successful_steps = []
10
10
  @failed_result = nil
11
11
  @dispatcher = dispatcher
12
+ @i18n_scope = i18n_scope
12
13
  end
13
14
 
14
15
  # `step(:name)` dispatches to `dispatcher.send(name, ctx)`. The method
@@ -55,11 +56,9 @@ module Hubbado
55
56
  end
56
57
 
57
58
  def result
58
- if @failed_result
59
- @failed_result
60
- else
61
- Result.success(@ctx, successful_steps: @successful_steps.dup)
62
- end
59
+ result = @failed_result || Result.success(@ctx, successful_steps: @successful_steps.dup)
60
+
61
+ result.with_i18n_scope(@i18n_scope)
63
62
  end
64
63
 
65
64
  private
@@ -95,12 +95,22 @@ module Hubbado
95
95
  # block, and returns the final Result — no trailing `.result` needed. The
96
96
  # non-block form returns the Pipeline so chained `.step(...)...result`
97
97
  # calls still work.
98
+ #
99
+ # Anything but a Ctx means an instance call skipped the Ctx that the
100
+ # class-level `.()` builds; run on a plain Hash, a missing key would
101
+ # read as nil rather than raise.
98
102
  def pipeline(ctx, &block)
99
- pipe = Pipeline.new(ctx, dispatcher: self)
103
+ unless ctx.is_a?(Ctx)
104
+ raise ArgumentError,
105
+ "#{self.class.name}#pipeline expects a Hubbado::Sequence::Ctx, got #{ctx.class}; " \
106
+ "pass Hubbado::Sequence::Ctx.build(...) to an instance call"
107
+ end
108
+
109
+ pipe = Pipeline.new(ctx, dispatcher: self, i18n_scope: i18n_scope)
100
110
 
101
111
  if block
102
112
  block.call(pipe)
103
- pipe.result.with_i18n_scope(i18n_scope)
113
+ pipe.result
104
114
  else
105
115
  pipe
106
116
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hubbado-sequence
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hubbado Devs
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-18 00:00:00.000000000 Z
11
+ date: 2026-10-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: evt-casing