Blog

Render Options in Rails 3

By | January 18th, 2010 at 11:01AM

This article was originally included in the October issue of the Engine Yard Newsletter. To read more posts like this one, subscribe to the Engine Yard Newsletter.

In Inside Rails, Yehuda Katz, Rails expert and core team member, and Carl Lerche, Rails expert and full-time contributor, present expert advice and insight on the Rails platform and Rails development.


In previous versions of Rails, adding a new rendering option to Rails required performing an alias_method_chain on the render method, adding your new options, and hoping they didn't conflict with any of the other code in the rendering pipeline.

Rails 3 makes rendering options a first class citizen, and uses the same new system internally that plugins authors are expected to use. Before we get into how you can use this feature yourself, let's take a look at how Rails uses it:

ActionController.add_renderer :json do |json, options|
 json = ActiveSupport::JSON.encode(json) unless json.respond_to?(:to_str)
 json = "#{options[:callback]}(#{json})" unless options[:callback].blank?
 self.content_type ||= Mime::JSON
 self.response_body = json
end

Here, we are creating a new render :json option, which behaves exactly like render :json in Rails 2.3. In the render_json method, we use the same lower-level (but still public) APIs that are used by Rails itself to set the MIME type and response body. Using a render option entirely skips the rest of the render pipeline, so you don't have to worry about blocking normal template selection and rendering, or any other internal changes that might break your added option.

Next, let's take a look at adding a new render :pdf option. We'll use JRuby and the Flying Saucer library, which takes HTML and CSS and converts it to a PDF file. The syntax for the new option will be render :pdf => "template_name", :css => %w(main.css print.pdf), :layout => "print".

In order to understand how to do this, let's first take a look at the code necessary to use Flying Saucer in JRuby. First, you'll need to grab the Flying Saucer jars (you can get them from my Muse git repo). Next, let's write the code necessary to convert HTML and CSS to PDF with Flying Saucer:

# Both of these are .jar files
require "/path/to/itext"
require "/path/to/core-renderer"
 
module PdfUtils
 def self.string_to_pdf(input_string)
  io = StringIO.new
  renderer = org.xhtmlrenderer.pdf.ITextRenderer.new
  renderer.set_document_from_string_input input_string
  renderer.layout
  renderer.create_pdf(io.to_outputstream)
  io.string # the PDF file in a String
 end
end

It's a bit verbose, but it does work. Now that we have a way to make the PDF, let's wire it up into a render option.

ActionController.add_renderer :pdf do |template, options|
 css_files = Array.wrap(options.delete(:css))
 css = css_files.map {|file| File.read(file) }.join("\n")
 
 # Reuse the render semantics to get a string from the
 # template and options
 string = render_to_string template, options
 
 # Drop in the CSS before the  in a style tag.
 # In practice, you would probably cache the file reads
 # and the merging of the CSS and HTML.
 string.gsub!(%r{}, "
<!--
#{css}
-->
 
")
 send_data PdfUtils.string_to_pdf(string), :type => Mime::PDF
end

You'd also need to register the PDF mime type:

Mime::Type.register "application/pdf", :pdf

You could now do the following in a controller:

class PostsController < ActionController::Base
 def show
  @post = Post.find(params[:id])
 
  respond_to do |format|
   format.html
   format.pdf
   # or
   format.pdf { render :pdf => "show", :css => %w(application print) }
  end
 end
end

The tricky bits of this process are now reserved to figuring out how to build and return the output, not how to inject your option into render. Pretty cool, no?

  • Tony Issakov

    Very Cool and inspiring.

    One note. I just did some trials and found the call:

    renderer.set_document_from_string_input input_string

    needed to drop the input at the end to:

    renderer.set_document_from_string input_string

  • http://programming.irip-ivf.com/blog/2010/02/11/rails-3-0-beta-36-links-and-resources-to-get-you-going/ Rails 3.0 Beta: 36 Links and Resources To Get You Going « Особое программирование

    [...] Render Options in Rails 3 – Yehuda Katz looks at how Rails 3’s rendering pipeline works and demonstrates how to add a custom PDF renderer to it. [...]

  • Sebastian

    Has the API for this since changed? Adding ActionController.add_renderer to an initializer throws an error: "undefined method `add_renderer' for ActionController:Module"

  • Jakob S

    Dependent on when you try to use the method, ActionController might not be fully loaded. You should be able to require 'action_controller/base' – or perhaps require 'action_controller/metal/renderers' if you want to be more specific.

    Alternatively, you can use ActionController::Renderers.add, which is effectively the same method, and the extra module looks like it triggers some of Rails' autoloading magic.

  • http://psychedeli.ca/ Tom

    where is the best place to plop this code? will it work if I put it in ApplicationController? it would certainly ensure that ActionController is loaded… :)

  • Gleb Mazovetskiy

    Can’t seem to make it work (Rails 3.0.5). I created a test case and SO question: http://stackoverflow.com/questions/5284443/rails-custom-renderer

  • Gleb Mazovetskiy

    config/initializers/my_renderer.rb

  • http://twitter.com/glebm Gleb Mazovetskiy

    I found out that in Rails > 3.0 you have to add the following lines for format.pdf to work:

    class ActionController::Responder
      def to_pdf
        controller.render :pdf => controller.action_name
      end
    end
    

    You can read more on this in my blog post (I show how to integrate xlsx rendering): http://glebm.blogspot.com/2011/03/rendering-xslx-from-rails-3-without.html

    @ Yehuda Katz: please update your blog post

  • glebm

    I’ve written a gem for this: http://glebm.blogspot.com/2011/05/annoucing-tospreadsheet-gem.htmlIt renders .xls and .xlsx, not .pdf but take a look at the source — it’s a much improved and clean version of a custom renderer based on this post

  • glebm
  • http://www.blog.roshanbook.com/2011/11/23/ruby-on-rails-resources/ Ruby on Rails resources | Roshan Book

    [...] Render Options in Rails 3 – Yehuda goes through the rendering options available in Rails 3. [...]