Ruby ServiceStack Reference

ServiceStack's Add ServiceStack Reference feature allows clients to generate Native Types for Ruby - providing a simple way to give Ruby clients typed access to your ServiceStack Services.

Ruby - ServiceStack productivity in a dynamic language

The servicestack gem (source) provides generated DTOs with explicit properties and API metadata, giving editors and developers a discoverable model of every request and response:

client = ServiceStack::JsonServiceClient.new(base_url)

response = client.send(Hello.new(name: 'World'))
puts response.result

Implemented with Ruby's standard library and no external runtime dependencies. Includes structured WebServiceException errors, field validation details, authentication, typed AutoQuery conventions, batch calls, one-way requests, custom URLs and file uploads.

First class development experience

Ruby enjoys enduring popularity thanks to its expressive syntax and its focus on developer productivity, where it continues to power a large ecosystem of Web Apps, CLI tools, automation scripts and DevOps tooling. To maximize the experience for calling ServiceStack APIs from these environments, Ruby is supported as a 1st class Add ServiceStack Reference language which gives Ruby developers an end-to-end typed API for consuming ServiceStack APIs, with DTOs generated from a single command-line.

Ideal idiomatic Typed Message-based API

Ruby DTOs are generated as plain Ruby classes with attr_accessor properties following Ruby's snake_case naming conventions, so they'll naturally fit into existing Ruby code bases. They're kept free of any serialization attributes, instead each DTO declares the wire name and Type of each of its properties which the servicestack gem uses to convert them to and from the JSON their APIs use.

Here's a sample of generated Ruby DTOs containing a string Enum, an AutoQuery Request and a data model with an inherited base class:

require 'json'
require 'servicestack'

module RoomType
    SINGLE = 'Single'
    DOUBLE = 'Double'
    QUEEN = 'Queen'
    TWIN = 'Twin'
    SUITE = 'Suite'
end

#
# Booking Details
#
class Booking < ServiceStack::AuditBase
    # @return [Integer]
    attr_accessor :id
    # @return [String]
    attr_accessor :name
    # @return [RoomType]
    attr_accessor :room_type
    # @return [DateTime]
    attr_accessor :booking_start_date
    # @return [Coupon]
    attr_accessor :discount

    def self.properties
        {
            id: { name: 'id' },
            name: { name: 'name' },
            room_type: { name: 'roomType' },
            booking_start_date: { name: 'bookingStartDate', type: DateTime },
            discount: { name: 'discount', type: Coupon },
        }
    end
end

#
# Find Bookings
#
# @Route("/bookings", "GET")
class QueryBookings < ServiceStack::QueryDb
    # @return [Integer]
    attr_accessor :id

    def self.properties
        {
            id: { name: 'id' },
        }
    end

    def response_type() = ServiceStack::QueryResponse.of(Booking)
    def get_type_name() = 'QueryBookings'
    def get_method() = 'GET'
end

The generated response_type, get_type_name and get_method declarations are what enable the end-to-end typed API, letting the client resolve each API's Response Type, route and HTTP Method from the Request DTO alone.

Installation

The only requirements for Ruby Apps to perform typed API Requests are the generated Ruby DTOs and the generic JsonServiceClient in the servicestack gem, which only uses the Ruby standard library:

gem install servicestack

Or add it to your Gemfile:

gem 'servicestack', '~> 0.1'

Requires Ruby 3.0+.

Simple command-line utility for Ruby

Ruby DTOs can be generated from the command-line with the cross-platform get-dtos script which can be run with Node.js without needing to install anything:

npx get-dtos

Running it without any arguments displays the available options for adding and updating ServiceStack References.

Adding a ServiceStack Reference

To Add a Ruby ServiceStack Reference just call npx get-dtos ruby with the URL of a remote ServiceStack instance:

Result:

Saved to: dtos.rb

Calling npx get-dtos ruby with just a URL will save the DTOs using the Host name, you can override this by specifying a FileName as the 2nd argument:

npx get-dtos ruby https://blazor-vue.web-templates.io Bookings

Result:

Saved to: Bookings.dtos.rb

Updating a ServiceStack Reference

To Update an existing ServiceStack Reference, call npx get-dtos ruby with the Filename:

npx get-dtos ruby dtos.rb

Result:

Updated: dtos.rb

