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:


323
324
325
326
327
328
329
330
331
# File 'lib/rschema/dsl.rb', line 323

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:


253
254
255
# File 'lib/rschema/dsl.rb', line 253

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:


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

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 schema

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:


160
161
162
163
164
165
166
# File 'lib/rschema/dsl.rb', line 160

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:


72
73
74
# File 'lib/rschema/dsl.rb', line 72

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:


279
280
281
# File 'lib/rschema/dsl.rb', line 279

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:


207
208
209
210
# File 'lib/rschema/dsl.rb', line 207

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:


193
194
195
196
# File 'lib/rschema/dsl.rb', line 193

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:


87
88
89
# File 'lib/rschema/dsl.rb', line 87

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.


312
313
314
# File 'lib/rschema/dsl.rb', line 312

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:


177
178
179
# File 'lib/rschema/dsl.rb', line 177

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:


117
118
119
# File 'lib/rschema/dsl.rb', line 117

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:


241
242
243
244
# File 'lib/rschema/dsl.rb', line 241

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:


228
229
230
# File 'lib/rschema/dsl.rb', line 228

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:


100
101
102
# File 'lib/rschema/dsl.rb', line 100

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:


40
41
42
# File 'lib/rschema/dsl.rb', line 40

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:


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

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