Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

first init

  • Loading branch information...
commit eb1ba56b4236634b8f4bf6094a680db96758cacc 0 parents
@muratguzel authored
BIN  .DS_Store
Binary file not shown
6 Gemfile
@@ -0,0 +1,6 @@
+source "http://rubygems.org"
+
+gem 'jquery-rails'
+
+# Specify your gem's dependencies in ka-rate.gemspec
+gemspec
64 Gemfile.lock
@@ -0,0 +1,64 @@
+PATH
+ remote: .
+ specs:
+ rateme (1.0.3)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ actionpack (3.2.2)
+ activemodel (= 3.2.2)
+ activesupport (= 3.2.2)
+ builder (~> 3.0.0)
+ erubis (~> 2.7.0)
+ journey (~> 1.0.1)
+ rack (~> 1.4.0)
+ rack-cache (~> 1.1)
+ rack-test (~> 0.6.1)
+ sprockets (~> 2.1.2)
+ activemodel (3.2.2)
+ activesupport (= 3.2.2)
+ builder (~> 3.0.0)
+ activesupport (3.2.2)
+ i18n (~> 0.6)
+ multi_json (~> 1.0)
+ builder (3.0.0)
+ erubis (2.7.0)
+ hike (1.2.1)
+ i18n (0.6.0)
+ journey (1.0.3)
+ jquery-rails (2.0.1)
+ railties (< 5.0, >= 3.2.0)
+ thor (~> 0.14)
+ json (1.6.5)
+ multi_json (1.1.0)
+ rack (1.4.1)
+ rack-cache (1.2)
+ rack (>= 0.4)
+ rack-ssl (1.3.2)
+ rack
+ rack-test (0.6.1)
+ rack (>= 1.0)
+ railties (3.2.2)
+ actionpack (= 3.2.2)
+ activesupport (= 3.2.2)
+ rack-ssl (~> 1.3.2)
+ rake (>= 0.8.7)
+ rdoc (~> 3.4)
+ thor (~> 0.14.6)
+ rake (0.9.2.2)
+ rdoc (3.12)
+ json (~> 1.4)
+ sprockets (2.1.2)
+ hike (~> 1.2)
+ rack (~> 1.0)
+ tilt (!= 1.3.0, ~> 1.1)
+ thor (0.14.6)
+ tilt (1.3.3)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ jquery-rails
+ rateme!
89 README.md
@@ -0,0 +1,89 @@
+# Rateit Rating Gem
+
+Provides the best way to add rating capabilites to your Rails application with jQuery Raty plugin.
+
+## Repository
+
+Find it at [github.com/muratguzel/rateit](github.com/muratguzel/rateit)
+
+## Instructions
+
+### Install
+
+You can add the rateit gem into your Gemfile
+
+ gem 'rateit'
+
+### Generate
+
+ rails g rateit User
+
+The generator takes one argument which is the name of your existing UserModelName. This is necessary to bind the user and rating datas.
+Also the generator copies necessary files (jquery raty plugin files, star icons and javascripts)
+
+Example:
+
+Suppose you will have a devise user model which name is User. The generator should be like below
+
+ rails g devise:install
+ rails g devise user
+ rails g rateit user # => This is rateit generator.
+
+This generator will create Rate and RatingCache models and link to your user model.
+
+### Prepare
+
+I suppose you have a car model
+
+ rails g model car name:string
+
+You should add the rateit_rateable function with its dimension option.
+
+ class Car < ActiveRecord::Base
+ rateit_rateable :dimensions => [:speed, :engine, :price]
+ end
+
+Then you need to add a call rateit_rater in the user model.
+
+ class User < ActiveRecord::Base
+ ratme_rater
+ end
+
+
+### Using
+
+There is a helper method which name is rating_for to add the star links. By default rating_for will display the average rating and accept the
+new rating value from authenticated user.
+
+ #show.html.erb -> /cars/1
+
+ Speed : <%= rating_for @car, "speed" %>
+ Engine : <%= rating_for @car, "engine" %>
+ Price : <%= rating_for @car, "price" %>
+
+### Important
+
+By default rating_for tries to call current_user method as the rater instance in the rater_controller.rb file. You can change the current_user method
+as you will.
+
+ #rater_controller.rb
+
+ def create
+ if current_user.present?
+ obj = eval "#{params[:klass]}.find(#{params[:id]})"
+ if params[:dimension].present?
+ obj.rate params[:score].to_i, current_user.id, "#{params[:dimension]}"
+ else
+ obj.rate params[:score].to_i, current_user.id
+ end
+
+ render :json => true
+ else
+ render :json => false
+ end
+ end
+
+## Feedback
+If you find bugs please open a ticket at [github.com/muratguzel/rateit/issues](github.com/muratguzel/rateit/issues)
+
+
1  Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
BIN  lib/.DS_Store
Binary file not shown
0  lib/generators/rateit/USAGE
No changes.
49 lib/generators/rateit/rateit_generator.rb
@@ -0,0 +1,49 @@
+require 'rails/generators/migration'
+class RateitGenerator < Rails::Generators::NamedBase
+ include Rails::Generators::Migration
+
+ source_root File.expand_path('../templates', __FILE__)
+
+ desc "copying jquery.raty files to assets directory ..."
+ def copying
+ copy_file 'jquery.raty.min.js', 'app/assets/javascripts/jquery.raty.min.js'
+ copy_file 'star-on.png', 'app/assets/images/star-on.png'
+ copy_file 'star-off.png', 'app/assets/images/star-off.png'
+ copy_file 'star-half.png', 'app/assets/images/star-half.png'
+ copy_file 'rateit.js', 'app/assets/javascripts/rateit.js.erb'
+ copy_file 'rater_controller.rb', 'app/controllers/rater_controller.rb'
+ end
+
+ desc "model is creating..."
+ def create_model
+ model_file = File.join('app/models', "#{file_path}.rb")
+ raise "User model (#{model_file}) must exits." unless File.exists?(model_file)
+ class_collisions 'Rate'
+ template 'model.rb', File.join('app/models', "rate.rb")
+ template 'cache_model.rb', File.join('app/models', "rating_cache.rb")
+ end
+
+ def add_rate_path_to_route
+ route "match '/rate' => 'rater#create', :as => 'rate'"
+ end
+
+ desc "cacheable rating average migration is creating ..."
+ def create_cacheable_migration
+ migration_template "cache_migration.rb", "db/migrate/create_rating_caches.rb"
+ end
+
+ desc "migration is creating ..."
+ def create_migration
+ migration_template "migration.rb", "db/migrate/create_rates.rb"
+ end
+
+
+ private
+ def self.next_migration_number(dirname)
+ if ActiveRecord::Base.timestamped_migrations
+ Time.now.utc.strftime("%Y%m%d%H%M%S%L")
+ else
+ "%.3d" % (current_migration_number(dirname) + 1)
+ end
+ end
+end
19 lib/generators/rateit/templates/cache_migration.rb
@@ -0,0 +1,19 @@
+class CreateRatingCaches < ActiveRecord::Migration
+
+ def self.up
+ create_table :rating_caches do |t|
+ t.belongs_to :cacheable, :polymorphic => true
+ t.float :avg, :null => false
+ t.integer :qty, :null => false
+ t.string :dimension
+ t.timestamps
+ end
+
+ add_index :rating_caches, [:cacheable_id, :cacheable_type]
+ end
+
+ def self.down
+ drop_table :rating_caches
+ end
+
+end
3  lib/generators/rateit/templates/cache_model.rb
@@ -0,0 +1,3 @@
+class RatingCache < ActiveRecord::Base
+ belongs_to :cacheable, :polymorphic => true
+end
12 lib/generators/rateit/templates/jquery.raty.min.js
@@ -0,0 +1,12 @@
+/*!
+ * jQuery Raty - A Star Rating Plugin
+ *
+ * Licensed under The MIT License
+ *
+ * @version 2.1.0
+ * @author Washington Botelho
+ * @documentation wbotelhos.com/raty
+ *
+ */
+
+;(function(b){var a={init:function(c){return this.each(function(){var g=b.extend({},b.fn.raty.defaults,c),n=b(this).data("options",g);if(g.number>20){g.number=20;}else{if(g.number<0){g.number=0;}}if(g.round.down===undefined){g.round.down=b.fn.raty.defaults.round.down;}if(g.round.full===undefined){g.round.full=b.fn.raty.defaults.round.full;}if(g.round.up===undefined){g.round.up=b.fn.raty.defaults.round.up;}if(g.path.substring(g.path.length-1,g.path.length)!="/"){g.path+="/";}if(typeof g.start=="function"){g.start=g.start.call(this);}var h=!isNaN(parseInt(g.start,10)),f="";if(h){f=(g.start>g.number)?g.number:g.start;}var o=g.starOn,d=(g.space)?4:0,k="";for(var l=1;l<=g.number;l++){o=(f<l)?g.starOff:g.starOn;k=(l<=g.hintList.length&&g.hintList[l-1]!==null)?g.hintList[l-1]:l;n.append('<img src="'+g.path+o+'" alt="'+l+'" title="'+k+'" />');if(g.space){n.append((l<g.number)?"&nbsp;":"");}}var j=b("<input/>",{type:"hidden",name:g.scoreName}).appendTo(n);if(h){if(g.start>0){j.val(f);}a.roundStar.call(n,f);}if(g.iconRange){a.fillStar.call(n,f);}a.setTarget.call(n,f,g.targetKeep);var e=g.width||(g.number*g.size+g.number*d);if(g.cancel){var m=b('<img src="'+g.path+g.cancelOff+'" alt="x" title="'+g.cancelHint+'" class="raty-cancel"/>');if(g.cancelPlace=="left"){n.prepend("&nbsp;").prepend(m);}else{n.append("&nbsp;").append(m);}e+=g.size+d;}if(g.readOnly){a.fixHint.call(n);n.children(".raty-cancel").hide();}else{n.css("cursor","pointer");a.bindAction.call(n);}n.css("width",e);});},bindAction:function(){var c=this,d=this.data("options"),e=this.children("input");c.mouseleave(function(){a.initialize.call(c,e.val());a.setTarget.call(c,e.val(),d.targetKeep);});var g=this.children("img").not(".raty-cancel"),f=(d.half)?"mousemove":"mouseover";if(d.cancel){c.children(".raty-cancel").mouseenter(function(){b(this).attr("src",d.path+d.cancelOn);g.attr("src",d.path+d.starOff);a.setTarget.call(c,null,true);}).mouseleave(function(){b(this).attr("src",d.path+d.cancelOff);c.mouseout();}).click(function(h){e.removeAttr("value");if(d.click){d.click.call(c[0],null,h);}});}g.bind(f,function(i){var j=parseInt(this.alt,10);if(d.half){var h=parseFloat((i.pageX-b(this).offset().left)/d.size),k=(h>0.5)?1:0.5;j=parseFloat(this.alt)-1+k;a.fillStar.call(c,j);if(d.precision){j=j-k+h;}a.showHalf.call(c,j);}else{a.fillStar.call(c,j);}c.data("score",j);a.setTarget.call(c,j,true);}).click(function(h){e.val((d.half||d.precision)?c.data("score"):this.alt);if(d.click){d.click.call(c[0],e.val(),h);}});},cancel:function(c){return this.each(function(){var d=b(this);if(d.data("readonly")=="readonly"){return false;}if(c){a.click.call(d,null);}else{a.start.call(d,null);}d.mouseleave().children("input").removeAttr("value");});},click:function(c){return this.each(function(){var e=b(this);if(e.data("readonly")=="readonly"){return false;}a.initialize.call(e,c);var d=e.data("options");if(d.click){d.click.call(e[0],c);}else{b.error('you must add the "click: function(score, evt) { }" callback.');}a.setTarget.call(e,c,true);});},fillStar:function(e){var d=this.data("options"),c=this.children("img").not(".raty-cancel"),f=c.length,k=0,g,j,l;for(var h=1;h<=f;h++){g=c.eq(h-1);if(d.iconRange&&d.iconRange.length>k){j=d.iconRange[k];if(d.single){l=(h==e)?(j.on||d.starOn):(j.off||d.starOff);}else{l=(h<=e)?(j.on||d.starOn):(j.off||d.starOff);}if(h<=j.range){g.attr("src",d.path+l);}if(h==j.range){k++;}}else{if(d.single){l=(h==e)?d.starOn:d.starOff;}else{l=(h<=e)?d.starOn:d.starOff;}g.attr("src",d.path+l);}}},fixHint:function(){var c=this.data("options"),d=this.children("input"),f=parseInt(d.val(),10),e=c.noRatedMsg;if(!isNaN(f)&&f>0){e=(f<=c.hintList.length&&c.hintList[f-1]!==null)?c.hintList[f-1]:f;}d.attr("readonly","readonly");this.css("cursor","default").data("readonly","readonly").attr("title",e).children("img").attr("title",e);},readOnly:function(c){return this.each(function(){var d=b(this),e=d.children(".raty-cancel");if(e.length){if(c){e.hide();}else{e.show();}}if(c){d.unbind();d.children("img").unbind();a.fixHint.call(d);}else{a.bindAction.call(d);a.unfixHint.call(d);}});},roundStar:function(f){var c=this.data("options"),e=(f-Math.floor(f)).toFixed(2);if(e>c.round.down){var d=c.starOn;if(e<c.round.up&&c.halfShow){d=c.starHalf;}else{if(e<c.round.full){d=c.starOff;}}this.children("img").not(".raty-cancel").eq(Math.ceil(f)-1).attr("src",c.path+d);}},score:function(){var d=[],c;this.each(function(){c=b(this).children("input").val();c=(c=="")?null:parseFloat(c);d.push(c);});return(d.length>1)?d:d[0];},setTarget:function(f,d){var e=this.data("options");if(e.target){var c=b(e.target);if(c.length==0){b.error("target selector invalid or missing!");}else{var g=f;if(g==null&&!e.cancel){b.error('you must enable the "cancel" option to set hint on target.');}else{if(!d||g==""){g=e.targetText;}else{if(e.targetType=="hint"){if(g===null&&e.cancel){g=e.cancelHint;}else{g=e.hintList[Math.ceil(g-1)];}}else{if(g!=""&&!e.precision){g=parseInt(g,10);}else{g=parseFloat(g).toFixed(1);}}}if(e.targetFormat.indexOf("{score}")<0){b.error('template "{score}" missing!');}else{if(f!==null){g=e.targetFormat.toString().replace("{score}",g);}}if(c.is(":input")){c.val(g);}else{c.html(g);}}}}},showHalf:function(e){var c=this.data("options"),d=(e-Math.floor(e)).toFixed(1);if(d>0&&d<0.6){this.children("img").not(".raty-cancel").eq(Math.ceil(e)-1).attr("src",c.path+c.starHalf);}},start:function(c){return this.each(function(){var e=b(this);if(e.data("readonly")=="readonly"){return false;}a.initialize.call(e,c);var d=e.data("options");a.setTarget.call(e,c,true);});},initialize:function(d){var c=this.data("options");if(d<0){d=0;}else{if(d>c.number){d=c.number;}}a.fillStar.call(this,d);if(d!=""){if(c.halfShow){a.roundStar.call(this,d);}this.children("input").val(d);}},unfixHint:function(){var d=this.data("options"),e=this.children("img").filter(":not(.raty-cancel)");for(var c=0;c<d.number;c++){e.eq(c).attr("title",(c<d.hintList.length&&d.hintList[c]!==null)?d.hintList[c]:c);}this.css("cursor","pointer").removeData("readonly").removeAttr("title").children("input").attr("readonly","readonly");}};b.fn.raty=function(c){if(a[c]){return a[c].apply(this,Array.prototype.slice.call(arguments,1));}else{if(typeof c==="object"||!c){return a.init.apply(this,arguments);}else{b.error("Method "+c+" does not exist!");}}};b.fn.raty.defaults={cancel:false,cancelHint:"cancel this rating!",cancelOff:"cancel-off.png",cancelOn:"cancel-on.png",cancelPlace:"left",click:undefined,half:false,halfShow:true,hintList:["bad","poor","regular","good","gorgeous"],iconRange:undefined,noRatedMsg:"not rated yet",number:5,path:"img/",precision:false,round:{down:0.25,full:0.6,up:0.76},readOnly:false,scoreName:"score",single:false,size:16,space:true,starHalf:"star-half.png",starOff:"star-off.png",starOn:"star-on.png",start:0,target:undefined,targetFormat:"{score}",targetKeep:false,targetText:"",targetType:"hint",width:undefined};})(jQuery);
20 lib/generators/rateit/templates/migration.rb
@@ -0,0 +1,20 @@
+class CreateRates < ActiveRecord::Migration
+
+ def self.up
+ create_table :rates do |t|
+ t.belongs_to :rater
+ t.belongs_to :rateable, :polymorphic => true
+ t.float :stars, :null => false
+ t.string :dimension
+ t.timestamps
+ end
+
+ add_index :rates, :rater_id
+ add_index :rates, [:rateable_id, :rateable_type]
+ end
+
+ def self.down
+ drop_table :rates
+ end
+
+end
7 lib/generators/rateit/templates/model.rb
@@ -0,0 +1,7 @@
+class Rate < ActiveRecord::Base
+ belongs_to :rater, :class_name => "<%= file_name.classify %>"
+ belongs_to :rateable, :polymorphic => true
+
+ attr_accessible :rate, :dimension
+
+end
24 lib/generators/rateit/templates/rateit.js
@@ -0,0 +1,24 @@
+$.fn.raty.defaults.path = "/assets";
+$.fn.raty.defaults.half_show = true;
+
+$(function(){
+ $(".star").raty({
+ start: function(){
+ return $(this).attr('data-rating')
+ },
+ click: function(score, evt) {
+ $.post('<%= Rails.application.class.routes.url_helpers.rate_path %>',
+ {
+ score: score,
+ dimension: $(this).attr('data-dimension'),
+ id: $(this).attr('data-id'),
+ klass: $(this).attr('data-classname')
+ },
+ function(data) {
+ if(data) {
+ // success code goes here ...
+ }
+ });
+ }
+ });
+});
19 lib/generators/rateit/templates/rater_controller.rb
@@ -0,0 +1,19 @@
+class RaterController < ApplicationController
+
+ def create
+ if current_user.present?
+ obj = eval "#{params[:klass]}.find(#{params[:id]})"
+ if params[:dimension].present?
+ obj.rate params[:score].to_i, current_user.id, "#{params[:dimension]}"
+ else
+ obj.rate params[:score].to_i, current_user.id
+ end
+
+ render :json => true
+ else
+ render :json => false
+ end
+ end
+
+
+end
BIN  lib/generators/rateit/templates/star-half.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  lib/generators/rateit/templates/star-off.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  lib/generators/rateit/templates/star-on.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 lib/rateit.rb
@@ -0,0 +1,7 @@
+require "rateit/version"
+require "rateit/model"
+require "rateit/helpers"
+
+module Rateit
+
+end
24 lib/rateit/helpers.rb
@@ -0,0 +1,24 @@
+module Helpers
+ def rating_for(rateable_obj, dimension=nil, options={})
+
+ if dimension.nil?
+ klass = rateable_obj.average
+ else
+ klass = rateable_obj.average "#{dimension}"
+ end
+
+ if klass.nil?
+ avg = 0
+ else
+ avg = klass.avg
+ end
+
+ content_tag :div, "", "data-dimension" => dimension, :class => "star", "data-rating" => avg,
+ "data-id" => rateable_obj.id, "data-classname" => rateable_obj.class.name
+ end
+
+end
+
+class ActionView::Base
+ include Helpers
+end
107 lib/rateit/model.rb
@@ -0,0 +1,107 @@
+module Rateit
+ module ClassMethods
+
+ def rateit_rater
+ has_many :ratings_given, :class_name => "Rate", :foreign_key => :rater_id
+ end
+
+ def rateit_rateable(options = {})
+ has_many :rates_without_dimension, :as => :rateable, :class_name => "Rate", :dependent => :destroy, :conditions => {:dimension => nil}
+ has_many :raters_without_dimension, :through => :rates_without_dimension, :source => :rater
+
+ has_one :rate_average_without_dimension, :as => :cacheable, :class_name => "RatingCache",
+ :dependent => :destroy, :conditions => {:dimension => nil}
+
+
+ options[:dimensions].each do |dimension|
+ has_many "#{dimension}_rates", :dependent => :destroy,
+ :conditions => {:dimension => dimension.to_s},
+ :class_name => "Rate",
+ :as => :rateable
+
+ has_many "#{dimension}_raters", :through => "#{dimension}_rates", :source => :rater
+
+ has_one "#{dimension}_average", :as => :cacheable, :class_name => "RatingCache",
+ :dependent => :destroy, :conditions => {:dimension => dimension.to_s}
+ end if options[:dimensions].is_a?(Array)
+ end
+
+ end
+
+ module InstanceMethods
+
+ def rate(stars, user_id, dimension=nil)
+ if can_rate? user_id, dimension
+ rates(dimension).build do |r|
+ r.stars = stars
+ r.rater_id = user_id
+ r.save!
+ end
+ update_rate_average(stars, dimension)
+ else
+ raise "User has already rated."
+ end
+ end
+
+ def update_rate_average(stars, dimension=nil)
+ if average(dimension).nil?
+ RatingCache.create do |avg|
+ avg.cacheable_id = self.id
+ avg.cacheable_type = self.class.name
+ avg.avg = stars
+ avg.qty = 1
+ avg.dimension = dimension
+ avg.save!
+ end
+ else
+ a = average(dimension)
+ a.avg = (a.avg + stars) / (a.qty+1)
+ a.qty = a.qty + 1
+ a.save!
+ end
+ end
+
+ def average(dimension=nil)
+ if dimension.nil?
+ self.send "rate_average_without_dimension"
+ else
+ self.send "#{dimension}_average"
+ end
+ end
+
+ def can_rate?(user_id, dimension=nil)
+ val = self.connection.select_value("select count(*) as cnt from rates where rateable_id=#{self.id} and rateable_type='#{self.class.name}' and rater_id=#{user_id} and dimension='#{dimension}'").to_i
+ if val == 0
+ true
+ else
+ false
+ end
+ end
+
+ def rates(dimension=nil)
+ if dimension.nil?
+ self.send "rates_without_dimension"
+ else
+ self.send "#{dimension}_rates"
+ end
+ end
+
+ def raters(dimension=nil)
+ if dimension.nil?
+ self.send "raters_without_dimension"
+ else
+ self.send "#{dimension}_raters"
+ end
+ end
+
+ end
+
+ def self.included(receiver)
+ receiver.extend ClassMethods
+ receiver.send :include, InstanceMethods
+ end
+end
+
+class ActiveRecord::Base
+ include Rateit
+end
3  lib/rateit/version.rb
@@ -0,0 +1,3 @@
+module Rateit
+ VERSION = "1.0.3"
+end
24 rateit.gemspec
@@ -0,0 +1,24 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "rateit/version"
+
+Gem::Specification.new do |s|
+ s.name = "rateit"
+ s.version = Rateit::VERSION
+ s.authors = ["Murat GUZEL"]
+ s.email = ["guzelmurat@gmail.com"]
+ s.homepage = ""
+ s.summary = %q{Provides the best solution to add rating functionality to your models.}
+ s.description = %q{Provides the best solution to add rating functionality to your models.}
+
+ s.rubyforge_project = "rateit"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ # specify any dependencies here; for example:
+ # s.add_development_dependency "rspec"
+ # s.add_runtime_dependency "rest-client"
+end
Please sign in to comment.
Something went wrong with that request. Please try again.