I feel like I've seen this in many Rails apps, and it never made sense to me (but maybe that's because it's well past midnight and my brain is mush).
When I edit (for example) a user at /admin/users/20/edit
, and I get a validation error, and the controller code is something like this:
def update
if @user.update(user_params)
redirect_to(some_path, notice: 'Record updated')
else
render("edit") # <<<<<<<<<<<<<<<<<<<
end
end
instead of going to /admin/users/20/edit
, it shows /admin/users/20
in the browser.
This seems all well and good, but without /edit
it is not a valid GET URI, so other code which is consuming the HTTP_REFERER, and which (naturally) expects it to be a valid GET URI will take the user to an error page.
In my case there is an internal gem which handles impersonation of users by admin users. Ending an impersonation takes the admin user back to the referrer, and if they have had the referer modified by a validation error, then they get an error.
I could
- modify the gem to handle this case (a hassle, but perhaps necessary)
- add a route to make this URL valid for editing without
/edit
(seems like it shouldn't be necessary, and seems a bit kludgey),
but I want to know if there is a reason this is happening. Is this in fact standard Rails behavior or have I overlooked something? If this is standard, is there a good widely-accepted fix? If it is not standard Rails behavior, where should I look for the culprit?
CodePudding user response:
It's pretty normal behaviour because when you update a user you will do a PUT
or PATCH
to /admin/users/20
. So if there is a validation error, you are rendering the edit
template and the url stays the same (/admin/users/20
)
You could do a redirect instead of render, but in that case you are losing some info about the validation error. Or you should send it a long with the redirect.
CodePudding user response:
This is a common Rails beginner hangup and the key here is really understanding HTTP verbs, the concept of idempotency and Rails flavored REST.
The /new
and /edit
actions in Rails respond to GET requests. They are idempotent - the page will look the same to any visitor and if you reload the page you'll get the exact same page. They only serve to display a form in classical applications.
Updating resources done with PATCH /users/1
(or PUT
in legacy Rails apps). These verbs are non-idempotent as they modify a resource. Unlike many other frameworks Rails uses the HTTP method to destinguish between different actions instead of using for example POST /users/1/update
or POST /users/1/delete
.
What you're doing when you call render("edit")
is not redirecting the user back to the form. You're rendering a view and displaying the result of performing a non-idempotent action. This is not something that can be linked to as the result depends on the input passed in the request body and neither can you reload the page without resending the exact same request - and in this case sending the result again is not guarenteed to give the same result. Some browsers do not allow this at all and almost all will warn you.
This seems all well and good, but without /edit it is not a valid GET URI, so other code which is consuming the HTTP_REFERER, and which (naturally) expects it to be a valid GET URI will take the user to an error page.
This is an X & Y problem. The result of editing a record is not idempotent and thus cannot be linked to. Using HTTP_REFERER
is in itself also problematic as its not guarenteed to be sent by the client.
While you can create a scheme to redirect back with the user input stuffed into the query string or the session this is the wrong answer to the wrong question.
where should I look for the culprit?
Whatever gem you're using might not be a good solution for the original problem - or even good at all. Impersonating a user might be a lot more fuss then just creating a separate endpoint for admins to edit users directly.
It certainly sounds like a very brittle solution.