githublinkedinemail
← /articles

You DON'T understand Rails until you understand the full Request cycle

Rails isn't magic. Rails is Rack, TCP, threads, middleware, SQL and Ruby objects on the heap. Understand what happens before and after your controller.

5 minrails

You DON'T understand Rails until you understand the full Request cycle

There's a lie the Rails community told for many years.

"Rails is magic."

No.

Rails isn't magic.

Rails is:

  • Rack
  • TCP
  • Threads
  • Middleware
  • SQL
  • IO
  • Serialization
  • Ruby objects on the heap

And the problem is that a lot of people spend years writing:

def show
  @user = User.find(params[:id])
end

…without understanding what happens before and after that line.

Then production starts to:

  • get slow
  • eat memory
  • block threads
  • time out
  • spike CPU
  • lock the database

…and debugging becomes superstition.

Because whoever doesn't understand the request cycle…
…debugs in the dark.


The "controller = Rails" illusion

Most beginners think Rails is:

  • controller
  • model
  • view

That's it.

But controller is literally ONE STAGE of the whole pipeline.

When you receive an HTTP request:

GET /users/1

There's a whole ecosystem happening before:

User.find(params[:id])

And that's exactly where these things live:

  • bottlenecks
  • concurrency
  • observability
  • authentication
  • sessions
  • real performance

It all starts outside of Rails

First important fact:

Rails DOES NOT accept TCP connections.

That's done by:

  • Puma
  • Unicorn
  • Passenger

Rails is just a Rack application.

That changes your whole perception of the stack.

The real flow starts like this:

Browser
   ↓
Linux Kernel
   ↓
TCP Socket
   ↓
Puma
   ↓
Rack
   ↓
Rails

See the difference?

Rails is FAR from being the first layer.


Puma and the reality of production

Puma listens on TCP sockets in Linux.

When a request arrives:

  1. it accepts the connection
  2. picks/reuses a thread
  3. creates the request context
  4. hands it off to Rack

And here a classic problem is born:

"My Rails is slow."

Maybe it isn't.

Maybe you're:

  • blocking threads
  • doing too much synchronous IO
  • destroying concurrency
  • causing starvation
  • holding DB connections too long

Rails performance is almost never "just Rails".

It's the whole cycle.


Rack: the invisible piece of Ruby Web

If you've worked with Rails for years and never studied Rack…
…you probably haven't really understood Rails yet.

Rack is the standard interface between:

  • Ruby web servers
  • Ruby frameworks

It's the equivalent of:

  • WSGI in Python
  • middleware pipeline in Express
  • the HTTP interface of the Ruby ecosystem

Puma basically does this:

env = { ... }
Rails.application.call(env)

That's it.

Rails receives a giant hash called env.

And responds in Rack format:

[
  status,   # 200, 404, 500...
  headers,  # { "Content-Type" => "text/html" }
  body      # ["<html>...</html>"]
]

Literally that.

All of Rails' "magic" exists on top of this absurdly simple abstraction.


Middleware: where half of Rails actually happens

This is one of the most underestimated parts of the stack.

Before reaching the controller…
…the request goes through dozens of middlewares.

Examples:

  • cookies
  • session
  • authentication
  • CSRF
  • compression
  • logs
  • static files

All middleware.

Simplified flow:

Request
   ↓
Rack::Sendfile
   ↓
Cookies
   ↓
Session
   ↓
Warden/Devise
   ↓
CSRF
   ↓
Rails App

And this is VERY important.

Because:

  • middleware can short-circuit a request
  • middleware can respond directly
  • middleware can modify the response
  • middleware can catch exceptions

Your controller doesn't always run.

A lot of people never realize this.


Routing doesn't call controllers "directly"

Another abstraction that confuses beginners.

When you define:

get "/users/:id"

Rails doesn't "execute the method".

It:

  1. matches the route
  2. resolves controller/action
  3. creates an executable endpoint
  4. builds the request context

Routing is much closer to a dispatcher than to a simple URL table.


Finally: the controller

Now we reach the part everyone knows.

Rails:

  1. instantiates the controller
  2. runs callbacks
  3. runs the action

Example:

before_action :authenticate_user!

def show
  @user = User.find(params[:id])
end

But here's another illusion.

That User.find looks innocent.

But under the hood:

  1. grabs a connection from the pool
  2. generates SQL
  3. talks to Postgres
  4. waits on IO
  5. deserializes the result
  6. instantiates Ruby objects

One line of ActiveRecord can cross:

  • network
  • database
  • serialization
  • memory
  • garbage collector

And then comes the classic phrase:

"Rails doesn't scale."

No.

Your code might not scale.


Rendering is WAY more expensive than it looks

After the action…
…Rails still has to generate the response.

If it's HTML:

  1. find the template
  2. process ERB
  3. apply the layout
  4. render partials
  5. assemble the final HTML

And this can get absurdly expensive.

Especially when you:

  • render partials in a loop
  • create too many helpers
  • build a huge component tree
  • run heavy logic in the view

Rails renders MANY Ruby objects.

And Ruby objects cost memory.


The response climbs back up the stack

After rendering:

  • the response goes back through middlewares
  • cookies get serialized
  • session gets saved
  • gzip may be applied
  • logs are written
  • metrics are collected

Tools like:

  • New Relic
  • Datadog
  • Skylight
  • Sentry

live by intercepting this cycle.

Modern observability totally depends on this pipeline.


The big beginner mistake

The mistake is thinking a web framework is:

Request → Controller → Response

It isn't.

In real life there's:

kernel → socket → scheduler → thread
   ↓
middleware → connection pool → IO
   ↓
serialization → cache → GC → database
   ↓
render pipeline → response

And Rails abstracts all of this VERY well.

So well…
…most people never actually learn it.


The big shift

The day you understand the request lifecycle…
…you stop "using Rails".

And you start understanding:

  • web applications
  • concurrency
  • throughput
  • observability
  • latency
  • architecture
  • real trade-offs

And then finally:

  • profiling makes sense
  • logs make sense
  • APM makes sense
  • pgBadger makes sense
  • N+1 makes sense
  • connection pools make sense

Because now you can see the whole request.

Not just the controller.


Conclusion

Rails was never magic.

The community just romanticized abstractions.

Underneath:

  • it's still TCP
  • it's still IO
  • it's still SQL
  • it's still memory
  • it's still concurrency
  • it's still Linux
  • it's still systems engineering

And honestly?

The more senior you get…
…the less "magical" Rails feels.

And the more elegant it becomes.


You DON'T understand Rails until you understand the full Request cycle
You DON'T understand Rails until you understand the full Request cycle