Which will update the File with the latest Ruby Server DTOs. You can also customize how DTOs are generated by uncommenting the Ruby DTO Customization Options and updating them again.

Updating all Ruby DTOs

Calling npx get-dtos ruby without any arguments will update all Ruby DTOs in the current directory:

npx get-dtos ruby

Result:

Updated: Bookings.dtos.rb
Updated: dtos.rb

Smart Generic JsonServiceClient

The generic JsonServiceClient is a 1st class client with the same rich featureset of the smart ServiceClients in other 1st class supported languages sporting a terse, typed flexible API with support for additional untyped params, custom URLs and HTTP Methods and raw Response bodies.

It includes built-in support for a number of ServiceStack Auth options including HTTP Basic Auth and stateless Bearer Token Auth Providers like API Key and JWT Auth as well as stateful Sessions used by the popular credentials Auth Provider, whose Session Cookies are retained by the client and sent on subsequent Requests. Refresh Tokens are also supported, where expired JWT Bearer Tokens are transparently refreshed behind-the-scenes before automatically retrying the failed Request.

A snapshot of these features is captured in the high-level public API below:

class ServiceStack::JsonServiceClient
  attr_accessor :base_url, :reply_base_url, :oneway_base_url, :headers,
                :bearer_token, :refresh_token, :refresh_token_uri,
                :user_name, :password, :request_filter, :response_filter,
                :timeout, :cookies, :on_authentication_required

  # Filters applied to every Request and Response of all clients
  self.global_request_filter
  self.global_response_filter

  def initialize(base_url)

  def set_base_path(base_path = '')
  def set_bearer_token(token)
  def set_refresh_token(token)
  def set_credentials(user_name, password)
  def set_header(name, value)

  # Typed API
  def send(request, method: nil, body: nil, args: nil)  # aliased as send_dto
  def get(request, args: nil)
  def post(request, body: nil, args: nil)
  def put(request, body: nil, args: nil)
  def patch(request, body: nil, args: nil)
  def delete(request, args: nil)
  def send_void(request, args: nil)
  def api(request, method: nil, args: nil)              # returns ApiResult
  def send_all(requests)                                # Auto Batched Reply Requests
  def publish(request)                                  # Oneway Request
  def authenticate(user_name, password)

  # URL API
  def get_url(path, response_as: nil, args: nil)
  def post_url(path, body: nil, response_as: nil, args: nil)
  def put_url(path, body: nil, response_as: nil, args: nil)
  def patch_url(path, body: nil, response_as: nil, args: nil)
  def delete_url(path, response_as: nil, args: nil)
  def send_url(path, method: HttpMethods::GET, body: nil, response_as: nil, args: nil)
  def send_url_string(path, method: HttpMethods::GET, body: nil, args: nil)
  def to_absolute_url(path_or_url)
  def create_url_from_dto(method, request)

  # File Uploads
  def post_file_with_request(request, file, args: nil)
  def post_files_with_request(request, files, args: nil)
  def post_files_with_request_url(path, request, files, response_as: nil, args: nil)
end

INFO

JsonServiceClient#send overrides Ruby's Object#send to keep the same API used by ServiceStack's other Service Clients. Use __send__ for Ruby's dynamic dispatch, or the send_dto alias if you'd prefer an unambiguous name.

Ruby Reference Example

Lets walk through a simple example to see how we can use ServiceStack's Ruby DTOs in our Ruby JsonServiceClient. Firstly we'll need to add a Ruby Reference to the remote ServiceStack Service:

This will import the remote Services DTOs into your local project which looks similar to:

# frozen_string_literal: true
# encoding: utf-8

# Options:
=begin
Date: 2026-08-06 15:38:36
Version: 10.09
Tip: To override a DTO option, remove "//" prefix before updating
BaseUrl: https://blazor-vue.web-templates.io

#AddResponseStatus: False
#AddImplicitVersion:
#IncludeTypes:
#ExcludeTypes:
=end

require 'json'
require 'servicestack'

class HelloResponse
    include ServiceStack::DTO

    # @return [String]
    attr_accessor :result

    def self.properties
        {
            result: { name: 'result' },
        }
    end
end

