Rails Path Helpers and the Mystery of the Missing Route Key

Stephanie Viccari

While working on a Rails project and adding test coverage for an existing view, I ran into the following error:

ActionView::Template::Error:
  No route matches { :action => "edit", :controller => "users" }, missing required keys: [:id]

This error is informing the reader that the path to edit a user (ex: users/:id/edit can’t be built because the required :id value is missing.

The intriguing part of this error is that locally this page renders the correct path to edit a user. Yet, attempting to render the same view in isolation, in a view spec, is raising an error.

To understand what’s happening, let’s take a look at the existing code:

# users_controller.rb

class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
  end
end
# routes.rb

resources :users
# users/show.html

<p>First Name: <%= @user.first_name %></p>
<p><%= link_to "Edit User", edit_user_path %></p>

Now, let’s take a look at the view spec that renders the user show page:

# spec/views/users/show.html.erb_spec.rb

describe "users/show", type: :view do
  it "displays information about the user" do
    user = create(:user)
    assign(:user, user)

    render

    expect(page).to have_text(user.first_name)
  end
end

With the current code, the expectation is never reached because render is raising an ActionView::Template error.

So why does the edit_user_path fail to build in the test but successfully builds when a user visits the same page?

Searching For Missing Route Values

Under the hood, Rails path helpers use ActionDispatch to generate paths that map to routes defined in routes.rb. When the path helper is not provided the necessary route key, two outcomes may occur:

1) The missing route key is filled in based on the current request. 2) An error is raised, stating the route cannot be built.

When the route key is missing, ActionDispatch will examine the current request for an identical key that matches the missing key. In this specific example, when a user navigates to GET /users/123, the id: 123 value is available in the current request. ActionDispatch will use the :id value as a substitute for the missing route key and build the path /users/123/edit.

ActionDispatch’s ability to search the current request options and fill in the missing value allows path helpers to build a valid path, even when the required information is not provided.

In our test scenario, a request has not been issued to GET /users/123 and ActionDispatch is unable to infer the missing :id value. This results in the test raising an ActionView::Template error.

Don’t Make ActionDispatch Guess

To resolve the error and restore the view’s ability to render without relying on values in the current request, supply the path helper with the necessary route key value:

# users/show.html

<p>First Name: <%= @user.first_name %></p>
<p><%= link_to "Edit User", edit_user_path(@user.id) %></p>

If you prefer, you can avoid the .id call and pass the object instance as Rails will derive the value by calling to_param on the object:

# users/show.html

<p>First Name: <%= @user.first_name %></p>
<p><%= link_to "Edit User", edit_user_path(@user) %></p>

Summary

Avoid relying on path helpers to play the role of detective by searching for the missing value. There’s a chance ActionDispatch will infer the wrong value and this approach prevents the view template from being rendered in isolation. A better approach is to provide the path helper with the necessary value(s) to build a valid path.