Archive for January, 2007

Using jQuery in Rails (Part I)

This is the first in a series of posts about using jQuery together with Ruby on Rails. In my professional life, I work on Procore, a project management tool for the construction industry. The application is massive by any standard. And before I came on, it made heavy use of the Prototype library, Rails’ native JavaScript mechanism.

When I came onboard, I introduced the team to jQuery, and they loved it. But it was far too much work to backtrack and remove all Prototype instances and replace them with jQuery. So our massive, complex app uses both, with no difficulties. Both libraries live side-by-side, causing no problems.

In this series, I will discuss setting up jQuery in Rails, and some techniques I’ve developed to make working with jQuery easier in Ruby on Rails applications.

First up, installation:

You’ll need to download a working copy of jQuery from jQuery.com. You can either get the most recent release (recommended), or, if you’re particularly adventuresome, you can build your own copy of bleeding-edge jQuery.

Drop jquery.js into public/javascripts, and add <%= javascript_include_tag "jquery", "application" %> to your main layout file.

Your site will now be loading in jQuery, as well as application.js in every page (application.js is your application’s default JavaScript file and is created by default when you run the “rails” command). If you want to run Prototype alongside jQuery, use <%= javascript_include_tag :defaults, "jquery" %>. This will load in the default prototype files, application.js, and jQuery.

In application.js, add the following:

jQuery.noConflict()

That will call jQuery.noConflict(), which relinquishes control of the $ alias to Prototype.

jQuery Commands

Virtually all jQuery commands won’t function correctly unless they’re run after the DOM has fully loaded. jQuery’s way of solving this is something called a “document ready block.”

jQuery(document).ready(function() {
  // commands go here
})

This waits until the document is ready, the fires off the function passed to ready. Here’s a shortcut syntax:

jQuery(function() {
  // commands go here
})

This does exactly what jQuery(document).ready(…) does, but is shorter. The strategy for using jQuery in Rails is to treat JavaScript in much the way you treat CSS. You’re binding behavior, not style, to a particular set of elements identified by particular markup.

In CSS, you might give an “a” tag the class “external” to indicate that the link goes to a page external to your site. Through a site-wide CSS page, you can then indicate that you want the tag to use a special graphic (like in Wikipedia), to illustrate to users that the link is external.

Using jQuery, you can then indicate what behavior you want this link to have. For instance, you might the link to open in a new window. Inside the document ready block, you would use:

jQuery("a.external").click(function() {
  window.open(this.url, "_blank")
})

I will use jQuery, not $, throughout this tutorial series, because some of you will be using Prototype alongside jQuery in your Rails apps. If you’re not, feel free to convert any instance of “jQuery” to “$”

The above snippet will bind a click handler to all links with class “external,” and open them in a new window. And what’s great is that with JavaScript turned off, the link opens the page perfectly well, just in the same window.
Now, let’s say you like this a lot, and want a simple Rails way to create links with the appropriate markup throughout your app (without having to train all other developers on the new markup requirements).

You might create a simple helper in application_helpers.rb. Say, something like:

def external_link_to name, options = {}, html_options = nil, *parameters_for_method_reference
  (html_options ||= {}).merge!({:class => "external"})
  link_to name, options, html_options, *parameters_for_method_reference
end

As you can see, I’ve grabbed the call signature from link_to, made some modifications to html_options (added :class => external), and then fired off a call to the original link_to.

This is a very simple example, but this is the strategy we will use throughout this series: create some markup that is used to identify a particular behavior, instantiate the behavior via jQuery, and then, if desired, write a helper to make markup generation easier.

Next up: Create a sortable list using jQuery and Interface. We will look at how to create the markup, write a helper, and write a single, reusable controller method to use the returned results to update a model.

A better assert_select (assert_elements)

I’ve been using Rails since the days of assert_tag (an abomination that required far too much work to do very must testing for specific tags), which was powerful enough to take a look at the responses to gets and posts and make sure that they fit basic criteria.

