Ruby on Rails is a robust framework with a heavy following, and with good reason, it's incredibly agile and loaded with features. While fantastic when faced with a complicated project, it can quickly become beast-like with pages of tech specs that devours system resources and leaves your clients with hefty hosting bills. There are many schools of thought when it comes to Rails optimization but from my experience the best way to handle performance pitfalls is to anticipate them ahead of time. While each project is different there are many similarities that can be identified early, and developing with them in mind can save you a lot of time.
Template Partials
Rails template partials are a great way to organize elements in your views and keep your code as DRY as possible, but there is a caveat, they will evaluate on each page request and you'll notice a performance hit the more you add. Using partials for large blocks of elements is good but when you're iterating over several records I recommend using helpers, if possible. Helper methods will be cached making it a much faster alternative for keeping your code DRY. In the event you decide you would like to switch back, you can call always just call render from within the helper and you're back to using a partial.
def awesome_helper model render partial: —awesome—, locals: { model: model } end
Consider the following scenario, we have a search grid that is returning 50 videos and representing them by thumbnails with a hidden video element that displays on hover. Here are the results on a local box:
With a partial:
Completed 200 OK in 1435ms (Views: 1266.5ms | ActiveRecord: 91.9ms)
Using a helper method with nested content_tag methods:
Completed 200 OK in 791ms (Views: 629.9ms | ActiveRecord: 87.1ms)
One argument in favor of partials is that it's often assumed that you will be using fragment caching in order to not process the partial each iteration, but my view is the level of optimization achieved through the use of helper methods is too great to ignore in this instance.
Omit Session-specific Data from Templates for Caching
There have been many times I wish I would have taken this approach from the get go. Imagine the scenario from the example above, we're displaying a grid of videos as the result of a search and we want to have a modal for each one. What if we want to add a download button that only displays for logged in users? We could easily just put <% if user_signed_in? %> and be done with it. But what if we want to use fragment caching? We could create two keys for the cached fragment, one for logged in users and one for anonymous users, but what if we want to display user specific data? Using a javascript object is a great way to keep data that you can use to update your cached elements, especially if they are going to be hidden on page load.
Use pluck when you can
When loading records from the database, rails will select every column by default and instantiate your model. This means you'll have full access to all the attributes needed by your methods, but it will come at a price, in many cases, you'll load data you won't necessarily need in your views. Take our search grid example from earlier, if we only wanted to display a thumbnail with a link to the content we might only need to load three attributes id, title and thumbnail. Here are three ways that load the same data we need for our view:
user system total real Full Object 0.530000 0.070000 0.600000 (0.713438) Using Select 0.320000 0.010000 0.330000 (0.363063) Pluck 0.120000 0.010000 0.130000 (0.156688)
Using pluck in this scenario gives us quite a boost, though there is a caveat, each record will be returned as an array of values in the order of the arguments passed to pluck. We can reprocess the results to pass to the template like so:
In controller:
@videos = Video.pluck(:id,:title,:thumb).map { |c| { :id => c[0], :title => c[1], :thumb => c[2]} }
In view:
<% @videos.each do |video| %> <%= link_to video_path(:id => video[:id]), :title => video[:title] do %> <%= image_tag video[:thumb] %> <% end %> <% end %>
Mapping the values to a hash isn't necessary but helps with visibility. We could also map a new object with similar attributes to our model but completely stripped, this way our views would not need to be written specifically for plucked collections.
In controller:
@videos = Video.pluck(:id,:title,:thumb).map { |c| Plucked.new({ :id => c[0], :title => c[1], :thumb => c[2]}) }
In view:
<% @videos.each do |video| %> <%= link_to video_path(:id => video.id]), :title => video.title do %> <%= image_tag video.thumb %> <% end %> <% end %>
In most cases, we should always be using select/pluck when we can to lighten the load on the database layer, but when should we select and not pluck? When we need to access model methods. For example, our code above assumes the thumbnail url is stored in the db, in most cases, it will be the filename by itself and additional logic will build the path. In this instance, loading the object will be necessary.
Rails provides such an agile environment that it's easy to get carried away, being cognizant of areas where you can quickly tune your app will really help out when you realize your page load times aren't going to cut it.