From e3c66025946c9053a7367197189eef21a0116169 Mon Sep 17 00:00:00 2001 From: Pablo Herrero Date: Sat, 27 Jul 2024 00:37:59 -0300 Subject: [PATCH] Add State#unwrap --- CHANGELOG.md | 4 ++ lib/pathway.rb | 18 ++++++- .../plugins/auto_deconstruct_state/ruby3.rb | 1 + lib/pathway/version.rb | 2 +- pathway.gemspec | 1 + spec/plugins/auto_deconstruct_state_spec.rb | 6 ++- spec/state_spec.rb | 49 +++++++++++++++++++ 7 files changed, 77 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 427b212..e05059f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## [0.12.2] - 2024-08-06 +### Added +- Add `Pathway::State#unwrap` and `Pathway::State#u` to access internal state + ## [0.12.1] - 2024-06-23 ### Added - Add support for pattern matching on `Result`, `State` and `Error` instances diff --git a/lib/pathway.rb b/lib/pathway.rb index 8c2df84..5263d81 100644 --- a/lib/pathway.rb +++ b/lib/pathway.rb @@ -83,7 +83,23 @@ def to_hash @hash end - alias :to_h :to_hash + def unwrap(&bl) + raise 'a block must be provided' if !block_given? + params = bl.parameters + unless params.all? { |(type,_)| [:block, :key, :keyreq, :keyrest].member?(type) } + raise 'only keyword arguments are supported for unwraping' + end + + if params.any? {|(type,_)| type == :keyrest } + bl.call(**to_hash) + else + keys = params.select {|(type,_)| type == :key || type == :keyreq }.map(&:last) + bl.call(**to_hash.slice(*keys)) + end + end + + alias_method :to_h, :to_hash + alias_method :u, :unwrap end module Plugins diff --git a/lib/pathway/plugins/auto_deconstruct_state/ruby3.rb b/lib/pathway/plugins/auto_deconstruct_state/ruby3.rb index 589c5f8..1578616 100644 --- a/lib/pathway/plugins/auto_deconstruct_state/ruby3.rb +++ b/lib/pathway/plugins/auto_deconstruct_state/ruby3.rb @@ -8,6 +8,7 @@ module DSLMethods def _callable(callable) if callable.is_a?(Symbol) && @operation.respond_to?(callable, true) && + @operation.method(callable).arity != 0 && @operation.method(callable).parameters.all? { _1 in [:key|:keyreq|:keyrest|:block,*] } -> state { @operation.send(callable, **state) } diff --git a/lib/pathway/version.rb b/lib/pathway/version.rb index b0092ab..87941e4 100644 --- a/lib/pathway/version.rb +++ b/lib/pathway/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Pathway - VERSION = '0.12.1' + VERSION = '0.12.2' end diff --git a/pathway.gemspec b/pathway.gemspec index fd71d15..bf6fe98 100644 --- a/pathway.gemspec +++ b/pathway.gemspec @@ -43,4 +43,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency "pry" spec.add_development_dependency "pry-byebug" spec.add_development_dependency "pry-doc" + spec.add_development_dependency "pry-stack" end diff --git a/spec/plugins/auto_deconstruct_state_spec.rb b/spec/plugins/auto_deconstruct_state_spec.rb index d952abd..8c433e0 100644 --- a/spec/plugins/auto_deconstruct_state_spec.rb +++ b/spec/plugins/auto_deconstruct_state_spec.rb @@ -36,8 +36,10 @@ def create_model(name:, email:, **) UserModel.new(name, email) end - def notify(value:, **) - @notifier.call(value) + def notify(s) + s.u do |value:| + @notifier.call(value) + end end end diff --git a/spec/state_spec.rb b/spec/state_spec.rb index 8158755..13cbfb2 100644 --- a/spec/state_spec.rb +++ b/spec/state_spec.rb @@ -27,6 +27,55 @@ class SimpleOp < Operation end end + describe '#unwrap' do + before { state.update(val: 'RESULT', foo: 99, bar: 131) } + it 'fails if no block is provided' do + expect { state.unwrap }.to raise_error('a block must be provided') + end + + context 'when a block is provided' do + it 'passes specified values by the keyword params', :aggregate_failures do + expect(state.unwrap {|val:| val }).to eq('RESULT') + expect(state.unwrap {|foo:| foo }).to eq(99) + expect(state.unwrap {|val:, bar:| [val, bar] }) + .to eq(['RESULT', 131]) + end + + it 'passes all values if **kwargs is part of the params', :aggregate_failures do + expect(state.unwrap {|**kargs| kargs }) + .to eq(foo: 99, bar: 131, val: 'RESULT', input: 'some value') + expect(state.unwrap {|input:, **kargs| input }).to eq('some value') + expect(state.unwrap {|input:, **kargs| kargs }) + .to eq(foo: 99, bar: 131, val: 'RESULT') + end + + it 'passes no arguments if no keyword params are defined' do + expect(state.unwrap { 77 }).to eq(77) + end + + it 'fails if at least one positional param is defined', :aggregate_failures do + expect { state.unwrap {|pos, input:| } } + .to raise_error('only keyword arguments are supported for unwraping') + expect { state.unwrap {|input| } } + .to raise_error('only keyword arguments are supported for unwraping') + end + + context 'and it takes a block argument' do + it 'fails if it has positional params' do + expect { state.unwrap {|input, &bl| } } + .to raise_error('only keyword arguments are supported for unwraping') + end + + it 'does not fails if only keyword params', :aggregate_failures do + expect(state.unwrap {|val:, &bl| val }).to eq('RESULT') + expect(state.unwrap {|val:, &_| val }).to eq('RESULT') + expect(state.unwrap {|&_| 77 }).to eq(77) + end + end + + end + end + describe '#update' do let(:result) { state.update(qux: 33, quz: 11) } it 'returns and updated state with the passed values' do