How Rails Dispatches a Request to a Controller
A clear explanation of what happens after routing chooses an endpoint: controller resolution, params setup, callback execution, and response handling.
In the last article, we looked at how the router matches a request and decides which controller action should handle it.
But that decision is only the handoff.
Once a route is chosen, Rails still has to turn that decision into an actual method call on a controller. It has to resolve the controller, build a fresh controller instance, assemble the request state, run callbacks, and only then execute the action.
That step is called dispatch.
From the outside, this part of Rails feels deceptively simple:
route matches ⭢ action runsInternally, there is more structure than that.
By the end, you should be able to answer this question clearly:
What actually happens between the router choosing a route and my controller action running?
Start With the Right Mental Model
Controller dispatch is the process of turning a matched route into a controller action execution.
The router decides where the request should go. Dispatch decides how that decision becomes live application code.
The flow looks like this:
Route match
⭢
Controller class identified
⭢
Controller instance created
⭢
Request context and params assigned
⭢
Callbacks run
⭢
Action invoked
⭢
Response returnedThat flow explains most controller-level confusion:
The router decides where the request goes.
Dispatch decides how it gets executed.

What the Router Actually Hands Off
By the time dispatch begins, the router has already done its job.
For a route like:
get "/users/:id", to: "users#show"and a request like:
GET /users/42Rails has already identified the basic routing outcome:
controller name:
"users"action name:
"show"route params:
{ id: "42" }
At this point, Rails has still not run controller code. It has only identified what should run.
Route recognition gives Rails a target. Dispatch turns that target into execution.
Routing Produces Identifiers. Dispatch Turns Them into Objects
The route target usually appears in Rails code as something like:
"users#show"That is not yet a controller instance. It is a pair of identifiers:
controller path:
"users"action name:
"show"
During dispatch, Rails resolves that controller path into the actual controller class:
"users" ⭢ UsersControllerFor a namespaced controller, the same pattern applies:
"admin/reports" ⭢ Admin::ReportsControllerThe routing layer and controller layer are separate parts of the framework. The route set identifies the target in a form Rails can carry forward, and then the controller dispatch layer resolves that target into the controller class that will actually handle the request.
There is no need to memorize the internal classes involved for this distinction to be useful.
Routing produces identifiers. Dispatch turns them into objects.
Each Request Gets a Fresh Controller Instance
Once Rails knows which controller class should handle the request, it creates a new instance of that controller for the request.
That means if the request is headed to:
UsersController#showRails doesn’t reuse some long-lived controller object sitting around in memory waiting for work. It instantiates a fresh controller object for this request.
controller instance variables are request-scoped
one request does not share the controller instance state with another
controller objects are part of per-request execution, not long-lived application state
Each request gets a fresh controller instance.
The object exists for one request-shaped unit of work.
Before the Action Runs, Rails Builds the Request Context
Once the controller instance exists, Rails still is not ready to run your action method.
It first has to attach the request context that controller code expects to exist.
requestresponseparamssessioncookies
become available through the controller layer.
That doesn’t mean all of that state is born in the controller itself. Much of it was prepared earlier by middleware or routing. But by the time dispatch sets up the controller execution context, Rails has gathered that state into the shape the controller code knows how to use.
By the time your action starts, a lot of the system work has already happened.
How Params Are Built Before Your Action Starts
One of the most important pieces of that request context is params.
We use params constantly, but it is easy to treat them as if they just appear fully formed inside the action. They do not.
params is assembled before your action runs.
Conceptually, Rails is combining multiple sources of request data:
route params
query params
request body params
So if you have a route like:
get "/users/:id", to: "users#show"and a request like:
GET /users/42?tab=profilethen by the time the action runs, the controller can see values that came from different places:
route param:
id = “42”query param:
tab = “profile”
Similarly, for a form submission or JSON request, body data joins the picture too.
The exact internal mechanics of parameter parsing deserve their own article1, so this piece should not overload that part of the system. But the distinction belongs here:
paramsis not created by your action. It is assembled before your action begins.
It also explains one common class of confusion: when params look wrong, the cause is often earlier than the action itself.
Your Action Does Not Run in Isolation
At this point, Rails has:
identified the controller class
created a controller instance
prepared request state
made
paramsavailable
At first glance, the next step seems simple:
call the action methodBut there is still one more layer that matters a lot in real applications.
Callbacks.
Your action runs inside a callback chain.
The simplified shape looks like this:
before_action ⭢ action ⭢ after_actionThat means the controller action is not an isolated Ruby method call. It runs inside a framework-managed pipeline that can prepare the state before the action and react after it.
Sometimes the thing affecting the request happened before the method you are looking at ever started.
Why before_action Order Matters
before_action is a good example of how dispatch gives Rails structure and also hides some visibility.
Suppose you have:
class UsersController < ApplicationController
before_action :authenticate_user!
before_action :load_user
def show
end
private
def authenticate_user!
redirect_to login_path unless current_user
end
def load_user
@user = User.find(params[:id])
end
endThis looks straightforward, but the order matters.
The authenticate_user! redirects, Rails can halt the normal path before the action runs. Depending on the callback setup, later callbacks may not behave the way you expected either.
Callback order is part of request execution, not decoration around it.
If something feels wrong in a controller, it is not enough to ask only what the action does.
Also ask:
which callbacks ran first?
did one of them redirect or render early?
did the action run at all?
Then Rails Invokes the Action Method
Once the callback chain has reached the action, Rails finally invokes the method corresponding to the action name. So if dispatch resolved:
controller: "users"
action: "show"Rails eventually executes the show instance method on the fresh UsersController instance created for the request.
That method call is the result of a sequence, not the whole story.
By the time Rails gets there, the request has already passed through:
middleware
routing
controller resolution
request-context setup
params assembly
callback execution
Rendering vs Returning
There is one more controller misconception worth clearing up here.
Many Ruby methods are understood mainly through their return value. Controllers are different.
In Rails, what matters most is not the action’s final Ruby expression. What matters is whether the action performs a response.
That response might happen through:
renderredirect_toheador implicit rendering when Rails chooses a template for you
The controller action does not “return HTML” directly. It performs a response.
That is why an action can end with something like:
def show
@user = User.find(params[:id])
endand still produce a response through implicit rendering.
It also explains why this:
def show
return "hello"
enddoesn’t mean what a normal Ruby method return would suggest in a controller context.
Rails is not treating your action like an ordinary function whose final value becomes the HTTP response body.
It is treating the action as one step in a controller dispatch and response-building process.
A Simple Dispatch Diagram
Here is the whole controller-dispatch path in one view:
Matched route
controller + action identified
⭢
controller instance created
⭢
params and request context prepared
⭢
before_action chain
⭢
action method
⭢
render/redirect/performed responseAnd the callback portion in isolation:
before_action ⭢ action ⭢ after_actionA Real Debugging Scenario
Let us make this practical with one example.
Suppose you are debugging a controller and asking: Why is my before_action not running?
The first instinct is often to open the controller and stare at the callback declarations. Sometimes that is the right place.
But sometimes, the issue is earlier in dispatch:
the route matched a different controller than you expected
the request never reached this action
another callback halted execution earlier
the action name was different from what you thought
Or take another question: Why is params[:id] missing?
Again, the problem may not be inside the action at all.
It may be:
the route pattern was different from what you assumed
the request matched a different route
the expected value was in the query string instead of the path
the request body did not parse the way you thought
When something feels wrong in a controller, the issue is often earlier in dispatch.
Why Rails Is Designed This Way
This dispatch layer exists because Rails is trying to separate several concerns that would otherwise blur together:
route recognition
controller resolution
request preparation
callback execution
response handling
That separation gives Rails a lot:
structure
extensibility
a place for callbacks
a place for request setup
a controller abstraction that feels consistent
But it also creates distance between “I matched a route” and “my code ran.”
You gain structure, but you lose some visibility.
The Final Mental Model
Routing decides where a request should go. Dispatch decides how that request gets executed.
The full flow now looks like this:
Request
⭢
Middleware
⭢
Dispatch
⭢
Controller
⭢
Response
⭢
Middleware
⭢
ClientAnd inside dispatch, the shape is:
matched route
⭢
controller resolved
⭢
controller instantiated
⭢
params prepared
⭢
callbacks run
⭢
action executed
⭢
response performedInternally, Rails uses ActionController’s dispatch mechanism to orchestrate this sequence, but you do not need to understand those classes to reason about how requests flow.
Once you understand dispatch, the controller is no longer the starting point.
It becomes one step in a larger system.
See How Rails Builds params Before Your Action Runs
