The Rails router is a constant source of headaches. Your specs are all passing, but you can’t seem to get the Rails error page to stop popping up when you’re clicking through. It’s really easy to throw up your hands in frustration; it’s even easier to want to strangle the forum poster who tells you to “just read the Rails Guide”.
It’s not just you. Here are 4 common issues with the router. I hope you feel a little less maddened after checking them out.
4. Nested resources and the complexities therein
It’s really easy for Rails experts to talk about REST and how wonderfully simple it is. But stacking and nesting simple things can often lead to a big mess. It’s going to be very difficult indeed to generate a route to a user’s post’s comment’s likes. You’re very often going to simply be told “no” by the router in that most wonderfully helpful of error messages:
ActionController::RoutingError in User#show No route matches ...
The best advice I can give, above and beyond what I said here, would be to limit nesting resources to one level. A user’s posts:
/users/1/posts. A post’s comments:
/posts/2/comments. Et cetera.
3. Parameter matching, Or: The Dance of Guessing and Hoping
This is best illustrated with an example.
Given this route setup:
resources :users do resources :posts end
link_to @post.title, [@user,@post] link_to @post.title, user_post_path(@user,@post) link_to @post.title, user_post_path(@post,user_id: @user) link_to @post.title, user_post_path(user_id: @user, id: @post) link_to @post.title, controller: "posts", action: "show", user_id: @user, id: @post
Produce this URL:
But you have to make sure the parameters you’re passing in match appropriately. In the first two, the user needs to come before the post. Since we’re looking for a
Post, its parameter is
link_to @post.title, [@post,@user] link_to @post.title, user_post_path(@post,@user) link_to @post.title, user_post_path(user_id: @user, post_id: @post) link_to @post.title, user_post_path(user_id: @user, post_id: @post)
ActionController::UrlGenerationError No route matches ...
and nobody wants that.
Rails 4 has made leaps and bounds in this arena. It will tell you the name of the parameter you’re missing, which is a great clue to what’s wrong. This is one of the arguments for always upgrading Rails whenever possible.
2. PUT/DELETE named routes
This one is super confusing and bites everyone at some point, but fortunately it’s easy to explain: Given a
Post resource, the route to either update or destroy it is the same as the route to show it:
post_path(@post). It depends on the helper you’re using, but you just have to specify the
:method to be
"put" (or, as is more fashionable now,
1. Evil done in the name of backward compatibility
There’s a long, storied, and bloody history to routes. On a Rails 1.2 project, lo those many years ago, our team was consistently using hashes for all our route generation. So many instances of
link_to("Show", :controller => "users", :action => "show", :id => @user.id)! Hash-formed routes were really hard to maintain and adapt, so they fell by the wayside in favor of named routes, so the above turned into
link_to("Show", user_show_path(@user)). Then came REST with its
resources :users and linking directly to instances, like
link_to("Show",@user). Add in concerns, formats, methods and you’ve got yourself a hulking, bloated library.
Since there are apps that have been upgraded from Rails version 1.x to 4.x, Rails has seen fit to deprecate very few of the features glommed onto the router over the years. Unlike ActiveRecord syntax, which is often changed irreversibly over time, support for these old route features continues, so knowing the right and modern way of routing is elusive.
My current pattern is to link directly to a resource where possible (
link_to "Show", @user) and to use named routes otherwise (
link_to "My posts", user_posts_path(@user)).
I hope this has helped take some of the venom out of routes for you. If you’ve got any questions, leave a comment below!
Like this post? Join my mailing list here and I can send more goodness your way.