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

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

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

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

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

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

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

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

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

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

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

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)

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

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.

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

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

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

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