Learning Ruby on Rails Day 41

So I modified my Shovell code to use unobtrusive jQuery. It works.

Instead of using the form_remote_tag helper, I am using a regular form_for helper:

<div id="vote-form">
  <% form_for [ @story, Vote.new ] do |f| %>
    <p><%= f.submit 'Shove This' %></p>
  <% end %>

The following HTML is generated:

<div id="vote-form">
  <form action="/stories/4-hell-breaks-loose/votes" class="new_vote" id="new_vote" method="post">
    <div style="margin:0;padding:0">
      <input name="authenticity_token" type="hidden"
value="qA83IKqd05mqmSl8zyigMzmPeK+o6A+m7b683FhN16U=" />
    <p><input id="vote_submit" name="commit" type="submit" value="Shove This" /></p>

Not a trace of JavaScript in it.

Both the jQuery library and application.js are included on the page, so I open application.js and add this JavaScript:

// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults
jQuery(function() {
  jQuery('#new_vote').submit(function() {
    // $.post(url, data, callback, type)
    return false;

Ryan Bates' code would have been — if I am to look at the source code provided with his jQuery screencast:

// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults 
  'beforeSend': function(xhr) {xhr.setRequestHeader("Accept", "text/javascript")}
jQuery.fn.submitWithAjax = function() {
  this.submit(function() {
    $.post(this.action, $(this).serialize(), null, "script");
    return false;
  return this;
$(document).ready(function() {

I don't need to set my request header to accept “text/javascript”. I ran my app in Safari, IE6, IE7, IE8 and Firefox, and it worked just fine there. Ryan Bates was using jQuery v1.2.6 when he recorded his screencast on Nov 17, 2008 — I'm using jQuery v1.3.2. Maybe that explains it?

Response Headers

I create a create.js.erb template in app/views/votes and in it I place a simple alert with embedded Ruby in it. See what I get:

Screen capture of Shovell

Of course, my votes controller has been set to be able to respond to both a regular POST request and a remote Ajax one:

class VotesController < ApplicationController	
  def create
    @story = Story.find(params[:story_id])
    flash[:notice] = 'Thanks.'
    # The following would work too.
    # unless request.xml_http_request?
      # redirect_to @story
    # end
    # Rails uses the HTTP Accept header to determine the request type.
    respond_to do |format|
      format.html { redirect_to @story }

Conjuring a *.js.erb template when you aren't very good at writing jQuery for visual effects

It was easier than I thought. It took me 3 minutes to figure things out. This demo page on the jQuery UI website was useful.

So, to accomplish exactly the same thing that was previously done by my create.js.rjs template:

# replace the content of #id with this HTML
page.replace_html 'vote-score', "Score: #{@story.votes.count}"
# apply visual effect :effet_type to the content of #id
page.visual_effect :highlight, 'vote-score', :duration => 2

I am now using these chained commands on my #vote-score div in create.js.erb:

jQuery('#vote-score').html("Score: <%= @story.votes.count %>").show('highlight', {}, 2000);

I am writing my own JavaScript, jRails is not writing it for me, but it's jQuery so it's mindnumbingly easy.

The JavaScript that was sent to the browser when using jRails was pretty much the same — except it was wrapped in a Try and Catch construct:

JavaScript returned as seen in Firebug

I am so dumb: I should have referred to it when figuring out how to code my effect.

The JavaScript now returned — the one I had to write, that was not generated by Rails — is this one:

JavaScript returned as seen in Firebug

If I feel lazy, I guess I can use RJS.


Of course, one can make his jQuery unobtrusive and still use RJS templates. As long as you don't use form_remote_for, form_remote_tag, link_to_remote and periodically_call_remote helpers, but add event handlers to application.js instead, then you're good.