Module: Ippon::Validate::Builder

Defined in:
lib/ippon/validate.rb

Overview

The Builder module contains helper methods for creating Schema objects. All the methods are available as module_functions which means you can both extend and include this class as you see fit.

Any helper method which accepts props as keyword paramter will pass them along to Step.

Examples:

Aliasing Builder

b = Ippon::Validate::Builder
even = b.trim | b.required | b.number | b.validate { |v| v % 2 == 0 }

Extending Builder to use it inside a moudle

module Schemas
  extend Ippon::Validate::Builder

  Even = trim | required | number | validate { |v| v % 2 == 0 }
end

Including Builder to use it inside a class

class SchemaBuilder
  include Ippon::Validate::Builder

  def even
    trim | required | number | validate { |v| v % 2 == 0 }
  end
end

SchemaBuilder.new.even

Class Method Summary collapse

Class Method Details

.boolean(**props) ⇒ Step

The boolean schema converts falsy values (false and nil) to false and all other values to true.

boolean.validate!(nil)    # => false
boolean.validate!("123")  # => true

Parameters:

  • props (Hash)

    a customizable set of options

Options Hash (**props):

  • :type (Symbol) — default: :boolean

Returns:


1061
1062
1063
1064
1065
# File 'lib/ippon/validate.rb', line 1061

def boolean(**props)
  transform(type: :boolean, **props) do |value|
    !!value
  end
end

.fetch(key, **props, &blk) ⇒ Step

The fetch schema extracts a field (given by key) from a value by using #fetch.

This is strictly equivalent to:

transform { |value| value.fetch(key) { error_is_produced } }

Parameters:

  • key

    The key which will be extracted. This value is stored under the :key parameter in the returned Step#props.

  • props (Hash)

    a customizable set of options

Options Hash (**props):

  • :type (Object) — default: :fetch
  • :message (Object) — default: "must be present"

Returns:


870
871
872
873
874
875
# File 'lib/ippon/validate.rb', line 870

def fetch(key, **props, &blk)
  blk ||= proc { Error }
  transform(type: :fetch, key: key, message: "must be present", **props) do |value|
    value.fetch(key, &blk)
  end
end

.float(**props) ⇒ Step

Returns a number schema with convert: :float.

Returns:

  • (Step)

    a number schema with convert: :float


1049
1050
1051
# File 'lib/ippon/validate.rb', line 1049

def float(**props)
  number(convert: :float, **props)
end

.for_each(schema) ⇒ ForEach

The for-each schema applies the given schema to each element of the input data.

Parameters:

  • schema (Schema)

    The scheme which will be applied to every element

Returns:


1108
1109
1110
# File 'lib/ippon/validate.rb', line 1108

def for_each(schema)
  ForEach.new(schema)
end

.form(fields) ⇒ Form

Returns a form schema.

Returns:

  • (Form)

    a form schema


1068
1069
1070
# File 'lib/ippon/validate.rb', line 1068

def form(fields)
  Form.new(fields)
end

.match(predicate, **props) ⇒ Step

The match schema produces an error if predicate === value is false.

This is a versatile validator which you can use for many different purposes:

# Number is within range
match(1..20)

# Value is of type
match(String)

# String matches regexp
match(/@/)

Parameters:

  • predicate

    An object which responds to ===. This value is stored under the :predicate parameter in the returned Step#props.

  • props (Hash)

    a customizable set of options

Options Hash (**props):

  • :type (Object) — default: :match
  • :message (Object) — default: "must match #{predicate}"

Returns:


1097
1098
1099
1100
1101
# File 'lib/ippon/validate.rb', line 1097

def match(predicate, **props)
  validate(type: :match, predicate: predicate, message: "must match #{predicate}", **props) do |value|
    predicate === value
  end
end

.number(**props) ⇒ Step

The number schema converts a String into a number.

The input value must be a String. You should use required, optional or match(String) to enforce this.

By default the number schema will ignore spaces and convert the number to an Integer. If the value contains a fractional part, a validation error is produced.

number.validate!("1 000")     # => 1000
number.validate!("1 000.00")  # => 1000
number.validate!("1 000.05")  # => Error

You can change the set of ignored characters with the :ignore option. This can either be a string (in which case all characters in the string will be removed) or a regexp (in which case all matches of the regexp will be removed).

# Also ignore commas
number(ignore: ", ").validate!("1,000")  # => 1000

# Remove dashes, but only in the middle by using a negative lookbehind
with_dash = number(ignore: / |(?<!\A)-/)
with_dash.validate!("-10")       # => -10
with_dash.validate!("10-10-10")  # => 101010

The :convert option instructs how to handle fractional parts. The following values are supported:

  • :integer: Return an Integer, but produce an error if it has a fractional part.

  • :round: Return an Integer by rounding it to the nearest integer.

  • :ceil: Return an Integer by rounding it down.

  • :floor: Return an Integer by rounding it up.

  • :float: Return a Float.

  • :decimal: Return a BigDecimal.

  • :rational: Return a Rational.

You can change the decimal separator as well:

# Convention for writing numbers in Norwegian
nor_number = number(decimal_separator: ",", ignore: " .", convert: :float)
nor_number.validate!("1.000,50")  # => 1000.50

