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? 🚀
Learn how we help our customers to reduce their time to market by prototyping on static pages, not on the real product.
Read full articleIn 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 articleProject 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