Nicolas Alpi, Web developer

A blog about productivity, startups and me.

Beginners Guide to Jquery + Ruby on Rails

First time I wanted to use Jquery with Ruby on Rails I had some difficulties understanding how it can works.

Sure, you can easily use Jrails, that’ll brings you RJS helpers with Jquery, but I quite like the unobstrusive way, so Jrails is not (for me) the best solutions.

Let’s code together

What do we want?

We are going to simulate a blog post comment system. We need to be able to list the comments/create a new comment/rate a comment/delete a comment.

All this need to work with and without JavaScript enabled.

I deliberately didn’t use any user/role filtering, the goal of the code is to show of to work with Rails and Jquery

Step1: Prepare the app

We want to work with Jquery, so we don’t need the Prototype files anymore. Just remove everything from your public/javascript/ appart application.js.

In your master layout add this bunch of lines

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.1/jquery.min.js"></script>
<% if protect_against_forgery? %>
<script type=‘text/javascript’>
//<![CDATA[
window.auth_token_name = "#{request_forgery_protectiontoken}";
window.auth_token = "#{form_authenticitytoken}";
//]]>
</script>
<% end %>
<%= javascript_include_tag ‘application’ %>

We are linking Jquery to the google hosted version, and calling the application.js and setting the forgery protection.

Then in your application.js copy and past the content of my default application.js (Also available as a gist)

jQuery.ajaxSetup({ 'beforeSend': function(xhr) {xhr.setRequestHeader(&quot;Accept&quot;, &quot;text/javascript&quot;)} })

function _ajax_request(url, data, callback, type, method) {
if (jQuery.isFunction(data)) {
callback = data;
data = {};
}
return jQuery.ajax({
type: method,
url: url,
data: data,
success: callback,
dataType: type
});
}

jQuery.extend({
put: function(url, data, callback, type) {
return ajax_request(url, data, callback, type, ‘PUT’);
},
delete
: function(url, data, callback, type) {
return _ajax_request(url, data, callback, type, ‘DELETE’);
}
});

jQuery.fn.submitWithAjax = function() {
this.unbind(‘submit’, false);
this.submit(function() {
$.post(this.action, $(this).serialize(), null, "script");
return false;
})

return this;

};

//Send data via get if JS enabled
jQuery.fn.getWithAjax = function() {
this.unbind(‘click’, false);
this.click(function() {
$.get($(this).attr("href"), $(this).serialize(), null, "script");
return false;
})
return this;
};

//Send data via Post if JS enabled
jQuery.fn.postWithAjax = function() {
this.unbind(‘click’, false);
this.click(function() {
$.post($(this).attr("href"), $(this).serialize(), null, "script");
return false;
})
return this;
};

jQuery.fn.putWithAjax = function() {
this.unbind(‘click’, false);
this.click(function() {
$.put($(this).attr("href"), $(this).serialize(), null, "script");
return false;
})
return this;
};

jQuery.fn.deleteWithAjax = function() {
this.removeAttr(‘onclick’);
this.unbind(‘click’, false);
this.click(function() {
$.delete_($(this).attr("href"), $(this).serialize(), null, "script");
return false;
})
return this;
};

//This will "ajaxify" the links
function ajaxLinks(){
$(‘.ajaxForm’).submitWithAjax();
$(‘a.get’).getWithAjax();
$(‘a.post’).postWithAjax();
$(‘a.put’).putWithAjax();
$(‘a.delete’).deleteWithAjax();
}

$(document).ready(function() {

// All non-GET requests will add the authenticity token
// if not already present in the data packet
$(document).ajaxSend(function(event, request, settings) {
if (typeof(window.AUTH_TOKEN) == "undefined") return;
// IE6 fix for http://dev.jquery.com/ticket/3155
if (settings.type == ‘GET’ || settings.type == ‘get’) return;

settings.data = settings.data || ""; settings.data += (settings.data ? "&" : "") + "authenticity_token=" + encodeURIComponent(window.AUTH_TOKEN); }); ajaxLinks();

});

This is my default application.js when I want to work with Jquery.

Basically what brings you these functions are :