Unfortunately, its syntax was far too verbose. Here’s an actual example from the Rails API:

assert_tag :tag => “div”,
:ancestor => { :tag => “ul” },
:parent => { :tag => “li”,
:attributes => { :class => “enum” } },
:descendant => { :tag => “span”, :child => /hello world/ }

As you can see, this is no fun, so I was really happy to see assert_select come into the picture, first as a plugin, and finally getting into the Rails core in Rails 1.2. Here’s what it does:

  • You can do assert_select “css_selector”
  • You can assert that there’s an exact number of matches, a minimum number, or a maximum number
  • You can narrow the set of matched elements by a specific text string (or match a RegExp)
  • You can pass assert_select a block, which contains more assert_selects and for which the root assert_select only returns true if the asserts inside pass for the collection of elements matched in the initial one.

It’s pretty cool, but it also has a two major drawbacks:

It makes a fundamentally wrong assertion about how it handles blocks. It tests all assert_selects in the block against the collection of elements matched by the initial selection. What that means is that if you want to test that at least one div on the page contains exactly two divs inside it, the basic block technique will fail:

assert_select “div” do
assert_select “div”, :count => 2
end

This will fail if you have two divs on the page each containing two div. That’s because the inner assert is being run against the set of all elements matched by the first assert_select “div,” which will find four elements (two divs for each matched div).

What you want instead is for assert_select to work if and only if the inner asserts pass for at least one of the matched divs.

The second drawback is that the plugin uses its own home-grown selector system, which is both slower than hpricot and, more importantly, more limited than hpricot. It uses CSS2 selectors, and has no support for things like ancestor selection (like the original assert_tag has).

Hpricot, on the other hand, supports full CSS3 as well as some basic XPath (pretty much the same stuff that jQuery supports; it seems to have started off as a Ruby port of jQuery’s selector engine).

