加入了对赞功能模块,同时预留了踩功能,但暂时不使用。
This commit is contained in:
parent
4676eed9a3
commit
0b5ea87f11
1
Gemfile
1
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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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 =
|
||||
$('<div>',
|
||||
{
|
||||
'class' : 'jRatingColor',
|
||||
css:{
|
||||
width:widthColor
|
||||
}
|
||||
}).appendTo($(this)),
|
||||
|
||||
average =
|
||||
$('<div>',
|
||||
{
|
||||
'class' : 'jRatingAverage',
|
||||
css:{
|
||||
width:0,
|
||||
top:- starHeight
|
||||
}
|
||||
}).appendTo($(this)),
|
||||
|
||||
jstar =
|
||||
$('<div>',
|
||||
{
|
||||
'class' : 'jStar',
|
||||
css:{
|
||||
width:widthRatingContainer,
|
||||
height:starHeight,
|
||||
top:- (starHeight*2),
|
||||
background: 'url('+bgPath+') repeat-x'
|
||||
}
|
||||
}).appendTo($(this));
|
||||
|
||||
$(this).css({width: widthRatingContainer,overflow:'hidden',zIndex:1,position:'relative'});
|
||||
|
||||
if(!jDisabled)
|
||||
$(this).unbind().bind({
|
||||
mouseenter : function(e){
|
||||
var realOffsetLeft = findRealLeft(this);
|
||||
var relativeX = e.pageX - realOffsetLeft;
|
||||
if (opts.showRateInfo)
|
||||
var tooltip =
|
||||
$('<p>',{
|
||||
'class' : 'jRatingInfos',
|
||||
html : getNote(relativeX)+' <span class="maxRate">/ '+opts.rateMax+'</span>',
|
||||
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) +' <span class="maxRate">/ '+opts.rateMax+'</span>');
|
||||
},
|
||||
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);
|
|
@ -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');
|
||||
$('<span class="text-success"><small style="display:inline-block;">Thanks for rating!</small></span>').insertAfter(element)
|
||||
},
|
||||
onError : function(element, rate) {
|
||||
$('<span class="text-error"><small style="display:inline-block;">You have already rated!</small></span>').insertAfter(element)
|
||||
}
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,4 @@
|
|||
/*
|
||||
Place all the styles related to the matching controller here.
|
||||
They will automatically be included in application.css.
|
||||
*/
|
|
@ -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?
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,4 @@
|
|||
class PraiseTread < ActiveRecord::Base
|
||||
attr_accessible :user_id,:praise_tread_object_id,:praise_tread_object_type,:praise_or_tread
|
||||
|
||||
end
|
|
@ -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
|
|
@ -103,7 +103,7 @@ class User < Principal
|
|||
acts_as_customizable
|
||||
############################added by william
|
||||
acts_as_taggable
|
||||
|
||||
seems_rateable
|
||||
############################# added by liuping 关注
|
||||
acts_as_watchable
|
||||
|
||||
|
|
|
@ -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' %>
|
||||
<!-- added by william -->
|
||||
<%= seems_rateable_stylesheet %>
|
||||
<!-- end -->
|
||||
<%= javascript_heads %>
|
||||
|
||||
<%= heads_for_theme %>
|
||||
<%= call_hook :view_layouts_base_html_head %>
|
||||
<!-- page specific tags -->
|
||||
|
@ -44,6 +48,11 @@
|
|||
</table></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id="praise_tread">
|
||||
<%= render :partial => "/praise_tread/praise_tread",:locals => {:obj => @user,:show_flag => false,:user_id => User.current.id}%>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<%= l(:label_user_watcher) %> (<strong class="font_small_watch"><%= User.watched_by(@user.id).count %></strong>)
|
||||
<%= l(:label_user_fans) %> (<strong class="font_small_watch"><%= @user.watcher_users.count %></strong>)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<div id="praise">
|
||||
<% if is_praise_or_tread(obj,user_id).size > 0 %>
|
||||
<%= image_tag("/images/praise.png") %>
|
||||
<%= link_to "取消贊",:controller=>"praise_tread",:action=>"praise_minus",:remote=>true,:obj => obj %>
|
||||
(<%= get_praise_num(obj)%>)
|
||||
<% else %>
|
||||
<%= image_tag("/images/tread.png") %>
|
||||
<%= link_to "贊",:controller=>"praise_tread",:action=>"praise_plus",:remote=>true,:obj => obj %>
|
||||
(<%= get_praise_num(obj)%>)
|
||||
<% end %>
|
||||
</div>
|
||||
<% if show_flag %>
|
||||
<div id="tread">
|
||||
<%= link_to image_tag("/images/tread.png"),:controller=>"praise_tread",
|
||||
:action=>"tread_minus",:remote=>true,:obj => obj %>踩
|
||||
</div>
|
||||
<% end %>
|
|
@ -0,0 +1,3 @@
|
|||
$('#praise_tread').html('<%= j(
|
||||
render :partial => "/praise_tread/praise_tread",:locals => {:obj => @obj,:show_flag => false,:user_id => User.current.id}
|
||||
)%>');
|
|
@ -0,0 +1,3 @@
|
|||
$('#praise_tread').html('<%= j(
|
||||
render :partial => "/praise_tread/praise_tread",:locals => {:obj => @obj,:show_flag => false,:user_id => User.current.id}
|
||||
)%>');
|
|
@ -0,0 +1,4 @@
|
|||
#SeemsRateable engine Initializer
|
||||
|
||||
#Configure owner class of the given ratings
|
||||
SeemsRateable::Engine.config.owner_class = "User"
|
|
@ -17,11 +17,13 @@
|
|||
|
||||
RedmineApp::Application.routes.draw do
|
||||
resources :shares
|
||||
|
||||
|
||||
|
||||
get "tags/index"
|
||||
|
||||
get "tags/show"
|
||||
get "praise_tread/praise_plus"
|
||||
get "praise_tread/praise_minus"
|
||||
get "praise_tread/tread_minus"
|
||||
|
||||
root :to => 'welcome#index', :as => 'home'
|
||||
|
||||
|
@ -95,7 +97,7 @@ RedmineApp::Application.routes.draw do
|
|||
match 'users/:id/memberships/:membership_id', :to => 'users#edit_membership', :via => :put, :as => 'user_membership'
|
||||
match 'users/:id/memberships/:membership_id', :to => 'users#destroy_membership', :via => :delete
|
||||
match 'users/:id/memberships', :to => 'users#edit_membership', :via => :post, :as => 'user_memberships'
|
||||
################# added by william
|
||||
################# added by william
|
||||
match 'users/tag_save', :to => 'users#tag_save', :via => :post, :as => 'tag'
|
||||
|
||||
post 'watchers/watch', :to => 'watchers#watch', :as => 'watch'
|
||||
|
@ -422,8 +424,12 @@ RedmineApp::Application.routes.draw do
|
|||
|
||||
match 'bids/:id', :controller => 'bids', :action => 'show', :as => 'respond'
|
||||
|
||||
|
||||
|
||||
########### added by liuping
|
||||
match 'tags/add_tag',:to => 'tags#add_tag',:as=>"add_tag"
|
||||
match 'tags/delete_tag',:to => 'tags#delete_tag',:as=>"add_tag"
|
||||
match 'parise_tread/praise_plus',:to => 'parise_tread#praise_plus',:as=>"praise"
|
||||
match 'parise_tread/tread_minus',:to => 'parise_tread#tread_minus',:as=>"tread"
|
||||
|
||||
end
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,17 @@
|
|||
class CreateSeemsRateableCachedRatings < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :seems_rateable_cached_ratings do |t|
|
||||
t.belongs_to :cacheable, :polymorphic => true
|
||||
t.float :avg, :null => false
|
||||
t.integer :cnt, :null => false
|
||||
t.string :dimension
|
||||
t.integer :cacheable_id, :limit => 8
|
||||
t.string :cacheable_type
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :cached_ratings
|
||||
end
|
||||
end
|
|
@ -0,0 +1,15 @@
|
|||
class CreatePraiseTreads < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :praise_treads do |t|
|
||||
t.column :user_id,:integer,:null => false
|
||||
t.column :praise_tread_object_id,:integer
|
||||
t.column :praise_tread_object_type,:string
|
||||
t.column :praise_or_tread,:integer
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :praise_treads
|
||||
end
|
||||
end
|
|
@ -0,0 +1,16 @@
|
|||
class CreatePraiseTreadCaches < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :praise_tread_caches do |t|
|
||||
t.column :object_id,:integer,:null => false
|
||||
t.column :object_type,:string
|
||||
t.column :praise_num,:integer
|
||||
t.column :tread_num,:integer
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :praise_tread_caches
|
||||
end
|
||||
end
|
41
db/schema.rb
41
db/schema.rb
|
@ -11,7 +11,7 @@
|
|||
#
|
||||
# It's strongly recommended to check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(:version => 20130805131602) do
|
||||
ActiveRecord::Schema.define(:version => 20130807021309) do
|
||||
|
||||
create_table "a_user_watchers", :force => true do |t|
|
||||
t.string "name"
|
||||
|
@ -432,6 +432,24 @@ ActiveRecord::Schema.define(:version => 20130805131602) do
|
|||
t.string "salt", :null => false
|
||||
end
|
||||
|
||||
create_table "praise_tread_caches", :force => true do |t|
|
||||
t.integer "object_id", :null => false
|
||||
t.string "object_type"
|
||||
t.integer "praise_num"
|
||||
t.integer "tread_num"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
create_table "praise_treads", :force => true do |t|
|
||||
t.integer "user_id", :null => false
|
||||
t.integer "praise_tread_object_id"
|
||||
t.string "praise_tread_object_type"
|
||||
t.integer "praise_or_tread"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
create_table "project_tags", :force => true do |t|
|
||||
t.integer "project_id"
|
||||
t.integer "tag_id"
|
||||
|
@ -507,6 +525,26 @@ ActiveRecord::Schema.define(:version => 20130805131602) do
|
|||
t.string "issues_visibility", :limit => 30, :default => "default", :null => false
|
||||
end
|
||||
|
||||
create_table "seems_rateable_cached_ratings", :force => true do |t|
|
||||
t.integer "cacheable_id", :limit => 8
|
||||
t.string "cacheable_type"
|
||||
t.float "avg", :null => false
|
||||
t.integer "cnt", :null => false
|
||||
t.string "dimension"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
create_table "seems_rateable_rates", :force => true do |t|
|
||||
t.integer "rater_id", :limit => 8
|
||||
t.integer "rateable_id"
|
||||
t.string "rateable_type"
|
||||
t.float "stars", :null => false
|
||||
t.string "dimension"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
end
|
||||
|
||||
create_table "settings", :force => true do |t|
|
||||
t.string "name", :default => "", :null => false
|
||||
t.text "value"
|
||||
|
@ -522,6 +560,7 @@ ActiveRecord::Schema.define(:version => 20130805131602) do
|
|||
t.string "url"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
t.integer "project_id"
|
||||
end
|
||||
|
||||
create_table "students", :force => true do |t|
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
*.gem
|
||||
*.rbc
|
||||
.bundle
|
||||
.config
|
||||
.yardoc
|
||||
Gemfile.lock
|
||||
InstalledFiles
|
||||
_yardoc
|
||||
coverage
|
||||
doc/
|
||||
lib/bundler/man
|
||||
pkg
|
||||
rdoc
|
||||
spec/reports
|
||||
test/tmp
|
||||
test/version_tmp
|
||||
tmp
|
||||
.project
|
||||
.rvmrc
|
||||
spec
|
||||
test
|
|
@ -0,0 +1,23 @@
|
|||
source "https://rubygems.org"
|
||||
|
||||
# Declare your gem's dependencies in seems_rateable.gemspec.
|
||||
# Bundler will treat runtime dependencies like base dependencies, and
|
||||
# development dependencies will be added by default to the :development group.
|
||||
gemspec
|
||||
|
||||
# Declare any dependencies that are still in development here instead of in
|
||||
# your gemspec. These might include edge Rails or gems from your path or
|
||||
# Git. Remember to move these dependencies to your gemspec before releasing
|
||||
# your gem to rubygems.org.
|
||||
|
||||
# To use debugger
|
||||
# gem 'debugger'
|
||||
|
||||
|
||||
group :development do
|
||||
gem 'sqlite3'
|
||||
gem 'jquery-rails'
|
||||
gem 'twitter-bootstrap-rails'
|
||||
gem 'sorcery'
|
||||
end
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
Copyright 2013 YOURNAME
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,111 @@
|
|||
# SeemsRateable
|
||||
|
||||
Star rating gem for Rails application using jQuery plugin <a href="http://www.myjqueryplugins.com/jquery-plugin/jrating">jRating</a>
|
||||
|
||||
## Demo
|
||||
|
||||
<a href="http://rateable.herokuapp.com/">Demo</a> application, requires to sign up before rating
|
||||
|
||||
## Instructions
|
||||
|
||||
### Installation
|
||||
|
||||
Add this line to your application's Gemfile:
|
||||
|
||||
gem 'seems_rateable'
|
||||
|
||||
And then execute:
|
||||
|
||||
$ bundle
|
||||
|
||||
Or install it yourself as:
|
||||
|
||||
$ gem install seems_rateable
|
||||
|
||||
### Generation
|
||||
|
||||
$ rails generate seems_rateable:install
|
||||
|
||||
Generator creates migration files, javascript files and initializer
|
||||
|
||||
### Prepare
|
||||
|
||||
Require javascript files by adding this line to application.js
|
||||
|
||||
#application.js
|
||||
//= require_directory ./rateable
|
||||
|
||||
Add <code>seems_rateable</code> to routes.rb file
|
||||
|
||||
Include stylesheet adding <code><%= seems_rateable_stylesheet %></code> to your layout header
|
||||
|
||||
Also make sure you have an existing <code>current_user</code> helper method
|
||||
|
||||
Don't forget to run
|
||||
|
||||
$ rake db:migrate
|
||||
|
||||
To prepare model add <code> seems_rateable </code> to your rateable model file. You can also pass a hash of options to
|
||||
customize the functionality
|
||||
|
||||
<ul>
|
||||
<li><code>:dimensions</code>Array of dimensions e.g <code>:dimensions => [:quality, :quantity]</code> </li>
|
||||
<li><code>:allow_update</code>Allowing user to re-rate his own ratings, default set to false e.g <code>:allow_update=> true</code></li>
|
||||
</ul>
|
||||
|
||||
class Post < ActiveRecord::Base
|
||||
seems_rateable :allow_update => true, :dimensions => [:quality, :length]
|
||||
end
|
||||
|
||||
To access object's rates use <code>rates</code> 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 <code>seems_rateable_rater</code> 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 <code>@user.ratings_given</code>
|
||||
|
||||
### Usage
|
||||
|
||||
To display star rating use helper method <code>rating_for</code> 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 :
|
||||
<ul>
|
||||
<li><code>:dimension</code>The dimension of the object</li>
|
||||
<li><code>:static</code>Set to true to display static star rating, default false</li>
|
||||
<li><code>:class</code>Class of the div, default set to 'rateable'</li>
|
||||
<li><code>:id</code>ID of the div e.g <code>:id => "info"</code>, default nil</li>
|
||||
</ul>
|
||||
|
||||
To edit the javascript options locate rateable.js file in /app/assets/javascripts/rateable/.
|
||||
The javascript options are explained directly in the file
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork it
|
||||
2. Create your feature branch (`git checkout -b my-new-feature`)
|
||||
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||
4. Push to the branch (`git push origin my-new-feature`)
|
||||
5. Create new Pull Request
|
|
@ -0,0 +1,32 @@
|
|||
begin
|
||||
require 'bundler/setup'
|
||||
rescue LoadError
|
||||
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
||||
end
|
||||
|
||||
require 'rdoc/task'
|
||||
|
||||
RDoc::Task.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'SeemsRateable'
|
||||
rdoc.options << '--line-numbers'
|
||||
rdoc.rdoc_files.include('README.rdoc')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
|
||||
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
||||
load 'rails/tasks/engine.rake'
|
||||
Bundler::GemHelper.install_tasks
|
||||
|
||||
APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
|
||||
load 'rails/tasks/engine.rake'
|
||||
|
||||
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
||||
require 'rspec/core'
|
||||
require 'rspec/core/rake_task'
|
||||
|
||||
task :default => :spec
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 572 B |
Binary file not shown.
After Width: | Height: | Size: 427 B |
Binary file not shown.
After Width: | Height: | Size: 1018 B |
|
@ -0,0 +1,15 @@
|
|||
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||
// listed below.
|
||||
//
|
||||
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
||||
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
||||
//
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// compiled file.
|
||||
//
|
||||
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
||||
// about supported directives.
|
||||
|
||||
//= require_tree .
|
||||
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
||||
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
||||
* compiled file, but it's generally better to create a new file per style scope.
|
||||
*
|
||||
*= require_self
|
||||
*= require_tree .
|
||||
*/
|
||||
|
||||
/*********************/
|
||||
/** jRating CSS **/
|
||||
/*********************/
|
||||
|
||||
/**Div containing the color of the stars */
|
||||
|
||||
|
||||
.jRatingAverage {
|
||||
background-color:#f62929;
|
||||
position:relative;
|
||||
top:0;
|
||||
left:0;
|
||||
z-index:2;
|
||||
height:100%;
|
||||
}
|
||||
.jRatingColor {
|
||||
background-color:#FFD400; /* bgcolor of the stars*/
|
||||
position:relative;
|
||||
top:0;
|
||||
left:0;
|
||||
z-index:2;
|
||||
height:100%;
|
||||
}
|
||||
|
||||
/** Div containing the stars **/
|
||||
.jStar {
|
||||
position:relative;
|
||||
left:0;
|
||||
z-index:3;
|
||||
}
|
||||
|
||||
/** P containing the rate informations **/
|
||||
p.jRatingInfos {
|
||||
position: absolute;
|
||||
z-index:9999;
|
||||
background: transparent url('bg_jRatingInfos.png') no-repeat;
|
||||
color: #CACACA;
|
||||
display: none;
|
||||
width: 91px;
|
||||
height: 29px;
|
||||
font-size:16px;
|
||||
text-align:center;
|
||||
padding-top:5px;
|
||||
}
|
||||
p.jRatingInfos span.maxRate {
|
||||
color:#c9c9c9;
|
||||
font-size:14px;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
module SeemsRateable
|
||||
class ApplicationController < ActionController::Base
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
require_dependency "seems_rateable/application_controller"
|
||||
|
||||
module SeemsRateable
|
||||
class RatingsController < ::ApplicationController
|
||||
def create
|
||||
raise NoCurrentUserInstanceError unless current_user
|
||||
|
||||
obj = params[:kls].classify.constantize.find(params[:idBox])
|
||||
begin
|
||||
obj.rate(params[:rate].to_i, current_user.id, params[:dimension])
|
||||
render :json => true
|
||||
rescue Errors::AlreadyRatedError
|
||||
render :json => {:error => true}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
module SeemsRateable
|
||||
module ApplicationHelper
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
module SeemsRateable
|
||||
module RatingsHelper
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
module SeemsRateable
|
||||
class CachedRating < ActiveRecord::Base
|
||||
belongs_to :cacheable, :polymorphic => true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,6 @@
|
|||
module SeemsRateable
|
||||
class Rate < ActiveRecord::Base
|
||||
belongs_to :rater, :class_name => SeemsRateable::Engine.config.owner_class
|
||||
belongs_to :rateable, :polymorphic => true
|
||||
end
|
||||
end
|
|
@ -0,0 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SeemsRateable</title>
|
||||
<%= stylesheet_link_tag "seems_rateable/application", media: "all" %>
|
||||
<%= javascript_include_tag "seems_rateable/application" %>
|
||||
<%= csrf_meta_tags %>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<%= yield %>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env ruby1.9.1
|
||||
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
|
||||
|
||||
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
||||
ENGINE_PATH = File.expand_path('../../lib/seems_rateable/engine', __FILE__)
|
||||
|
||||
require 'rails/all'
|
||||
require 'rails/engine/commands'
|
|
@ -0,0 +1,3 @@
|
|||
SeemsRateable::Engine.routes.draw do
|
||||
resources :ratings, :only => :create
|
||||
end
|
|
@ -0,0 +1,39 @@
|
|||
require 'rails/generators/migration'
|
||||
require 'fileutils'
|
||||
|
||||
module SeemsRateable
|
||||
module Generators
|
||||
class InstallGenerator < ::Rails::Generators::Base
|
||||
include Rails::Generators::Migration
|
||||
source_root File.expand_path('../templates', __FILE__)
|
||||
|
||||
def self.next_migration_number(path)
|
||||
unless @prev_migration_nr
|
||||
@prev_migration_nr = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
|
||||
else
|
||||
@prev_migration_nr += 1
|
||||
end
|
||||
@prev_migration_nr.to_s
|
||||
end
|
||||
|
||||
desc "generating migration files"
|
||||
def copy_migrations
|
||||
migration_template "rates_migration.rb", "db/migrate/create_seems_rateable_rates.rb"
|
||||
migration_template "cached_ratings_migration.rb", "db/migrate/create_seems_rateable_cached_ratings.rb"
|
||||
end
|
||||
|
||||
desc "generating initializer"
|
||||
def copy_initializer
|
||||
template "initializer.rb", "config/initializers/seems_rateable.rb"
|
||||
end
|
||||
|
||||
desc "generating javascript files"
|
||||
def copy_javascript_asset
|
||||
Dir.mkdir "app/assets/javascripts/rateable" unless File.directory?("app/assets/javascripts/rateable")
|
||||
copy_file "rateable.js.erb", "app/assets/javascripts/rateable/rateable.js.erb" unless File.exists?("app/assets/javascripts/rateable/rateable.js.erb")
|
||||
copy_file "jRating.js.erb", "app/assets/javascripts/rateable/jRating.js.erb" unless File.exists?("app/assets/javascripts/rateable/jRating.js.erb")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
class CreateSeemsRateableCachedRatings < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :seems_rateable_cached_ratings do |t|
|
||||
t.belongs_to :cacheable, :polymorphic => true
|
||||
t.float :avg, :null => false
|
||||
t.integer :cnt, :null => false
|
||||
t.string :dimension
|
||||
t.integer :cacheable_id, :limit => 8
|
||||
t.string :cacheable_type
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :cached_ratings
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
#SeemsRateable engine Initializer
|
||||
|
||||
#Configure owner class of the given ratings
|
||||
SeemsRateable::Engine.config.owner_class = "User"
|
|
@ -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 =
|
||||
$('<div>',
|
||||
{
|
||||
'class' : 'jRatingColor',
|
||||
css:{
|
||||
width:widthColor
|
||||
}
|
||||
}).appendTo($(this)),
|
||||
|
||||
average =
|
||||
$('<div>',
|
||||
{
|
||||
'class' : 'jRatingAverage',
|
||||
css:{
|
||||
width:0,
|
||||
top:- starHeight
|
||||
}
|
||||
}).appendTo($(this)),
|
||||
|
||||
jstar =
|
||||
$('<div>',
|
||||
{
|
||||
'class' : 'jStar',
|
||||
css:{
|
||||
width:widthRatingContainer,
|
||||
height:starHeight,
|
||||
top:- (starHeight*2),
|
||||
background: 'url('+bgPath+') repeat-x'
|
||||
}
|
||||
}).appendTo($(this));
|
||||
|
||||
$(this).css({width: widthRatingContainer,overflow:'hidden',zIndex:1,position:'relative'});
|
||||
|
||||
if(!jDisabled)
|
||||
$(this).unbind().bind({
|
||||
mouseenter : function(e){
|
||||
var realOffsetLeft = findRealLeft(this);
|
||||
var relativeX = e.pageX - realOffsetLeft;
|
||||
if (opts.showRateInfo)
|
||||
var tooltip =
|
||||
$('<p>',{
|
||||
'class' : 'jRatingInfos',
|
||||
html : getNote(relativeX)+' <span class="maxRate">/ '+opts.rateMax+'</span>',
|
||||
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) +' <span class="maxRate">/ '+opts.rateMax+'</span>');
|
||||
},
|
||||
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);
|
|
@ -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');
|
||||
$('<span class="text-success"><small style="display:inline-block;">Thanks for rating!</small></span>').insertAfter(element)
|
||||
},
|
||||
onError : function(element, rate) {
|
||||
$('<span class="text-error"><small style="display:inline-block;">You have already rated!</small></span>').insertAfter(element)
|
||||
}
|
||||
});
|
||||
|
||||
});
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
|||
module SeemsRateable
|
||||
module Routes
|
||||
def seems_rateable
|
||||
mount SeemsRateable::Engine => '/rateable', :as => :rateable
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,3 @@
|
|||
module SeemsRateable
|
||||
VERSION = "1.0.9"
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
# desc "Explaining what the task does"
|
||||
# task :seems_rateable do
|
||||
# # Task goes here
|
||||
# end
|
|
@ -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
|
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
Loading…
Reference in New Issue