dry-rb is a collection of next-generation Ruby libraries

dry-rb helps you write clear, flexible, and more maintainable Ruby code. Each dry-rb gem fulfils a common task, and together they make a powerful platform for any kind of Ruby application.

Check out the examples below to get a taste for how our libraries work

Use predicate logic to validate any kind of data with explicitness, clarity and precision.

schema = Dry::Validation.Form do
  required(:name).filled
  required(:age).filled(:int?, gt?: 18)
end

schema.("name" => "Jane", "age" => "30").to_h
# => {name: "Jane", age: 30}

schema.("name" => "Jane", "age" => "17").messages
# => {:age=>["must be greater than 18"]}

Build your own strict data types, constraints and coercsions with dry-types.

module Types
  include Dry::Types.module

  Greeting = Strict::String.enum("Hello", "Hola")
end

Types::Greeting["Hello"]
# => "Hello"

Types::Greeting["Goodbye"]
# Dry::Types::ConstraintError: "Goodbye" violates constraints...

Define types with constraints

class Person < Dry::Struct
  attribute :name, Types::Strict::String
  attribute :age, Types::Strict::Int
end

Person.new(name: "Jane", age: 30)
# => #<Person name="Jane" age=30>

Define strictly typed structs

Dependency management

Use dry-system to build your application from a collection of discrete, single-purpose components. Compose these objects and easily achieve larger functionality with auto-injection.

class App < Dry::System::Container
  configure do |config|
    config.auto_register = %w[lib]
  end

  load_paths! "lib"
end

Manage your app’s objects in a system

require "app/import"

class CreateArticle
  include App::Import[repo: "repositories.articles"]

  def call(input)
    # Work with the injected dependency
    repo.create(input)
  end
end

Build callable functional objects with automatic dependency resolution

Safely and gracefully model complex transformations with monads, and use pattern matching to make robust error handling a first-class feature of your application.

Dry::Monads::Maybe(user).fmap(&:address).fmap(&:street)

# If user with address exists
# => Some("Street Address")
# If user or address is nil
# => None()

Safely model complex, chained transformations

class CreateArticle
  include Dry::Monads::Either::Mixin
  include Dry::Matcher.for(:call, with: Dry::Matcher::EitherMatcher)

  def call(input)
    if success?(input)
      output = some_action(input)
      Right(output)
    else
      Left(input)
    end
  end
end

create = CreateArticle.new
create.(input) do |m|
  m.success do |output|
    # Handle success
  end

  m.failure do |err|
    # Handle failure
  end
end

Use the Either monad to model success and failure and match on the result