Why a Rails form validation error renders the wrong URL and how to fix it

Last updated on December 20, 2021

Before I encountered the following problem, I didn't pay attention to render and redirect_to. I just assumed how they work and let Rails do its magic. But even though correct, the Rails magic confused me whenever I encountered this "bug". I decided to write a short article addressing the simple "cheat" I used.

The following problem is very obscure, but here it comes.

I have used edit/update actions for the following example, but you can apply the same technique to your exact situation.

The problem

For the sake of this article, I have a normal User controller with a show, edit and update actions.

ruby
1
class UserController < ApplicationController
2
def show
3
@user = User.first # for testing purposes
4
end
5
6
def edit
7
@user = User.first # for testing purposes
8
end
9
10
def update
11
@user = User.first # for testing purposes
12
13
if @user.update user_params
14
flash[:notice] = 'Details updated.'
15
redirect_to action: :show
16
else
17
flash[:notice] = 'Wrong details. Try again'
18
render :edit
19
end
20
end
21
22
private
23
def user_params
24
params.require(:user).permit(:name, :email)
25
end
26
end

Routes.rb defines the user resource for these actions

ruby
1
resources :user, only: [:show, :edit, :update]

which creates the following routes

shell
1
Prefix Verb URI Controller#Action
2
edit_user GET /user/:id/edit(.:format) user#edit
3
user GET /user/:id(.:format) user#show
4
PATCH /user/:id(.:format) user#update
5
PUT /user/:id(.:format)

Important! Notice that the paths for the show and update are the same, but the method verbs are different.

The purpose of the update action is to redirect to the show action if the update has been successful or render the edit form with the validation errors.

In order to test if it works, I go to /user/1/edit and I enter the false information so that the user update fails.

As intended, the form is rendered with the error information. But the new URL is /user/1.

The problem I had is that I refresh the page, I go to the show action - I don't stay in the edit action.

Why does this happen?

Whenever you press the Submit button of your form, you are being redirected to /user/1 with a PUT verb, which routes to the update action as instructed by our routes.rb file.

If the update action fails the validation, it renders the edit form again - it does not redirect!

Therefore, the URL remains unchanged -/user/1.

The solution I used

At first, we might think that we can change the render to redirect_to - but then we won't get the validation error. So this is not an option.

I decided to update the routes to the following

ruby
1
resources :user, only: [:show, :edit]
2
put '/user/:id/edit', to: 'user#update', as: 'update_user'

This maps the PUT http method to /user/:id/edit, so even if the validation fails, I still redirect to the edit action.

If the user refreshes the page, it doesn't matter - the URL still is /user/:id/edit.

The URL of the form needs to be updated to the new URL. In this example, we need to change the form from

erb
1
<%= form_for @user, url: user_path(@user), method: :put do |f| %>

to

erb
1
<%= form_for @user, url: update_user_path(@user), method: :put do |f| %>

Invite us to your inbox.

Articles, guides and interviews about web development and career progression.

Max 1-2x times per month.