Thankfully, Luke Redpath already did the initial work to make it possible to test Rails views with hpricot. He made it possible to do stuff like (his examples):

  • assert_equal “My Funky Website”, tag(’title’)
  • assert_equal 20, tags(’div.boxout’).size
  • assert_equal ‘visible’, element(’div#site_container’).attributes['class']

That’s really cool, and I was strongly considering leaving it at that and using Luke’s plugin, but there were some really cool things you could do with assert_select that you cannot do as easily with Luke’s plugin.

So I built on Luke’s plugin to create a new assert, called assert_elements:

  • It has identical syntax to assert_select
  • You can use full CSS3 and limited XPath syntax (hello again to ancestor support)
  • If you pass an assert_elements a block, it tests the results on each matched elements, and if all of the asserts return true at least once, the entire assert_elements passes.
  • If you pass a block, and have :count => 0, it’ll pass if there is no element in the set of matched elements for which all of the child asserts pass.
  • It is fast.

Known Issues:

  • This is my first release to the world of assert_elements. There will probably be issues I have not yet resolved. Please email me at wycats AT gmail DOT com with any bug reports.
  • The error messages are obtuse, especially with blocks.
  • As with Luke’s original plugin, there are no tests. I have to figure how to test an assert plugin.
  • Hpricot 0.4 has a serious issue with certain selectors (including [@att*=val]). The issue is no longer present in hpricot’s most recent candidate build (hpricot 0.4.99) so install it via:

gem install hpricot –source http://code.whytheluckystiff.net

You can get assert_elements as a plugin at http://tools.assembla.com/svn/assert_elements/trunk/assert_elements/ by doing:

script/plugin install http://tools.assembla.com/svn/assert_elements/trunk/assert_elements/

Very many thanks to Luke Redpath. This plugin build very much on the work he did (in fact, most of the source excepting the assert_elements method itself and some minor changes to support blocks is Luke’s!).

My Preferred Rails Development Environment

I’ve been chatting with a bunch of new Rails guys lately, and have been recommending my particular mix of Eclipse, RDT, RadRails, Subclipse, and Aptana as a basic IDE for new guys.

It occurred to me that there’s no one-stop shop for instructions on how to do that, so here goes.

  1. Download Eclipse
    1. You will need to download the version for your particular environment (Win32, OSX, Linux)
    2. Extract the zip file you downloaded into someplace you use for program files
    3. Run Eclipse
  2. You will now need to install some plugins
    1. In Eclipse, go to Help->Updates->Find and Install
    2. Select “Search for new features to install” and click Next
    3. Click “New Remote Site” and add each of the following plugins:
      1. Eclipse plugins use “update sites,” which provide all of the information for updates to the software after initial installation.
      2. RadRails: http://radrails.sourceforge.net/update
      3. RDT: http://updatesite.rubypeople.org/release
      4. SubClipse: http://subclipse.tigris.org/update_1.0.x
      5. Aptana: http://update.aptana.com/install/
    4. Make sure all of the new items are selected, and click “Finish”
    5. Some updates will come up. Select them all and click “Next”
    6. Several Licensing questions may come up. Accept the terms of the licensing agreements and click “Next”
    7. A list of features to be installed will appear. Click “Finish”
    8. Eclipse will download all of the features and install them. It will then ask you to verify any features that have not been “signed.” Accept all features.
    9. When Eclipse is done installing, it will ask you to restart Eclipse. Do so.
    10. Congrats. You are done.
  3. You may need to do some additional configuration.
    1. Go to Window->Preferences
    2. Go to General->Editors->File Associations
      1. Make sure the default editor for “htm” and “html” is “Aptana HTML Editor”
      2. Make sure the default editor for “js” is “Aptana JS Editor”
      3. Make sure the default editor for “css” is “Aptana CSS Editor”
      4. Make sure the default editor for “rb” is “Ruby Editor”
      5. Make sure the default editor for “rhtml” is “RHTML Editor”
      6. Make sure the default editor for “yml” is “YML Editor”
    3. Go to Ruby->Installed Interpreters
      1. Make sure there is an entry there.
      2. If not, click “Add” and find your ruby binary (it might be in /local/bin or /opt/local/bin)
      3. NOTE: It does not matter what name you give this. You just need to point it to the binary.
    4. Go to Ruby->Ri/rdoc
      1. If either of the two is missing, add the binary (in Windows, it might be a .bat file)
    5. Go to Rails->Configuration
      1. Make sure Rails and Rake both have entries. Otherwise, add them (the ones in the main bin directory may not work. I needed to use /opt/local/lib/ruby/gems/1.8/gems/rails-1.1.6/bin/rails, for instance). If you go this route, make sure to change the path to your binaries if you upgrade.
      2. If you plan to use Mongrel with the built-in IDE, make sure there’s an entry in the Mongrel path.
    6. Go to General->Editors->Text Editors and set your desired tab width
    7. Go to Ruby->Formatter.
      1. Click “Show” next to Eclipse [built-in]
      2. Select your desired tab policy
      3. Click Ok and select a new name for your formatting settings.
      4. Click Ok.
    8. Click Rails->Editors->RHTML Editor
      1. Choose your desired tab settings
    9. Go to Aptana->Editors
      1. Choose your desired tab settings under “Formatting”
  4. Your basic settings should now work.

I know the above sounds like a lot of work, but it’s not. Besides, most of the settings are good from the beginning. If you choose to go this route (and I hope you will), make sure to install Dr. Nic’s RadRails Templates, which make all of the pretty TextMate shortcuts (warning: PDF) work in Eclipse.

Happy Hacking!

Update: Digg it!

Status Update

Hi guys. My previous site got exploited, and I’m still trying to recover the posts. I will hopefully be able to transfer them over sometime this week.

I will be posting again about jQuery and Rails, hopefully more frequently.

Next up: My hpricot-based replacement for assert_select in Rails. Stay tuned!