A disciplined service-object pattern for Ruby and Rails.
Most mature Rails apps grow a services/ directory. Servus gives each service the same shape — one entrypoint, one response object, and opt-in layers for validation, guards, events, and async.
Add to your Gemfile:
gem 'servus'bundle installGenerate a service:
rails g servus:service treasury/transfer_gold from_account to_account gold_dragonsWrite the service:
module Treasury
module TransferGold
class Service < Servus::Base
def initialize(from_account:, to_account:, gold_dragons:)
@from_account = from_account
@to_account = to_account
@gold_dragons = gold_dragons
end
def call
@from_account.withdraw!(@gold_dragons)
@to_account.deposit!(@gold_dragons)
success(
transferred: @gold_dragons,
from_balance: @from_account.balance,
to_balance: @to_account.balance
)
end
end
end
endCall it:
result = Treasury::TransferGold::Service.call(
from_account: crown_account,
to_account: night_watch_account,
gold_dragons: 50
)
result.success? # => true
result.data.transferred # => 50
result.data.from_balance # => 950
result.data.to_balance # => 550Full documentation at zarpay.github.io/servus
- Schema validation — JSON Schema for arguments, results, and failure data
- Error handling — HTTP-aligned error hierarchy with
rescue_from - Guards — reusable precondition checks with structured errors
- Events — decouple follow-up work from the service that triggered it
- Async execution —
.call_asyncruns the same service through ActiveJob - Lazy resolvers — accept an instance or an ID, resolve on first access
- Logging — automatic call, outcome, and duration logging
- Rails integration — controller helpers, generators, autoloading via Railtie
- Ruby >= 3.0
- ActiveSupport >= 8.0
- Rails integration is automatic via Railtie; core works in any Ruby application