Any link for a class get/post/put/delete will be called via ajax only if JavaScript enabled. Same for form with ajaxForm class.

The code

You can find the original code on my github at

http://github.com/spyou/railsjquery/tree/master

I’ve created a comment resource. The comments table has a body and a score field.

Here is the comments_controller.rb

class CommentsController &lt; ApplicationController
#This is a virtual comment manager. There is no user detection, no session
before_filter :load_post
#This is our entry point, I’m just going to display a factice post to comment
def index
@comments = Comment.all
respond_to do |format|
format.html #we only respond to the html request
end
end
def new
@comment = Comment.new
if !request.xhr?
@comments = Comment.all
end
respond_to do |format|
format.html
format.js {render :layout => false}
end
end
def create
@comment = Comment.create(params[:comment])
respond_to do |format|
format.html { redirect_to comments_path() }
format.js {render :layout => false}
end
end
#This is not a "normal" update, I’ll use this one to add points to the comment
def update
@comment = Comment.find(params[:id])
@comment.score = @comment.score + 1
@comment.save
respond_to do |format|
format.html { redirect_to comments_path }
format.js {render :layout => false}
end
end
def destroy
@comment = Comment.find(params[:id])
@comment.destroy
respond_to do |format|
format.html { redirect_to comments_path }
format.js {render :layout => false}
end
end
protected
def load_post
@post = "This is a factice post, I hope you like it. Just comment on the bottom!"
end

end

As you can see on it, we handle 2 different types of responses

respond_to do |format|
format.html
format.js {render :layout => false}
end

Format.html will load your [actionName].html.erb, but the format.js will load a page called [actionName].js.erb were you can put all the callback javascript you need.

For example here is my index.html.erb

&lt;%=
#Use a helper to display the current comments of the post
comments_display(@comments)
%>

<div class="newComment">
<%= link_to ‘Create a new comment’, new_comment_path(),:class => "get"%>
</div>

and the _comment.html.erb

&lt;div id=&quot;comment&lt;%= comment.id %&gt;&quot;&gt;

<%=
content_tag(:p,
comment.body)
>
<!— We are accessing the update function so we use put as class —>
<span class="score"><
= comment.score ></span><span> – <= link_to ‘Add one point!’,comment_path(comment),:class => "put" ></span>
<p> – <
= link_to ‘*Delete this comment’,comment_path(comment),:method => :delete,:class => "delete" ></p>
<p><
= comment.created_at %></p>
<hr />
</div>

You can see the use of get/put/delete class.

Here is now the new.js.erb (Where I call a _form.html.erb to render the new comment form)

$(&quot;.newComment&quot;).html(&quot;&lt;%= escape_javascript(render :partial =&gt; 'form') %&gt;&quot;);

ajaxLinks();

And here is the update.js.erb (Where I’m suppose to update the score of the comment without refresh)

//Update the score

$(‘#comment<%= @comment.id %> span.score’).html("<%= @comment.score %>");

The tests

Well, the problem with the test is, you can test the rendering of you page (in plain text) but you can’t be sure of the javascript execution.

I’m made some simple tests (testing the plain text) that you can find in the test/functional folder.

(Example with the new comment method)

#Testing the comment creation
context "handle :create" do
context "by html" do
setup do
post :create, :comment => { :body => "My comment" }
end
should_change "Comment.count", :by => 1
should_redirect_to("the index"){comments_path()}
end
context "by xhr" do
setup do
xhr :post, :create, :comment => { :body => "My xhr comment" }
end
should_change "Comment.count", :by => 1
should "Display the new comment link" do
assert_match(/new comment/,@response.body)
end
should "display the created new created message" do
assert_match(%(My xhr comment),@response.body)
end
end
end

This is just the beginning, I’m now looking to some Javascript testing solution for Rails, will be part of another tutorial.

In a nutshell

So in a nutshell, what’d learn here is :

  • Rails + Jquery is easy
  • format.js + file.js.erb and you’r done
  • A good application.js can save your ass

Just checkout the complete source code

git clone git@github.com:spyou/railsjquery.git

rake gems:install
rake db:create

jquery-rails-tutorial_1242648062355

I hope it was useful.

Comments