Module: RSchema::DSL

Included in:
DefaultDSL
Defined in:
lib/rschema/dsl.rb

Overview

Note:

Do not include your custom DSL methods into this module. Include them into the DefaultDSL class instead.

A mixin containing all the standard RSchema DSL methods.

This mixin contains only the standard RSchema DSL methods, without any of the extra ones that may have been included by third-party gems/code.

See Also:

Defined Under Namespace

Classes: OptionalWrapper

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object

Convenient way to create Schemas::Type schemas

See #type for details.

See Also:


330
331
332
333
334
335
336
337
338
# File 'lib/rschema/dsl.rb', line 330

def method_missing(sym, *args, &block)
  type = sym.to_s
  if type.start_with?('_') && args.empty? && block.nil?
    constant = Object.const_get(type[1..-1])
    type(constant)
  else
    super
  end
end

Instance Method Details

#anythingSchemas::Anything

Returns the Schemas::Anything schema.

Examples:

The anything schema

schema = RSchema.define { anything }
schema.valid?(nil) #=> true
schema.valid?(6.2) #=> true
schema.valid?({ hello: Time.now }) #=> true

Returns:


260
261
262
# File 'lib/rschema/dsl.rb', line 260

def anything
  Schemas::Anything.instance
end

#array(*subschemas) ⇒ Schemas::VariableLengthArray, Schemas::FixedLengthArray

Creates a Schemas::VariableLengthArray if given one argument, otherwise creates a Schemas::FixedLengthArray

Examples:

A variable-length array schema

schema = RSchema.define { array(_Integer) }
schema.valid?([1,2,3]) #=> true
schema.valid?([]) #=> true

A fixed-length array schema

schema = RSchema.define { array(_Integer, _String) }
schema.valid?([5, "hello"]) #=> true
schema.valid?([5]) #=> false
schema.valid?([5, "hello", "world"]) #=> false

Parameters:

  • subschemas (Array<schema>)

    one or more schema objects representing elements in the array.

Returns:


57
58
59
60
61
62
63
64
65
# File 'lib/rschema/dsl.rb', line 57

def array(*subschemas)
  subschemas = subschemas.map { |ss| inconvenience(ss) }

  if subschemas.count == 1
    Schemas::VariableLengthArray.new(subschemas.first)
  else
    Schemas::FixedLengthArray.new(subschemas)
  end
end

#attributes(attribute_hash) ⇒ Array<Schemas::FixedHash::Attribute>

Turns an "attribute hash" into an array of Schemas::FixedHash::Attribute. Primarily for use with Schemas::FixedHash#merge.

Examples:

Merging new attributes into an existing Schemas::FixedHash

person_schema = RSchema.define_hash {{
  name: _String,
  age: _Integer,
}}
person_schema.valid?(name: "t", age: 5) #=> true
person_schema.valid?(name: "t", age: 5, id: 3) #=> false

person_with_id_schema = RSchema.define do
  person_schema.merge(attributes(
    id: _Integer,
  ))
end
person_with_id_schema.valid?(name: "t", age: 5, id: 3) #=> true
person_with_id_schema.valid?(name: "t", age: 5) #=> false

Parameters:

  • attribute_hash (Hash<key, schema>)

    A hash of keys to subschemas. The values of this hash must be schema objects. The keys should be the exact keys expected in the represented Hash (Strings, Symbols, whatever). Keys can be wrapped with #optional to indicate that they can be missing from the represented Hash.

Returns:

See Also:


163
164
165
166
167
168
169
170
171
# File 'lib/rschema/dsl.rb', line 163

def attributes(attribute_hash)
  attribute_hash.map do |dsl_key, value_schema|
    optional = dsl_key.is_a?(OptionalWrapper)
    key = optional ? dsl_key.key : dsl_key
    Schemas::FixedHash::Attribute.new(
      key, inconvenience(value_schema), optional,
    )
  end
end

#booleanSchemas::Boolean

Returns the Schemas::Boolean schema

Examples:

The boolean schema

schema = RSchema.define { boolean }
schema.valid?(true)  #=> true
schema.valid?(false) #=> true
schema.valid?(nil)   #=> false

Returns:


74
75
76
# File 'lib/rschema/dsl.rb', line 74

def boolean
  Schemas::Boolean.instance
end

#convenience(schema) ⇒ Schemas::Convenience

Wraps a schema in a Schemas::Convenience

It is not normally necessary to do this wrapping manually. Methods like RSchema.define, RSchema.define_predicate and RSchema.define_hash already return schema objects wrapped in Schemas::Convenience.

Examples:

Manually wrapping a schema with convenience