# @Route("/hello/{Name}")
class Hello
    include ServiceStack::DTO

    # @return [String]
    attr_accessor :name

    def self.properties
        {
            name: { name: 'name' },
        }
    end

    def response_type() = HelloResponse
    def get_type_name() = 'Hello'
    def get_method() = 'GET'
end

In keeping with the idiomatic style of local .rb sources, generated types aren't wrapped in a module by default, so you can reference them directly after requiring the file:

require_relative 'dtos'

Making Typed API Requests

Making API Requests in Ruby is the same as all other ServiceStack's Service Clients by sending a populated Request DTO using a JsonServiceClient which returns a typed Response DTO.

So the only things we need to make any API Request is the JsonServiceClient from the servicestack gem and any DTOs we're using from the generated Ruby ServiceStack Reference, e.g:

require 'servicestack'
require_relative 'dtos'

client = ServiceStack::JsonServiceClient.new('https://blazor-vue.web-templates.io')

request = Hello.new
request.name = 'World'

response = client.send(request)   # typed to HelloResponse
puts response.result

send uses the HTTP Method the API is annotated with, use get, post, put, patch or delete to send a Request DTO with a specific HTTP Method:

response = client.post(CreateBooking.new(name: 'Booking'))

APIs that don't return a Response Body can be sent with send_void:

client.send_void(DeleteBooking.new(id: 1))

Resolving the HTTP Method

The Verb returned by a DTO's generated get_method also determines whether the Request DTO is sent in the QueryString or the JSON Request Body:

client.send(Hello.new(name: 'World'))           # GET /api/Hello?name=World
client.send(CreateBooking.new(name: 'Booking')) # POST /api/CreateBooking {"name":"Booking"}

Request DTOs that aren't annotated with a Verb fall back to inferring it from the Request DTO name:

Request DTO Name HTTP Method
Get* Query* Find* Search* GET
Create* POST
Update* Replace* PUT
Patch* PATCH
Delete* Remove* DELETE
(anything else) POST

Which is why hand-written DTOs only need to declare what they can't infer, e.g. this Request DTO is sent as a GET to /api/GetWeather?city=Seattle and its JSON Response converted into a typed GetWeatherResponse:

class GetWeather
    include ServiceStack::DTO
    attr_accessor :city

    def self.properties() = { city: { name: 'city' } }
    def response_type() = GetWeatherResponse
end

Any Request DTO can also be sent with an explicit Verb:

client.send(request, method: ServiceStack::HttpMethods::PUT)

Constructors Initializer

All Ruby DTOs support populating their properties with keyword arguments, so instead of:

request = ServiceStack::Authenticate.new
request.provider = 'credentials'
request.user_name = user_name
request.password = password
request.remember_me = true
response = client.post(request)

You can populate DTOs with a single constructor expression:

response = client.post(ServiceStack::Authenticate.new(
  provider: 'credentials',
  user_name: user_name,
  password: password,
  remember_me: true))

AutoQuery Requests

AutoQuery Request DTOs inherit the query params of their base type, whose Responses are returned in a typed QueryResponse:

response = client.send(QueryBookings.new(take: 5, order_by_desc: 'id'))

response.results.each do |booking|  # each booking is a Booking
  puts "#{booking.id} #{booking.name} #{booking.booking_start_date}"
end

Nested DTOs, Dates and collections are converted using the metadata each generated DTO declares, so booking.discount is a Coupon, booking.booking_start_date is a DateTime and inherited properties like booking.created_by are populated from their createdBy wire names.

As AutoQuery Request DTOs inherit from ServiceStack::QueryDb (or QueryData for AutoQuery Data) they can use any of the query params supported by all AutoQuery APIs:

response = client.send(QueryBookings.new(
  skip: 10, take: 5,                 # paging
  order_by_desc: 'id',               # or order_by: 'name'
  fields: 'id,name,roomType',        # only return these fields
  include: 'total'))                 # include the total number of results

puts response.offset                 # 10
puts response.total                  # 25

Sending additional arguments with Typed API Requests

Many AutoQuery Services utilize implicit conventions to query fields that aren't explicitly defined on AutoQuery Request DTOs, these can be queried by specifying additional arguments with the typed Request DTO, e.g:

response = client.get(QueryBookings.new, args: { 'nameStartsWith' => 'A' })

Making API Requests with URLs

In addition to making Typed API Requests you can also call Services using relative or absolute urls, e.g:

client.get_url('/hello/World', response_as: HelloResponse)

client.get_url('https://blazor-vue.web-templates.io/hello/World', response_as: HelloResponse)

# /api/Hello?name=World
client.get_url('/api/Hello', args: { 'name' => 'World' }, response_as: HelloResponse)

as well as POST Request DTOs to custom urls:

client.post_url('/custom-path', body: request, response_as: HelloResponse)

client.post_url('http://example.org/custom-path', body: request, response_as: HelloResponse)

Raw Data Responses

Use send_url_string to access a raw Response Body, useful for APIs returning content like CSV:

csv = client.send_url_string('/api/QueryBookings.csv')

Batched Requests

Multiple Request DTOs of the same Type can be sent together in a single Request with send_all, which returns all their Responses:

responses = client.send_all([Hello.new(name: 'A'), Hello.new(name: 'B')])

Or send them to a one-way endpoint that ignores their Responses:

client.publish(Hello.new(name: 'World'))

Error Handling

Failed API Requests raise a ServiceStack::WebServiceException containing the HTTP Status Code and the API's structured ResponseStatus error:

begin
  client.send(CreateBooking.new)
rescue ServiceStack::WebServiceException => e
  puts e.status_code           # 400
  puts e.error_code            # "NotEmpty"
  puts e.error_message         # "'Name' must not be empty."
  puts e.field_error('Name')   # "'Name' must not be empty."
  puts e.unauthorized?         # false
  puts e.validation_error?     # true
end

Alternatively api returns errors in its result instead of raising, which can be preferable when handling validation errors is part of normal control flow:

api = client.api(CreateBooking.new)
if api.failed?
  puts api.error_code
  puts api.field_error('Name')
else
  puts api.response.id
end

INFO

Redirects aren't followed, so Services that redirect to a HTML sign in page raise a WebServiceException with a Redirect ErrorCode instead of returning an empty Response.

Authenticating using Basic Auth

Basic Auth support is implemented in JsonServiceClient and follows the same API made available in the C# Service Clients where the user_name/password properties can be set individually, e.g:

client = ServiceStack::JsonServiceClient.new(base_url)
client.user_name = user
client.password = pass

response = client.send(SecureRequest.new)

Or use client.set_credentials() to have them set both together.

Authenticating using Credentials

Alternatively you can authenticate using userName/password credentials by sending a populated Authenticate Request DTO, e.g:

response = client.post(ServiceStack::Authenticate.new(
  provider: 'credentials',
  user_name: user_name,
  password: password,
  remember_me: true))

Or with the authenticate shorthand, which also uses any Bearer and Refresh Tokens the Server returns:

response = client.authenticate(user_name, password)

This will populate the JsonServiceClient with Session Cookies which will transparently be sent on subsequent requests to make authenticated requests.

Authenticating using JWT

Use the bearer_token property to Authenticate with a ServiceStack JWT Provider using a JWT Token:

client.set_bearer_token(jwt)

Alternatively you can use just a Refresh Token instead:

client.set_refresh_token(refresh_token)

Where the client will automatically fetch a new JWT Bearer Token using the Refresh Token for authenticated requests.

Authenticating using an API Key

Use the bearer_token property to Authenticate with an API Key:

client.set_bearer_token(api_key)

Built-in ServiceStack DTOs

The servicestack gem includes typed DTOs for ServiceStack's built-in Auth APIs, letting you call them without needing to generate them:

# Authentication
auth = client.send(ServiceStack::Authenticate.new(provider: 'credentials',
  user_name: 'test', password: 'test', remember_me: true))

reg = client.send(ServiceStack::Register.new(user_name: 'new-user',
  email: 'user@example.org', password: 'p@55wOrd', auto_login: true))

# JWT
token = client.send(ServiceStack::ConvertSessionToToken.new)   # Session -> JWT
access = client.send(ServiceStack::GetAccessToken.new(refresh_token: refresh_token))

Together with the built-in Response Types and base classes that generated DTOs reference:

