Skip to Content
Course content

180: Common Ruby Interview Questions on Rails Request Lifecycle

Click on the "Edit" button in the top corner of the screen to edit your slide content.

One of the most common things I see in technical interviews for mid-to-senior Rails roles is a question like, "Walk me through the lifecycle of a request." Most candidates can give a textbook answer: "It hits the router, goes to the controller, hits the model, and renders a view." But that's the surface level. The real meat—and where people usually trip up—is what happens before the router even knows the request exists.

Let's look at a bug I encountered a few years ago. I was building a custom audit logger as a piece of Rack middleware to track which users were accessing specific internal API endpoints. I wanted to log the user_id from the session for every single request.

# app/middleware/request_audit_logger.rb
class RequestAuditLogger
  def initialize(app)
    @app = app
  end

  def call(env)
    # I wanted to grab the user_id from the session here
    user_id = env['rack.session'][:user_id]
    Rails.logger.info "Request received for User: #{user_id}"
    
    @app.call(env)
  end
end

# config/application.rb
config.middleware.use RequestAuditLogger

The "Nil" Session Mystery

When I deployed this, the logs didn't show user IDs. In fact, they just showed User: . Even worse, if I tried to call a method on user_id, the whole app crashed with a NoMethodError. I checked my session logic, and it was working perfectly in the controllers. I couldn't understand why the session was empty in the middleware but full in the controller.

The problem is that I forgot that the "Request Lifecycle" is actually a stack. When you use config.middleware.use, Rails appends your middleware to the end of the stack, but the order still matters relative to other built-in middleware. In this case, I was trying to access env['rack.session'] before the ActionDispatch::Session::CookieStore middleware had even run. The session hasn't been "hydrated" from the cookies yet, so env['rack.session'] is simply nil.

Ordering Middleware for Session Access

To fix this, I had to tell Rails exactly where to put my logger in the sequence. I needed it to run after the session middleware but before the request hit the router.

# config/application.rb
# Instead of .use, we explicitly place it after the session store
config.middleware.insert_after ActionDispatch::Session::CookieStore, RequestAuditLogger

By moving the middleware down the chain, the request first passes through the CookieStore (which populates the session hash), and then it hits my logger. Now, env['rack.session'] is populated, and the bug disappears. This is the "onion" model of Rails: every request peels through layers of middleware before it ever reaches your routes.rb file.

The Path from Rack to ActionController

When an interviewer asks about the lifecycle, I recommend you describe it as a series of hand-offs. If you can explain it in this order, you'll sound like someone who actually understands the internals, not someone who just memorized a cheat sheet:

  • The Web Server: Puma or Unicorn receives the raw HTTP request and wraps it in a Rack environment hash (env).
  • The Rack Middleware Stack: The request passes through a series of "filters" (like Rack::Sendfile, ActionDispatch::Cookies, and ActionDispatch::Session). This is where the "heavy lifting" of the environment setup happens.
  • The Router: ActionDispatch::Routing looks at the URL and HTTP method to determine which controller and action should handle the request.
  • The Controller Callbacks: Before the action runs, Rails executes before_action filters. This is the primary place for authentication and authorization.
  • The Action: The actual method in your controller executes, interacting with Models (ActiveRecord) to fetch or save data.
  • The View Rendering: The controller passes instance variables to the view (ActionView), which compiles the HTML or JSON.
  • The Response: The resulting string travels back up the middleware stack (where middleware can modify the headers or body) and is sent back to the client.

Controller Callbacks vs. Middleware

You might wonder: "Why put logic in middleware if I can just use a before_action?" I've seen this come up in interviews a lot. The key difference is scope. A before_action only runs if the Router successfully matches a request to a controller. If a request hits a 404 or a middleware crashes, your before_action never executes.

Middleware is for "global" concerns—things like request timing, security headers, or session management—that must happen regardless of which controller is being called. Controller callbacks are for "business" concerns—like checking if a user is an admin for a specific set of pages.




📋 Practical Task

Exercise: Resolving the Middleware Collision

You are tasked with implementing a RequestTimerMiddleware that calculates how long a request takes to process. However, you've noticed that your application has a custom MaintenanceModeMiddleware that returns a 503 status code if a global flag is set. Currently, your timer is running before the maintenance check, meaning you are timing requests that are blocked by maintenance, which is skewing your performance metrics.

Your Goal: Modify the middleware configuration to ensure the RequestTimerMiddleware only starts timing after the MaintenanceModeMiddleware has allowed the request to pass through.

# Current configuration in config/application.rb
config.middleware.use MaintenanceModeMiddleware
config.middleware.use RequestTimerMiddleware

Requirements:

  • Update the config/application.rb logic to ensure the RequestTimerMiddleware is positioned after MaintenanceModeMiddleware.
  • While .use appends to the end, assume for this exercise that other middlewares are being dynamically inserted. Use insert_after to be explicit about the dependency.
  • Write a brief comment explaining why insert_after is safer than .use in a complex application with many gems adding their own middleware.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.