Passing flash messages back to client in json and html escaping

From the controller, I send json, which contains a flash message that is rendered in a custom flash via coffeescript. I want to allow <br>, <b>, etc. tags in the HTML rendered. I suppose if I don’t escape at the server, I’d have to send a flag akin to the html_safe so that the coffeescript knows if the HTML should or should not be escaped. Currently, my client code just inserts the HTML when rendering the dialog.

Here’s the coffeescript flash code I wrote below. It assumes that the msg is html (good one to refactor the name to htmlMsg)

This is an interesting design discussion, probably akin to this article: SafeBuffers and Rails 3.0

Options:

  1. Change param message in method postFlashMessage to htmlMsg, suggesting that caller should be sure to escape.
  2. Change the json passing of flash_message to client to have optional param html_safe, and change the flash method in coffeescript to always escape html unless msg passed is an object composed of:
    { msg: "Something to display in <b>Bold</b>."
      html_safe: true }

I think option 2 is better, as it’s very easy to forget the need to escape when rendering. Unfortunately, to change this, I’d have to scrub my code very thoroughly.

  1. Do option 2, but don’t do any escaping by default, requiring one to pass in the message as:
    { msg: "Something to display in <b>Bold</b>."
      html_safe: false }

The problem with #3 is that making html escaping the exception goes against the grain of rails.

  # type: warning, danger || error, notice, success
  # parentSelector: place to fill in flash, or else .flash-area
  @postFlashMessage: (message, type, flashAreaSelector) ->
    flashAreaSelector ||= ".flash-area"
    type ||= "notice"
    type = 'danger' if type == 'error'
    type = 'info' if type == 'notice'
    type = 'warning' if type == 'alert'
    html = """
        <div class="alert alert-#{type} alert-dismissable">
          <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
          #{message}
        </div>
      """
    $(flashAreaSelector).html(html)

My way to send json to client is to create js.erb file. If you have a controller UsersController with the action index you can have a view users/index.js.erb. In this file you can do :

{ message: "<%= escape_javascript(render 'index') %>" }

in users/_index.html.erb, you can add your view as a normal. The method j is an alias for escape_javascript.

Is it what you want?

@Guirec_Corbel, in this case, I have service objects that returning status to the controller via a class I call ControllerResponse. This class encapsulates the result of the service and is invoked via a JSON ajax call, and it should send back a JSON response.

Insteed to return JSON, you can return a javascript which will be executed. In a project, I have this action :

https://github.com/GCorbel/lescollectionneursassocies/blob/caf87fa78f4d3f99a99ffc28cc1bcdbfe24abec4/app/controllers/provider_services_controller.rb#L16-L29

As you can see, it respond to js with a this :

https://github.com/GCorbel/lescollectionneursassocies/blob/8ee640d9185325bffdc56c8c1f775d87f8c9f4a7/app/views/shared/error.js.erb

And it render this view :

https://github.com/GCorbel/lescollectionneursassocies/blob/8ee640d9185325bffdc56c8c1f775d87f8c9f4a7/app/views/shared/_error.html.erb

You can call it with a normal ajax request and it will show the message.

I think it’s something like what you want. Isn’t it?

@Guirec_Corbel Thanks for the detailed response. Your technique is super if you only need to display a flash message result. In my case, I needed to pass back data values as well as set the http status on the response, so that the Coffeescript client can take the appropriate action. I ended up creating this class, ControllerResponse, which the service objects pass back to the controller.


 # Return value object from Service Objects and the Models back to the controller.
 # Rather than throwing an exception from the Service Objects, this class will encapsulate any
 # error status to send back to the client.
class ControllerResponse
  attr_reader :flash_message
  attr_reader :flash_type
  attr_reader :http_status_code
  attr_reader :data
  attr_reader :redirect_path
  attr_reader :session_data

  # http_status can be either symbol or number
  # Pass in flash_message as a html_safe string if you do not want it escaped.
  def initialize(flash_type: :notice,
    flash_now: false,
    flash_message: "",
    http_status: :ok,
    data: {},
    redirect_path: nil,
    session_data: {})
    @flash_type       = flash_type
    @flash_now        = flash_now
    @flash_message    = flash_message
    @http_status_code = Rack::Utils::status_code(http_status)
    @data             = data
    @redirect_path    = redirect_path
    @session_data     = session_data
  end

  def self.symbol_to_status_code(symbol)
    Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol]
  end

  # Currently supports applying the:
  # 1. session_data
  # 2. flash, flash_type, and flash_now
  # 3. redirect_path
  # Does not apply the http_status_code, nor the data
  def apply(controller)
    @session_data.each { |k, v| controller.session[k] = v }
    if @flash_message.present?
      if @flash_now
        # NOTE: There is no need to escape, as Rails will do that unless the flash_message is html_safe
        controller.flash.now[@flash_type] = @flash_message
      else
        # NOTE: There is no need to escape, as Rails will do that unless the flash_message is html_safe
        controller.flash[@flash_type] = @flash_message
      end
    end
    controller.redirect_to @redirect_path if @redirect_path.present?
  end

  def set_flash(flash)
    # NOTE: There is no need to escape, as Rails will do that unless the flash_message is html_safe
    flash[flash_type] = flash_message if flash_message.present?
  end

  # Returns the html escaped version of the flash.
  # This method should be used when setting value in json.
  # In the case of setting the flash on the controller, there is no need to escape, as Rails will do 
  # that unless the flash_message is html_safe
  def flash_message_escaped
    if @flash_message.html_safe?
      @flash_message
    else
      CGI::escapeHTML(@flash_message)
    end
  end
end

I’m not sure to understand the problem. You have some action in a javascript file which do an ajax call. In a callback, you want to display a message which can contain html. The callback can also change some properties of the object. Am I right?

The first thing I think is to change the properties in a js.erb file like this :

// show a message
myObject.doSomething(withValue)

You have access to your object in rails callbacks.

Second thing. Why you don’t always escape text like this :

"test\<br\>".html_safe #will send "test<br>"
"test<br>".html_safe #will send the same text

What is the problem with those solutions?

@Guirec_Corbel, those will work. However, when I need data return to set values or to conditionally take some action, I prefer to have this code with the request, rather than with the js.erb file.