D r y

Ruby gems for better code

Dry is a collection of Ruby gems that help you write clear, flexible, and maintainable Ruby code.

Covering business logic to everyday utilities, these gems work great together or standalone in any kind of app.

View the Dry docs

Business logic

These gems help you write business logic that’s explicit, predictable, and testable.

Validation

With Dry validation contracts, you can specify precisely the data you want to accept for each use case in your app.

Since contracts are standalone, they help you keep your validation logic reusable, testable, and separate from your data persistence.

View validation docs
class UserContract < Dry::Validation::Contract
  params do
    required(:name).filled(:string)
    required(:age).filled(:integer)
  end

  rule(:age) { key.failure("must be greater than 18") if value < 18 }
end

Types

With Dry types, you can build your app’s own library of self-documenting type checks and data transformations. You can use your types standalone, or bring them together in immutable structs.

Together, this helps you write more expressive, confident code, knowing you’re working with data you can trust.

View types docs
Types = Dry.Types(default: :strict)

module Types
  Email = String.constrained(format: /@/)
  UserRole = String.enum("admin", "member", "guest")
end

class User < Dry::Struct
  attribute :email, Types::Email
  attribute :name, Types::String
  attribute :age, Types::Integer.constrained(gteq: 0)
  attribute :role, Types::UserRole
end

Operations and results

Operations help you organize your essential business logic. Create your operation from a series of steps, each returning a success or failure. When you call your operation, if any step fails, the operation stops and that failure is returned right away.

You can chain operations together into higher-level workflows, and when you need even more control, you can tap into a full library of monads for advanced composition.

View operations docs
class CreateUser < Dry::Operation
  def call(input)
    attrs = step validate(input)
    user = step persist(attrs)
    step notify(user)
    user
  end

  private

  def validate(input)
    # Return Success(attrs) or Failure(error)
  end

  def persist(attrs)
    # Return Success(user) or Failure(error)
  end

  def notify(user)
    # Return Success(true) or Failure(error)
  end
end

Foundations

All sorts of goodies here: these gems are practical, standalone utilities for both libraries and apps.

Logging

Dry Logger is a powerful and flexible replacement for Ruby’s standard logger. Use it to log with consistent formatting and to multiple outputs.

You can tag entries and route logs where you need them.

View logger docs
# From simple logging
logger = Dry.Logger(:my_app)
logger.info("App started")

# To structured logging and output routing
logger = Dry.Logger(:my_app, formatter: :json) { |setup|
  setup.add_backend(stream: "logs/app.log", template: :details)
  setup.add_backend(stream: "logs/json.log", formatter: :json)
  setup.add_backend(stream: "logs/error.log", log_if: :error?)
}
logger.info("User signed in", user_id: 123, role: "admin")

Inflections

Dry Inflector is a lightweight inflections gem that you can use anywhere. It comes with a familiar API that does all the things you expect: pluralize, singularize, and convert cases, all based on your own configuration.

View inflector docs
inflector = Dry::Inflector.new

# Standard transformations
inflector.pluralize("person")         # => "people"
inflector.singularize("categories")   # => "category"
inflector.camelize("preferred_name")  # => "PreferredName"
inflector.underscore("PreferredName") # => "preferred_name"

# Configure for your domain
inflector = Dry::Inflector.new do |inflections|
  inflections.acronym("API", "JSON", "HTTP")
end

Initializers

Use Dry Initializer to avoid repetitive class constructors and just declare what your objects need, along with optional type checks and defaults.

View initializer docs
class User
  extend Dry::Initializer

  option :name, type: Types::String
  option :age, type: Types::Integer, default: proc { 18 }
  option :role, type: Types::String, default: proc { "member" }
end

user = User.new(name: "Alice", age: 30)

Configuration

Dry Configurable helps you fulfil a common pattern within Ruby: configurable objects with validation and type checking.

View configurable docs
class CacheStore
  extend Dry::Configurable

  setting :backend, constructor: Types::String
  setting :ttl, default: 3600, constructor: Types::Integer

  setting :redis do
    setting :host, default: "localhost"
    setting :port, default: 6379
    setting :db, default: 0
  end
end

CacheStore.config.backend = "redis"
CacheStore.config.redis.host = "redis.example.com"
CacheStore.config.redis.port = 6380

Architecture

Structure whole apps around clear boundaries and loosely-coupled components.

Systems and auto-injection

Take the core of the Hanami framework and tailor it exactly to your needs. Dry System and Dry Auto Inject help you construct apps from clear, focused components with explicit dependencies.

View system docs
class App < Dry::System::Container
  configure do |config|
    config.component_dirs.add "lib"
  end
end

Deps = App.injector

# lib/users/create.rb
class Create < Dry::Operation
  include Deps["user_repo", "emails.welcome_email"]

  def call(attributes)
    user = user_repo.create(attributes)
    step welcome_email.delivery(user)
    Success(user)
  end
end

Command line tools

Create rich command line interfaces with argument parsing, subcommands, and help text.

Build your CLI like you would a real app, with commands as classes, and the full power of Ruby at your disposal.

View CLI docs
module MyApp
  module CLI
    module Commands
      extend Dry::CLI::Registry

      class Generate < Dry::CLI::Command
        desc "Generate a new file"

        argument :name, required: true, desc: "File name"
        option :type, default: "rb", desc: "File type"

        def call(name:, type:, **)
          puts "Generating #{name}.#{type}..."
        end
      end

      register "generate", Generate
    end
  end
end

Dry::CLI.new(MyApp::CLI::Commands).call

…and more!

We’ve just scratched the surface with Dry. There’s a lot more to love:

  • A comprehensive toolkit. We offer 25+ gems spanning business logic, architecture, and everyday utilities. You can use what you need, and skip what you don’t.
  • Works everywhere. Dry gems work everywhere, from Hanami to Rails to standalone gems. We make no framework assumptions, and don’t lock you in.
  • Proven in production. Built on over a decade of refinement and real-world use, these are proven tools you can depend on for the long-term.
Explore the Dry docs

Built on community

For people who bring kindness, curiosity, and care

Photograph of cute people being cute

The Hanakai community is a place where people of all backgrounds and experience levels can feel respected, and can share and grow. A place for people to be proud of, and feel safe within.

We do not tolerate nazis, transphobes, racists, or any kind of bigotry. See our Code of Conduct for more.

Supported by

Hanakai is made possible by our wonderful sponsors

We’re also supported by our many community patrons.

Become a Hanakai sponsor or patron today, and help us build a diverse future for Ruby.

Support the Hanakai project