May 15, 2015 by Daniel P. Clark

Speed Up Your Rails Views By Deferring Changes

Nobody likes a slow website.  And the more notifications and content privileges you calculate beforehand the longer your web requests take to load and send.  The most efficient way to change this is to make as much of your site static as you can and defer your changes to after the page has fully loaded.  This will let you cache the static content for the fastest load time.

Let’s say you have an Inbox link on your site’s navigation menu and you want display the number of messages in the Inbox as a number next to the link.  If you have to hit the database, then calculate if the number is greater that 0 to show it, and then render the view; you’re slowing down your view too much.  This is just one thing.  As your site grows you’ll likely have much more dynamic content and you views will continue to slow down the more you add.

So lets look at the HTML view for the Inbox and count.

<a href="/inbox">INBOX</a>
<span class="label label-warning inbox_count_num"
style="font-size: 10px; padding: 2px 4px;">
  <%= inbox_count %>
</span>

The inbox_count could be from a Rails helper like as follows:

# app/helpers/application_helper.rb
module ApplicationHelper
  def inbox_count
    Message.where(user_id: current_user.id).count
  end
end

Since this value is prone to change every time the page is loaded it creates an issue for caching and adds overhead to loading the view.  What we want to do is have the value that changes inserted after the page is fully loaded.  We can do this simply by using Rails’ UJS to request what’s needed with Ajax after the page finishes loading.

First we simply remove <%= inbox_count %> from the page view.  That has removed our changing content for the navigation menu so now we can cache it on the server to speed up the user’s experience.  I won’t be covering caching at this time.  Notice we still have the span field that displayed the Inbox count still in the page view.  This is fine as this will be our target for JavaScript to insert the value with later.

Let’s create a controller for content handled by UJS.  Let’s George Bush it and call it countifications.  That way if we want to load other calculations later with it we can use the same resource.  (Although since anything could be loaded later you could name it whatever you prefer eg: post_loader)

# config/routes.rb
scope :ujs, defaults: { format: :ujs } do
  patch 'countifications' => 'ujs#countifications'
end

Make sure you restart any Rails server you may be running after you change the routes.

Next we’ll make a controller for it.

# app/controllers/ujs_controller.rb
class UjsController < ApplicationController
  def countifications; end
end

And then we create the JavaScript view for this resource.

# app/views/ujs/countifications.js.erb
var inboxCount = <%= inbox_count %>;
if (inboxCount > 0){ 
  $('.inbox_count_num').html(inboxCount).show()
} else {
  $('.inbox_count_num').hide()
} 

Here we take the value returned from the inbox_count helper and assign it to a JavaScript variable inboxCount.  Then if it’s greater than zero we write the HTML value inside the span tag with $(‘.inbox_count_num’).html(inboxCount) which also calls the jQuery show() method to make it visible if it’s hidden.  If the count is zero we just call hide() on the span tag with $(‘.inbox_count_num’).hide() .

Just so you know, the .html() method will let us insert any HTML into our target so you can even load other Rails views/partials in it with .html(“<%= j render(partial: ‘folder/html_partial’) %>”) .  So this gives a great user experience as the website itself loads quickly, and the dynamic content still appears.

The last piece we’re missing so far is the actual JavaScript in the main website to call our UJS countifications resource after the page loads.  For that we add this to the end of our application template.

# app/views/layouts/application.html.erb
<%= link_to ".", url_for({controller: "/ujs", action: "countifications"}), method: :patch, remote: true,
    class: "countifications", style: "position: absolute; top: -50px; left: -50px; background-color: white important!; color: white important!;" %>
<script>
  var target = ".countifications";
  var flt = ":first";
  <%= %[$(target).filter(flt).click();] if user_signed_in? %>
</script>

All the extra styling done on the link is to make it be outside of the view of the page so no one will ever see it.  The reason I’ve written the JavaScript as I have above is because I experienced some difficulties with quotation handling when passing quoted strings inside other quotations.  The %[] makes the inside text a string “$(target).filter(flt).click();” and target and flt are JavaScript variables I’ve predefined so I don’t have to deal with string quotations in this.  So this jQuery command will click the selected link if the user is signed in.  And the link is the first part we’ve written in which calls our UJS resource, loads it into the current page and executes the JavaScript on the same page.  That’s what makes UJS so awesome in Rails.

On my server I have countifications getting values for two targets from three different database queries and the load time for that UJS call is only 17 milliseconds.  But the real performance advantage is now being able to cache more of the website which makes everything load more quickly.  This has made my website run considerably faster.

CAUTION! Do not use jQuery or JavaScript’s document ready to execute the UJS Ajax request.  This has a N+1 peculiarity with it where for the number of pages you visit it will make that many (number of) Ajax requests on the resource.

Summary

It’s best to give the user an efficient experience when they visit your site.  Nothing makes people leave quicker than a site that makes them wait.  Feel free to look more into Rails’ UJS.  I first learned about it from Chris Oliver’s GoRails: jQuery UJS and Ajax video tutorial.  Also feel free to search my site for additional UJS content as I’ve written several other posts on it!

Please feel free to comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!
-Daniel P. Clark

Image by photophilde via the Creative Commons Attribution-ShareAlike 2.0 Generic License.

#ajax#Fast#jquery#load#load time#rails#Speed#ujs

Comments

  1. Sebastian Wilgosz
    May 16, 2015 - 9:28 am

    Ajax requests are fine and powerful feature, however if your site generates lot of traffic, generating to much additional request isn’t recommended. For counters I often prefer to add someghing like `notification_count` column to proper table.

    Then you can load counter really fast, and updating it after creating notification.

    • Daniel P. Clark
      May 16, 2015 - 9:37 am

      I’d be curious to see a heavy load comparison between the two.

      I know caching offers a huge performance boost. And content that should be checked each time should not be cached.

      • Chris Oliver
        May 19, 2015 - 12:01 pm

        Depends on what you’re doing and why. Facebook defers almost everything on their site and it ends up alright. When you’re running a bunch of separate services, you can distribute the load across various services rather than just your primary app. Obviously, this many AJAX requests makes a lot more sense when you’ve got the team and scale to support it and not as much sense if you’re doing this entirely on a single Rails app.

Leave a Reply

Your email address will not be published / Required fields are marked *