Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
144: Sinatra Middleware and Extensions
When you start building with Sinatra, the before filter feels like a superpower. It's the go-to for everything: authenticating a user, setting up a database connection, or initializing some request-scoped variables. But as your application grows—or worse, as you start managing three or four different Sinatra services—you'll notice a pattern. You're copy-pasting the same "setup" logic into every single app. This is where we need to stop thinking about routes and start thinking about the pipeline.
The temptation of the before filter
Let's say you're building a suite of microservices and you need a unique Request-ID for every single incoming call to make distributed tracing possible. Your first instinct might be to do this in every app:
before do
env['request_id'] = SecureRandom.uuid
logger.info "Processing request #{env['request_id']}"
end
It works. For a while. But there's a structural flaw here: this code only runs after the request has already been handed off to Sinatra. If the server crashes before it hits the route, or if a different piece of middleware throws an exception, your Request-ID is never generated. Furthermore, if you have five different Sinatra apps, you're maintaining this snippet in five different places. If you decide to change the header name from Request-ID to X-Correlation-ID, you're in for a tedious afternoon of find-and-replace.
Moving up the stack to Rack
Since Sinatra is built on Rack, the "correct" way to handle cross-cutting concerns like this is through middleware. Middleware sits outside your application. It's a layer in an onion; the request passes through the middleware before it ever touches your Sinatra routes, and the response passes back through it on the way out.
Here is how I'd rewrite that Request-ID logic as a Rack middleware. Notice that we aren't using Sinatra methods here—we're using the raw Rack interface:
class RequestIdMiddleware
def initialize(app)
@app = app
end
def call(env)
# We assign the ID before the app even sees the request
request_id = SecureRandom.uuid
env['request_id'] = request_id
# Now we pass the request down the chain to the next middleware or the app
status, headers, body = @app.call(env)
# We can even modify the response on the way back out
headers['X-Request-Id'] = request_id
[status, headers, body]
end
end
By using use RequestIdMiddleware in your Sinatra config, you've decoupled the infrastructure (tracing) from the business logic (your routes). The trade-off is a slight increase in complexity; you're now dealing with the [status, headers, body] array that Rack expects, rather than Sinatra's friendly helper methods.
Packaging logic with Extensions
Middleware is great for the pipeline, but sometimes you want to provide a set of helper methods and that middleware to multiple apps without making the developers manually configure both. This is where Sinatra Extensions come in. An extension allows you to wrap middleware and helpers into a single module that can be "registered".
Instead of telling your colleagues to "add this middleware and copy-paste these three helper methods," you give them a single module:
module Sinatra
module RequestTracker
def self.registered(app)
# This tells the app to use the middleware we wrote earlier
app.use RequestIdMiddleware
# We can also add helpers directly to the app's scope
app.helpers do
def current_request_id
env['request_id']
end
end
end
end
end
Now, in any of your Sinatra apps, you just add register Sinatra::RequestTracker. It's a clean, professional way to share functionality. You've essentially created your own "plugin" for Sinatra. I prefer this approach because it creates a single source of truth. If the logic for how we track requests changes, I update it in the extension, and every app gets the update upon the next deploy.
📋 Practical Task
Build a "Maintenance Mode" Extension
Your goal is to create a Sinatra Extension called Sinatra::MaintenanceMode. This extension should allow an application to be put into a read-only/maintenance state based on a simple configuration flag.
Requirements:
- Create a Rack middleware that checks if
settings.maintenance_modeis set totrue. - If maintenance mode is active, the middleware should intercept the request and return a
503 Service Unavailablestatus with a plain text body saying "Site is currently down for maintenance." - If maintenance mode is inactive, the request should pass through to the application normally.
- Wrap this middleware into a Sinatra Extension so that the app only needs to call
register Sinatra::MaintenanceMode. - Verify your implementation by creating a small Sinatra app that toggles
set :maintenance_mode, trueandfalseto see the middleware in action.
There are no comments for now.