Class: FlexArgs::Constraint

Inherits:
Object
  • Object
show all
Defined in:
lib/flex_args/constraint.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, *constraints, &block) ⇒ Constraint

## Creates a new constraint for the value argument named name

Constraints can be many, which is indicated by the first element in the constraints list, or a constraint can be indicated by a custom made block.

The available constraints are:

### Regexp constraint

These rdoc_specs show the interface that the FlexArgs::Parser uses to validate constraints, the class is therefore part of the public API and can be used in isolation where it serves.

“‘spec # regexp

new(:for_some_value, %r{x}).("x") => [:ok, "x"]
new(:for_some_value, %r{x}).("y") => [:error, "regexp constraint for_some_value: (?-mix:x) violated by value \"y\""]

“‘

### Range constraint

“‘spec # range

new(:for_some_int, 1..9).("1") => [:ok, 1]
new(:for_some_int, 1..9).("0") => [:error, "range constraint for_some_int: 1..9 violated by value \"0\""]
new(:for_some_int, 1..9).("y") => [:error, "range constraint for_some_int: 1..9 violated by value \"y\""]

new(:for_string, "a".."c").("a") => [:ok, "a"]
new(:for_string, "a".."c").("d") => [:error, 'member constraint for_string: "a".."c" violated by value "d"']

“‘

N.B. that ranges not of type integer are treated as Member contraints below.

### Member constraint

While the Range constraint above does int type casting if needed, the Member constraint cannot do that, because it cannot access the type of the container against which membership will be tested.

Member constraints can be triggered by providing a Set, Array or Hash second parameter

“‘spec # Various membership rdoc specs

vowels = %w[a e i o u y]
hashy = Hash[vowels.product([true])]
set = Set.new(vowels)
in_array = new(:in_array, vowels)
in_set   = new(:in_set, set)
in_hash  = new(:in_hash, hashy)

expect(in_array.("a")).to eq([:ok, "a"])
expect(in_array.("y")).to eq([:ok, "y"])
expect(in_set.("y")).to eq([:ok, "y"])
expect(in_hash.("o")).to eq([:ok, "o"])

expect(in_array.("b"))
  .to eq([:error, "member constraint in_array: #{vowels.inspect} violated by value \"b\""])

expect(in_hash.("ec"))
  .to eq([:error, "member constraint in_hash: #{hashy.inspect} violated by value \"ec\""])

expect(in_set.("aa"))
  .to eq([:error, "member constraint in_set: #{set.inspect} violated by value \"aa\""])

“‘

### Custom constraint

This is easily implemented by passing a block which will return a pair as described above, but also note two more features of the custom constraint

  • exceptions will be caught and transformed into an error, thusly simplyfing the code block:

  • and values can be transformed

“‘spec # custom

constraint = new :even do
  value = Integer(it)
  value.even? ? [:ok, value / 2] : [:error, "not even"]
end

expect(constraint.("84")).to eq([:ok, 42])
expect(constraint.("21")).to eq([:error, "custom constraint even: violated by value \"21\" with message: \"not even\""])

exception_message = '"invalid value for Integer(): \\"abc\\" (ArgumentError)"'
complete_message = "custom constraint even: violated by value \"abc\" with message: #{exception_message}"
expect(constraint.("abc")).to eq([:error, complete_message])

“‘

Also note that the correct format of the block’s return value is important

“‘spec # enforce correct format of custom constraint’s block

constraint = new(:bad) { 42 }

expect { constraint.(nil) }
 .to raise_error(ArgumentError, "badly formatted custom block for custom constraint bad returned 42, needed [:ok|:error, value_or_message]")

“‘



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/flex_args/constraint.rb', line 103

def initialize(name, *constraints, &block)
  @name = name
  case constraints
  in [Regexp => constraint]
    _init_regexp(name, constraint)
  in [Range => range]
    _init_range(name, range)
  in [Array | Hash | Set => container]
    _init_membership(name, container, false)
  in [Array | Hash | Set => container, :autocast]
    _init_membership(name, container, true)
  in [:member, container]
    _init_membership(name, container)
  else
    raise ArgumentError,
      "illegal constraint spec #{constraints.inspect} for value #{name}" unless block
    _init_custom(name, &block)
  end
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



6
7
8
# File 'lib/flex_args/constraint.rb', line 6

def name
  @name
end

Instance Method Details

#call(value) ⇒ Object



123
124
125
# File 'lib/flex_args/constraint.rb', line 123

def call(value)
  @constrainer.(value)
end