Kreeki
Kreeki

Reputation: 3732

Render template and change url string in browser?

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

Answers (5)

max
max

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

haroldus
haroldus

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

Confused Vorlon
Confused Vorlon

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

CL Chang
CL Chang

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

Pan Thomakos
Pan Thomakos

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

Related Questions