Using Sinatra Helpers to Clean Up Your Code

Image courtesy of Shutterstock

A couple of months ago, I wrote an article about how I rapidly built my personal site using Sinatra. While I was building the site I started thinking about the best way to add JavaScript files to the pages. After playing with it for a while, I ended up using some custom helper methods to add JavaScript files to any page.

It’s actually very easy to add javascript files to pages in Sinatra using the layout file. For example, if you want to include JQuery and a custom JavaScript file (called application.js) on all pages, then all you need to do is put the following lines of code in your layout file:

(This assumes that the Javascript files are in the public folder).

Putting this code in the layout file means that they will be included on every page. However, some JavaScript files might only be needed on certain pages. For example, you might only want the file email.js to be loaded on the contact page. It turns out that there’s a simple way to add things dynamically to layout files – instance variables. These can be set in the routes and then refererred to in the views. I use the instance variable @js to add a custom JavaScript file to a route:

I can then add an extra line to my layout file that will include the relevant script tag if the @js variable has been set.

This is a big improvement. It means that you can add a javascript file on a route-by-route basis. The only problem is that you can only add one file per route. To get round this, you can change the instance variable to an array of JavaScript files:

This also requires the line in the layout file to be changed so that it iterates through each string in the array and adds a script tag for each JavaScript file:

So far, so good – we are now able inject custom JavaScript files into the layout of individual routes. Since we’re looping through all of the custom JavaScript files in the @js array, why not just add scripts that will appear on all pages (application.js and JQuery) to the array as well? This can be achieved by creating another array, using Sinatra’s settings method, called settings.javascripts. This array can be placed anywhere in your ruby file (I like to keep it near the top) and contains all the JavaScript files that are to be included on all the pages:

These two arrays need to be added together to create one big array to iterate over. The uniq method is used to avoid any repetition in case a file gets added to the @js array that has already been placed in the settings.javascripts array:

This amount of logic in a view looks messy, which is always a bad sign. The best way to deal with lots of view logic is to abstract it into a helper method. Helpers are easy to set up in Sinatra, you simply open up a block and place the methods inside. These methods are then available inside routes and views. Here’s a helper that can be used to clean up the layout:

Now all that’s needed in the layout file is this line:

Much cleaner.

Since we’re now using a helper method, why not allow it to have arguments? This would allow us to add JavaScript files from within the layout file like before. This doesn’t take too much extra effort – adding an asterix before the name of the method’s parameter allows the method to take as many arguments as required. This means that we can add as many JavaScript files as we need for each route. Each argument forms part of the args array, which can now simply be added to the big array of JavaScript files that is iterated over in the layout (a bonus here is that we don’t have to check if it exists as it defaults to an empty array if no arguments are given):

Now we have three ways to add JavaScript files to a page:

  1. In the settings (this is for JavaScript files that are global to the whole site)
  2. In the layout file using the javascripts helper. This is used for JavaScript files that are common to all pages using this layout.
  3. In the handler using the @js instance variable. This is used to add JavaScript files on a route by route basis.

It may seem at first that methods 1 and 2 are virtually the same thing – a way to add global JavaScript files. But it is not uncommon to have different layouts for a site and it may be likely that each layout will require specific JavaScript files. The settings.javascripts array should only be used for truly global JavaScript that are used throughout the site. The javascripts helper can be used to add JavaScript files on a layout by layout basis.

We can improve on this further by using a helper method to set the @js instansce method. Rather than setting it directly in the handler, we can use a helper method called js that could be used to add any custom js files that are required for that route. This is also a good opportunity to clean up the javascripts helper method as it’s looking a bit brittle (it assumes that settings.javascripts exists at the moment).

Our new js helper method means that things look a bit neater when called in the handler. This is especially true since the files are no longer required to be placed inside an array:

To finish off, let’s make it easier to add JQuery and other popular libraries when they’re required. This involves writing another helper method that uses shortcut symbols in place of the full url:

Now if you want to add a specific library all you need is the following code inside a route handler:

Notice that custom JavaScript files can now be entered as symbols and don’t need to have ‘.js’ appended to the end, as this is all done in the path_to helper method.

With the helper method working how we want, it’s time to go modular and move it into a separate file. We first need to create a file called javascripts.rb and save it in a folder called ‘sinatra’ in the root directory of the app. All of the code inside the helpers block is then placed inside a module called JavaScripts. This module is then placed inside a module called Sinatra (which is considered best practice when extending Sinatra with modules). The last thing to do is make sure to require Sinatra::Base at the top of the file.

The line helpers JavaScripts right at the end of the Sinatra module registers the helper methods so that they are available for use in the application.

To use these helper methods, all you need is the following line of code in the main application file (main.rb):

We now have a convenient little helper that allows us to easily add JavaScript files exactly where they’re needed. A nice touch is that JavaScript files can also be added on a conditional basis in route handlers, like so:

More Helper Methods

The method outlined above is not limited ot JavaScript files. A similar method could be used for adding CSS in a fine-grained way:

I’ve taken the whole concept further and built a module called HeadCleaner that has a helper method for all the common items found inside the head of an html page such as meta, title, webfonts etc. This means that you can just use method calls to create a very minimal looking head section of the layout file such as the one shown below:

You can see the code here.

It’s by no means perfect and I’d appreciate any feedback or help in developing this further (fork the code on github!). Ideas that I have for developing this are:

  • Allow users to set the path to javascripts
  • Have a separate yaml file with the shortcuts to external libraries in it so that it can be easily edited
  • Integrate with a JavaScript loader rather than having a separate script tag for each JavaScript file.

I hope these helpers are, well, helpful. If you’ve developed any helpers in your Sinatra development, do tell in the comments. Cheers!

(Main Image courtesy of Shutterstock)

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.linkedin.com/in/web2samus Samus_

    be careful with the extensions, if you include .js on the instance variables do not add it on the source when reading them.

  • http://padrinorb.com Nathan Esquenazi

    Anyone interested in this article should check out padrino helpers which takes this to the logical conclusion giving you all the helpers you might need in a sinatra app: http://www.padrinorb.com/guides/application-helpers http://www.padrinorb.com/guides/standalone-usage-in-sinatra

    • baiki

      cool. will try

  • vinicius gati

    nice and tanks,

    btw u misspelled the variable name

    def js *scripts
    @js ||= []
    @js = args
    end

    should be

    def js *scripts
    @js ||= []
    @js = scripts
    end

    • http://daz4126.com/ Darren Jones

      Thanks for spotting this Vinicius, I’ve updated it now!

      DAZ

  • jec006

    Just a note – all the strings like:

    “”

    in the gists actually have both internal ” escaped like

    “”

    Gist is just hiding them