Skip to content

Instantly share code, notes, and snippets.

@jcasimir
Created September 11, 2011 21:29
Show Gist options
  • Save jcasimir/1210155 to your computer and use it in GitHub Desktop.
Save jcasimir/1210155 to your computer and use it in GitHub Desktop.
Render and Redirect in Rails 3

Render and Redirect

The normal controller/view flow is to display a view template corresponding to the current controller action, but sometimes we want to change that. We use render in a controller when we want to respond within the current request, and redirect_to when we want to spawn a new request.

Render

The render method is very overloaded in Rails. Most developers encounter it within the view template, using render :partial => 'form' or render @post.comments, but here we'll focus on usage within the controller.

:action

We can give render the name of an action to cause the corresponding view template to be rendered.

For instance, if you're in an action named update, Rails will be attempting to display an update.html.erb view template. If you wanted it to display the edit form, associated with the edit action, then render can override the template selection.

This is often used when the model fails validation:

def update
  @book = Book.find(params[:id])
  if @book.update_attributes(params[:book])
    redirect_to(@book)
  else
    render :action => :edit
  end
end

When render :action => :edit is executed it only causes the edit.html.erb view template to be displayed. The actual edit action in the controller will not be executed.

As of Rails 3, the same effect can be had by abbreviating to render :edit.

In Another Controller

Most commonly you want to render the template for an action in this controller. Occasionally, you might want to render an action from another controller. Use a string parameter and prefix it with the other controller's name:

render 'articles/new'

Even if this were executed in the CommentsController it would pull the view template corresponding to ArticlesController#new.

Content Without a View Template

You can use render to display content directly from the controller without using a view template.

Text

You can render plain text with the :text parameter:

render :text => "Hello, World!"

This can be useful for debugging but is otherwise rarely used.

XML and JSON

You can render XML or JSON version of an object:

render :xml => @article
render :json => @article

Rails will automatically call .to_json or .to_xml on the passed object for you.

:layout

When using render you can override the default layout with the :layout option:

render :show, :layout => 'top_story'

Or turn off they layout system completely:

render :show, :layout => false

Redirect

Use redirect_to to spawn a new request.

Why care about a new request? When a user submits data it comes in as a POST request. If we successfully process that data we likely next display them the data they just created. If they wrote an article and click SAVE, then we'd probably show them that article. We could display the article using render in the same POST that sent us the data.

But, what's going to happen if they hit refresh? Or click a link, then use their browser's BACK button? They'll get a pop-up from the browser: "Submit form data again?" Do they push yes? No? Will clicking yes create a duplicate article? Will clicking no somehow mess up the old article? It's confusing for the user.

Instead, when you successfully store data you want to respond with an HTML redirect. That will force the browser to start a new request. In our scenario, we'd redirect to the show action for the new article. They could refresh this page, navigate forward then back, and it would all be normal GET requests -- no warning from the browser.

redirect_to

The redirect_to method is typically used with a named route helper:

redirect_to articles_path

If you're linking to a specific resource outside your application, you might use a full URL string:

redirect_to 'http://rubyonrails.org'

Status Code

By default Rails will use the HTTP status code for "temporary redirect." If you wanted to respond with some other status code, you can add the :status parameter:

redirect_to 'http://rubyonrails.org', :status => 301

The request would be marked with status code 301, indicating a permanent relocation.

With Flash

You can set a flash message within your call to redirect_to. It will accept the keys :notice or :alert:

redirect_to articles_path, :notice => "Article Created"
redirect_to login_path, :alert => "You must be logged in!"

redirect_to is not return

Keep in mind that redirect_to does not cause the action to stop executing. It is not like calling return in a Ruby method.

Here's how that could go wrong. Imagine you have a delete action like this:

def destroy
  article = Article.destroy(params[:id])
  redirect_to articles_path, :notice => "Article '#{article.title}' was deleted."
end

Then you begin adding security to your application. You've seen "guard clauses" used in Ruby code, where a return statement cuts a method off early. You decide to imitate that here:

def destroy
  redirect_to login_path unless current_user.admin?
  article = Article.destroy(params[:id])
  redirect_to articles_path, :notice => "Article '#{article.title}' was deleted."
end

When an admin triggers destroy, here's what happens:

  1. The unless condition is true, so the first redirect_to is skipped
  2. The article is destroyed
  3. The browser is redirected the the index

Then some non-admin user comes and triggers the destroy action:

  1. The unless condition is false, so the redirect_to runs, a redirect response is set, and execution continues
  2. The article is destroyed
  3. The second redirect_to runs, it sees that a redirect has already been set, and raises an exception (AbstractController::DoubleRenderError)

The article gets destroyed either way. The redirect_to does not stop execution of the method, it just sets information in the response. The correct way to achieve this protection would be:

def destroy
  if current_user.admin?
    article = Article.destroy(params[:id])
    redirect_to articles_path, :notice => "Article '#{article.title}' was deleted."
  else
    redirect_to login_path, :notice => "Only admins can delete articles."
  end
end

Reference

@lbvf50mobile
Copy link

Amazing and clear article. Especially explanation about redirect_to to avoid duplication of an entity. Thank you!

@raquelhortab
Copy link

When render :action => :edit is executed it only causes the edit.html.erb view template to be displayed. The actual edit action in the controller will not be executed.

There is no way to execute another action in the same controller without calling redirect, then?

@jcasimir
Copy link
Author

@raquelhortab you found a 12-year-old gist!! ;)

No, you typically would not want to execute a second action within a single request. I'd encourage you to think about it a bit differently. Like say you're running the create action but are thinking you also want to run the new action. Instead, what is common about them that you're wanting to run in both? Extract that to it's own method. Maybe that's something like an authenticate method that gets called from both create and new, then the actions don't need to rely on / call each other.

Does that make sense?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment