Render Options in Rails 3 »

Created at: 18.01.2010 21:30, source: Engine Yard Blog, tagged: Technology ActionController json newsletter rails Rails 3

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{}, "<style>#{css}</style>")
 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?


more »

JSONQuerying Your Rails Responses »

Created at: 23.06.2009 01:44, source: RailsTips - Home, tagged: javascript json jsonquery testing

In which I show how to use a Ruby implementation of JSONQuery to test JSON in Rails apps.

I’m writing an application right now that is really JSON heavy. Some of the functional tests are cucumber and some of them are just rails functional tests using shoulda.

I hit a point today where I wanted to verify that the JSON getting output was generally what I want. I could have just JSON parsed the response body and compared that with what I was looking for, but a little part of me thought this might be a cool application of JSONQuery.

JSONQuery provides a comprehensive set of data querying tools including filtering, recursive search, sorting, mapping, range selection, and flexible expressions with wildcard string comparisons and various operators.

The quote above is fancy and can be boiled down to “a query language for JSON”. If you want to read more about JSONPath and JSONQuery here are some posts:

Finding a Ruby JSONQuery Implementation

I knew there was a JavaScript implementation of JSONQuery and that Jon Crosby has been doing some cool stuff with it in CloudKit, but I couldn’t find a Ruby implemenation that didn’t require johnson.

After some googling and Github searching, I came across Siren. Siren was pretty much what I wanted, so I started playing around with it. I forked it, gem’d it and wrapped it with some shoulda goodness.

What I ended up with was pretty specific to my needs at the moment, but I post it here in hopes that it sparks some ideas.

Bringing It All Together

First, I added the following to my environments/test.rb file.

config.gem 'jnunemaker-siren',
            :lib     => 'siren',
            :version => '0.1.1',
            :source  => 'http://gems.github.com'

Then I added the following in my test helper (actually put it in separate module and file and included it but I’m going for simplicity in this post).

class ActiveSupport::TestCase
  def self.should_query_json(expression, string_or_regex)
    should "have json response matching #{expression}" do
      assert_jsonquery expression, string_or_regex
    end
  end

  def assert_jsonquery(expression, string_or_regex)
    json = ActiveSupport::JSON.decode(@response.body)
    query = Siren.query(expression, json)

    if string_or_regex.kind_of?(Regexp)
      assert_match string_or_regex, query, "JSONQuery expression #{expression} value did not match regex" 
    else
      assert_equal string_or_regex, query, "Expression #{expression} value #{query} did not equal #{string_or_regex}" 
    end
  end
end

The code is quick and dirty. The first thing you’ll notice is that assert_jsonquery actually uses @response.body, which means it can only be used in a controller test. I could easily expand it, but, like I said above, I just got it working for what I needed right now. The cool part is that now in my functional tests, I can do stuff like this:

context "on POST to :create" do
  setup { post :create, :status => {'action' => 'In', 'body' => 'Working on PB' }

  # ... code removed for brevity ...
  should_query_json "$['replace']['#current_status']", /Working on PB/
end

That is a really basic query. Trust me you can do a heck of a lot more. Check out the Siren tests if you don’t believe me.

Conclusion

Overall, working with siren was a little rough because I wasn’t familiar with JSONQuery syntax. Also, siren tends to return nil instead of a more helpful error message about my expression compilation failing, but I’m kind of excited to see how this works out in the long run.

What are you doing to test JSON in your apps? Does something like this seem cool or overkill? Just kind of curious.


more »