dry-rb Ecosystem Workshop
A hands-on workshop exploring the dry-rb ecosystem - a collection of next-generation Ruby libraries that embrace functional programming concepts while remaining pragmatic and Ruby-like.
Immutability by default - Data structures that don't change unexpectedly
Explicit over implicit - Clear contracts and expectations
Composition over inheritance - Build complex behavior from simple parts
Type safety without ceremony - Catch errors early without boilerplate
Part
Topic
Duration
Description
1
dry-types
20 min
Type system for coercion, constraints, and composition
2
dry-struct
15 min
Immutable, typed value objects
3
dry-validation
25 min
Schemas and contracts for business rule validation
4
dry-monads
25 min
Result, Maybe, and Try monads for error handling
5
dry-container
15 min
IoC container and dependency injection
6
dry-transaction
10 min
Business transaction DSL with failure handling
# Clone the repository
git clone https://github.com/jwalsh/dry-rb-workshop.git
cd dry-rb-workshop
# Install dependencies
bundle install
# Run individual examples
ruby lib/examples/01_types_primitives.rb
ruby lib/examples/12_monads_result.rb
ruby lib/examples/19_transaction.rb
# Run the complete application demo
ruby lib/application.rb
gem "dry-types" , "~> 1.7"
gem "dry-struct" , "~> 1.6"
gem "dry-validation" , "~> 1.10"
gem "dry-monads" , "~> 1.6"
gem "dry-container" , "~> 0.11"
gem "dry-auto_inject" , "~> 1.0"
gem "dry-transaction" , "~> 0.15"
dry-types - Type System Foundation
# Strict types raise on invalid input
strict_string = Types ::Strict ::String
strict_string . ( "hello" ) # => "hello"
# Coercible types attempt conversion
coercible_int = Types ::Coercible ::Integer
coercible_int . ( "42" ) # => 42
# Constrained types with validation
PositiveInt = Types ::Strict ::Integer . constrained ( gt : 0 )
Email = Types ::Strict ::String . constrained ( format : /\A [\w +\- .]+@[a-z\d \- ]+\. [a-z]+\z /i )
dry-struct - Immutable Value Objects
class Person < Dry ::Struct
attribute :name , Types ::Strict ::String
attribute :age , Types ::Coercible ::Integer
attribute :email , Types ::Strict ::String . optional
end
alice = Person . new ( name : "Alice" , age : "30" , email : "alice@example.com" )
older_alice = alice . new ( age : 31 ) # Returns new instance
dry-monads - Functional Error Handling
class UserService
include Dry ::Monads [ :result , :do ]
def create ( params )
validated = yield validate ( params )
user = yield persist ( validated )
Success ( user )
end
end
# Pattern matching on results
case service . create ( params )
in Success ( user ) then puts "Created: #{ user . name } "
in Failure ( :validation , errors ) then puts "Invalid: #{ errors } "
in Failure ( :database , error ) then puts "DB Error: #{ error } "
end
dry-transaction - Business Transaction DSL
class CreateOrder
include Dry ::Transaction
step :validate
step :check_inventory
step :charge_payment
step :create_record
step :send_confirmation
end
Type Family
Behavior
Example
Types::Strict::
No coercion, raises on mismatch
Strict::String.("hi")
Types::Coercible::
Ruby-style coercion
Coercible::Integer.("42")
Types::Params::
HTTP param coercion
Params::Bool.("true")
Types::JSON::
JSON-style coercion
JSON::Date.("2025-01-01")
. constrained ( gt : 0 ) # greater than
. constrained ( gteq : 0 ) # greater than or equal
. constrained ( min_size : 1 ) # minimum length
. constrained ( format : /regex/ ) # regex match
. constrained ( included_in : [ ] ) # enum values
Operation
Description
Success(val)
Wrap success value
Failure(err)
Wrap failure value
.value!
Unwrap (raises on Failure)
.value_or(x)
Unwrap with default
.bind { }
Chain (must return Result)
.fmap { }
Transform success value
yield result
Do-notation unwrap
MIT