class Stannum::Constraints::Types::HashType

A Hash type constraint asserts that the object is a Hash.

@example Using a Hash type constraint

constraint = Stannum::Constraints::Types::HashType.new

constraint.matches?(nil)              # => false
constraint.matches?(Object.new)       # => false
constraint.matches?({})               # => true
constraint.matches?({ key: 'value' }) # => true

@example Using a Hash type constraint with a key constraint

constraint = Stannum::Constraints::Types::HashType.new(key_type: String)

constraint.matches?(nil)                  # => false
constraint.matches?(Object.new)           # => false
constraint.matches?({})                   # => true
constraint.matches?({ key: 'value' })     # => false
constraint.matches?({ 'key' => 'value' }) # => true

@example Using a Hash type constraint with a value constraint

constraint = Stannum::Constraints::Types::HashType.new(value_type: String)

constraint.matches?(nil)              # => false
constraint.matches?(Object.new)       # => false
constraint.matches?({})               # => true
constraint.matches?({ key: :value })  # => false
constraint.matches?({ key: 'value' }) # => true

@example Using a Hash type constraint with a presence constraint

constraint = Stannum::Constraints::Types::HashType.new(allow_empty: false)

constraint.matches?(nil)              # => false
constraint.matches?(Object.new)       # => false
constraint.matches?({})               # => false
constraint.matches?({ key: :value })  # => true
constraint.matches?({ key: 'value' }) # => true

Public Class Methods

new(allow_empty: true, key_type: nil, value_type: nil, **options) click to toggle source

@param allow_empty [true, false] If false, then the constraint will not

match against a Hash with no keys.

@param key_type [Stannum::Constraints::Base, Class, nil] If set, then the

constraint will check the types of each key in the Hash against the
expected type and will fail if any keys do not match.

@param value_type [Stannum::Constraints::Base, Class, nil] If set, then

the constraint will check the types of each value in the Hash against
the expected type and will fail if any values do not match.

@param options [Hash<Symbol, Object>] Configuration options for the

constraint. Defaults to an empty Hash.
Calls superclass method Stannum::Constraints::Type::new
# File lib/stannum/constraints/types/hash_type.rb, line 54
def initialize(allow_empty: true, key_type: nil, value_type: nil, **options)
  super(
    ::Hash,
    allow_empty: !!allow_empty,
    key_type:    coerce_key_type(key_type),
    value_type:  coerce_value_type(value_type),
    **options
  )
end

Public Instance Methods

allow_empty?() click to toggle source

@return [true, false] if false, then the constraint will not

match against a Hash with no keys.
# File lib/stannum/constraints/types/hash_type.rb, line 66
def allow_empty?
  options[:allow_empty]
end
does_not_match?(actual) click to toggle source

Checks that the object is not a Hash instance.

@return [true, false] true if the object is not a Hash instance, otherwise

false.

@see Stannum::Constraints::Types::HashType#matches?

# File lib/stannum/constraints/types/hash_type.rb, line 76
def does_not_match?(actual)
  !matches_type?(actual)
end
errors_for(actual, errors: nil) click to toggle source

(see Stannum::Constraints::Base#errors_for)

Calls superclass method Stannum::Constraints::Type#errors_for
# File lib/stannum/constraints/types/hash_type.rb, line 81
def errors_for(actual, errors: nil)
  return super unless actual.is_a?(expected_type)

  errors ||= Stannum::Errors.new

  return add_presence_error(errors) unless presence_matches?(actual)

  update_key_errors_for(actual: actual, errors: errors)

  update_value_errors_for(actual: actual, errors: errors)

  errors
end
key_type() click to toggle source

@return [Stannum::Constraints::Base, nil] the expected type for the keys

in the hash.
# File lib/stannum/constraints/types/hash_type.rb, line 97
def key_type
  options[:key_type]
end
match?(actual)
Alias for: matches?
matches?(actual) click to toggle source

Checks that the object is a Hash instance and that the keys/values match.

If the constraint was configured with a key_type, each key in the hash will be compared to the expected type. Likewise, if the constraint was configured with a value_type, each value in the hash will be compared. If any keys and/or values do not match the expectation, then matches? will return false.

@return [true, false] true if the object is a Hash instance with matching

keys and values, otherwise false.

@see Stannum::Constraints::Types::HashType#does_not_match?

Calls superclass method Stannum::Constraints::Type#matches?
# File lib/stannum/constraints/types/hash_type.rb, line 113
def matches?(actual)
  return false unless super

  return false unless presence_matches?(actual)

  return false unless key_type_matches?(actual)

  return false unless value_type_matches?(actual)

  true
end
Also aliased as: match?
value_type() click to toggle source

@return [Stannum::Constraints::Base, nil] the expected type for the values

in the hash.
# File lib/stannum/constraints/types/hash_type.rb, line 128
def value_type
  options[:value_type]
end

Private Instance Methods

add_presence_error(errors) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 134
def add_presence_error(errors)
  errors.add(
    Stannum::Constraints::Presence::TYPE,
    **error_properties
  )
end
coerce_key_type(key_type) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 141
def coerce_key_type(key_type)
  Stannum::Support::Coercion.type_constraint(
    key_type,
    allow_nil: true,
    as:        'key type'
  )
end
coerce_value_type(value_type) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 149
def coerce_value_type(value_type)
  Stannum::Support::Coercion.type_constraint(
    value_type,
    allow_nil: true,
    as:        'value type'
  )
end
error_properties() click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 157
def error_properties
  super().merge(allow_empty: allow_empty?)
end
key_type_matches?(actual) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 161
def key_type_matches?(actual)
  return true unless key_type

  return true if actual.nil?

  actual.each_key.all? { |key| key_type.matches?(key) }
end
non_matching_keys(actual) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 169
def non_matching_keys(actual)
  return [] unless key_type && actual.is_a?(Hash)

  actual.each_key.reject { |key| key_type.matches?(key) }
end
non_matching_values(actual) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 175
def non_matching_values(actual)
  return [] unless value_type && actual.is_a?(Hash)

  actual.each.reject { |_, value| value_type.matches?(value) }
end
presence_matches?(actual) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 181
def presence_matches?(actual)
  allow_empty? || !actual.empty?
end
update_key_errors_for(actual:, errors:) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 185
def update_key_errors_for(actual:, errors:)
  non_matching_keys(actual).each do |key|
    mapped_key = Stannum::Support::Coercion.error_key(key)

    key_type.errors_for(key, errors: errors[:keys][mapped_key])
  end
end
update_value_errors_for(actual:, errors:) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 193
def update_value_errors_for(actual:, errors:)
  non_matching_values(actual).each do |key, value|
    mapped_key = Stannum::Support::Coercion.error_key(key)

    value_type.errors_for(value, errors: errors[mapped_key])
  end
end
value_type_matches?(actual) click to toggle source
# File lib/stannum/constraints/types/hash_type.rb, line 201
def value_type_matches?(actual)
  return true unless value_type

  return true if actual.nil?

  actual.each_value.all? { |value| value_type.matches?(value) }
end