Star rating System in Ruby on Rails (AJAX based)

After looking at couple of websites, I realized that AJAX based rating system are a must for any socieal networking website :). So here is how I implemented it (I used mostly instructions from Dave Naffi's website to implement this.


Steps


1)Download Acts as rateable plugin from Juxie.com

Command> cd
Command> ruby script\install http://juixe.com/svn/acts_as_rateable


2) Creating ratings table where you will be storing your ratings:


mysql> desc ratings;

+---------------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| rating | int(11) | YES | | 0 | |
| created_at | datetime | NO | | | |
| rateable_type | varchar(15) | NO | | | |
| rateable_id | int(11) | NO | | 0 | |
| member_id | int(11) | YES | MUL | NULL | |
+---------------+-------------+------+-----+---------+----------------+



OR you can use migrations (should you migrations ;)) in a file named xxx_add_ratings_table.rb in db/migrate directory of your application


class AddRatingsTable< force =""> true do |t|
t.column :rating, :integer, :default => 0
t.column :created_at, :datetime, :null => false
t.column :rateable_type, :string, :limit => 15,
:default => "", :null => false
t.column :rateable_id, :integer, :default => 0, :null => false
t.column :member_id, :integer , :null => false
end
end

def self.down
drop_table :ratings
end

end


Please note that here my ratings is associated with a model named "Association". The neat thing about acts_as_rateable plugin is that it will automatically notice this and accordingly calculate the weight by averaging the votes by number of members who voted. (pretty cool :)). Also one other good thing is that you can more than one model as rateable. I haven't tried this feature but I noticed that the ratings table stores the name of the rateable model in the field named "rateable_type"

3) Now its time to associate our model ("Association" in my case) to ratings table.
Here is how you do it (for my case the file is /model/Association.rb)


class Association <>

If you are eager to see how you databae entries would look like, here is an e.g


mysql> select * from ratings limit 5;
+----+--------+---------------------+---------------+-------------+-----------+
| id | rating | created_at | rateable_type | rateable_id | member_id |
+----+--------+---------------------+---------------+-------------+-----------+
| 3 | 5 | 2006-12-18 22:08:12 | Association | 42 | 18 |

| 4 | 5 | 2006-12-19 07:03:04 | Association | 27 | 18 |

| 8 | 5 | 2006-12-19 07:03:55 | Association | 36 | 18 |

| 9 | 5 | 2006-12-19 07:03:57 | Association | 39 | 18 |

| 11 | 3 | 2006-12-19 07:04:04 | Association | 40 | 18 |

+----+--------+---------------------+---------------+-------------+-----------+

- id: is the id of ratings table
- rating: is the ratings itself (In my case ratings go from 1 to 5)
- created_at: date time when the entry was created
- rateable_type : The model , Association in my case
- rateable_id : Association id (just a reminder...association is the model which stores some text and this text is being rated by users. You can imagine it as a testimonial in orkut/linkedin)
- member_id: The user id of the user. In my case all this info is stored in members table



4) Now its time to add images, views, partials and controller

Controller (/controllers/ratings_controller.rb)


class RatingController < association =" Association.find(params[:id])" rateable_type =" 'Association'" rateable_id =" ?" member_id =" ?" rating =""> params[:rating], :member_id => current_member.id) #add a new entry containinig the rating (1-5), association id (both this stored in the param[:rating] and member id)
end

end




views (/views/view_in_which_you_want_to_display_your_ratings html)

Views - Partial (/views/rating/_rating.html). I have 5 list items because I am rating my "association" with 0 to 5 stars

<ul class='star-rating'>
<li class='current-rating' style='width:<%(association.rating * 30).to_i -%>px;'>
Currently <%= number_with_precision(association.rating, 1) %>/5 Stars.
</li>
<li>
<%= link_to_remote( "1", {:url => { :controller => "rating",
:action => "rate", :id => association.id, :rating => 1}},
:class => 'one-star', :name => '1 star out of 5') %>
</li>
<li>
<%= link_to_remote( "2", {:url => { :controller => "rating",
:action => "rate", :id => association.id, :rating => 2}},
:class => 'two-stars', :name => '2 stars out of 5') %>
</li>
<li>
<%= link_to_remote( "3", {:url => { :controller => "rating",
:action => "rate", :id => association.id, :rating => 3}},
:class => 'three-stars', :name => '3 stars out of 5') %>
</li>
<li>
<%= link_to_remote( "4", {:url => { :controller => "rating",
:action => "rate", :id => association.id, :rating => 4}},
:class => 'four-stars', :name => '4 stars out of 5') %>
</li>
<li>
<%= link_to_remote( "5", {:url => { :controller => "rating",
:action => "rate", :id => association.id, :rating => 5}},
:class => 'five-stars', :name => '5 stars out of 5') %>
</li>
</ul>





Views (/views/ratings/rate.rjs) This calls the page.replace_html method which replaces html in a given div (start-ratings-block in my case)



page.replace_html "star-ratings-block#{@association.id}", :partial => 'rating/rating', :locals => { :association => @association }


#note I am replacing the div named "start-ratings-block" (e.g. star-ratings-block45). This is possible because I named by div by conctenating the id of "association".



Lastly we need to add css for class 'star-rating'

Here is it




/* styles for the star rater */
.star-rating{
list-style:none;
margin: 0px;
padding:0px;
width: 150px;
height: 30px;
position: relative;
background: url(/images/star_rating.gif) top left repeat-x;
}
.star-rating li{
padding:0px;
margin:0px;
/*\*/
float: left;
/* */
}
.star-rating li a{
display:block;
width:30px;
height: 30px;
text-decoration: none;
text-indent: -9000px;
z-index: 20;
position: absolute;
padding: 0px;
}
.star-rating li a:hover{
background: url(/images/star_rating.gif) left center;
z-index: 2;
left: 0px;
}
.star-rating a.one-star{
left: 0px;
}
.star-rating a.one-star:hover{
width:30px;
}
.star-rating a.two-stars{
left:30px;
}
.star-rating a.two-stars:hover{
width: 60px;
}
.star-rating a.three-stars{
left: 60px;
}
.star-rating a.three-stars:hover{
width: 90px;
}
.star-rating a.four-stars{
left: 90px;
}
.star-rating a.four-stars:hover{
width: 120px;
}
.star-rating a.five-stars{
left: 120px;
}
.star-rating a.five-stars:hover{
width: 150px;
}
.star-rating li.current-rating{
background: url(/images/star_rating.gif) left bottom;
position: absolute;
height: 30px;
display: block;
text-indent: -9000px;
z-index: 1;
}




Note that my images are copied in images directory inside public directory of application.



Here are the images incase you want to use them (Please change the width in the *-stars* block in your css if you end up using some other image

star_rating.gif
all_star.gif

Comments

Popular posts from this blog

Impossible