Module: Brut::Framework::FussyTypeEnforcement

Overview

Include this to enable methods to help with type checking. Generally, you should not use this. You should only really use this if all of the following are true:

  • Passing in the wrong type Would Be Bad.
  • The developer passing it in would not easily be able to figure out what went wrong.

Instance Method Summary collapse

Instance Method Details

#type!(value, type_descriptor, variable_name_for_error_message, required: false, coerce: false) ⇒ Object

Perform basic type checking, ideally inside a constructor when assigning ivars. This is really intended for internal classes that will not be exposed to user input, but rather to catch programmer bugs and programmer mis-use.

Parameters:

  • value (Object)

    the value that is to be type-checked

  • type_descriptor (Class|Array<Object>)

    a class or an array of allowed values. If a class, value must be kind_of? that class. If an array, value must be one of the values in the array.

  • variable_name_for_error_message (String)

    the name of the variable begin type-checked so that error messages make sense

  • required (true|false) (defaults to: false)

    if true, the value may not be nil. If false, nil values are allowed. Note that in this context a blank string counts as nil, so required strings may not be blank.

  • coerce (Symbol|false) (defaults to: false)

    if set, this is the symbol that will be used to coerce the value before type checking. For example, if you accept a string but know it should be a number, pass in :to_i.

Returns:

  • (Object)

    the value, if it matches the expectations of its type

Raises:

  • (ArgumentError)

    if the value doesn't confirm to the described type



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/brut/framework/fussy_type_enforcement.rb', line 20

def type!(value,type_descriptor,variable_name_for_error_message, required: false, coerce: false)

  value_blank = value.nil? || ( value.kind_of?(String) && value.strip == "" )

  if !required && value_blank
    return value
  end

  if required && value_blank
    raise ArgumentError.new("'#{variable_name_for_error_message}' must have a value")
  end

  if type_descriptor.kind_of?(Class)
    coerced_value = coerce ? value.send(coerce) : value
    if !coerced_value.kind_of?(type_descriptor)
      class_description = if coerce
                            "but was a #{value.class}, coerced to a #{coerced_value.class} via #{coerce}"
                          else
                            "but was a #{value.class}"
                          end
      raise ArgumentError.new("'#{variable_name_for_error_message}' must be a #{type_descriptor}, #{class_description} (value as a string is #{value})")
    end
    value = coerced_value
  elsif type_descriptor.kind_of?(Array)
    if !type_descriptor.include?(value)
      description_of_values = type_descriptor.map { |value|
        "#{value} (a #{value.class})"
      }.join(", ")
      raise ArgumentError.new("'#{variable_name_for_error_message}' must be one of #{description_of_values}, but was a #{value.class} (value as a string is #{value})")
    end
  else
    raise ArgumentError.new("Use of type! with a #{type_descriptor.class} (#{type_descriptor}) is not supported")
  end
  value
end