Getting jasminerice & requirejs-rails to play nicely together

Published:

Jasminerice uses an isolated Rails engine to display the Jasmine spec runner. Unfortunately, this means it cannot see requirejs-rails is included in your project. Adding #= require.js to the spec/javascripts/spec.js.coffee file will cause problems because it cannot read your config/requirejs.yml configuration file. Nightmare!

So how do we fix this, whilst maintaining a nice DRY setup where Jasmine is setup with RequireJS using the options you’ve set in the configuration file?

The Jasminerice spec controller attempts to include a helper called Jasminerice::HelperMethods, which doesn’t actually exist, but as Brad has wrapped it in a begin … end block, does not error. We can start by including requirejs by placing the code below in lib/jasminerice/helper_methods.rb:

require "requirejs-rails"

module Jasminerice
  module HelperMethods
    def self.included(base)
      base.class_eval do
        helper RequirejsHelper
      end
    end
  end
end

Note: ensure the lib path is being auto-loaded by Rails (see your config/application.rb).

We can now overwrite the view for the engine by creating the file app/views/jasminerice/spec/index.html.erb with the following contents:

<!doctype html>
<head>
  <title>Jasmine Spec Runner</title>
  <%= stylesheet_link_tag "jasmine", "spec" %>
  <%= requirejs_include_tag %>
  <%= javascript_include_tag "jasminerice", "spec" %>
  <%= csrf_meta_tags %>
</head>
<body>
</body>
</html>

The key line above is the requirejs_include_tag call, which must be above the other JavaScript includes.

Note: with this setup, you do not need to follow the instructions provided by jasminerice, they are designed for vanilla requirejs usage - not usage with the requirejs-rails gem.

You can now write your specs as if they were normal JS or CoffeeScript files using the modules, here’s an example:

require ["app"], (App) ->
  describe "App", ->
    it "should have a method called 'boot'", ->
      app = new App()
      expect(app.boot).toBeDefined()

Whilst this might be a pretty rare setup, it may be of use to some people, and hopefully RequireJS usage and JavaScript testing will become more common in the Rails community.

IE, Media Queries and SASS

Published:

Media queries are what make responsive design possible; the ability to adjust the applied CSS styles based on the width of the viewport, however, as with most helpful web technologies, Internet Explorer 8 and below doesn’t support it.

You can use a variety of JavaScript-only hacks to get it working, but using SASS we can improve the situation a little.

For our modern browsers, we can follow the mobile-up technique, starting with a small screen and building styles up to the larger screen, as seen in 320 and up by Andy Clarke.

For Internet Explorer, we’ll assume the user is browsing full-screen (as most do), however if they have JavaScript enabled, a quick snippet will allow us to adjust the styles as if media queries were supported.

HTML

First of all we’ll need to adjust the <head> section of your HTML:

<!doctype html>
<!--[if lte IE 8]> <html class="unknown-size" lang="en"> <![endif]-->
<!--[if gt IE 8]><!--> <html lang="en"> <!--<![endif]-->

<head>
  <!-- usual <title> tag etc... -->
  
  <!--[if gt IE 8]><!-->
    <link rel="stylesheet" type="text/css" media="screen" href="stylesheets/mq-enabled.css" />
  <!--<![endif]-->
  <!--[if lt IE 9]>
    <link rel="stylesheet" type="text/css" media="screen" href="stylesheets/mq-disabled.css" />
  <![endif]-->

We’ll use the ‘unknown-size’ class for JavaScript to identify when it should add a shim to calculate the width.

Notice that two stylesheets are used, one for IE 8 and below, one for modern browsers (and IE 9). Except, thanks to SASS we won’t be writing two sets of code.

SASS

We’ll use includes to process each size, here’s our main ‘enabled’ SASS file:

@import "layout/_base.sass"
@media only screen and (min-width: 480px)
  @import "layout/_480.sass"
@media only screen and (min-width: 640px)
  @import "layout/_640.sass"
@media only screen and (min-width: 960px)
  @import "layout/_960.sass"
@media only screen and (min-width: 1120px)
  @import "layout/_1120.sass"

You can adjust the sizes as required. SASS will compile all 6 of these files into one single CSS file.

The underscore prefixes used in SASS file names indicate that SASS shouldn’t process the file into an output.

So now, how do we deal with IE? Well for starters, we’ll pass all styles to the ‘unknown-size’ class:

@import "layout/_base.sass"
.unknown-size
  @import "layout/_480.sass"
.unknown-size
  @import "layout/_640.sass"
.unknown-size
  @import "layout/_960.sass"
.unknown-size
  @import "layout/_1120.sass"

Now we can add in our classes for the particular sizes that will be set via JavaScript:

@import "layout/_base.sass"
.unknown-size, .four-eighty, .six-fourty, .nine-sixty, .eleven-twenty
  @import "layout/_480.sass"
.unknown-size, .six-fourty, .nine-sixty, .eleven-twenty
  @import "layout/_640.sass"
.unknown-size, .nine-sixty, .eleven-twenty
  @import "layout/_960.sass"
.eleven-twenty
  @import "layout/_1120.sass"

