Curated library of atomic AI agent skills for Hanami, dry-rb, and ROM Ruby development. Covers actions, slices, repositories, relations, changesets, providers, DI, operations, TDD, CLI, views, routing, and validation. Shared Ruby process skills have moved to ruby-core-skills. Uses Markdown + Front-matter architecture.
92
94%
Does it follow best practices?
Impact
92%
1.33xAverage score across 35 eval scenarios
Passed
No known issues
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.
| 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 |
Use dry-monads in service objects registered in the DI container:
# app/operations/create_user.rb
# frozen_string_literal: true
require "dry/monads"
require "dry/monads/do"
module MyApp
module Operations
class CreateUser
include Dry::Monads[:result]
include Dry::Monads::Do.for(:call)
include Deps["repos.user_repo"]
def call(attrs)
validated = yield validate(attrs)
user = yield create_user(validated)
Success(user)
end
private
def validate(attrs)
return Failure("Email is required") if attrs[:email].nil? || attrs[:email].empty?
return Failure("Name is required") if attrs[:first_name].nil? || attrs[:first_name].empty?
Success(attrs)
end
def create_user(attrs)
user = user_repo.create(attrs)
Success(user)
rescue StandardError => e
Failure("Database error: #{e.message}")
end
end
end
endRegister the service object in the container:
# config/providers/operations.rb
Hanami.app.register_provider(:operations) do
start do
register("operations.create_user", MyApp::Operations::CreateUser.new)
end
endInject and call the service object in an Action:
# app/actions/users/create.rb
module MyApp
module Actions
module Users
class Create < MyApp::Action
include Deps["operations.create_user"]
def handle(request, response)
result = create_user.call(request.params[:user])
case result
in Dry::Monads::Success(user)
response.status = 201
response.body = user.to_json
in Dry::Monads::Failure(error)
response.status = 422
response.body = { error: error }.to_json
end
end
end
end
end
endChain operations with bind:
result = validate_params(params)result.bind { |valid| find_user(valid[:id]).bind { |user| update_user(user, valid[:attrs]) } }.fmap { |user| format_user(user) }
5. **Use `Do notation`** for sequential operations:
```ruby
def call(attrs)
validated = yield validate(attrs)
user = yield create_user(validated)
notify = yield send_welcome_email(user)
Success(user)
endModel failures as data, not exceptions:
# GOOD: Return Failure with structured error
Failure({ code: :email_taken, message: "Email already registered" })
# BAD: Raise exception for expected failures
raise EmailTakenError, "Email already registered"Keep service objects focused. One operation = one class. Do not create monolithic operation classes.
Reviewers and developers should check for these patterns:
Failure instead.value! without verifying status. Use pattern matching (in Success(v)) or flow-based chains (bind/fmap).nil or false values instead of explicit Success or Failure.bind blocks. Use Do notation (yield) to flatten sequential operations.| Related Skill | When to chain |
|---|---|
| inject-dependencies | Service objects are injected into Actions via Deps[]. |
| create-action | Actions call service objects and handle Success/Failure. |
| handle-errors | Failure results map to HTTP error responses (422, 404, etc.). |
| validation-contract | Validation results are often Success/Failure from dry-validation. |
| refactor-code | Extract business logic from Actions into Success/Failure service objects. |
For developers transitioning from Rails/ActiveRecord exception patterns, see the side-by-side RAILS_COMPARISON.md guide.
docs
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
skills
actions
build-json-api
create-action
handle-errors
validate-params
context
load-context
db
create-changeset
create-repository
define-relation
write-migration
dry-monads
handle-result-pattern
dry-rb
create-operation
create-validation-contract
providers
configure-providers
implement-di
review-security
routing
define-routes
slices
configure-slice
create-slice
extract-slice
review-slice-boundaries
test-slice