# Unlike `RSchema.define`, the `RSchema.dsl_eval` method does not
# wrap the return value with RSchema::Schemas::Convenience, so the
# returned schema is missing convenience methods like `valid?`
schema = RSchema.dsl_eval { _Integer }
schema.valid?(5) #=> NoMethodError: undefined method `valid?'

# After manually wrapping the schema, the convenience methods are
# available
schema = RSchema.dsl_eval { convenience(_Integer) }
schema.valid?(5) #=> true

Parameters:

  • schema (schema)

    The schema to wrap

Returns:


286
287
288
# File 'lib/rschema/dsl.rb', line 286

def convenience(schema)
  Schemas::Convenience.wrap(schema)
end

#either(*subschemas) ⇒ Schemas::Sum

Creates a Schemas::Sum schema.

Examples:

A schema that matches both Integers and Strings

schema = RSchema.define { either(_String, _Integer) }
schema.valid?("hello") #=> true
schema.valid?(5) #=> true

Parameters:

  • subschemas (Array<schema>)

    Schemas representing all the possible valid values.

Returns:


214
215
216
217
# File 'lib/rschema/dsl.rb', line 214

def either(*subschemas)
  subschemas = subschemas.map { |ss| inconvenience(ss) }
  Schemas::Sum.new(subschemas)
end

#enum(valid_values, subschema = nil) ⇒ Schemas::Enum

Creates a Schemas::Enum schema

Examples:

Rock-Paper-Scissors values

schema = RSchema.define { enum([:rock, :paper, :scissors]) }
schema.valid?(:rock)  #=> true
schema.valid?(:paper) #=> true
schema.valid?(:gun)   #=> false

Parameters:

  • valid_values (Array<Object>)

    An array of all possible valid values.

  • subschema (schema) (defaults to: nil)

    A schema that represents all enum members. If this is nil, the schema is inferred to be the type of the first element in valid_values (e.g. enum([:a,:b,:c]) will have _Symbol as the inferred subschema).

Returns:


198
199
200
201
202
203
# File 'lib/rschema/dsl.rb', line 198

def enum(valid_values, subschema = nil)
  subschema = inconvenience(subschema) if subschema
  Schemas::Enum.new(
    valid_values, subschema || type(valid_values.first.class),
  )
end

#fixed_hash(attribute_hash) ⇒ Schemas::FixedHash Also known as: hash

Creates a Schemas::FixedHash schema

Examples:

A typical fixed hash schema

schema = RSchema.define do
  fixed_hash(
    name: _String,
    optional(:age) => _Integer,
  )
end
schema.valid?({ name: "Tom" }) #=> true
schema.valid?({ name: "Dane", age: 55 }) #=> true

Parameters:

  • attribute_hash (Hash<key, schema>)

    A hash of keys to subschemas. The values of this hash must be schema objects. The keys should be the exact keys expected in the represented Hash (Strings, Symbols, whatever). Keys can be wrapped with #optional to indicate that they can be missing from the represented Hash.

Returns:

See Also:


89
90
91
# File 'lib/rschema/dsl.rb', line 89

def fixed_hash(attribute_hash)
  Schemas::FixedHash.new(attributes(attribute_hash))
end

#inconvenience(schema) ⇒ schema

Removes any Schemas::Convenience wrappers from a schema.

This method is only really useful when defining your own custom DSL methods.

When creating a composite schema that contains other subschemas, it is unneccessary to have the subschemas wrapped in Schemas::Convenience. Using wrapped subschemas should not cause any errors, but unwrapped subschemas will have slightly better performance. So, when your custom DSL method is creating a composite schema, use #inconvenience to unwrap all the subschemas.

Examples:

Unwrapping subschemas in a custom DSL method

module MyCustomDSL
  def pair(subschema)
    unwrapped = inconvenience(subschema)
    RSchema::Schemas::FixedLengthArray.new([unwrapped, unwrapped])
  end
end

RSchema::DefaultDSL.include(MyCustomDSL)

schema = RSchema.define{ pair(_Integer) }
schema.valid?([4, 6]) #=> true

Returns:

  • (schema)

    The underlying schema object, once all convenience wrappers have been removed.


319
320
321
# File 'lib/rschema/dsl.rb', line 319

def inconvenience(schema)
  Schemas::Convenience.unwrap(schema)
end

#maybe(subschema) ⇒ Schemas::Maybe

Creates a Schemas::Maybe schema

Examples:

A nil-able Integer

schema = RSchema.define{ maybe(_Integer) }
schema.valid?(5) #=> true
schema.valid?(nil) #=> true

Parameters:

  • subschema (schema)

    A schema representing the value, if the value is not nil.

Returns:


182
183
184
# File 'lib/rschema/dsl.rb', line 182

