Reputation: 3732
I have 2 actions - Edit and Update. Form in Edit submits the values to Update action. When saving a model fails I render edit teplate, where user sees errors and fields are prepopulated with what he filled before. There's a huge but for me - in URL panel in user's browser there's /user/update, even when (and because) I rendered edit template. Can I somehow change that with passing some parameters to render method in update action? I don't want the user to see that there's any (update) action aside of edit. Is it possible?
Upvotes: 16
Views: 11388
Reputation: 102443
I would say you simple have not understood how and more importantly why REST works the way it does in Rails and you should try to learn the framework before imposing your own ideas on it.
First off the idomatic rails routes do not have the action in it. You perform an update by sending a PATCH or PUT request to things/:id
. The exception are new
and edit
since these actions are used to render forms.
Prefix Verb URI Pattern Controller#Action
things GET /things(.:format) things#index
POST /things(.:format) things#create
new_thing GET /things/new(.:format) things#new
edit_thing GET /things/:id/edit(.:format) things#edit
thing GET /things/:id(.:format) things#show
PATCH /things/:id(.:format) things#update
PUT /things/:id(.:format) things#update
DELETE /things/:id(.:format) things#destroy
The edit action (GET /things/:id/edit) displays the form for editing a resource. This is an idempotent action since it should return the same result and does not alter the resource.
The update action (PATCH|PUT /things/:id) renders the result of performing a non-idempotent transformation to a resource.
You should also note that rendering in Rails has nothing to do with a redirect. Which is a common missconception.
render :edit
Is actually just shorthand for:
render "things/edit"
It does not call the edit action - the two simply share a view but are completely different actions conceptually.
Reloading that page will of course not show the same result as thats a GET request for /things/1
- not PATCH/PUT. Remember that GET requests should always be idempotent.
Redirecting back will create an entry in the history as it a separate GET request plus the fact that you need to pass the entire form body as GET parameters which is hardly ideal. Plus you´re basically tossing away the real power of rails which is the productivity you get from embracing its conventions.
Upvotes: 1
Reputation: 131
One could use the pushState() method described here: https://developer.mozilla.org/en-US/docs/Web/API/History_API#The_pushState()_method
For example, you could put this in a javascript function which is invoked when the validation error(s) occur:
var url = document.referrer;
window.history.pushState(null, null, url);
Upvotes: 0
Reputation: 10466
A slightly more modern version based on CL Chang's answer
resources :jobs, except: [:update] do
member do
patch 'edit', action: :update, :as => :update_edit
end
end
This generates all the standard restful routes, except for the standard update route. (if you don't want all the actions, then you can use only: [<your routes not including :update>]
In addition, it generates
update_edit_job PATCH /jobs/:id/edit(.:format)
Now you just update your form to specify the path
<%= form_for @job, :url => update_edit_job_path do |f| %>
...
<% end %>
so, instead of sending to /update via patch, it goes to /jobs//edit via patch, and this hits your update action
now if there is an error, you can render edit, and all the errors will be visible - but the user won't notice that the url is different to the edit one (because only the method is different)
def update
@job = Job.find(params[:id])
if @job.update_attributes(user_params)
#redirect somewhere???
else
render 'edit'
end
end
Upvotes: 0
Reputation: 158
Here is the third way around this:
In your routes.rb
resources :users
match 'users/:id/edit' => 'users#update', :via => :put, :as => :put_user
In your view (edit.html.erb for example)
<%= form_for @user, :url => put_user_path do |f| %>
...
<% end %>
In your controller (users_controller.rb for example)
def update
@user = User.find(params[:id])
if @user.update_attributes(params[:user])
...
else
render 'edit'
end
end
Upvotes: 8
Reputation: 34350
There are two ways around this:
1) Redirect from the update action back to the edit action, instead of just rendering the template, and pass the error messages and attributes that you want to use to populate the object that is being edited. This will result in the URL being /user/edit.
def update @user = User.find(params[:id]) if @user.update_attributes params[:user] ... else redirect_to edit_user_path(@user, :messages => @user.errors) end end
2) Post to the edit action instead of the update action and remove the update action entirely. You can use request.post? in your edit action to check if the request is a post or get request and then perform your update and edit actions using the same function definition.
def edit @user = User.find(params[:id]) if request.post? @user.update_attributes params[:user] ... else ... end end
NOTE: Keep in mind though that you can't ever really hide the POST action from the client because they can always view your source code and see the action to which you are posting in your form.
Upvotes: 4