Structs

Overview

Structs turn schema definitions into objects graphs with attribute readers.

Add optional Paradocs::Struct module to define struct-like objects with schema definitions.

require 'parametric/struct'

class User
  include Paradocs::Struct

  schema do
    field(:name).type(:string).present
    field(:friends).type(:array).schema do
      field(:name).type(:string).present
      field(:age).type(:integer)
    end
  end
end

User objects can be instantiated with hash data, which will be coerced and validated as per the schema definition.

user = User.new(
  name: 'Joe',
  friends: [
    {name: 'Jane', age: 40},
    {name: 'John', age: 30},
  ]
)

# properties
user.name # => 'Joe'
user.friends.first.name # => 'Jane'
user.friends.last.age # => 30

Errors

Both the top-level and nested instances contain error information:

user = User.new(
  name: '', # invalid
  friends: [
    # friend name also invalid
    {name: '', age: 40},
  ]
)

user.valid? # false
user.errors['$.name'] # => "is required and must be present"
user.errors['$.friends[0].name'] # => "is required and must be present"

# also access error in nested instances directly
user.friends.first.valid? # false
user.friends.first.errors['$.name'] # "is required and must be valid"

.new!(hash)

Instantiating structs with .new!(hash) will raise a Paradocs::InvalidStructError exception if the data is validations fail. It will return the struct instance otherwise.

Paradocs::InvalidStructError includes an #errors property to inspect the errors raised.

begin
  user = User.new!(name: '')
rescue Paradocs::InvalidStructError => e
  e.errors['$.name'] # "is required and must be present"
end

Nested structs

You can also pass separate struct classes in a nested schema definition.

class Friend
  include Paradocs::Struct

  schema do
    field(:name).type(:string).present
    field(:age).type(:integer)
  end
end

class User
  include Paradocs::Struct

  schema do
    field(:name).type(:string).present
    # here we use the Friend class
    field(:friends).type(:array).schema Friend
  end
end

Inheritance

Struct subclasses can add to inherited schemas, or override fields defined in the parent.

class AdminUser < User
  # inherits User schema, and can add stuff to its own schema
  schema do
    field(:permissions).type(:array)
  end
end

#to_h

Struct#to_h returns the ouput hash, with values coerced and any defaults populated.

class User
  include Paradocs::Struct
  schema do
    field(:name).type(:string)
    field(:age).type(:integer).default(30)
  end
end

user = User.new(name: "Joe")
user.to_h # {name: "Joe", age: 30}

Struct equality

Paradocs::Struct implements #==() to compare two structs Hash representation (same as struct1.to_h.eql?(struct2.to_h).

Users can override #==() in their own classes to do whatever they need.