If you're dealing with numbers where there's a smaller, fractional unit, you can provide the :scale option in order to represent the number exactly as an Integer:

dollars = number(ignore: " $,", scale: 100)
dollars.validate!("$100")      # => 10000
dollars.validate!("$100.33")   # => 10033
dollars.validate!("$100.333")  # => Error

:scale works together with :convert as expected. For instance, if you want to round numbers that are smaller than the fractional unit, you can combine it with convert: :round.

Parameters:

  • props (Hash)

    a customizable set of options

Options Hash (**props):

  • :ignore (String, Regexp) — default: " "

    Characters to ignore while parsing number

  • :convert (Symbol) — default: :integer

    Technique to convert the final number

  • :scale (Integer)

    Scaling factor

  • :decimal_separator (String) — default: "."

    decimal separator

  • :message (String) — default: "must be a number"
  • :type (Symbol) — default: :number

Returns:


994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
# File 'lib/ippon/validate.rb', line 994

def number(**props)
  transform(type: :number, message: "must be a number", **props) do |value|
    ignore = props.fetch(:ignore, / /)

    ignore_regex = case ignore
    when Regexp
      ignore
    when String
      /[#{Regexp.escape(ignore)}]/
    else
      raise ArgumentError, "unknown pattern: #{ignore}"
    end

    value = value.gsub(ignore_regex, "")

    if sep = props[:decimal_separator]
      value = value.sub(sep, ".")
    end

    begin
      num = Rational(value)
    rescue ArgumentError
      next Error
    end

    if scale = props[:scale]
      num *= scale
    end

    case convert = props.fetch(:convert, :integer)
    when :integer
      if num.denominator == 1
        num.numerator
      else
        Error
      end
    when :round
      num.round
    when :floor
      num.floor
    when :ceil
      num.ceil
    when :float
      num.to_f
    when :rational
      num
    when :decimal
      BigDecimal.new(num, value.size)
    else
      raise ArgumentError, "unknown convert: #{convert.inspect}"
    end
  end
end

.optional(**props) ⇒ Step .optional(**props) {|value| ... } ⇒ Step

The optional schema halts on nil input values.

Overloads:

  • .optional(**props) ⇒ Step

    Halts the execution if the input value is nil

  • .optional(**props) {|value| ... } ⇒ Step

    Halts the execution and sets the value to nil if the block yields true

    Yields:

    • (value)

      The input value

    Yield Returns:

    • Boolean

Parameters:

  • props (Hash)

    a customizable set of options

Options Hash (**props):

  • :type (Object) — default: :optional

Returns:


919
920
921
922
923
924
925
926
927
928
# File 'lib/ippon/validate.rb', line 919

def optional(**props, &blk)
  Step.new(type: :optional, **props) do |result|
    value = result.value
    should_halt = blk ? blk.call(value) : value.nil?
    if should_halt
      result.value = nil
      result.halt
    end
  end
end

.partial_form(fields) ⇒ Form

Returns a form schema.

Returns:

  • (Form)

    a form schema


1073
1074
1075
# File 'lib/ippon/validate.rb', line 1073

def partial_form(fields)
  Form.new(fields, partial: true)
end

.required(**props) ⇒ Step

The required schema produces an error if the input value is non-nil.

Parameters:

  • props (Hash)

    a customizable set of options

Options Hash (**props):

  • :type (Object) — default: :required
  • :message (Object) — default: "is required"

Returns:


899
900
901
902
903
# File 'lib/ippon/validate.rb', line 899

def required(**props)
  validate(type: :required, message: "is required", **props) do |value|
    !value.nil?
  end
end

.transform(**props) { ... } ⇒ Step

The transform schema yields the value and updates the result with the returned value.

transform { |val| val * 2 }.validate!(2)  # => 4

Yields:

  • value

Returns:


1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
# File 'lib/ippon/validate.rb', line 1137

def transform(**props, &blk)
  step = Step.new(**props) do |result|
    new_value = yield result.value
    if Error.equal?(new_value)
      result.halt
      result.add_error(step)
    else
      result.value = new_value
    end
  end
end

.trim(**props) ⇒ Step

The trim schema trims leading and trailing whitespace and then converts the value to nil if it's empty. The input data must either be a String or nil (in which case nothing happens).

Parameters:

  • props (Hash)

    a customizable set of options

Options Hash (**props):

  • :type (Object) — default: :trim

Returns:


883
884
885
886
887
888
889
890
891
# File 'lib/ippon/validate.rb', line 883

def trim(**props)
  transform(type: :trim, **props) do |value|
    if value
      value = value.strip
      value = nil if value.empty?
    end
    value
  end
end

.validate(**props) { ... } ⇒ Step

The validate schema produces an error if the yielded block returns false.

validate { |num| num.even? }

Yields:

  • value

Yield Returns:

  • Boolean

Returns:


1120
1121
1122
1123
1124
1125
1126
1127
1128
# File 'lib/ippon/validate.rb', line 1120

def validate(**props, &blk)
  step = Step.new(**props) do |result|
    is_valid = yield result.value
    if !is_valid
      result.halt
      result.add_error(step)
    end
  end
end