class BloodContracts::Core::Tuple
Meta refinement type, represents product of several refinement types
Attributes
List of types in the Tuple
@return [Array<Refined>]
Accessor to define alternative to ContractFailure
for failure
method to use
@return [ContractFailure]
Names of attributes
@return [Array<Symbol>]
List of values in Tuple
@return [Array<Object>]
Public Class Methods
Helper which registers attribute in the Tuple
, also defines a reader
# File lib/blood_contracts/core/tuple.rb, line 43 def attribute(name, type = nil, &block) if type.nil? type = type_from_block(name, &block) else raise ArgumentError unless type < Refined end @attributes << type @names << name define_method(name) do match.context.dig(:attributes, name) end end
BloodContracts::Core::Refined::inherited
# File lib/blood_contracts/core/tuple.rb, line 64 def inherited(new_klass) new_klass.instance_variable_set(:@attributes, []) new_klass.instance_variable_set(:@names, []) new_klass.instance_variable_set(:@finalized, true) new_klass.failure_klass ||= TupleContractFailure super end
# File lib/blood_contracts/core/tuple.rb, line 33 def inspect; super; end
Metaprogramming around constructor Turns input into Tuple
meta-class
@param (see initialze)
rubocop:disable Style/SingleLineMethods
BloodContracts::Core::Refined::new
# File lib/blood_contracts/core/tuple.rb, line 26 def new(*args, **kwargs, &block) args = lookup_hash_args(*args, kwargs) return super(*args, **kwargs) if @finalized names = args.pop.delete(:names) if args.last.is_a?(Hash) raise ArgumentError unless args.all? { |type| type < Refined } tuple = Class.new(self) { def inspect; super; end } tuple.instance_variable_set(:@attributes, args) tuple.instance_variable_set(:@names, names.to_a) tuple.instance_variable_set(:@finalized, true) tuple.class_eval(&block) if block_given? tuple end
Tuple
constructor, builds Tuple
from list of data values
@param [Array<Object>] *values that we'll keep inside the Tuple
@option [Hash<Symbol, Object>] context to share between types
# File lib/blood_contracts/core/tuple.rb, line 125 def initialize(*values, context: {}, **) @context = context @context[:attributes] ||= {} additional_context = values.last if values.last.is_a?(Hash) additional_context ||= {} @values = parse_values_from_context(context.merge(additional_context)) @values ||= values @errors = [] end
Private Class Methods
Handle arguments passed as hash with string or symbol keys
@param [Array<Object>] *args that can contain an array of arguments or a single Hash we're looking for
@param [Hash<Object, Object>] **kwargs hash that can be converted to args when args array is empty
@return [Array<Object>]
# File lib/blood_contracts/core/tuple.rb, line 103 def lookup_hash_args(*args, **kwargs) if args.empty? [kwargs] elsif args.length == 1 && args.last.is_a?(Hash) [args.first.symbolize_keys] else args end end
Generate an anonymous type from a block
@param String name of the attribute to define the type for
@param Proc block which will be evaluated in a context of our anonymous type
@return Class
# File lib/blood_contracts/core/tuple.rb, line 83 def type_from_block(name, &block) raise ArgumentError unless block_given? const_set( "InlineType_#{name.to_s.capitalize}", Class.new(Refined) { class_eval(&block) } ) end
Public Instance Methods
Subset of attributes which are invalid
@return [Hash<String, ContractFailure>]
# File lib/blood_contracts/core/tuple.rb, line 191 def attribute_errors {} end
Hash of attributes (name & type pairs)
@return [Hash<String, Refined>]
# File lib/blood_contracts/core/tuple.rb, line 183 def attributes @context[:attributes] end
Turns match into array of unpacked values
@return [Array<Object>]
# File lib/blood_contracts/core/tuple.rb, line 158 def mapped @matches.map(&:unpack) end
The type which is the result of data matching process For Tuple
it verifies that all the attributes data are valid types
@return [BC::Refined]
# File lib/blood_contracts/core/tuple.rb, line 143 def match @matches = attributes_enumerator.map do |(type, value), index| attribute_name = self.class.names[index] attributes.store( attribute_name, type.match(value, context: @context.dup) ) end return if (failures = @matches.select(&:invalid?)).empty? failures.unshift(failure).reduce(:merge!) end
Unpacked value in form of a hash per attribute
@return [Hash<String, ContractFailure>]
# File lib/blood_contracts/core/tuple.rb, line 172 def unpack_h @unpack_h ||= attributes.transform_values(&:unpack) end
Private Instance Methods
@private
# File lib/blood_contracts/core/tuple.rb, line 211 def attributes_enumerator self.class.attributes.zip(@values).each.with_index end
Helper to build a ContractFailure
with shared context
@return [ContractFailure]
# File lib/blood_contracts/core/tuple.rb, line 199 def failure(*) self.class.failure_klass.new(context: @context) end
@private
# File lib/blood_contracts/core/tuple.rb, line 225 def inspect "#<tuple #{self.class.name} of (#{values_by_names.join(', ')})>" end
@private
# File lib/blood_contracts/core/tuple.rb, line 204 def parse_values_from_context(context) return if context.empty? return unless (self.class.names - context.keys).empty? context.values_at(*self.class.names) end
@private
# File lib/blood_contracts/core/tuple.rb, line 216 def values_by_names if self.class.names.empty? values else self.class.names.zip(values).map { |k, v| [k, v].join("=") } end end