0

I want to add a post render step to all rendered views from a centralized point in a rails app with the goal to remove certain keywords from all server responses.

So far I have found this Rails : post-processing a rendered view that shows how to render view as a string. - Nice.

Since I have a base controller I was thinking of adding some sort of before_render filter that would pass me the rendered_string to allow me to replace it and then render it from there. Here I got stuck and am now thinking whether this is at all possible. Some high-level code looks like this:

class BaseClass
  before_render :replace_some_key_words 

  def replace_some_key_words(rendered_string)
    render rendered_string.sub! 'foo', 'bar'
  end

(I found an implementation of before_render at http://qiita.com/cryeo/items/d116192fb355411f9008 but I am simply just not enought into rails to understand if what I want is possible from this)

Thankful for all suggestions.

Community
  • 1
  • 1
alfthan
  • 463
  • 4
  • 13
  • Way back (rails 1.x) you could do this with an after_filter - don't know if it still works (we use to render xml from the rails app and use the after filter to rewrite the response as html with xslt) – Frederick Cheung Sep 07 '16 at 21:15

1 Answers1

0

What if you tried some Rack Middleware? Questions here, here, and here seem to have answers that use this approach. With Rails 4.5, I found the body of the Rack::Response to be immutable, so this answer proved particularly useful. Here is the code I ended up with:

module AfterRender
  require 'rack'
  CONTENT_LENGTH = 'Content-Length'

  class SanitizeResponse
    def initialize(app)
      @app=app
    end

    def call(env)
      status, headers, response = @app.call(env)
      if response.present? and response.respond_to? :body
        new_response_body = sanitize_body(response.body)
        headers[CONTENT_LENGTH] = new_response_body.length.to_s if headers[CONTENT_LENGTH].present?
        new_response = Rack::Response.new([new_response_body], status, headers)
        return [status, headers, new_response]
      end
      return [status, headers, response]
    end

    def sanitizeBody(body)
      to_ret = body.dup
      to_ret.gsub!('string_to_be_replaced', 'replacement_string')
      to_ret
    end
  end
end

I check that response.body is defined to ensure no funkiness occurs while sending over files for my angular app (they use Sprockets::Asset instead of Rack::Response). I don't want to be sanitizing those files, anyway. We also only reset the headers['Content-Length'] if it was already defined, which is not likely the case if Transfer-Encoding is chunked. new_response could simply be an array (e.g. [new_response_body]), but I find it safer to send response out the same way it came in, as a Rack::Response. This documentation is helpful to understand how the return of call must be shaped (i.e. as an array).

To ensure this middleware is actually used, I placed it in a new file, found at app/middleware/after_render.rb. In my config/application.rb, I added require_relative '../app/middleware/after_render.rb' at the top and config.middleware.use AfterRender::SanitizeResponse inside the Application class. It looks something like this:

require File.expand_path('../boot', __FILE__)
require_relative '../app/middleware/after_render.rb'

require 'rails/all'

module MyModule
  class Application < Rails::Application
    config.autoload_paths << "#{Rails.root}/app"
    config.middleware.use AfterRender::SanitizeResponse
  end
end

Hope this helps!

Community
  • 1
  • 1
G Klausner
  • 11
  • 2