she's a beauty

Jared Carroll

Recently I was writing an action in one of my controllers that needed to do a POST to a remote API.

One of the parameters in that POST was a block of HTML.

The first pass was similar to this (ignoring execption handling for the remote call for now):

class UsersController < ApplicationController

  def create
    @user = User.new params[:user]
    if @user.save
      markup =<<-EOS
        <h2>John Pepper</h2>
        Makes amazing [burritos.](http://www.boloco.com)
        Is in touch with the blogosphere.
      EOS
      RemoteApi.update_profile :markup => markup
      redirect_to user_path(@user)
    else
      render :action => :new
    end
  end

end

I don’t like that HTML in my controller. Plus that <a> tag is terrible, I want to use #link_to but that’s not available in controllers (and it shouldnt’ be).

Second pass:

class UsersController < ApplicationController

  def create
    @user = User.new params[:user]
    if @user.save
      RemoteApi.update_profile
        :markup => render(:partial => 'user')
      redirect_to user_path(@user)
    else
      render :action => :new
    end
  end

end

In user.rhtml:

<h2>John Pepper</h2>
Makes amazing <%= link_to 'burritos', 'http://www.boloco.com' %>.
Is in touch with the blogosphere.

That’s better. I got the HTML out of the controller and I can use #link_to instead of an <a> tag.

But wait: ActionController::DoubleRenderError. You can’t call render and/or redirect multiple times in an action (as a side, whoever wrote ActionController::DoubleRenderError is awesome - that is the best error message of any Rails exception).

After looking through the docs i found ActionController::Base#render_to_string. This beauty is the same as #render but does not actually send the result as the response body i.e. it doesn’t count as a #render call in an action, allowing you to avoid the ActionController::DoubleRenderError.

Third pass:

class UsersController < ApplicationController

  def create
    @user = User.new params[:user]
    if @user.save
      RemoteApi.update_profile
        :markup => render_to_string(:partial => 'user')
      redirect_to user_path(@user)
    else
      render :action => :new
    end
  end

In user.rhtml:

<h2>John Pepper</h2>
Makes amazing <%= link_to 'burritos', 'http://www.boloco.com' %>.
Is in touch with the blogosphere.

Sweet. Exactly what I wanted.

Someone else here at t-bot recommended using this strategy for flash messages. Since flash messages are just text, whenever they get more complicated than 1 sentence it might be good to put them in a partial to keep all that view code out of your controller, #render_to_string would allow you to do that.

About thoughtbot

We've been helping engineering teams deliver exceptional products for over 20 years. Our designers, developers, and product managers work closely with teams to solve your toughest software challenges through collaborative design and development. Learn more about us.