Skip to main content

Common Patterns

This guide covers common patterns and best practices for building applications with Rage.

Concurrent Processing​

Problem​

Consider the following controller:

class UsersController < RageController::API
def show
user = Net::HTTP.get(URI("http://users.service/users/#{params[:id]}"))
bookings = Net::HTTP.get(URI("http://bookings.service/bookings?user_id=#{params[:id]}"))

render json: { user: user, bookings: bookings }
end
end

This code fires two consecutive HTTP requests. If each request takes 1 second, the total execution time will be 2 seconds.

Solution​

Rage allows you to significantly reduce execution time by firing requests concurrently using fibers:

  1. Wrap each request in a separate fiber using Fiber.schedule
  2. Pass the newly created fibers into Fiber.await
class UsersController < RageController::API
def show
user, bookings = Fiber.await([
Fiber.schedule { Net::HTTP.get(URI("http://users.service/users/#{params[:id]}")) },
Fiber.schedule { Net::HTTP.get(URI("http://bookings.service/bookings?user_id=#{params[:id]}")) }
])

render json: { user: user, bookings: bookings }
end
end

With this change, both requests execute concurrently. If each request takes 1 second, the total execution time is still 1 second.

info

Many developers think of fibers as "lightweight threads" that require fiber pools, similar to thread pools for threads.

Instead, treat fibers as regular Ruby objects. Just as we create arrays on demand without using an "array pool", you can create fibers freely and let Ruby and the garbage collector manage them.

Environment-Specific Code​

Use Rage.env to write environment-aware code. This is particularly useful for enabling development features or test helpers that shouldn't run in production.

Example: Test Authentication​

In this example, we allow test token authentication in non-production environments:

class ApplicationController < RageController::API
before_action :authenticate_user

private

def authenticate_user
is_user_authenticated = verify_user_token

unless Rage.env.production?
is_user_authenticated ||= request.headers["Test-Token"] == ENV["TEST_TOKEN"]
end

head :forbidden unless is_user_authenticated
end
end

Rage.env dynamically detects the current environment. If you start the server with rage s -e preprod, then Rage.env.preprod? will return true.

Delaying Initialization​

Problem​

Sometimes you need to reference application-specific constants (like models) in your initializers located in config/initializers. However, initializers run before the application code loads, so these constants aren't yet available.

Solution​

Use the Rage.config.after_initialize method to schedule code to run after the application loads.

In this example, we ensure an admin user always exists. Since User is an application-level constant, we use after_initialize to delay execution until User is available:

config/initializers/admin_user.rb
Rage.config.after_initialize do
User.find_or_create_by!(username: "admin", password: "admin")
end

When to Use This​

Use after_initialize when you need to:

  • Reference application models or controllers in initializers
  • Run setup code that depends on the full application being loaded
  • Configure gems that need access to your application's constants

File Server​

Problem​

You need to serve static files to clients, such as configuration files for the frontend, custom documentation pages, or other assets.

Solution​

Enable static file serving in your Rage configuration:

Rage.configure do
config.public_file_server.enabled = true
end

Once enabled, Rage will serve files from the public folder. Files are served at the root path based on their location in the directory.

Example​

If you have the following file structure:

public/
├── config.json
└── docs/
└── index.html

These files will be accessible at:

  • /config.json
  • /docs/index.html

Error Reporting​

Rage::Errors provides a centralized interface for error reporting across all Rage components. Controllers, deferred tasks, event subscribers, Cable apps, and SSE streams all forward caught exceptions through this interface, eliminating the need to add manual error handling in each location.

Configuration​

Add error reporters to receive exceptions caught by the framework:

Rage.configure do
config.error_reporters << MyErrorReporter.new
end

Each reporter must respond to call and accept an exception:

class MyErrorReporter
def call(exception)
Sentry.capture_exception(exception)
end
end

You can configure multiple reporters, and all will be invoked when an exception occurs.

Manual Reporting​

In rare cases where you need to report an error explicitly:

Rage::Errors.report(exception)

Best Practices​

Errors generally fall into two categories:

Expected errors represent flows that can naturally occur in your application. Handle these explicitly and log them:

begin
authenticate_user!(user)
rescue UserNotAuthenticatedError
Rage.logger.error "invalid password when signing in", user_id: user.id
end

Unexpected errors are conditions that shouldn't occur during normal operation. Let the framework catch these and report them through Rage::Errors:

# Let database connection errors bubble up
User.create!(user_params)

This means application code should rarely need to call Rage::Errors.report directly - the framework handles unexpected errors automatically.

Daemons​

Rage provides Rage::Daemon for managing long-lived background processes. Use daemons when you need to continuously listen to external services, consume message queues, or run persistent background tasks that should stay alive for the entire application lifetime. The framework automatically handles daemon lifecycle, including startup, crash recovery, and graceful shutdown.

Creating a Daemon​

Subclass Rage::Daemon and implement the #perform method:

class RedisListener < Rage::Daemon
def perform
redis = Redis.new
redis.subscribe("notifications") do |on|
on.message do |channel, message|
Rage::Cable.broadcast("notifications", message)
end
end
end
end

Register daemons in your application configuration:

config/application.rb
Rage.configure do
config.daemons << RedisListener
end

Custom Renderers​

Rage allows you to define custom renderers to integrate your preferred templating libraries. This is useful for rendering HTML with libraries like Phlex, Slim, or any other templating system.

Configuration​

Define a custom renderer in your Rage configuration using config.renderer. The block executes in the controller context, giving you access to params, headers, cookies, and other controller methods:

config/application.rb
Rage.configure do
config.renderer(:phlex) do |component, **props|
headers["content-type"] = "text/html"
component.new(**props).call
end
end

The renderer receives:

  • The first argument passed to render (in this case, the component class)
  • Any additional keyword arguments as **props

The block's return value becomes the response body.

Usage​

Once configured, use your custom renderer by name in any controller:

class GreetingsController < RageController::API
def show
render phlex: GreetingComponent, name: params[:name]
end
end

This calls your configured :phlex renderer with GreetingComponent as the first argument and name: as a prop.

Example: Slim Templates​

Here's another example using Slim templates:

config/application.rb
Rage.configure do
config.renderer(:slim) do |template, **locals|
headers["content-type"] = "text/html"
Slim::Template.new("app/views/#{template}.slim").render(self, **locals)
end
end
render slim: "users/show", user: @user

Agent Skills​

Rage provides official skills for coding agents to help them understand and work with Rage applications effectively. Skills give agents context about Rage-specific patterns, APIs, and best practices.

Installing Skills​

Run the following command in your Rage project directory:

rage skills install

This installs agent skills based on the coding agent you use (e.g., Claude Code, Cursor, etc.).

Updating Skills​

To update skills to the latest version:

rage skills update

OpenTelemetry​

Rage has an official OpenTelemetry integration for distributed tracing and observability. Install the opentelemetry-instrumentation-rage gem to enable automatic instrumentation of your Rage application.