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:



1155
1156
1157
1158
1159
# File 'lib/ippon/validate.rb', line 1155

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:



963
964
965
966
967
968
# File 'lib/ippon/validate.rb', line 963

def fetch(key, **props, &blk)
  blk ||= proc { StepError }
  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



1143
1144
1145
# File 'lib/ippon/validate.rb', line 1143

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:



1197
1198
1199
# File 'lib/ippon/validate.rb', line 1197

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

.form(fields = {}, &blk) ⇒ Form

Returns a form schema.

Returns:

  • (Form)

    a form schema



1162
1163
1164
# File 'lib/ippon/validate.rb', line 1162

def form(fields = {}, &blk)
  Form.new(&blk).with_keys(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:



1186
1187
1188
1189
1190
# File 'lib/ippon/validate.rb', line 1186

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:



1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
# File 'lib/ippon/validate.rb', line 1088

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 StepError
    end

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

    case convert = props.fetch(:convert, :integer)
    when :integer
      if num.denominator == 1
        num.numerator
      else
        StepError
      end
    when :round
      num.round
    when :floor
      num.floor
    when :ceil
      num.ceil
    when :float
      num.to_f
    when :rational
      num
    when :decimal
      BigDecimal(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:



1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
# File 'lib/ippon/validate.rb', line 1012

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
    result
  end
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:



992
993
994
995
996
# File 'lib/ippon/validate.rb', line 992

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:



1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
# File 'lib/ippon/validate.rb', line 1227

def transform(**props, &blk)
  step = Step.new(**props) do |result|
    new_value = yield result.value
    if StepError.equal?(new_value)
      result.halt
      result.mutable_errors.add_step(step)
    else
      result.value = new_value
    end
    result
  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:



976
977
978
979
980
981
982
983
984
# File 'lib/ippon/validate.rb', line 976

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:



1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
# File 'lib/ippon/validate.rb', line 1209

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