These classes can be called anything, I’ve merely chosen to use the English wordings for each number.

You may note that the last class, .eleven-twenty, doesn’t have the .unknown-size class as an alternate selector, this is because we’re only assuming people browsing with IE 8 and below (without JS enabled) are using browsers around 1024px wide - a more safe assumption than 1120px upwards, however feel free to play around with these figures to get the right adjustments for your website.

Now to finish up, we can handle those classes via JavaScript.

JavaScript

Note: requires JQuery, however should be straight-forward to rewrite in pure JS.

var adjust_dimension_class = function() {
  // load the HTML element, and window width
  var $html = $('html'),
      width = $(window).outerWidth()
  // remove all classes
  $html.removeClass('eleven-twenty nine-sixty six-fourty four-eighty')
  // add the one for the right size
  if (width >= 1120) {
    $html.addClass('eleven-twenty')
  } else if (width >= 960) {
    $html.addClass('nine-sixty')
  } else if (width >= 640) {
    $html.addClass('six-fourty')
  } else if (width >= 480) {
    $html.addClass('four-eighty')
  }
}

$(function() {
  // load the HTML element
  var $html = $('html')
  // if is needs to know it's size
  if($html.hasClass('unknown-size')) {
    // remove the class
    $html.removeClass('unknown-size')
    // update it now
    adjust_dimension_class()
    // watch for browser resizes
    $(window).resize(adjust_dimension_class)
  }
})

The JavaScript will adjust which class is on the root HTML element, allowing the ‘disabled’ CSS to know which styles to apply.

Example

I’ve hooked up a small example, which you can access here, you can also download an example skeleton of files.

If you download my skeleton, all you need do is go ahead and manage your styles in the stylesheets/layout/*.sass files.

The desktop web experience on mobile with responsive design

Published:

An example of a responsive website viewed on an iPhone

When offering mobile versions of websites, you can often anger users who are wanting the full desktop experience and are more comfortable zooming around as they wish. Because of this, most mobile sites offer a toggle to the desktop version.

1 out of every 3 people coming to sites via mobile devices chose to view the full site, when that option was available.

Source: Strangeloop

With responsive design, Paul Boag has argued (and rightly so) that by taking mobile browsing into account as you’re building a site, this should avoid issues with content or navigation being missing, however until people become comfortable that differences in mobile and desktop browsing will occur, offering a toggle between mobile and desktop browsing can be helpful.

Toggling responsive design

So can we achieve the best of both worlds? A responsive site, with a toggle to turn off the relevant media queries? Yes, and the solution is rather simple.

As media queries work the same as any other CSS, we can effectively namespace our styles using a mobile-on class on the <body> element. This class will then be toggled to mobile-off using JavaScript, thus deactivating the relevant media queries.

/* turn off the toggle for large screens */
#mobile-toggle { display: none }

@media only screen and (max-width: 540px) {
  /* show toggle for small screens - regardless of toggle */
  #mobile-toggle { display: block }
  /* add our responsive changes when the toggle is on */
  .mobile-on { /* your styles */ }
}
<p id="mobile-toggle">
  <a href="javascript:document.body.className=document.body.className=='mobile-on'?'mobile-off':'mobile-on';">
    Toggle Mobile Styles
  </a>
</p>

I’m aware this code isn’t the most unobtrusive, but for the purposes of this experiment, it’ll do for now.

Warning: From experience there can be slight hiccups in iOS. When toggling back to mobile from non-mobile, I found that font-size was not always recalcuated.

Amazon CloudFront CDN on Rails

Published:

There are many benefits to using Content Delivery Networks (CDNs), but complexity of implementation or deployment is typically cited as a disadvantage of their use. Thankfully, for a while now, Amazon’s CloudFront has supported a feature called ‘origin pull’, this is a common feature across different CDN brands and what this effectively does it is creates a mirror from your own hosting - pulling content from your own origin server. This means you don’t have to worry about synchronising your files with the CDN’s storage system (e.g. S3).

The only trouble is, file expiry… or rather how does the CDN know when your file has been updated?

CloudFront has an expiry API, however it’s not instant, it’s not free, and it’ll be a whole lot of additional work to implement. Now that Rails has the asset pipeline, there is a much more elegant solution: let the file paths indicate new versions.

As part of the compilation Rails injects a hashed timestamp into the path, e.g. /assets/application.js may become /assets/1234567890abcdef/application.js, upon deployment, those random strings will change meaning the CDN will pull in the updated file.

Note: Older versions of Rails used timestamps in the query parameters of files e.g. /javascripts/application.js?123456789. Unfortunately many CDNs (including CloudFront) and HTTP accelerators will strip these from the URL so if you’re running a version of Rails before the asset pipeline was implemented, you may need to install something like asset_fingerprint.

Origin Setup

I find it’s best to setup a CNAME to your application first, so in your DNS setup the origin subdomain (e.g. origin.example.com) to point to your app’s main domain (e.g. example.com). You can then update your Rails rails_root/config/environments/production.rb to test this domain is working:

config.action_controller.asset_host = "//origin.example.com"