Type Description
ServiceStack::ResponseStatus ServiceStack's structured error response
ServiceStack::ResponseError An individual field validation error
ServiceStack::EmptyResponse Response of APIs that don't return a Response Body
ServiceStack::IdResponse Response returning the Id of the created or updated entity
ServiceStack::StringResponse Response returning a single result string
ServiceStack::StringsResponse Response returning a list of string results
ServiceStack::AuditBase Audit fields inherited by AutoQuery CRUD data models
ServiceStack::QueryBase Query params supported by all AutoQuery Requests
ServiceStack::QueryDb Base class of AutoQuery RDBMS Requests
ServiceStack::QueryData Base class of AutoQuery Data Requests
ServiceStack::QueryResponse Typed Response of AutoQuery Requests, e.g. QueryResponse.of(Booking)
ServiceStack::UploadFile A file uploaded in a multipart/form-data Request
ServiceStack::HttpMethods The HTTP Verbs used by ServiceStack APIs, e.g. HttpMethods::GET

Transparently handle 401 Unauthorized Responses

If the server returns a 401 Unauthorized Response either because the client was Unauthenticated or the configured Bearer Token or API Key used had expired or was invalidated, you can use the on_authentication_required callback to re-configure the client before automatically retrying the original request, e.g:

auth_client = ServiceStack::JsonServiceClient.new(AUTH_URL)

client.on_authentication_required = lambda { |c|
  auth = auth_client.authenticate(user_name, password)
  c.set_bearer_token(auth.bearer_token)
}

# Automatically retries requests returning 401 Responses with new bearerToken
response = client.send(Secured.new)

The callback also accepts a zero-arity lambda when it doesn't need the client. A configured Refresh Token takes precedence over the callback, which is only used when no Refresh Token is set or refreshing it failed. If the callback raises, the original 401 Response is returned.

Requires servicestack v0.1.2+.

Automatically refresh Access Tokens

With the Refresh Token support in JWT you can use the refresh_token property to instruct the Service Client to automatically fetch new JWT Tokens behind the scenes before automatically retrying failed requests due to invalid or expired JWTs, e.g:

# Authenticate to get new Refresh Token
auth_client = ServiceStack::JsonServiceClient.new(AUTH_URL)
auth_response = auth_client.authenticate(user_name, password)

# Configure client with RefreshToken
client.set_refresh_token(auth_response.refresh_token)

# Call authenticated Services and clients will automatically retrieve new JWT Tokens as needed
response = client.send(Secured.new)

i.e. when a Request fails with a 401 Unauthorized the client sends its Refresh Token to the GetAccessToken API, updates its Bearer Token then transparently retries the original Request:

POST /api/Hello           -> 401 Unauthorized
POST /api/GetAccessToken  -> 200 { "accessToken": "..." }
POST /api/Hello           -> 200 { "result": "Hello, World!" }

Use the refresh_token_uri property when refresh tokens need to be sent to a different ServiceStack Server, e.g:

client.set_refresh_token(refresh_token)
client.refresh_token_uri = AUTH_URL + '/access-token'

Uploading Files

Use post_file_with_request to upload a file with an API Request, whose contents can be supplied as a String or any IO that responds to read:

res = File.open('photo.png', 'rb') do |file|
  client.post_file_with_request(UploadPhoto.new(album: 'Holiday'),
    ServiceStack::UploadFile.new(field_name: 'file', file_name: 'photo.png',
                                 content_type: 'image/png', stream: file))
end

The Request DTO's populated properties are sent as form fields alongside the file. To upload multiple files use post_files_with_request:

client.post_files_with_request(UploadPhoto.new(album: 'Holiday'), [
  ServiceStack::UploadFile.new(field_name: 'file1', file_name: 'a.png', stream: a),
  ServiceStack::UploadFile.new(field_name: 'file2', file_name: 'b.png', stream: b)
])

Requires servicestack v0.1.1+.

Serializing DTOs

As DTOs declare the wire name and Type of each of their properties, they can also be used to convert between Ruby objects and the JSON their APIs use outside of any API Request, useful for persisting API Responses, populating DTOs from other sources or testing:

request = Hello.new(name: 'World')
request.to_hash                 # { "name" => "World" }
request.to_json                 # {"name":"World"}

res = HelloResponse.from_json('{"result":"Hello, World!"}')
res = HelloResponse.from_hash({ 'result' => 'Hello, World!' })
res.result                      # "Hello, World!"

