Back

Speeding up views rendering in Rails 4

Captain's log, stardate d69.y40/AB

MarsBased Rails Ruby on Rails Development
Oriol Collell Martín
Backend developer
Speeding up views rendering in Rails 4

It is well known that rendering views and partials in Rails is somewhat slow compared to other frameworks. In one particular part of the Rails migration for a large corporate client of ours, I had to migrate an endpoint rendering ~ 200 views. Using the standard render approach, the request took ~ 23s. Now, it's down to ~ 300ms.

Want to know how I did it? Keep on reading! 👇

This is Rails 4, so the standard approach to render a view outside of a controller is this:

renderer = ActionView::Base.new(ActionView::Base.paths, {})
render.render('dir/view_name')

I used RubyProf to analyse this piece of code and it was taking ~ 150ms for each view just to find the file to read from the filesystem! Under the hood, ActionView builds a query to use with the _Dir_ method in Ruby in order to locate the file. The problem is that the query contains all the possible extensions and handlers, something like this:

dir/view_name{.js,.html,.txt,.yml,.foo,.bar,.whatever,.more,.extensions}{.erb,.haml,.whatever}

In order to make this query simpler, a custom LookupContext can be given, like this:

lookup_context = ActionView::LookupContext.new(
  ActionController::Base.view_paths,
  locale: [],
  formats: [:html],
  variants: [],
  handlers: [:erb]
)
renderer = ActionView::Base.new(lookup_context, {})

The arguments given to the object are self-explanatory. Basically we are telling ActionView to only search for .html.erb files, because in this particular scenario all the views are created this way. This already provided a massive improvement but we could still do better.

Under the hood, when we call render, ActionView initializes an ActionView::Template object and calls render on it. In order to build this object, it needs to find the file in the filesystem and do other operations. In this particular scenario, we already know the exact path of the view to read, so there is no need for Rails to do the effort. So, I ended up creating Template objects manually:

template_contents = File.binread(path_to_view)
handler = ActionView::Template.handler_for_extension(:erb)
ActionView::Template.new(template_contents, file_name, handler,
                         format: :html,
                         locals: ['var1', 'var2'])

renderer = ActionView::Base.new(lookup_context, {})
renderer.render(template: template, locals: { var1: 'foo', var2: 'bar' })

This piece of code basically skips almost all the rendering code and just evaluates the file with ERB to produce the output. The locals parameter given to the instance needs to match the keys of the locals hash passed to the render call. The rest of parameters are self-explanatory.

Pretty cool stuff, isn't it? 🚀

Share this post

Related Articles

Speed

How to do agile prototyping using static pages to reduce development time

Learn how we help our customers to reduce their time to market by prototyping on static pages, not on the real product.

Read full article
Reduce everything! Explaining the Reduce function

Reduce everything! Explaining the Reduce function

In this post, we explain how the reduce function works and how it can make your development easier, as it is available in all modern programming languages.

Read full article
How I use Docker for Rails development: running services

How I use Docker for Rails development: running services

Project setup can be a very cumbersome process for developers. In this blog post, our developer Dani explains how he uses Docker to develop in Rails

Read full article