diff --git a/CHANGELOG.md b/CHANGELOG.md index 1789a99..8c0ffd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,50 +1,58 @@ - [\[Unreleased\]](#unreleased) -- [1.0.0 - 2024-03-16](#100---2024-03-16) - [Added](#added) +- [1.0.0 - 2024-03-16](#100---2024-03-16) + - [Added](#added-1) - [Changed](#changed) - [\[0.13.0\] - 2024-02-01](#0130---2024-02-01) - - [Added](#added-1) + - [Added](#added-2) - [Changed](#changed-1) - [\[0.12.0\] - 2024-01-07](#0120---2024-01-07) - - [Added](#added-2) + - [Added](#added-3) - [Changed](#changed-2) - [\[0.11.0\] - 2024-01-02](#0110---2024-01-02) - - [Added](#added-3) + - [Added](#added-4) - [Changed](#changed-3) - [\[0.10.0\] - 2023-12-31](#0100---2023-12-31) - - [Added](#added-4) + - [Added](#added-5) - [\[0.9.1\] - 2023-12-12](#091---2023-12-12) - [Changed](#changed-4) - [Fixed](#fixed) - [\[0.9.0\] - 2023-12-12](#090---2023-12-12) - - [Added](#added-5) + - [Added](#added-6) - [Changed](#changed-5) - [\[0.8.0\] - 2023-12-11](#080---2023-12-11) - - [Added](#added-6) + - [Added](#added-7) - [Changed](#changed-6) - [Removed](#removed) - [\[0.7.0\] - 2023-10-27](#070---2023-10-27) - - [Added](#added-7) + - [Added](#added-8) - [Changed](#changed-7) - [\[0.6.0\] - 2023-10-11](#060---2023-10-11) - - [Added](#added-8) + - [Added](#added-9) - [Changed](#changed-8) - [\[0.5.0\] - 2023-10-09](#050---2023-10-09) - - [Added](#added-9) -- [\[0.4.0\] - 2023-09-28](#040---2023-09-28) - [Added](#added-10) +- [\[0.4.0\] - 2023-09-28](#040---2023-09-28) + - [Added](#added-11) - [Changed](#changed-9) - [Removed](#removed-1) - [\[0.3.0\] - 2023-09-26](#030---2023-09-26) - - [Added](#added-11) -- [\[0.2.0\] - 2023-09-26](#020---2023-09-26) - [Added](#added-12) +- [\[0.2.0\] - 2023-09-26](#020---2023-09-26) + - [Added](#added-13) - [Removed](#removed-2) - [\[0.1.0\] - 2023-09-25](#010---2023-09-25) - - [Added](#added-13) + - [Added](#added-14) ## [Unreleased] +### Added + +- Add some Hash's methods to `BCDD::Context`. They are: + - `#slice` to extract only the desired keys. + - `#[]`, `#dig`, `#fetch` to access the values. + - `#values_at` and `#fetch_values` to get the values of the desired keys. + ## 1.0.0 - 2024-03-16 ### Added diff --git a/README.md b/README.md index fa7da3b..4ca5b90 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,7 @@ Use it to enable the [Railway Oriented Programming](https://fsharpforfunandprofi - [`BCDD::Result::Expectations.mixin` add-ons](#bcddresultexpectationsmixin-add-ons) - [`BCDD::Context`](#bcddcontext) - [Defining successes and failures](#defining-successes-and-failures) - - [Constant aliases](#constant-aliases) - - [`BCDD::Context.mixin`](#bcddcontextmixin) - - [Class example (Instance Methods)](#class-example-instance-methods-1) + - [Hash methods](#hash-methods) - [`and_expose`](#and_expose) - [Module example (Singleton Methods)](#module-example-singleton-methods-1) - [`BCDD::Context::Expectations`](#bcddcontextexpectations) @@ -1436,20 +1434,33 @@ BCDD::Context::Success(:ok, **{ message: 'hashes can be converted to keyword arg

⬆️  back to top

-#### Constant aliases +#### Hash methods -You can configure `Context` or `BCDD::Context` as an alias for `BCDD::Context`. This is helpful to define a standard way to avoid the full constant name/path in your code. +The `BCDD::Context` only accepts hashes as its values. Because of this, its instances have some Hash's methods to query/access the values. The available methods are: + +- `#slice` to extract only the desired keys. +- `#[]`, `#dig`, `#fetch` to access the values. +- `#values_at` and `#fetch_values` to get the values of the desired keys. ```ruby -BCDD::Result.configuration do |config| - config.context_alias.enable!('BCDD::Context') +result = BCDD::Context::Success(:ok, a: 1, b: 2, c: {d: 4}) - # or +result[:a] # 1 +result.fetch(:a) # 1 +result.dig(:c, :d) # 4 - config.context_alias.enable!('Context') -end +result.slice(:a, :b) # {:a=>1, :b=>2} + +result.values_at(:a, :b) # [1, 2] +result.fetch_values(:a, :b) # [1, 2] ``` +These methods are available for `BCDD::Context::Success` and `BCDD::Context::Failure` instances. + +

⬆️  back to top

+ +```ruby +

⬆️  back to top

#### `BCDD::Context.mixin` diff --git a/lib/bcdd/context.rb b/lib/bcdd/context.rb index a41fbfc..93ca60b 100644 --- a/lib/bcdd/context.rb +++ b/lib/bcdd/context.rb @@ -20,7 +20,7 @@ def self.Failure(type, **value) def initialize(type:, value:, source: nil, expectations: nil, terminal: nil) value.is_a?(::Hash) or raise ::ArgumentError, 'value must be a Hash' - @acc = {} + @memo = {} super end @@ -32,14 +32,38 @@ def and_then(method_name = nil, **injected_value, &block) def and_then!(source, **injected_value) _call = injected_value.delete(:_call) - acc.merge!(injected_value) + memo.merge!(injected_value) super(source, injected_value, _call: _call) end + def [](key) + value[key] + end + + def dig(...) + value.dig(...) + end + + def fetch(...) + value.fetch(...) + end + + def slice(...) + value.slice(...) + end + + def values_at(...) + value.values_at(...) + end + + def fetch_values(...) + value.fetch_values(...) + end + protected - attr_reader :acc + attr_reader :memo private @@ -54,31 +78,31 @@ def and_then!(source, **injected_value) end def call_and_then_source_method!(method, injected_value) - acc.merge!(value.merge(injected_value)) + memo.merge!(value.merge(injected_value)) case SourceMethodArity[method] when 0 then source.send(method.name) - when 1 then source.send(method.name, **acc) + when 1 then source.send(method.name, **memo) else raise Error::InvalidSourceMethodArity.build(source: source, method: method, max_arity: 1) end end def call_and_then_block!(block) - acc.merge!(value) + memo.merge!(value) - block.call(acc) + block.call(memo) end def call_and_then_callable!(source, value:, injected_value:, method_name:) - acc.merge!(value.merge(injected_value)) + memo.merge!(value.merge(injected_value)) - CallableAndThen::Caller.call(source, value: acc, injected_value: injected_value, method_name: method_name) + CallableAndThen::Caller.call(source, value: memo, injected_value: injected_value, method_name: method_name) end def ensure_result_object(result, origin:) raise_unexpected_outcome_error(result, origin) unless result.is_a?(BCDD::Context) - return result.tap { _1.acc.merge!(acc) } if result.source.equal?(source) + return result.tap { _1.memo.merge!(memo) } if result.source.equal?(source) raise Error::InvalidResultSource.build(given_result: result, expected_source: source) end diff --git a/lib/bcdd/context/callable_and_then.rb b/lib/bcdd/context/callable_and_then.rb index 349baaf..b5afe5b 100644 --- a/lib/bcdd/context/callable_and_then.rb +++ b/lib/bcdd/context/callable_and_then.rb @@ -28,7 +28,7 @@ def self.call_method!(source, method, value, _injected_value) end def self.ensure_result_object(source, value, result) - return result.tap { result.send(:acc).then { _1.merge!(value.merge(_1)) } } if result.is_a?(Context) + return result.tap { result.send(:memo).then { _1.merge!(value.merge(_1)) } } if result.is_a?(Context) raise Result::Error::UnexpectedOutcome.build(outcome: result, origin: source, expected: Context::EXPECTED_OUTCOME) diff --git a/lib/bcdd/context/success.rb b/lib/bcdd/context/success.rb index 1979503..6f90bc4 100644 --- a/lib/bcdd/context/success.rb +++ b/lib/bcdd/context/success.rb @@ -8,12 +8,12 @@ class Error < BCDD::Result::Error class Success < self include ::BCDD::Success - FetchValues = ->(acc_values, keys) do - fetched_values = acc_values.fetch_values(*keys) + FetchValues = ->(memo_values, keys) do + fetched_values = memo_values.fetch_values(*keys) keys.zip(fetched_values).to_h rescue ::KeyError => e - message = "#{e.message}. Available to expose: #{acc_values.keys.map(&:inspect).join(', ')}" + message = "#{e.message}. Available to expose: #{memo_values.keys.map(&:inspect).join(', ')}" raise Error::InvalidExposure, message end @@ -25,9 +25,9 @@ def and_expose(type, keys, terminal: true) EventLogs.tracking.reset_and_then! - acc_values = acc.merge(value) + memo_values = memo.merge(value) - value_to_expose = FetchValues.call(acc_values, keys) + value_to_expose = FetchValues.call(memo_values, keys) expectations = type_checker.expectations diff --git a/sig/bcdd/context.rbs b/sig/bcdd/context.rbs index 52a3acf..5679e90 100644 --- a/sig/bcdd/context.rbs +++ b/sig/bcdd/context.rbs @@ -3,7 +3,7 @@ class BCDD::Context < BCDD::Result SourceMethodArity: ^(Method) -> Integer - attr_reader acc: Hash[Symbol, untyped] + attr_reader memo: Hash[Symbol, untyped] def initialize: ( type: Symbol, diff --git a/test/bcdd/context/success_test.rb b/test/bcdd/context/success_test.rb index a310873..6e5e660 100644 --- a/test/bcdd/context/success_test.rb +++ b/test/bcdd/context/success_test.rb @@ -20,5 +20,94 @@ class ContextSuccessTest < Minitest::Test result.inspect ) end + + test '#[]' do + result1 = Context::Success(:ok) + result2 = Context::Success(:ok, a: 1, b: 2) + + assert_nil result1[:a] + assert_nil result1[:b] + + assert_equal 1, result2[:a] + assert_equal 2, result2[:b] + end + + # rubocop:disable Style/SingleArgumentDig + test '#dig' do + result1 = Context::Success(:ok) + result2 = Context::Success(:ok, a: { b: 1 }) + + assert_nil result1.dig(:a, :b) + assert_nil result2.dig(:a, :c) + + assert_equal({ b: 1 }, result2.dig(:a)) + assert_equal 1, result2.dig(:a, :b) + end + # rubocop:enable Style/SingleArgumentDig + + test '#fetch' do + result1 = Context::Success(:ok) + result2 = Context::Success(:ok, a: 1, b: 2) + + assert_raises(KeyError) { result1.fetch(:a) } + assert_raises(KeyError) { result1.fetch(:b) } + + assert_equal 1, result2.fetch(:a) + assert_equal 2, result2.fetch(:b) + + # --- + + assert_equal 3, result1.fetch(:a, 3) + assert_equal 4, result1.fetch(:b, 4) + + assert_equal 1, result2.fetch(:a, 3) + assert_equal 2, result2.fetch(:b, 4) + + # --- + + # rubocop:disable Style/RedundantFetchBlock + assert_equal(5, result1.fetch(:a) { 5 }) + assert_equal(6, result1.fetch(:b) { 6 }) + + assert_equal(1, result2.fetch(:a) { 7 }) + assert_equal(2, result2.fetch(:b) { 8 }) + # rubocop:enable Style/RedundantFetchBlock + end + + test '#slice' do + result1 = Context::Success(:ok) + result2 = Context::Success(:ok, a: 1, b: 2) + + assert_equal({}, result1.slice(:a, :b)) + assert_equal({ a: 1, b: 2 }, result2.slice(:a, :b)) + end + + test '#values_at' do + result1 = Context::Success(:ok) + result2 = Context::Success(:ok, a: 1, b: 2) + + assert_equal [nil, nil], result1.values_at(:a, :b) + assert_equal [1, 2], result2.values_at(:a, :b) + end + + test '#fetch_values' do + result1 = Context::Success(:ok) + result2 = Context::Success(:ok, a: 1, b: 2) + + assert_raises(KeyError) do + result1.fetch_values(:a, :b) + end + + assert_equal [1, 2], result2.fetch_values(:a, :b) + + # --- + + values1 = result1.fetch_values(:a, :b, :c, :d) { |key| key.to_s.upcase } + values2 = result2.fetch_values(:a, :b, :c, :d) { |key| key.to_s.upcase } + + assert_equal %w[A B C D], values1 + + assert_equal [1, 2, 'C', 'D'], values2 + end end end