Last active
May 30, 2023 05:52
-
-
Save estum/fe5c83ea06fa39fce95f5a84689da503 to your computer and use it in GitHub Desktop.
Dry::Types::Atomic
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# frozen_string_literal: true | |
require 'concurrent/atomic/atomic_reference' | |
module Dry | |
module Types | |
# Atomic types denote references in advance to target ones due to help achieve a cross-reference. | |
# | |
# @example | |
# module T | |
# extend Dry::Container::Mixin | |
# include Dry.Types() | |
# | |
# AnyKeyval = Atomic() | |
# | |
# Simple = Dry.Struct(key: String, value: String) | |
# Complex = Dry.Struct(key: String, value: AnyKeyval) | |
# end | |
# | |
# begin | |
# summarize = -> alt, old { old ? (old | alt) : alt }.curry(2) | |
# T::AnyKeyval.try_update!(&summarize[T::Simple]) | |
# T::AnyKeyval.try_update!(&summarize[T::Complex]) | |
# end | |
# | |
# begin | |
# input = { key: 'topmost', | |
# value: { key: 'complex', | |
# value: { key: 'inner', | |
# value: 'simple' } } } | |
# foo = T::AnyKeyval[input] | |
# foo.class # => T::Complex | |
# foo.value.class # => T::Complex | |
# foo.value.value.class # => T::Simple | |
# end | |
# | |
class Atomic < Concurrent::AtomicReference | |
include Type | |
include Decorator | |
# include Builder | |
include Printable | |
undef :type | |
send :include, Dry.Equalizer(:type, inspect: false, immutable: false) | |
# @param [Type] placeholder | |
# initial type | |
def initialize(placeholder = Undefined, *, **) | |
super | |
remove_instance_variable(:@type) | |
Undefined.map(placeholder) { set(placeholder) } | |
end | |
def type | |
get | |
end | |
# @return [::String] | |
# @api private | |
def to_s | |
format("#<Dry::Types[Atomic<%s>]>", PRINTER.(get) { get.to_s }) | |
end | |
alias_method :inspect, :to_s | |
private | |
# @return [false] | |
# @api private | |
def decorate?(*) | |
false | |
end | |
# @!method set(target) | |
# Sets the reference | |
# @param [Type] target type | |
# @see Concurrent::AtomicReference#set | |
# @!method get() | |
# Resolves the reference | |
# @return [Type] target type | |
# @see Concurrent::AtomicReference#get | |
# @!method update(&block) | |
# Pass the current value to the given block, replacing it with the block's result. May retry. | |
# @yield [Type] Calculate a new value for the atomic reference using given (old) value | |
# @yieldparam [Object] old_value the starting value of the atomic reference | |
# @return [Type] | |
# @see Concurrent::AtomicReference#update | |
# @!method try_update!(&block) | |
# @yield [Type] Calculate a new value for the atomic reference using given (old) value | |
# @yieldparam [Object] old_value the starting value of the atomic reference | |
# @return [Type] | |
# @raise [Concurrent::ConcurrentUpdateError] if the update fails | |
# @see Concurrent::AtomicReference#try_update! | |
end | |
module BuilderMethods | |
# @return [Atomic] | |
def Atomic(placeholder = Undefined) | |
Atomic.new(placeholder) | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Dry | |
module Types | |
RSpec.describe Atomic do | |
let!(:summarize) { -> alt, old { old ? (old | alt) : alt }.curry(2) } | |
subject(:atomic) { t.Atomic(simple_struct) } | |
it { is_expected.to be_instance_of(described_class) } | |
let(:t) { stub_const('T', Dry::Types()) } | |
let(:simple_struct) { stub_const('Simple', Dry.Struct(key: t::String, value: t::String)) } | |
let(:complex_struct) { stub_const('Complex', Dry.Struct(key: t::String, value: atomic)) } | |
let(:sample_input) do | |
{ key: 'topmost', | |
value: { key: 'complex', | |
value: { key: 'inner', | |
value: 'simple' } } } | |
end | |
before { allow(simple_struct).to receive(:|).and_call_original } | |
context 'on referenced value' do | |
subject(:ref) { atomic.get } | |
it { is_expected.to eq(simple_struct) } | |
context 'after the second update' do | |
before { atomic.try_update!(&summarize[complex_struct]) } | |
context 'on simple struct' do | |
subject { simple_struct } | |
it { is_expected.to have_received(:|).once } | |
end | |
it { is_expected.to be_kind_of(Dry::Struct::Sum) } | |
end | |
end | |
context 'on atomic reference' do | |
before { atomic.try_update!(&summarize[complex_struct]) } | |
let(:expected_attrs) do | |
{ type: be_kind_of(Dry::Struct::Sum).and(have_attributes(left: simple_struct, right: complex_struct)) } | |
end | |
it { is_expected.to have_attributes(expected_attrs) } | |
context 'on the result of calling on a sample input' do | |
subject(:result) { atomic[sample_input] } | |
it { is_expected.to be_kind_of(complex_struct) } | |
context 'on the value of the topmost object' do | |
subject(:topmost_value) { result.value } | |
it { is_expected.to be_kind_of(complex_struct) } | |
context 'on the value of the bottom object' do | |
subject(:bottom_value) { topmost_value.value } | |
it { is_expected.to be_kind_of(simple_struct) } | |
end | |
end | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment