Dry-rb gems for functional programming patterns

Sarah Mitchell Feb 2026
2 tabs
# Gemfile
gem 'dry-types'
gem 'dry-struct'

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

module Types
  include Dry.Types()
end

# Define typed struct
class User < Dry::Struct
  attribute :name, Types::String
  attribute :email, Types::String
  attribute :age, Types::Integer.optional
  attribute :role, Types::String.enum('user', 'admin', 'moderator')
  attribute :active, Types::Bool.default(true)
end

# Create instance
user = User.new(
  name: 'John Doe',
  email: 'john@example.com',
  age: 30,
  role: 'admin'
)

user.name   # => "John Doe"
user.active # => true (default)

# Structs are immutable
user.name = "Jane"  # NoMethodError

# Type errors caught at initialization
User.new(name: 123)  # Dry::Struct::Error (type mismatch)

# Optional attributes
User.new(name: 'John', email: 'john@example.com', role: 'user')
# age is nil (optional)

# Custom types
module Types
  Email = String.constrained(format: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
  PositiveInteger = Integer.constrained(gt: 0)
  Name = String.constrained(min_size: 2, max_size: 100)
end

class ValidatedUser < Dry::Struct
  attribute :name, Types::Name
  attribute :email, Types::Email
  attribute :age, Types::PositiveInteger.optional
end

# Type coercion
module Types
  CoercibleString = Coercible::String
  CoercibleInteger = Coercible::Integer
end

class Post < Dry::Struct
  transform_keys(&:to_sym)  # Auto-convert string keys to symbols

  attribute :id, Types::CoercibleInteger
  attribute :title, Types::CoercibleString
  attribute :published, Types::Params::Bool  # Coerces "1", "true", etc.
end

Post.new(id: "123", title: 123, published: "1")
# Auto-coerces to: id: 123, title: "123", published: true

# Sum types (union types)
module Types
  StringOrInteger = String | Integer
  StringOrNil = String.optional  # String | Nil
end

class FlexibleStruct < Dry::Struct
  attribute :value, Types::StringOrInteger
end

FlexibleStruct.new(value: "hello")  # OK
FlexibleStruct.new(value: 42)       # OK
FlexibleStruct.new(value: true)     # Error

# Nested structs
class Address < Dry::Struct
  attribute :street, Types::String
  attribute :city, Types::String
  attribute :zip, Types::String
end

class UserWithAddress < Dry::Struct
  attribute :name, Types::String
  attribute :address, Address
  attribute :tags, Types::Array.of(Types::String)
end

user = UserWithAddress.new(
  name: 'John',
  address: { street: '123 Main', city: 'NYC', zip: '10001' },
  tags: ['developer', 'ruby']
)
2 files · ruby Explain with highlit

Dry-rb provides functional programming tools for Ruby. dry-validation creates complex validation schemas with type checking. dry-types defines strict types—coercion, constraints, sum types. I use dry-struct for immutable data structures with typed attributes. dry-monads brings Result, Maybe, Try monads for error handling without exceptions. dry-transaction composes operations into workflows. Dry gems reduce bugs through explicitness and immutability. They enable railway-oriented programming—success/failure tracks. Understanding monads simplifies complex business logic. Dry gems integrate well with Rails—service objects, form objects. They represent Ruby moving toward type safety while staying dynamic. Dry-rb balances functional programming with Ruby pragmatism.