Use when using the dry-monads Result pattern in Hanami 2.x — wrap outcomes in `Success(value)` or `Failure(error_object)`, chain operations with `Do notation` using `include Dry::Monads::Do.for(:call)` and `yield` inside the `call` method for sequential flows, inject the service object via `Deps["key"]` after registering it in a provider, handle results in Actions with pattern matching (`case result; in Success(v); ...; in Failure(e); ...; end`) rather than unsafe `value!` unwrapping, and model expected failures as data not exceptions. Covers bind/fmap chaining, and use in service objects registered in the DI container.
Use when using the dry-monads Result pattern in Hanami 2.x — wrap outcomes in `Success(value)` or `Failure(error_object)`, chain operations with `Do notation` using `include Dry::Monads::Do.for(:call)` and `yield` inside the `call` method for sequential flows, inject the service object via `Deps["key"]` after registering it in a provider, handle results in Actions with pattern matching (`case result; in Success(v); ...; in Failure(e); ...; end`) rather than unsafe `value!` unwrapping, and model expected failures as data not exceptions. Covers bind/fmap chaining, and use in service objects registered in the DI container.
Use this skill when using the dry-monads Result pattern in Hanami 2.x.
Core principle: Explicitly model success and failure. Return Success or Failure from operations that can fail, then chain them with bind and fmap.
Quick Reference
Scenario
Approach
Wrap a success
Success(value)
Wrap a failure
Failure(error_message) or Failure(error_object)
Chain on success
result.bind { |value| next_operation(value) }
Transform success value
result.fmap { |value| value.upcase }
Handle failure
result.or { |error| fallback_operation(error) }
Extract value
result.value! (raises on Failure)
Check status
result.success? / result.failure?
Pattern match
case result; in Success(v); ...; in Failure(e); ...; end
Core Rules
Use dry-monads in service objects registered in the DI container:
# app/operations/create_user.rb# frozen_string_literal: truerequire"dry/monads"require"dry/monads/do"moduleMyAppmoduleOperationsclassCreateUserincludeDry::Monads[:result]
includeDry::Monads::Do.for(:call)
includeDeps["repos.user_repo"]
defcall(attrs)
validated = yield validate(attrs)
user = yield create_user(validated)
Success(user)
endprivatedefvalidate(attrs)
returnFailure("Email is required") if attrs[:email].nil? || attrs[:email].empty?
returnFailure("Name is required") if attrs[:first_name].nil? || attrs[:first_name].empty?
Success(attrs)
enddefcreate_user(attrs)
user = user_repo.create(attrs)
Success(user)
rescueStandardError => e
Failure("Database error: #{e.message}")
endendendend
Register the service object in the container:
# config/providers/operations.rbHanami.app.register_provider(:operations) do
start do
register("operations.create_user", MyApp::Operations::CreateUser.new)
endend
Inject and call the service object in an Action:
# app/actions/users/create.rbmoduleMyAppmoduleActionsmoduleUsersclassCreate < MyApp::ActionincludeDeps["operations.create_user"]
defhandle(request, response)
result = create_user.call(request.params[:user])
case result
inDry::Monads::Success(user)
response.status = 201
response.body = user.to_json
inDry::Monads::Failure(error)
response.status = 422
response.body = { error: error }.to_json
endendendendendend