Home Notes Web development Ruby on Rails 3: Simple voting system with AJAX (with jQuery)

Ruby on Rails 3: Simple voting system with AJAX (with jQuery) E-mail
Tuesday, 12 April 2011 15:28

Introduction

You have a site based around a model - it could be blog posts, photos, videos, comments on any of those other models - and you want to add a simple voting feature where visitors can vote on a blog post, photo, or whatever else it might be.

 

There are plugins available for Rails to implement voting, thumbs_up being the most popular one for Rails 3, but it's not hard to create your own simple voting mechanism.

 

The one I'm going to demonstrate here will have the following features:

  • Item being voted on: item, item serving as the vote: vote (simple enough?)
  • Only one type of voting - the plugins allow for "for" and "against" votes, but I didn't need that in my project.  It wouldn't be too hard to modify this method to accommodate this.
  • Time-limited voting - a user can vote for any one "item" once every 24 hours.  For this to work your site needs a registration system, or some method that uniquely identifies users, so you can track who has voted on what and when.
  • Items will be sortable by votes
  • AJAX response - when a user votes the vote will be saved asynchronously and a response will be returned via AJAX.  I'll be using jQuery.
  • Administrators will not be creating, modifiying, or deleting votes manually

Requirements

In addition to what comes bundled with Rails, you will need:

You can now link to both these files with:

<%= javascript_include_tag 'http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.1.js', 'rails' %>

This code can go in your application.html.erb layout file so it's included everywhere in your application, or you can place it only in the files that need it, that will have something to do with the voting functionality you're adding.

Code generation

(I'm showing the final code here, but I'll break it down further on in the next step of the tutorial.)

At the command prompt execute:

rails g scaffold vote item_id:integer user_id:integer

If you haven't used Rails scaffolding before, this will generate the model, controller, views to execute CRUD operations, and related test files.  You won't need most of the functions and methods the scaffolding will generate, but it's quicker to delete what you don't need than to create what you need from scratch.

Create a column in your items table to store a votes_count integer value

rails g migration add_votes_to_items votes_count:integer

This will run both the scaffold-generated migration for vote and the one you just generated for item

rake db:migrate

 
 
class Item < ActiveRecord::Base
 
    has_many :votes
 
 

Delete all files under views/votes, we won't be needing them, and trim your model and controller to look like the following:

vote.rb

 
 
class Vote < ActiveRecord::Base
  belongs_to :item, :counter_cache => true
 
# rest of your code here
end
 
 

votes_controller.rb

Copy this code to a text file or somewhere else where you can reference it as you read the explanation that follows.

In the views/votes folder that you just emptied, create a create.js.erb file.  This is the response that will be returned by the votes' controller's create action after a vote has been successfully created.

 
 
$(document).ready(function(){
 
    alert('This is a pathetically simple AJAX response!');
 
});
 
 

All you need is a simple button that allows a user to vote for an item in, for example, the items index and item show views.  The form should have hidden fields for the two values you need to store,  item_id and user_id.  Notice the :remote => true in the form_for tag.

 
 
<%= form_for(@vote, :remote => true) do |f| %>
<%= f.hidden_field "item_id", :value => item.id %>
<%= f.hidden_field "user_id", :value => @user.id %>
<%= image_submit_tag "buttons/vote-for-item.png" %>
<% end %>
 
 

You also need to add a line to the corresponding actions in items_controller.rb.  If you don't add this you'll get an error when you go to pull up your views.

 
 
def index
 
    @vote = Vote.new(params[:vote])
 
    #rest of your code here
 
end
 
 

 
 
def show
 
    @vote = Vote.new(params[:vote])
 
    #rest of your code here
 
 

Explanation

I'll break this down by file:

 
 
class Vote < ActiveRecord::Base
  belongs_to :item, :counter_cache => true
 
# rest of your code here
end
 
 

  • belongs_to you should already know by now.  It creates a one-to-many relationship between item and vote (with has_many :votes in item.rb)
  • counter_cache is a great feature of Rails that keeps track of the "count" of votes an item has in the additional votes_count column we added to the items table.  So if later you want to sort items by votes, you can do something like:

 
 
@items = Item.order("votes_count DESC")
 
 

Lines 18-24, 34-36

These lines set up the check for whether the current user (line 19) has voted for the current item (line 18) within the last 24 hours (line 24).  If not, the vote is saved, otherwise the user is alerted (line 35) that they cannot vote again just yet.

Line 27

This line is required for the AJAX response.  This tells the controller that this action can return a JavaScript response in addition to the HTML response that's there by default.  The create.js.erb will now be rendered because we've told the controller to respond to JS requests and we've named the view the same as the controller action.

The key here is the :remote => true, which enables the AJAX functionality of this simple form.

And that's it!  Your AJAX'ed voting system is ready!