Note the // prefix, to browsers this indicates they should use the same protocol for the request as the HTML page, so if a user is on the HTTP version of your site, it will connect to http://origin.example.com/ for the files, and on the other hand, if they are connecting over HTTPS, it will connect to https://origin.example.com/.

Before deploying the update, you should update your web server configuration, the official Rails guides site has a guide on how to configure both Apache/Nginx for the asset pipeline.

You should now be able to deploy and verify the origin sub-domain is working.

CDN Setup

Once you’ve verified the origin asset host is working, you can setup the CDN mirror in your CloudFront control panel. When logged in, you’ll need to create a new ‘distribution’:

CDN Distribution Setup

You’ll need to select ‘Custom Origin’ to use your own asset host, rather than S3. Add your subdomain as you defined earlier.

If you want to allow HTTPS connections, you’ll need to select ‘Match Viewer’ as the ‘Protocol Policy’.

Access Setup

In the second step, you simply need to enter your chosen CDN subdomain - this will be setup like your origin except it will point to Amazon’s servers, not your own, in the above example I’ve entered cdn.example.com.

Note: CNAMEs will not work if you require HTTPS support, so in that case, leave the ‘CNAMEs’ field blank.

CDN Distribution Details

You should now see your distribution is being setup (see right). So now we need to update our Rails asset_host. If you require HTTPS you’ll need to use the subdomain provided by Amazon e.g:

config.action_controller.asset_host = "//d1aar6ipvvlxt7.cloudfront.net"

If you don’t need HTTPS, you can use your own subdomain:

config.action_controller.asset_host = "http://cdn.example.com"

Note: in this case, you’ll need to setup this subdomain (cdn.example.com) to point to the subdomain Amazon provided (d1aar6ipvvlxt7.cloudfront.net).

There you have it, your site is now serving ultra-fast static assets from Amazon’s local CDN edge-servers, without the need for any further maintenance from you.

Unobtrusive object deletion in Rails, the easy way

Published:

Out of the box, Rails only has one route to the destroy action of each controller (when using RESTful resources) - see below.

resources :posts
    posts GET     /posts(.:format)           { :action=>"index",   :controller=>"posts" }
          POST    /posts(.:format)           { :action=>"create",  :controller=>"posts" }
 new_post GET     /posts/new(.:format)       { :action=>"new",     :controller=>"posts" }
edit_post GET     /posts/:id/edit(.:format)  { :action=>"edit",    :controller=>"posts" }
     post GET     /posts/:id(.:format)       { :action=>"show",    :controller=>"posts" }
          PUT     /posts/:id(.:format)       { :action=>"update",  :controller=>"posts" }
          DELETE  /posts/:id(.:format)       { :action=>"destroy", :controller=>"posts" }

The trouble with this is handling deletion, you need to perform a DELETE request which requires a form to be submitted, rather than a link to be clicked, which would perform a GET request. Handily, Rails includes an option on link_to allowing you to specify a :method, this is then enhanced by JavaScript to perform the deletion.

link_to 'Delete', @post, :method => 'delete', :confirm => 'Are you sure?'

This all looks great until you realise it won’t work without JavaScript. Now for some people, this is a moot point, but personally, I want my applications to be as widely accessible as possible and it also means if there is a slight bug in your JS code, the deletion won’t work.

Simple fix

The solution comprises two parts - handling when JavaScript is unavailable (disabled / erroneous) and then handling the changes to when it is available.

First of all, we’ll need a new route within our controller, this will render the deletion form to HTML:

resources :posts do
  get :delete, :on => :member
end
delete_post GET  /posts/:id/delete(.:format)  { :action=>"delete", :controller=>"posts" }

Our action should follow the same pattern as your edit action, below is an example.

Note: this action SHOULD NOT process the deletion, merely display a form (a confirmation) which the user must submit manually. Otherwise you will run into CSRF attacks.

def delete
  @post = Post.find(params[:id])
end

Now, we’ll need a simple form for this action, much like your edit form, so create delete.html.erb:

<%= form_for(@post, :url => { :action => :destroy }, :html => { :method => :delete }) do |f| %>
  <p>Are you sure you want to delete the post entitled '<%= @post.title %>'?</p>
  
  <div class="footer">
    <%= link_to 'No', :back %>
    <button type="submit">Yes &raquo;</button>
  </div>
<% end %>

Now we can update our link_to to point to this form:

link_to 'Delete', delete_post_path(@post), :method => 'delete', :confirm => 'Are you sure?'

The trouble we have now, is that if the user does have JavaScript enabled, it will perform a DELETE request to our delete action, which isn’t a valid route, nor does the action handle deletion. To get round this, we just need to handle that route, but redirect it to the existing destroy method:

resources :posts do
  get    :delete, :on => :member
  delete :delete, :on => :member, :action => :destroy
end
DELETE  /posts/:id/delete(.:format)  { :action=>"destroy", :controller=>"posts" }

Now, if a user has JavaScript, the deletion will happen as usual: using JavaScript, Rails will create a delete form and submit it to the destroy method. If they do not, it will render the delete confirmation form.