Where:

  • to_hash (aliased to_h) uses each property's wire name and omits any properties that aren't populated
  • from_hash matches JSON properties on their wire name first, falling back to the DTO's own snake_case name
  • Nested DTOs, Arrays, Hashes and Dates are converted using the Types their properties declare, incl. inherited properties
  • Dates are parsed from ISO-8601 as well as ServiceStack's /Date(1670000000000)/ format
  • DTOs implement == and a readable inspect for their populated properties, e.g. #<Hello name="World">

Client Configuration

client = ServiceStack::JsonServiceClient.new('https://example.org')
client.set_header('X-Custom', 'Value')
client.timeout = 30                        # open + read timeout in secs (default: 60)
client.set_bearer_token(api_key)

# Inspect or modify each Request and Response
client.request_filter = ->(req) { puts "#{req.method} #{req.path}" }
client.response_filter = ->(res) { puts res.code }

All set_* methods return the client so they can be chained:

client = ServiceStack::JsonServiceClient.new(base_url)
                     .set_bearer_token(jwt)
                     .set_header('X-Tenant', tenant_id)

Requests are sent to ServiceStack's pre-defined /api route by default, use set_base_path for older ServiceStack instances that only have the /json/reply routes enabled, or to send them to a custom base path:

client.set_base_path('')            # /json/reply/Hello and /json/oneway/Hello
client.set_base_path('custom/api')  # /custom/api/Hello

The client also retains any Session Cookies the Server returns which can be inspected or populated directly, e.g. to resume an existing Session:

client.cookies['ss-id']             # "SESSION_ID"

Global Request and Response Filters

In addition to per-client filters, global filters can be registered to inspect or decorate the Net::HTTP Requests and Responses of every client, useful for adding tracing headers or logging and diagnostics:

ServiceStack::JsonServiceClient.global_request_filter = ->(req) {
  req['X-Correlation-Id'] = correlation_id
}
ServiceStack::JsonServiceClient.global_response_filter = ->(res) {
  logger.info("#{res.code} #{res.message}")
}

QueryString Serialization

Request DTO properties and additional args sent in the QueryString use ServiceStack's JS Object notation for complex types and ISO-8601 for Dates:

Ruby Value QueryString
'World' name=World
true enabled=true
1.5 rate=1.5
[1, 2, 3] ids=[1,2,3]
{ 'a' => 1, 'b' => 2 } meta={a:1,b:2}
DateTime.new(2001, 1, 1) date=2001-01-01T00:00:00+00:00
nil (omitted)

Values are URL encoded, whilst nil properties are omitted from both the QueryString and JSON Request Bodies so APIs only receive the properties that were populated.

Use create_url_from_dto to resolve the URL a Request DTO is sent to, or to_absolute_url to resolve a relative path against the client's Base URL:

client.create_url_from_dto('GET', Hello.new(name: 'World'))
# https://example.org/api/Hello?name=World

client.to_absolute_url('/api/Hello')
# https://example.org/api/Hello

Calling AI Chat and OpenAI compatible APIs

As Request DTOs are just Ruby classes, more advanced APIs like AI Chat's OpenAI-compatible ChatCompletion API can also be called with typed DTOs, where polymorphic content parts are sent in an Array of the DTOs of each content type:

request = ChatCompletion.new(
  model: 'openai/gpt-oss-120b',
  messages: [
    AiMessage.new(
      role: 'user',
      # Content parts are polymorphic, e.g. text, image_url or input_audio
      content: [AiTextContent.new(type: 'text', text: 'Capital of France? Answer in 3 words')]
    )
  ]
)

begin
  res = client.send(request)
  puts res.choices.first.message.content
rescue ServiceStack::WebServiceException => e
  # A shared LLM can be rate limited or temporarily unavailable
  warn "ChatCompletion unavailable: #{e.message}" if [429, 502, 503, 504].include?(e.status_code)
  raise
end

Testing

As the client accepts any Base URL, APIs can be tested against a local ServiceStack instance or a stubbed HTTP Server without needing to mock the client, e.g. using WebMock to assert the exact Request each API sends:

stub_request(:post, 'https://example.org/api/Hello')
  .with(body: '{"name":"World"}', headers: { 'Content-Type' => 'application/json' })
  .to_return(body: { result: 'Hello, World!' }.to_json)

client = ServiceStack::JsonServiceClient.new('https://example.org')
res = client.send(Hello.new(name: 'World'))

