diff --git a/Gemfile b/Gemfile index 261b71b7..f4decb75 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,7 @@ gem "coderay", "~> 1.0.6" gem "fastercsv", "~> 1.5.0", :platforms => [:mri_18, :mingw_18, :jruby] gem "builder", "3.0.0" gem 'acts-as-taggable-on' +gem 'seems_rateable' # Optional gem for LDAP authentication group :ldap do gem "net-ldap", "~> 0.3.1" diff --git a/Gemfile.lock b/Gemfile.lock index 99f29e91..aa29558c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -98,6 +98,9 @@ GEM rmagick (2.13.2) ruby-openid (2.1.8) rubyzip (0.9.9) + seems_rateable (1.0.9) + jquery-rails + rails selenium-webdriver (2.33.0) childprocess (>= 0.2.5) multi_json (~> 1.0) @@ -150,6 +153,7 @@ DEPENDENCIES rdoc (>= 2.4.2) rmagick (>= 2.0.0) ruby-openid (~> 2.1.4) + seems_rateable shoulda (~> 3.3.2) sqlite3 yard diff --git a/app/assets/javascripts/praise_tread.js b/app/assets/javascripts/praise_tread.js new file mode 100644 index 00000000..dee720fa --- /dev/null +++ b/app/assets/javascripts/praise_tread.js @@ -0,0 +1,2 @@ +// Place all the behaviors and hooks related to the matching controller here. +// All this logic will automatically be available in application.js. diff --git a/app/assets/javascripts/rateable/jRating.js.erb b/app/assets/javascripts/rateable/jRating.js.erb new file mode 100644 index 00000000..4f43a4f9 --- /dev/null +++ b/app/assets/javascripts/rateable/jRating.js.erb @@ -0,0 +1,225 @@ +/************************************************************************ +************************************************************************* +@Name : jRating - jQuery Plugin +@Revison : 3.0 +@Date : 28/01/2013 +@Author: ALPIXEL - (www.myjqueryplugins.com - www.alpixel.fr) +@License : Open Source - MIT License : http://www.opensource.org/licenses/mit-license.php + +************************************************************************** +*************************************************************************/ +(function($) { + $.fn.jRating = function(op) { + var defaults = { + /** String vars **/ + bigStarsPath : '<%= image_path "seems_rateable/stars.png" %>', // path of the icon stars.png + smallStarsPath : '<%= image_path "seems_rateable/small.png" %>', // path of the icon small.png + path : '<%= SeemsRateable::Engine.routes.url_helpers.ratings_path %>', + type : 'big', // can be set to 'small' or 'big' + + /** Boolean vars **/ + step:false, // if true, mouseover binded star by star, + isDisabled:false, + showRateInfo: false, + canRateAgain : false, + + /** Integer vars **/ + length:5, // number of star to display + decimalLength : 0, // number of decimals.. Max 3, but you can complete the function 'getNote' + rateMax : 20, // maximal rate - integer from 0 to 9999 (or more) + rateInfosX : -45, // relative position in X axis of the info box when mouseover + rateInfosY : 5, // relative position in Y axis of the info box when mouseover + nbRates : 1, + + /** Functions **/ + onSuccess : null, + onError : null + }; + + if(this.length>0) + return this.each(function() { + /*vars*/ + var opts = $.extend(defaults, op), + newWidth = 0, + starWidth = 0, + starHeight = 0, + bgPath = '', + hasRated = false, + globalWidth = 0, + nbOfRates = opts.nbRates; + + if($(this).hasClass('jDisabled') || opts.isDisabled) + var jDisabled = true; + else + var jDisabled = false; + + getStarWidth(); + $(this).height(starHeight); + + + + var average = parseFloat($(this).attr('data-average')), // get the average of all rates + idBox = parseInt($(this).attr('data-id')), // get the id of the box + kls = $(this).attr('data-kls'), + dimension = $(this).attr('data-dimension'), + widthRatingContainer = starWidth*opts.length, // Width of the Container + widthColor = average/opts.rateMax*widthRatingContainer, // Width of the color Container + quotient = + $('
',{ + 'class' : 'jRatingInfos', + html : getNote(relativeX)+' / '+opts.rateMax+'', + css : { + top: (e.pageY + opts.rateInfosY), + left: (e.pageX + opts.rateInfosX) + } + }).appendTo('body').show(); + }, + mouseover : function(e){ + $(this).css('cursor','pointer'); + }, + mouseout : function(){ + $(this).css('cursor','default'); + if(hasRated) average.width(globalWidth); + else average.width(0); + }, + mousemove : function(e){ + var realOffsetLeft = findRealLeft(this); + var relativeX = e.pageX - realOffsetLeft; + if(opts.step) newWidth = Math.floor(relativeX/starWidth)*starWidth + starWidth; + else newWidth = relativeX; + average.width(newWidth); + if (opts.showRateInfo) + $("p.jRatingInfos") + .css({ + left: (e.pageX + opts.rateInfosX) + }) + .html(getNote(newWidth) +' / '+opts.rateMax+''); + }, + mouseleave : function(){ + $("p.jRatingInfos").remove(); + }, + click : function(e){ + var element = this; + + /*set vars*/ + hasRated = true; + globalWidth = newWidth; + nbOfRates--; + + if(!opts.canRateAgain || parseInt(nbOfRates) <= 0) $(this).unbind().css('cursor','default').addClass('jDisabled'); + + if (opts.showRateInfo) $("p.jRatingInfos").fadeOut('fast',function(){$(this).remove();}); + e.preventDefault(); + var rate = getNote(newWidth); + average.width(newWidth); + + + $.post(defaults.path, + { + idBox : idBox, + rate : rate, + kls : kls, + dimension : dimension + /** action : 'rating' **/ + }, + function(data) { + if(!data.error) + { + /** Here you can display an alert box, + or use the jNotify Plugin :) http://www.myqjqueryplugins.com/jNotify + exemple : */ + if(opts.onSuccess) opts.onSuccess( element, rate ); + } + else + { + + /** Here you can display an alert box, + or use the jNotify Plugin :) http://www.myqjqueryplugins.com/jNotify + exemple : */ + if(opts.onError) opts.onError( element, rate ); + } + }, + 'json' + ); + } + }); + + function getNote(relativeX) { + var noteBrut = parseFloat((relativeX*100/widthRatingContainer)*opts.rateMax/100); + switch(opts.decimalLength) { + case 1 : + var note = Math.round(noteBrut*10)/10; + break; + case 2 : + var note = Math.round(noteBrut*100)/100; + break; + case 3 : + var note = Math.round(noteBrut*1000)/1000; + break; + default : + var note = Math.round(noteBrut*1)/1; + } + return note; + }; + + function getStarWidth(){ + switch(opts.type) { + case 'small' : + starWidth = 12; // width of the picture small.png + starHeight = 10; // height of the picture small.png + bgPath = opts.smallStarsPath; + break; + default : + starWidth = 23; // width of the picture stars.png + starHeight = 20; // height of the picture stars.png + bgPath = opts.bigStarsPath; + } + }; + + function findRealLeft(obj) { + if( !obj ) return 0; + return obj.offsetLeft + findRealLeft( obj.offsetParent ); + }; + }); + + } +})(jQuery); diff --git a/app/assets/javascripts/rateable/rateable.js.erb b/app/assets/javascripts/rateable/rateable.js.erb new file mode 100644 index 00000000..da6cc309 --- /dev/null +++ b/app/assets/javascripts/rateable/rateable.js.erb @@ -0,0 +1,25 @@ +$(document).ready(function(){ + $(".rateable").jRating({ + //default options displayed below -> + + rateMax: 5, //Maximal rate + length : 5, //Number of stars + //decimalLength : 0, //Number of decimals in the rate + //type : 'big', //Big or small + //step : true, //If set to true, filling of the stars is done star by star (step by step). + //isDisabled: false, //Set true to display static rating + //showRateInfo:false, //Rate info panel, set true to display + //rateInfosX : 45, //In pixel - Absolute left position of the information box during mousemove. + //rateInfosY : 5, //In pixel - Absolute top position of the information box during mousemove. + path : '<%= SeemsRateable::Engine.routes.url_helpers.ratings_path %>', + onSuccess : function(element, rate){ + //something like -> + //alert('success'); + $('Thanks for rating!').insertAfter(element) + }, + onError : function(element, rate) { + $('You have already rated!').insertAfter(element) + } + }); + +}); diff --git a/app/assets/stylesheets/praise_tread.css b/app/assets/stylesheets/praise_tread.css new file mode 100644 index 00000000..afad32db --- /dev/null +++ b/app/assets/stylesheets/praise_tread.css @@ -0,0 +1,4 @@ +/* + Place all the styles related to the matching controller here. + They will automatically be included in application.css. +*/ diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2a4b5fb5..f57983e3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -95,6 +95,9 @@ class ApplicationController < ActionController::Base # Returns the current user or nil if no user is logged in # and starts a session if needed + def current_user + find_current_user + end def find_current_user user = nil unless api_request? diff --git a/app/controllers/praise_tread_controller.rb b/app/controllers/praise_tread_controller.rb new file mode 100644 index 00000000..52801110 --- /dev/null +++ b/app/controllers/praise_tread_controller.rb @@ -0,0 +1,68 @@ +class PraiseTreadController < ApplicationController + + def praise_plus + @obj = nil + if request.get? + @obj = params[:obj] # 传的是对象,最后变成id了 + + #首先创建或更新praise_tread 表 + @pt = PraiseTread.find_by_user_id_and_praise_tread_object_id(User.current.id,@obj) + @pt = @pt.nil? ? PraiseTread.new : @pt + + @pt.user_id = User.current.id + @pt.praise_tread_object_id = @obj.to_i + @pt.praise_tread_object_type = User.find_by_id(@obj).class.name.underscore + @pt.praise_or_tread = 1 + @pt.save + + #再创建或更新praise_tread_cache表 + @ptc = PraiseTreadCache.find_by_object_id(@obj) + @ptc = @ptc.nil? ? PraiseTreadCache.new : @ptc + @ptc.object_id = @obj.to_i + @ptc.object_type = User.find_by_id(@obj).class.name.underscore + @ptc.plus(1) + @ptc.save + end + @obj = User.find_by_id(@obj) + respond_to do |format| + format.html + format.js + end + end + + def praise_minus + @obj = nil + if request.get? + @obj = params[:obj] # 传的是对象,最后变成id了 + + #首先更新praise_tread 表 删除关注记录 + @pt = PraiseTread.find_by_user_id_and_praise_tread_object_id_and_praise_tread_object_type(User.current.id,@obj,"user") + @pt.delete + + #再更新praise_tread_cache表 使相应的记录减1 当为0时删除 + @ptc = PraiseTreadCache.find_by_object_id(@obj) + @ptc.minus(1) + if @ptc.praise_num == 0 + @ptc.delete + end + + end + @obj = User.find_by_id(@obj) + respond_to do |format| + format.html + format.js + end + end + + def tread_plus + + end + + def tread_minus + respond_to do |format| + format.html + format.js + end + end + +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 8d14e63f..9935b577 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -26,12 +26,8 @@ module ApplicationHelper include GravatarHelper::PublicMethods include Redmine::Pagination::Helper include AvatarHelper - - ### added by william - include ActsAsTaggableOn::TagsHelper - # include WatchersHelper - - + include PraiseTreadHelper + extend Forwardable def_delegators :wiki_helper, :wikitoolbar_for, :heads_for_wiki_formatter diff --git a/app/helpers/praise_tread_helper.rb b/app/helpers/praise_tread_helper.rb new file mode 100644 index 00000000..9b2eba96 --- /dev/null +++ b/app/helpers/praise_tread_helper.rb @@ -0,0 +1,23 @@ +module PraiseTreadHelper + #added by william + def is_praise_or_tread(object,user_id) + @obj_type = object.class.name.underscore + @obj_id = object.id + @is_praise = PraiseTread.find_by_sql("select * from praise_treads where user_id=#{user_id} and " + + "praise_tread_object_type='#{@obj_type}' and praise_tread_object_id=#{@obj_id} ") + return @is_praise + end + #end + + def get_praise_num(object) + @obj_type = object.class.name.underscore + @obj_id = object.id + @record = PraiseTreadCache.find_by_object_id_and_object_type(@obj_id,@obj_type) + if @record + return @record.praise_num + else + return 0 + end + end + +end diff --git a/app/models/praise_tread.rb b/app/models/praise_tread.rb new file mode 100644 index 00000000..901a3166 --- /dev/null +++ b/app/models/praise_tread.rb @@ -0,0 +1,4 @@ +class PraiseTread < ActiveRecord::Base + attr_accessible :user_id,:praise_tread_object_id,:praise_tread_object_type,:praise_or_tread + +end diff --git a/app/models/praise_tread_cache.rb b/app/models/praise_tread_cache.rb new file mode 100644 index 00000000..330e197c --- /dev/null +++ b/app/models/praise_tread_cache.rb @@ -0,0 +1,11 @@ +class PraiseTreadCache < ActiveRecord::Base + attr_accessible :object_id,:object_type,:praise_num,:tread_num + + def plus(num) + self.update_attribute(:praise_num, self.praise_num.to_i + num) + end + + def minus(num) + self.update_attribute(:praise_num, self.praise_num.to_i - num) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index 6e720c06..8050082a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -103,7 +103,7 @@ class User < Principal acts_as_customizable ############################added by william acts_as_taggable - + seems_rateable ############################# added by liuping 关注 acts_as_watchable diff --git a/app/views/layouts/base_users.html.erb b/app/views/layouts/base_users.html.erb index 985fdc90..f6abf5e6 100644 --- a/app/views/layouts/base_users.html.erb +++ b/app/views/layouts/base_users.html.erb @@ -9,7 +9,11 @@ <%= favicon %> <%= stylesheet_link_tag 'jquery/jquery-ui-1.9.2', 'application', :media => 'all' %> <%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %> + + <%= seems_rateable_stylesheet %> + <%= javascript_heads %> + <%= heads_for_theme %> <%= call_hook :view_layouts_base_html_head %> @@ -44,6 +48,11 @@ + +
seems_rateable
to routes.rb file
+
+Include stylesheet adding <%= seems_rateable_stylesheet %>
to your layout header
+
+Also make sure you have an existing current_user
helper method
+
+Don't forget to run
+
+ $ rake db:migrate
+
+To prepare model add seems_rateable
to your rateable model file. You can also pass a hash of options to
+customize the functionality
+
+:dimensions
Array of dimensions e.g :dimensions => [:quality, :quantity]
:allow_update
Allowing user to re-rate his own ratings, default set to false e.g :allow_update=> true
rates
method, to get dimension rates pass an argument eg :
+
+ @object.rates
+ @object.rates(:quality)
+ @object.rates(:quantity)
+
+This also applies to cached average rating e.g
+
+ @object.average
+ @object.average(:quality)
+ @object.average(:quantity)
+
+And to object's raters e.g
+
+ @object.raters
+ @object.raters(:quality)
+ @object.raters(:quantity)
+
+To track user's given ratings add seems_rateable_rater
to your rater model.
+If your rater class is not "User"(e.g "Client" or "Customer") change configuration in initializer generated by this engine.
+Now you can access user's ratings by @user.ratings_given
+
+### Usage
+
+To display star rating use helper method rating_for
in your view
+
+ #index.html.erb
+
+ rating_for @post
+
+ rating_for @post, :dimension => :quality, :class => 'post', :id => 'list'
+
+ rating_for @post, :static => true
+
+You can specify these options :
+:dimension
The dimension of the object:static
Set to true to display static star rating, default false:class
Class of the div, default set to 'rateable':id
ID of the div e.g :id => "info"
, default nil',{ + 'class' : 'jRatingInfos', + html : getNote(relativeX)+' / '+opts.rateMax+'', + css : { + top: (e.pageY + opts.rateInfosY), + left: (e.pageX + opts.rateInfosX) + } + }).appendTo('body').show(); + }, + mouseover : function(e){ + $(this).css('cursor','pointer'); + }, + mouseout : function(){ + $(this).css('cursor','default'); + if(hasRated) average.width(globalWidth); + else average.width(0); + }, + mousemove : function(e){ + var realOffsetLeft = findRealLeft(this); + var relativeX = e.pageX - realOffsetLeft; + if(opts.step) newWidth = Math.floor(relativeX/starWidth)*starWidth + starWidth; + else newWidth = relativeX; + average.width(newWidth); + if (opts.showRateInfo) + $("p.jRatingInfos") + .css({ + left: (e.pageX + opts.rateInfosX) + }) + .html(getNote(newWidth) +' / '+opts.rateMax+''); + }, + mouseleave : function(){ + $("p.jRatingInfos").remove(); + }, + click : function(e){ + var element = this; + + /*set vars*/ + hasRated = true; + globalWidth = newWidth; + nbOfRates--; + + if(!opts.canRateAgain || parseInt(nbOfRates) <= 0) $(this).unbind().css('cursor','default').addClass('jDisabled'); + + if (opts.showRateInfo) $("p.jRatingInfos").fadeOut('fast',function(){$(this).remove();}); + e.preventDefault(); + var rate = getNote(newWidth); + average.width(newWidth); + + + $.post(defaults.path, + { + idBox : idBox, + rate : rate, + kls : kls, + dimension : dimension + /** action : 'rating' **/ + }, + function(data) { + if(!data.error) + { + /** Here you can display an alert box, + or use the jNotify Plugin :) http://www.myqjqueryplugins.com/jNotify + exemple : */ + if(opts.onSuccess) opts.onSuccess( element, rate ); + } + else + { + + /** Here you can display an alert box, + or use the jNotify Plugin :) http://www.myqjqueryplugins.com/jNotify + exemple : */ + if(opts.onError) opts.onError( element, rate ); + } + }, + 'json' + ); + } + }); + + function getNote(relativeX) { + var noteBrut = parseFloat((relativeX*100/widthRatingContainer)*opts.rateMax/100); + switch(opts.decimalLength) { + case 1 : + var note = Math.round(noteBrut*10)/10; + break; + case 2 : + var note = Math.round(noteBrut*100)/100; + break; + case 3 : + var note = Math.round(noteBrut*1000)/1000; + break; + default : + var note = Math.round(noteBrut*1)/1; + } + return note; + }; + + function getStarWidth(){ + switch(opts.type) { + case 'small' : + starWidth = 12; // width of the picture small.png + starHeight = 10; // height of the picture small.png + bgPath = opts.smallStarsPath; + break; + default : + starWidth = 23; // width of the picture stars.png + starHeight = 20; // height of the picture stars.png + bgPath = opts.bigStarsPath; + } + }; + + function findRealLeft(obj) { + if( !obj ) return 0; + return obj.offsetLeft + findRealLeft( obj.offsetParent ); + }; + }); + + } +})(jQuery); diff --git a/lib/plugins/seems_rateable-master/lib/generators/seems_rateable/install/templates/rateable.js.erb b/lib/plugins/seems_rateable-master/lib/generators/seems_rateable/install/templates/rateable.js.erb new file mode 100644 index 00000000..da6cc309 --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/generators/seems_rateable/install/templates/rateable.js.erb @@ -0,0 +1,25 @@ +$(document).ready(function(){ + $(".rateable").jRating({ + //default options displayed below -> + + rateMax: 5, //Maximal rate + length : 5, //Number of stars + //decimalLength : 0, //Number of decimals in the rate + //type : 'big', //Big or small + //step : true, //If set to true, filling of the stars is done star by star (step by step). + //isDisabled: false, //Set true to display static rating + //showRateInfo:false, //Rate info panel, set true to display + //rateInfosX : 45, //In pixel - Absolute left position of the information box during mousemove. + //rateInfosY : 5, //In pixel - Absolute top position of the information box during mousemove. + path : '<%= SeemsRateable::Engine.routes.url_helpers.ratings_path %>', + onSuccess : function(element, rate){ + //something like -> + //alert('success'); + $('Thanks for rating!').insertAfter(element) + }, + onError : function(element, rate) { + $('You have already rated!').insertAfter(element) + } + }); + +}); diff --git a/lib/plugins/seems_rateable-master/lib/generators/seems_rateable/install/templates/rates_migration.rb b/lib/plugins/seems_rateable-master/lib/generators/seems_rateable/install/templates/rates_migration.rb new file mode 100644 index 00000000..40303f11 --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/generators/seems_rateable/install/templates/rates_migration.rb @@ -0,0 +1,18 @@ +class CreateSeemsRateableRates < ActiveRecord::Migration + def self.up + create_table :seems_rateable_rates do |t| + t.belongs_to :rater + t.belongs_to :rateable, :polymorphic => true + t.float :stars, :null => false + t.integer :rater_id, :limit => 8 + t.integer :rateable_id + t.string :rateable_type + t.string :dimension + t.timestamps + end + end + + def self.down + drop_table :rates + end +end diff --git a/lib/plugins/seems_rateable-master/lib/seems_rateable.rb b/lib/plugins/seems_rateable-master/lib/seems_rateable.rb new file mode 100644 index 00000000..5f84a8b8 --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/seems_rateable.rb @@ -0,0 +1,14 @@ +begin + require 'rails' +rescue LoadError +end + +require "seems_rateable/engine" +require "seems_rateable/errors" +require "seems_rateable/helpers" +require "seems_rateable/model" +require "seems_rateable/routes" +require "seems_rateable/version" + +module SeemsRateable +end diff --git a/lib/plugins/seems_rateable-master/lib/seems_rateable/engine.rb b/lib/plugins/seems_rateable-master/lib/seems_rateable/engine.rb new file mode 100644 index 00000000..185d6f9d --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/seems_rateable/engine.rb @@ -0,0 +1,17 @@ +module SeemsRateable + class Engine < ::Rails::Engine + isolate_namespace SeemsRateable + + config.generators do |g| + g.test_framework :rspec, :fixture => false + g.fixture_replacement :factory_girl, :dir => 'spec/factories' + end + + initializer :seems_rateable do + ActiveRecord::Base.send :include, SeemsRateable::Model + ActionView::Base.send :include, SeemsRateable::Helpers + ActionDispatch::Routing::Mapper.send :include, SeemsRateable::Routes + end + + end +end diff --git a/lib/plugins/seems_rateable-master/lib/seems_rateable/errors.rb b/lib/plugins/seems_rateable-master/lib/seems_rateable/errors.rb new file mode 100644 index 00000000..8a83059b --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/seems_rateable/errors.rb @@ -0,0 +1,21 @@ +module SeemsRateable + module Errors + class InvalidRateableObjectError < StandardError + def to_s + "Stated object is not rateable. Add 'seems_rateable' to your object's class model." + end + end + + class NoCurrentUserInstanceError < StandardError + def to_s + "User instance current_user is not available." + end + end + + class AlreadyRatedError < StandardError + def to_s + "User has already rated an object." + end + end + end +end diff --git a/lib/plugins/seems_rateable-master/lib/seems_rateable/helpers.rb b/lib/plugins/seems_rateable-master/lib/seems_rateable/helpers.rb new file mode 100644 index 00000000..399f06fd --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/seems_rateable/helpers.rb @@ -0,0 +1,27 @@ +module SeemsRateable + module Helpers + def rating_for(obj, opts={}) + raise Errors::InvalidRateableObjectError unless obj.class.respond_to?(:rateable?) + + options = { + :dimension => nil, + :static => false, + :class => 'rateable', + :id => nil + }.update(opts) + + content_tag :div, "", "data-average" => obj.average(options[:dimension]) ? obj.average(options[:dimension]).avg : 0, :id => options[:id], + :class => "#{options[:class]}#{jdisabled?(options[:static])}", + "data-id" => obj.id, "data-kls" => obj.class.name, "data-dimension" => options[:dimension] + end + + def seems_rateable_stylesheet + stylesheet_link_tag "seems_rateable/application", media: "all", "data-turbolinks-track" => true + end + + private + def jdisabled?(option) + " jDisabled" if option || !current_user + end + end +end diff --git a/lib/plugins/seems_rateable-master/lib/seems_rateable/model.rb b/lib/plugins/seems_rateable-master/lib/seems_rateable/model.rb new file mode 100644 index 00000000..fa1b7af1 --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/seems_rateable/model.rb @@ -0,0 +1,111 @@ +require 'active_support/concern' +module SeemsRateable + module Model + extend ActiveSupport::Concern + + def rate(stars, user_id, dimension=nil) + if !has_rated?(user_id, dimension) + self.rates.create do |r| + r.stars = stars + r.rater_id = user_id + end + update_overall_average_rating(stars, dimension) + elsif has_rated?(user_id, dimension) && can_update? + update_users_rating(stars, user_id, dimension) + else + raise Errors::AlreadyRatedError + end + end + + def update_overall_average_rating(stars, dimension=nil) + if average(dimension).nil? + CachedRating.create do |r| + r.avg = stars + r.dimension = dimension + r.cacheable_id = self.id + r.cacheable_type = self.class.name + r.cnt = 1 + end + else + r = average(dimension) + r.avg = (r.avg * r.cnt + stars) / (r.cnt+1) + r.cnt += 1 + r.save! + end + end + + def update_users_rating(stars, user_id, dimension=nil) + obj = rates(dimension).where(:rater_id => user_id).first + current_record = average(dimension) + current_record.avg = (current_record.avg*current_record.cnt - obj.stars + stars) / (current_record.cnt) + current_record.save! + obj.stars = stars + obj.save! + end + + + def average(dimension=nil) + if dimension.nil? + self.send "rate_average_without_dimension" + else + self.send "#{dimension}_average" + 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 + + def has_rated?(user_id, dimension=nil) + record = self.rates(dimension).where(:rater_id => user_id) + record.empty? ? false : true + end + + def can_update? + self.class.can_update? + end + + module ClassMethods + def seems_rateable(opts={}) + #has_many :rates_without_dimension, -> { where(dimension: nil) }, :as => :rateable, :class_name => SeemsRateable::Rate, :dependent => :destroy + has_many :rates_without_dimension, :conditions => { dimension: nil }, :as => :rateable, :class_name => SeemsRateable::Rate, :dependent => :destroy + has_many :raters_without_dimension, :through => :rates_without_dimension, :source => :rater + has_one :rate_average_without_dimension, :conditions => { dimension: nil }, :as => :cacheable, :class_name => SeemsRateable::CachedRating, :dependent => :destroy + + @permission = opts[:allow_update] ? true : false + + def self.can_update? + @permission + end + + def self.rateable? + true + end + + if opts[:dimensions].is_a?(Array) + opts[:dimensions].each do |dimension| + has_many :"#{dimension}_rates", :conditions => { dimension: dimension.to_s }, :dependent => :destroy, :class_name => SeemsRateable::Rate, :as => :rateable + has_many :"#{dimension}_raters", :through => :"#{dimension}_rates", :source => :rater + has_one :"#{dimension}_average", :conditions => { dimension: dimension.to_s }, :as => :cacheable, :class_name => SeemsRateable::CachedRating, :dependent => :destroy + end + end + end + + def seems_rateable_rater + has_many :ratings_given, :class_name => SeemsRateable::Rate, :foreign_key => :rater_id + end + end + end +end diff --git a/lib/plugins/seems_rateable-master/lib/seems_rateable/routes.rb b/lib/plugins/seems_rateable-master/lib/seems_rateable/routes.rb new file mode 100644 index 00000000..81a24333 --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/seems_rateable/routes.rb @@ -0,0 +1,7 @@ +module SeemsRateable + module Routes + def seems_rateable + mount SeemsRateable::Engine => '/rateable', :as => :rateable + end + end +end diff --git a/lib/plugins/seems_rateable-master/lib/seems_rateable/version.rb b/lib/plugins/seems_rateable-master/lib/seems_rateable/version.rb new file mode 100644 index 00000000..180e4782 --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/seems_rateable/version.rb @@ -0,0 +1,3 @@ +module SeemsRateable + VERSION = "1.0.9" +end diff --git a/lib/plugins/seems_rateable-master/lib/tasks/seems_rateable_tasks.rake b/lib/plugins/seems_rateable-master/lib/tasks/seems_rateable_tasks.rake new file mode 100644 index 00000000..98c5403a --- /dev/null +++ b/lib/plugins/seems_rateable-master/lib/tasks/seems_rateable_tasks.rake @@ -0,0 +1,4 @@ +# desc "Explaining what the task does" +# task :seems_rateable do +# # Task goes here +# end diff --git a/lib/plugins/seems_rateable-master/seems_rateable.gemspec b/lib/plugins/seems_rateable-master/seems_rateable.gemspec new file mode 100644 index 00000000..b4f32a9f --- /dev/null +++ b/lib/plugins/seems_rateable-master/seems_rateable.gemspec @@ -0,0 +1,25 @@ +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "seems_rateable/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "seems_rateable" + s.version = SeemsRateable::VERSION + s.authors = ["Peter Toth"] + s.email = ["proximin@gmail.com"] + s.homepage = "http://rateable.herokuapp.com" + s.summary = "Star Rating Engine" + s.description = "Star rating engine using jQuery plugin jRating for Rails applications" + + s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"] + + s.add_dependency "rails" + s.add_dependency "jquery-rails" + + s.add_development_dependency "sqlite3" + s.add_development_dependency 'rspec-rails' + s.add_development_dependency 'capybara' + s.add_development_dependency 'factory_girl_rails' +end diff --git a/public/images/praise.png b/public/images/praise.png new file mode 100644 index 00000000..808d1813 Binary files /dev/null and b/public/images/praise.png differ diff --git a/public/images/tread.png b/public/images/tread.png new file mode 100644 index 00000000..a843cc3d Binary files /dev/null and b/public/images/tread.png differ