class Grape::Validations::Types::CustomTypeCoercer

Instances of this class may be passed to Virtus::Attribute.build as the :coercer option for custom types that do not otherwise satisfy the requirements for Virtus::Attribute::coerce and Virtus::Attribute::value_coerced? to work as expected.

Subclasses of Virtus::Attribute or Axiom::Types::Type (or for which an axiom type can be inferred, i.e. the primitives, Date, Time, etc.) do not need any such coercer to be passed with them.

Coercion


This class will detect type classes that implement a class-level parse method. The method should accept one String argument and should return the value coerced to the appropriate type. The method may raise an exception if there are any problems parsing the string.

Alternately an optional method may be supplied (see the coerce_with option of {Grape::Dsl::Parameters#requires}). This may be any class or object implementing parse or call, with the same contract as described above.

Type Checking


Calls to value_coerced? will consult this class to check that the coerced value produced above is in fact of the expected type. By default this class performs a basic check against the type supplied, but this behaviour will be overridden if the class implements a class-level coerced? or parsed? method. This method will receive a single parameter that is the coerced value and should return true iff the value meets type expectations. Arbitrary assertions may be made here but the grape validation system should be preferred.

Alternately a proc or other object responding to call may be supplied in place of a type. This should implement the same contract as coerced?, and must be supplied with a coercion method.

Public Class Methods

build(type, method = nil) click to toggle source

Uses Virtus::Attribute.build to build a new attribute that makes use of this class for coercion and type validation logic.

@return [Virtus::Attribute]

# File lib/grape/validations/types/custom_type_coercer.rb, line 54
def self.build(type, method = nil)
  Virtus::Attribute.build(type, coercer: new(type, method))
end
new(type, method = nil) click to toggle source

A new coercer for the given type specification and coercion method.

@param type [Class,#coerced?,#parsed?,#call?]

specifier for the target type. See class docs.

@param method [#parse,#call]

optional coercion method. See class docs.
# File lib/grape/validations/types/custom_type_coercer.rb, line 65
def initialize(type, method = nil)
  coercion_method = infer_coercion_method type, method

  @method = enforce_symbolized_keys type, coercion_method

  @type_check = infer_type_check(type)
end

Public Instance Methods

call(value) click to toggle source

This method is called from somewhere within Virtus::Attribute::coerce in order to coerce the given value.

@param value [String] value to be coerced, in grape

this should always be a string.

@return [Object] the coerced result

# File lib/grape/validations/types/custom_type_coercer.rb, line 80
def call(value)
  @method.call value
end
success?(_primitive, value) click to toggle source

This method is called from somewhere within Virtus::Attribute::value_coerced? in order to assert that the value has been coerced successfully.

@param _primitive [Axiom::Types::Type] primitive type

for the coercion as detected by axiom-types' inference
system. For custom types this is typically not much use
(i.e. it is +Axiom::Types::Object+) unless special
inference rules have been declared for the type.

@param value [Object] a coerced result returned from {#call} @return [true,false] whether or not the coerced value

satisfies type requirements.
# File lib/grape/validations/types/custom_type_coercer.rb, line 96
def success?(_primitive, value)
  @type_check.call value
end

Private Instance Methods

enforce_symbolized_keys(type, method) click to toggle source

Enforce symbolized keys for complex types by wrapping the coercion method such that any Hash objects in the immediate heirarchy have their keys recursively symbolized. This helps common libs such as JSON to work easily.

@param type see new @param method see infer_coercion_method @return [#call] method wrapped in an additional

key-conversion step, or just returns +method+
itself if no conversion is deemed to be
necessary.
# File lib/grape/validations/types/custom_type_coercer.rb, line 159
def enforce_symbolized_keys(type, method)
  # Collections have all values processed individually
  if type == Array || type == Set
    lambda do |val|
      method.call(val).tap do |new_value|
        new_value.map do |item|
          item.is_a?(Hash) ? symbolize_keys(item) : item
        end
      end
    end

  # Hash objects are processed directly
  elsif type == Hash
    lambda do |val|
      symbolize_keys method.call(val)
    end

  # Simple types are not processed.
  # This includes Array<primitive> types.
  else
    method
  end
end
infer_coercion_method(type, method) click to toggle source

Determine the coercion method we're expected to use based on the parameters given.

@param type see new @param method see new @return [#call] coercion method

# File lib/grape/validations/types/custom_type_coercer.rb, line 108
def infer_coercion_method(type, method)
  if method
    if method.respond_to? :parse
      method.method :parse
    else
      method
    end
  else
    # Try to use parse() declared on the target type.
    # This may raise an exception, but we are out of ideas anyway.
    type.method :parse
  end
end
infer_type_check(type) click to toggle source

Determine how the type validity of a coerced value should be decided.

@param type see new @return [#call] a procedure which accepts a single parameter

and returns +true+ if the passed object is of the correct type.
# File lib/grape/validations/types/custom_type_coercer.rb, line 128
def infer_type_check(type)
  # First check for special class methods
  if type.respond_to? :coerced?
    type.method :coerced?
  elsif type.respond_to? :parsed?
    type.method :parsed?
  elsif type.respond_to? :call
    # Arbitrary proc passed for type validation.
    # Note that this will fail unless a method is also
    # passed, or if the type also implements a parse() method.
    type
  elsif type.is_a?(Enumerable)
    ->(value) { value.respond_to?(:all?) && value.all? { |item| item.is_a? type[0] } }
  else
    # By default, do a simple type check
    ->(value) { value.is_a? type }
  end
end
symbolize_keys(hash) click to toggle source
# File lib/grape/validations/types/custom_type_coercer.rb, line 190
def symbolize_keys(hash)
  hash.inject({}) do |new_hash, (key, value)|
    new_key = key.respond_to?(:to_sym) ? key.to_sym : key
    new_hash.merge!(new_key => value)
  end
end
symbolize_keys!(hash) click to toggle source
# File lib/grape/validations/types/custom_type_coercer.rb, line 183
def symbolize_keys!(hash)
  hash.each_key do |key|
    hash[key.to_sym] = hash.delete(key) if key.respond_to?(:to_sym)
  end
  hash
end