assert_equal 'Hello, World!', res.result

Which is how the client's own test suite verifies the Requests it sends, in addition to its integration tests which are run against the live test.servicestack.net Services.

Its unit tests are run with:

rake test

Whilst its integration tests are run with:

rake test:integration

Use the SERVICESTACK_TEST_URL Environment Variable to run them against a different ServiceStack instance.

DTO Customization Options

In most cases you'll just use the generated Ruby DTOs as-is, however you can further customize how the DTOs are generated by overriding the default options.

The header in the generated DTOs show the different options Ruby native types support with their defaults. To override a value, remove the # and specify the value to the right of the :. Any uncommented value will be sent to the server to override any server defaults.

# Options:
=begin
Date: 2026-08-06 15:38:36
Version: 10.09
Tip: To override a DTO option, remove "//" prefix before updating
BaseUrl: https://blazor-vue.web-templates.io

#MakePartial: True
#MakeVirtual: True
#MakeInternal: False
#MakeDataContractsExtensible: False
#AddReturnMarker: True
#AddDescriptionAsComments: True
#AddDataContractAttributes: False
#AddIndexesToDataMembers: False
#AddGeneratedCodeAttributes: False
#AddResponseStatus: False
#AddImplicitVersion:
#InitializeCollections: False
#ExportValueTypes: False
#IncludeTypes:
#ExcludeTypes:
#AddNamespaces:
#AddDefaultXmlNamespace: http://schemas.servicestack.net/types
=end

We'll go through and cover each of the more relevant options to see how they affect the generated DTOs:

AddResponseStatus

Automatically add a response_status property on all Response DTOs, regardless if it wasn't already defined:

class GetTechnologyResponse
    # @return [ServiceStack::ResponseStatus]
    attr_accessor :response_status
end

AddImplicitVersion

Lets you specify the Version number to be automatically populated in all Request DTOs sent from the client:

class Hello
    attr_accessor :version
end

This lets you know what Version of the Service Contract that existing clients are using making it easy to implement ServiceStack's recommended versioning strategy.

IncludeTypes

Is used as a Whitelist to specify only the types you would like to have code-generated:

=begin
IncludeTypes: GetTechnology,GetTechnologyResponse
=end

Will only generate GetTechnology and GetTechnologyResponse DTOs.

To include a Request DTO and all its dependent types, use the .* suffix:

=begin
IncludeTypes: GetTechnology.*
=end

Or include all types within a Tag Group with:

=begin
IncludeTypes: {tag}
=end

ExcludeTypes

Is used as a Blacklist to specify which types you would like excluded from being generated:

=begin
ExcludeTypes: GetTechnology,GetTechnologyResponse
=end

AddDescriptionAsComments

Emits any [Description] attributes as comments above the generated Types and properties:

#
# Find Bookings
#
class QueryBookings < ServiceStack::QueryDb

Change Default Server Configuration

The above defaults are also overridable on the ServiceStack Server by modifying the default config on the NativeTypesFeature Plugin, e.g:

//Server example in C#
var nativeTypes = this.GetPlugin<NativeTypesFeature>();
nativeTypes.MetadataTypesConfig.AddResponseStatus = true;

Ruby specific functionality can be added by the RubyGenerator, e.g. to add additional requires to all generated DTOs:

RubyGenerator.DefaultImports.Add("date");

Customize DTO Type generation

Additional Ruby specific customization can be statically configured like PreTypeFilter, InnerTypeFilter & PostTypeFilter (available in all languages) which can be used to inject custom code in the generated DTOs output.

Use the InnerTypeFilter to generate source code at the start of a Type definition, e.g. this adds a Comparable mixin to all non enum & interface types:

RubyGenerator.InnerTypeFilter = (sb, type) => {
    if (type.IsEnum != true && type.IsInterface != true)
    {
        sb.AppendLine("include Comparable");
    }
};

There's also PrePropertyFilter & PostPropertyFilter for generating source before and after properties, e.g:

RubyGenerator.PrePropertyFilter = (sb, prop, type) => {
    if (prop.Name == "Id")
    {
        sb.AppendLine("# @!attribute [rw] id");
    }
};

Whilst the generic [EmitCode] attribute lets you emit the same code in multiple languages with the same syntax.