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()
end

User = Dry.Struct(name: Types::String, age: Types::Integer)

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 which 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 built-in 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::String
  attribute :age,  Types::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
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
  • Pass values directly to Dry::Types without creating an object 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 application
  • Annotating objects

Other gems using dry-types

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