def maybe(subschema)
  Schemas::Maybe.new(inconvenience(subschema))
end

#optional(key) ⇒ OptionalWrapper

Wraps a key in an OptionalWrapper, for use with the #fixed_hash or #attributes methods.

Examples:

A typical fixed hash schema

schema = RSchema.define do
  fixed_hash(
    name: _String,
    optional(:age) => _Integer,
  )
end
schema.valid?({ name: "Tom" }) #=> true
schema.valid?({ name: "Dane", age: 55 }) #=> true

Parameters:

  • key (Object)

    Any arbitrary value

Returns:

See Also:


119
120
121
# File 'lib/rschema/dsl.rb', line 119

def optional(key)
  OptionalWrapper.new(key)
end

#pipeline(*subschemas) ⇒ Schemas::Pipeline

Creates a Schemas::Pipeline schema.

Examples:

A schema for positive floats

schema = RSchema.define do
  pipeline(
    _Float,
    predicate{ |f| f > 0.0 },
  )
end
schema.valid?(6.2) #=> true
schema.valid?('hi') #=> false (because it's not a Float)
schema.valid?(-6.2) #=> false (because predicate failed)

Parameters:

  • subschemas (Array<schema>)

    The schemas to be pipelined together, in order.

Returns:


248
249
250
251
# File 'lib/rschema/dsl.rb', line 248

def pipeline(*subschemas)
  subschemas = subschemas.map { |ss| inconvenience(ss) }
  Schemas::Pipeline.new(subschemas)
end

#predicate(name = nil) {|value| ... } ⇒ Schemas::Predicate

Creates a Schemas::Predicate schema.

Examples:

A predicate that checks if numbers are odd

schema = RSchema.define do
  predicate('odd'){ |x| x.odd? }
end
schema.valid?(5) #=> true
schema.valid?(6) #=> false

Parameters:

  • name (String) (defaults to: nil)

    An optional name for the predicate schema. This serves no purpose other than to provide useful debugging information, or perhaps some metadata.

Yields:

  • Values being validated are yielded to the given block. The return value of the block indicates whether the value is valid or not.

Yield Parameters:

  • value (Object)

    The value being validated

Yield Returns:

  • (Boolean)

    Truthy if the value is valid, otherwise falsey.

Returns:

See Also:


235
236
237
# File 'lib/rschema/dsl.rb', line 235

def predicate(name = nil, &block)
  Schemas::Predicate.new(name, &block)
end

#set(subschema) ⇒ Schemas::Set

Creates a Schemas::Set schema

Examples:

A set of integers

require 'set'
schema = RSchema.define { set(_Integer) }
schema.valid?(Set[1, 2, 3]) #=> true
schema.valid?(Set[:a, :b, :c]) #=> false

Parameters:

  • subschema (schema)

    A schema representing the elements of the set

Returns:


102
103
104
# File 'lib/rschema/dsl.rb', line 102

def set(subschema)
  Schemas::Set.new(inconvenience(subschema))
end

#type(type) ⇒ Schemas::Type

Creates a Schemas::Type schema.

The preferred way to create type schemas is using an underscore, like:

_Integer

The DSL will turn the above code into:

type(Integer)

Underscores will not work for namespaced types (types that include ::). In that case, it is necessary to use the type method.

Examples:

An Integer schema

schema = RSchema.define { _Integer }
schema.valid?(5) #=> true

A namespaced type

schema = RSchema.define do
  # This will not work:
  # _ActiveWhatever::Thing

  # This will work:
  type(ActiveWhatever::Thing)
end

Parameters:

  • type (Class)

Returns:


42
43
44
# File 'lib/rschema/dsl.rb', line 42

def type(type)
  Schemas::Type.new(type)
end

#variable_hash(subschemas) ⇒ Schemas::VariableHash

Creates a Schemas::VariableHash schema

Examples:

A hash of integers to strings

schema = RSchema.define { variable_hash(_Integer => _String) }
schema.valid?({ 5 => "hello", 7 => "world" }) #=> true
schema.valid?({}) #=> true

Parameters:

  • subschemas (Hash)

    A hash with a single key, and a single value. The key is a schema representing all keys. The value is a schema representing all values.

Returns:

See Also:


135
136
137
138
139
140
141
142
143
144
145
# File 'lib/rschema/dsl.rb', line 135

def variable_hash(subschemas)
  unless subschemas.is_a?(Hash) && subschemas.size == 1
    raise ArgumentError, 'argument must be a Hash of size 1'
  end

  key_schema, value_schema = subschemas.first
  Schemas::VariableHash.new(
    inconvenience(key_schema),
    inconvenience(value_schema),
  )
end