Tuesday, June 10, 2008

Using Google Ajax Libraries API with Ruby on Rails


Intro

The new Google Ajax Libraries API can help you offload bandwidth from your site. Their servers are configured with gzip, expires headers and e-tags, which leads to good scores from YSlow. I will show two ways to use Google's new API to offload Ruby on Rails javascript (prototype and scriptaculous). In both solutions, your existing calls to javascript_include_tag can be left the same, because all of the magic is handled beneath the covers.


Implementation #1: Create a module
This first example is a module that chains the expand_javascript_sources
method found in ActionView::Helpers::AssetTagHelper. If prototype or a scriptaculous file is loaded via javascript_include_tag, we will replace the normal functionality with a path to the file on Google's servers. Start by creating the file /config/initializers/google_asset_tag_helper.rb. Paste in the following:

 1 module ActionView
2 module Helpers
3 module GoogleAssetTagHelper
4 GOOGLE_PATHS = {
5 'prototype' => 'http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/prototype',
6 'controls' => 'http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.1/controls',
7 'dragdrop' => 'http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.1/dragdrop',
8 'effects' => 'http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.1/effects'
9 }
10
11 def self.included(base)
12 base.send :alias_method_chain, :expand_javascript_sources, :google unless ActionController::Base.consider_all_requests_local
13 end
14
15 def expand_javascript_sources_with_google(sources)
16 google_sources, normal_sources = sources.partition { |source| GOOGLE_PATHS.include? source }
17 GOOGLE_PATHS.values_at(*google_sources) + expand_javascript_sources_without_google(normal_sources)
18 end
19 end
20 end
21 end
22
23 ActionView::Base.class_eval do
24 include ActionView::Helpers::GoogleAssetTagHelper
25 end


This defines GoogleAssetTagHelper, which chains expand_javascript_sources. The module is included into ActionView::Base. Notice that on line 12, the original method is only aliased if ActionController::Base.consider_all_requests_local is false. Since this is set to true in development mode, it is a sufficient way to determine whether or not Google AJAX Library should be used.
If using the remote libraries in development is desirable, the condition can be safely removed.


Implementation #2: Assign lambda to ActionController::Base.asset_host
An alternative solution uses ActionController::Base.asset_host to determine the root path to the javascript files. In /config/environments/production.rb, you find:

1 # config.action_controller.asset_host = "http://some.asset.host/"

As of Rails 2.0.2, asset_host can a string or Proc. Since we want to conditionally change the asset_host based on what file is being include, a Proc is needed:

 1 google_paths = {
2 'prototype' => 'http://ajax.googleapis.com/ajax/libs/prototype/1.6.0.2/',
3 'controls' => 'http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.1/',
4 'dragdrop' => 'http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.1/',
5 'effects' => 'http://ajax.googleapis.com/ajax/libs/scriptaculous/1.8.1/'
6 }
7
8 config.action_controller.asset_host = Proc.new do |source, request|
9 google_asset = google_paths.keys.detect { |asset| source.starts_with?("/javascripts/#{asset}") }
10 google_asset ? google_paths[google_asset] : "#{request.protocol}#{request.host_with_port}"
11 end

In this example, the code assumes that you do not already have an asset_host defined. If you do, simply replace the false expression on line 10 with your original asset_host string.

(The variable google_paths is defined inside of production.rb for brevity. However, I recommend defining it as a constant elsewhere in your own project.)

Using the javascript_include_tag
As mentioned in the introduction, your existing javascript_include_tag calls remain the same:


1 javascript_include_tag 'prototype', 'effects', 'controls', 'application'



9 comments:

Gregg said...

Very cool, thanks for this code, I'm going to mention it on the next RailsEnvy.com podcast.

I think you should publish this as a plugin so people can just drop it into their application.

Matthew Higgins said...

Sweet.

I'll make the plugin and put it on github soon.

daniel said...

Christopher Warren already has a plugin on github. Maybe you can work with him?

Matt said...

anything know if there's similar available for jQuery?

Matthew Higgins said...

I could add jquery support. However, it's debatable as to whether that is appropriate. If you look at the plugin code, it would be trivial to revise it for jQuery.

Jauder Ho said...

Rather than doing this, why not just use the google.load() instead of monkey patching?

The javascript_include_tag is/was useful for redirecting to asset hosts but since this is hosted at Google, it makes no difference.

Matte E said...

It'd be great if the URLs could be modified to recognize an SSL website include. "https://" instead of "http://".

Matthew Higgins said...

@matt e - Thanks for pointing this out. I will add SSL support tomorrow morning. (Google added SSL support on July 29th. http://googleajaxsearchapi.blogspot.com/2008/07/ajax-libraries-ssl-support-is-live.html)

Matthew Higgins said...

FYI, the above functionality is available in the Googtaculous plugin.