Introduction

dry-types is a simple and extendable type system for Ruby; useful for value coercions, applying constraints, defining complex structs or value objects and more. It was created as a successor to Virtus.

Example usage

require 'dry-types'
require 'dry-struct'

module Types
  include Dry::Types.module
end

class User < Dry::Struct
  attribute :name, Types::String
  attribute :age,  Types::Integer
end

User.new(name: 'Bob', age: 35)
# => #<User name="Bob" age=35>

See Built-in Types for a full list of available types.

By themselves, the basic type definitions like Types::String and Types::Integer don't do anything except provide documentation about type an attribute is expected to have. However, there are many more advanced possibilities:

  • 'Strict' types will raise an error if passed an attribute of the wrong type:
class User < Dry::Struct
  attribute :name, Types::Strict::String
  attribute :age,  Types::Strict::Integer
end

User.new(name: 'Bob', age: '18')
# => Dry::Struct::Error: [User.new] "18" (String) has invalid type for :age
  • 'Coercible' types will attempt to convert an attribute to the correct class using Ruby's inbuilt coercion methods:
class User < Dry::Struct
  attribute :name, Types::Coercible::String
  attribute :age,  Types::Coercible::Integer
end

User.new(name: 'Bob', age: '18')
# => #<User name="Bob" age=18>
User.new(name: 'Bob', age: 'not coercible')
# => ArgumentError: invalid value for Integer(): "not coercible"
  • Use .optional to denote that an attribute can be nil (see Optional Values):
class User < Dry::Struct
  attribute :name, Types::Strict::String
  attribute :age,  Types::Strict::Integer.optional
end

User.new(name: 'Bob', age: nil)
# => #<User name="Bob" age=nil>
# name is not optional:
User.new(name: nil, age: 18)
# => Dry::Struct::Error: [User.new] nil (NilClass) has invalid type for :name
# keys must still be present:
User.new(name: 'Bob')
# => Dry::Struct::Error: [User.new] :age is missing in Hash input
  • You can add your own custom constraints (see Constraints):
class User < Dry::Struct
  attribute :name, Types::Strict::String
  attribute :age,  Types::Strict::Integer.constrained(gteq: 18)
end

User.new(name: 'Bob', age: 17)
# => Dry::Struct::Error: [User.new] 17 (Fixnum) has invalid type for :age
  • Add custom metadata to a type:
class User < Dry::Struct
  attribute :name, Types::String
  attribute :age,  Types::Integer.meta(info: 'extra info about age')
end

... and more.

Note that you don't have to use Dry::Struct. You can interact with your type definitions however you like using []:

Types::Strict::String["foo"]
# => "foo"
Types::Strict::String["10000"]
# => "10000"
Types::Coercible::String[10000]
# => "10000"
Types::Strict::String[10000]
# Dry::Types::ConstraintError: 1000 violates constraints

Features

Use cases

dry-types is suitable for many use-cases, for example:

  • Value coercions
  • Processing arrays
  • Processing hashes with explicit schemas
  • Defining various domain-specific information shared between multiple parts of your applications
  • Annotating objects

Other gems using dry-types

dry-types is often used as a low-level abstraction. The following gems use it already:

octocatEdit on GitHub