Pothibo

I can't tell you why it's useful, but here's how ActionDispatch route URL to controller.

When I started this serie of post about Rails, I wanted to talk about ActionDispatch::Journey. I'm not really sure why, but I was always fascinated how a framework could just take a normal path and reach the right controller.

To explain Journey, I had the perfect candidate. In the last 6 months, I've had the idea to have a websocket initialized on DOMContentReady and then pass every ajax call through that websocket instead of using the normal HTTP route. Figuratively, it would be like HTTP persistent connection on steroid.

I wanted to use the same routing system Rails uses a.k.a Journey to pipe the request to the controller and use the whole middleware system already in place to build the response as expected, using the views as normal. The only thing that wouldn't be sent back to the client would be the header since the websocket doesn't need those. Status code and content only!

The main advantage of doing AJAX through a websocket is that XSS vulnerabilities would become a thing of the past. Also, since it's a single connection all along, AJAX requests would become stateful.

Bottom line:

  • As fast or faster than turbolinks on AJAX call (turbolinks still used for full page load)
  • Lighter response (Headers aren't needed anymore)
  • No more XSS

Unfortunately, I haven't had the time to build that yet. So no example today. Yeah, it sucks. I really wish I could find another example to use to show how Journey handles request but I couldn't find any that was good enough.

It doesn't matter, understanding Journey is still worthwhile because understanding how a request lands on your controller's action is paramount to understanding the flow of your application.

If you read this post, I am confident that you will have a much better understanding of how rails works and it will benefits you down the road.

Meet ActionDispatch::Routing::RouteSet

If you execute Rails::Application.routes.class in rails console, you'll have ActionDispatch::Routing::RouteSet. The route set used to be the way rails was handling routing. However, since rails 4, Journey has taken over. Now, RouteSet is merely a proxy for Journey::Router.

So basically, when dispatching paths to a controller, RouteSet doesn't do anything. However, it does hold an anonymous module that you use everyday in your project which is known as Rails.application.routes.url_helpers.

What Journey needs to map a request

For Journey to map a request, it only needs PATH_INFO from the env variable that is built by the rack middleware. Once the router has found a route it will call the app that is assigned to that route.

What is linked to that route can be anything. In a normal application, it would be one of the following depending on the path:

  • ActionDispatch::Routing::RouteSet::Dispatcher (Most if not all of your routes in config/routes.rb)
  • Sprocket::Environment
  • Any other Rails::Engine that is mounted

ActionDispatch::Routing::RouteSet::Dispatcher

Once Journey::Router has found the route, it calls the associated app (also known as a rack middleware). When the app is a dispatcher, it will normalize the params variable you will be using in your controller and then dispatch the request to the controller. Here's what it does (as of 4.0.0).

def dispatch(controller, action, env)
  controller.action(action).call(env)
end

The controller variable is actually the class of the controller, not the instance. So basically, if you have a PostsController and you would access the index, the code above would be equal to PostsController.action(:index).call(env).

Here's a copy of the source of what the action method does:

def self.action(name, klass = ActionDispatch::Request)
  middleware_stack.build(name.to_s) do |env|
    new.dispatch(name, klass.new(env))
  end
end

Don't you find that interesting? Instead of simply calling controller.new.send(action), it actually builds another rack middleware from the stack and then call the action name on that.

The block's self is actually the controller's class. So, when calling new, it initializes the controller and dispatch initializes the controller and then calls the action and returns an array with 3 elements (status code, header and body) that will then go through all the rack middleware instances and eventually make it out to the client.

Rails::Engine

When a route matches an engine, the Engine's Journey::Router will be used for the rest of the query. So basically, the whole process continues until a dispatcher is found. Which makes this process somewhat recursive.

Sprocket::Environment

Sprocket is quite different and kinda outside the focus of this post. However, when a browser looks for an asset file, the router will pass the request to Sprocket::Environment. From there, it will try to find an asset and return the same tuple consisting of 3 elements (status code, header and content). The content will be obviously be your asset.

That wraps it!

This does make a nice summary of how a request is sent to a controller. I believe not many of you will have read this whole post so for those of you who did, you can leave comments at the bottom of the page if you have any question.

Also, if you enjoyed this post, you should subscribe to my brand new mailing list! I'll be sending e-mail once or twice a month, talking about ruby on rails stuff that might improve your understanding of the framework as a whole or even give you tips on some features I saw on the tubes!

Have a good day!

Get more ideas like this to your inbox

You will never receive spam, ever.