thumbs_up 0.3.2 → 0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.markdown +19 -1
- data/Rakefile +46 -15
- data/VERSION +1 -1
- data/lib/acts_as_voteable.rb +54 -0
- data/lib/acts_as_voter.rb +1 -1
- data/lib/generators/thumbs_up/templates/migration.rb +3 -1
- data/lib/generators/thumbs_up/templates/vote.rb +2 -1
- data/lib/generators/thumbs_up/thumbs_up_generator.rb +2 -0
- data/rails/init.rb +3 -3
- metadata +4 -12
data/.gitignore
CHANGED
data/README.markdown
CHANGED
@@ -65,7 +65,7 @@ Usage
|
|
65
65
|
voter.vote(voteable, vote) # Adds either a +1 or -1 vote: vote => true (+1), vote => false (-1)
|
66
66
|
|
67
67
|
voter.vote_exclusively_for(voteable) # Removes any previous votes by that particular voter, and votes for.
|
68
|
-
voter.
|
68
|
+
voter.vote_exclusively_against(voteable) # Removes any previous votes by that particular voter, and votes against.
|
69
69
|
|
70
70
|
### Querying votes
|
71
71
|
|
@@ -98,6 +98,21 @@ This will select the Items with between 1 and 10,000 votes, the votes having bee
|
|
98
98
|
:at_least - Item must have at least X votes
|
99
99
|
:at_most - Item may not have more than X votes
|
100
100
|
|
101
|
+
##### Tallying Rank
|
102
|
+
|
103
|
+
Similar to tallying votes, but this will actually return voteable object collections based on a rating
|
104
|
+
system where up votes and down votes get equal rating. For Instance, a voteable with 3 upvotes and 2
|
105
|
+
downvotes will have a rating in this instance of 1.
|
106
|
+
|
107
|
+
##### Rank_Tally Options:
|
108
|
+
:start_at - Restrict the votes to those created after a certain time
|
109
|
+
:end_at - Restrict the votes to those created before a certain time
|
110
|
+
:conditions - A piece of SQL conditions to add to the query
|
111
|
+
:limit - The maximum number of voteables to return
|
112
|
+
:ascending - Boolean Default false. If specified true, results will be returned in ascending order (from bottom up)
|
113
|
+
:at_least - Item must have at least X votes
|
114
|
+
:at_most - Item may not have more than X votes
|
115
|
+
|
101
116
|
#### Lower level queries
|
102
117
|
|
103
118
|
positiveVoteCount = voteable.votes_for
|
@@ -123,6 +138,9 @@ ThumbsUp by default only allows one vote per user. This can be changed by removi
|
|
123
138
|
|
124
139
|
add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only"
|
125
140
|
|
141
|
+
You can also use `--unique-voting false` when running the generator command:
|
142
|
+
|
143
|
+
rails generate thumbs_up --unique-voting false
|
126
144
|
|
127
145
|
Credits
|
128
146
|
=======
|
data/Rakefile
CHANGED
@@ -1,19 +1,50 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'rubygems'
|
3
|
-
require '
|
3
|
+
require 'bundler'
|
4
4
|
|
5
5
|
begin
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
gem.name = "thumbs_up"
|
17
|
+
gem.summary = "Voting for ActiveRecord with multiple vote sources and karma calculation."
|
18
|
+
gem.description = "ThumbsUp provides dead-simple voting capabilities to ActiveRecord models with karma calculation, a la stackoverflow.com."
|
19
|
+
gem.email = "brady@ldawn.com"
|
20
|
+
gem.homepage = "http://github.com/brady8/thumbs_up"
|
21
|
+
gem.authors = ["Brady Bouchard", "Peter Jackson", "Cosmin Radoi", "Bence Nagy", "Rob Maddox", "Wojciech Wnętrzak"]
|
22
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
23
|
+
end
|
24
|
+
Jeweler::RubygemsDotOrgTasks.new
|
25
|
+
|
26
|
+
require 'rake/testtask'
|
27
|
+
Rake::TestTask.new(:test) do |test|
|
28
|
+
test.libs << 'lib' << 'test'
|
29
|
+
test.pattern = 'test/**/test_*.rb'
|
30
|
+
test.verbose = true
|
31
|
+
end
|
32
|
+
|
33
|
+
require 'rcov/rcovtask'
|
34
|
+
Rcov::RcovTask.new do |test|
|
35
|
+
test.libs << 'test'
|
36
|
+
test.pattern = 'test/**/test_*.rb'
|
37
|
+
test.verbose = true
|
38
|
+
end
|
39
|
+
|
40
|
+
require 'rake/rdoctask'
|
41
|
+
Rake::RDocTask.new do |rdoc|
|
42
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
43
|
+
|
44
|
+
rdoc.rdoc_dir = 'rdoc'
|
45
|
+
rdoc.title = "leaderboard #{version}"
|
46
|
+
rdoc.rdoc_files.include('README*')
|
47
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
48
|
+
end
|
49
|
+
|
50
|
+
task :default => :test
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4
|
data/lib/acts_as_voteable.rb
CHANGED
@@ -15,6 +15,60 @@ module ThumbsUp
|
|
15
15
|
end
|
16
16
|
|
17
17
|
module SingletonMethods
|
18
|
+
|
19
|
+
# The point of this function is to return rankings based on the difference between up and down votes
|
20
|
+
# assuming equal weighting (i.e. a user with 1 up vote and 1 down vote has a Vote_Total of 0.
|
21
|
+
# First the votes table is joined twiced so that the Vote_Total can be calculated for every ID
|
22
|
+
# Then this table is joined against the specific table passed to this function to allow for
|
23
|
+
# ranking of the items within that table based on the difference between up and down votes.
|
24
|
+
# Options:
|
25
|
+
# :start_at - Restrict the votes to those created after a certain time
|
26
|
+
# :end_at - Restrict the votes to those created before a certain time
|
27
|
+
# :conditions - A piece of SQL conditions to add to the query
|
28
|
+
# :limit - The maximum number of voteables to return
|
29
|
+
# :ascending - Default false - normal order DESC (i.e. highest rank to lowest)
|
30
|
+
# :at_least - Item must have at least X votes
|
31
|
+
# :at_most - Item may not have more than X votes
|
32
|
+
def rank_tally(*args)
|
33
|
+
options = args.extract_options!
|
34
|
+
|
35
|
+
tsub0 = Vote
|
36
|
+
tsub0 = tsub0.where("vote = ?", false)
|
37
|
+
tsub0 = tsub0.where("voteable_type = ?", self.name)
|
38
|
+
tsub0 = tsub0.group("voteable_id")
|
39
|
+
tsub0 = tsub0.select("DISTINCT voteable_id, COUNT(vote) as Votes_Against")
|
40
|
+
|
41
|
+
tsub1 = Vote
|
42
|
+
tsub1 = tsub1.where("vote = ?", true)
|
43
|
+
tsub1 = tsub1.where("voteable_type = ?", self.name)
|
44
|
+
tsub1 = tsub1.group("voteable_id")
|
45
|
+
tsub1 = tsub1.select("DISTINCT voteable_id, COUNT(vote) as Votes_For")
|
46
|
+
|
47
|
+
t = self.joins("LEFT OUTER JOIN (SELECT DISTINCT #{Vote.table_name}.*,
|
48
|
+
(COALESCE(vfor.Votes_For, 0)-COALESCE(against.Votes_Against, 0)) AS Vote_Total
|
49
|
+
FROM (#{Vote.table_name} LEFT JOIN
|
50
|
+
(#{tsub0.to_sql}) AS against ON #{Vote.table_name}.voteable_id = against.voteable_id)
|
51
|
+
LEFT JOIN
|
52
|
+
(#{tsub1.to_sql}) as vfor ON #{Vote.table_name}.voteable_id = vfor.voteable_id)
|
53
|
+
AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} =
|
54
|
+
joined_#{Vote.table_name}.voteable_id")
|
55
|
+
|
56
|
+
t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'")
|
57
|
+
t = t.group("joined_#{Vote.table_name}.voteable_id, joined_#{Vote.table_name}.Vote_Total, #{column_names_for_tally}")
|
58
|
+
t = t.limit(options[:limit]) if options[:limit]
|
59
|
+
t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at]
|
60
|
+
t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at]
|
61
|
+
t = t.where(options[:conditions]) if options[:conditions]
|
62
|
+
t = options[:ascending] ? t.order("joined_#{Vote.table_name}.Vote_Total")
|
63
|
+
: t.order("joined_#{Vote.table_name}.Vote_Total DESC")
|
64
|
+
|
65
|
+
t = t.having(["COUNT(joined_#{Vote.table_name}.voteable_id) > 0",
|
66
|
+
(options[:at_least] ? "joined_votes.Vote_Total >= #{sanitize(options[:at_least])}" : nil),
|
67
|
+
(options[:at_most] ? "joined_votes.Vote_Total <= #{sanitize(options[:at_most])}" : nil)
|
68
|
+
].compact.join(' AND '))
|
69
|
+
|
70
|
+
t.select("#{self.table_name}.*, joined_#{Vote.table_name}.Vote_Total")
|
71
|
+
end
|
18
72
|
|
19
73
|
# Calculate the vote counts for all voteables of my type.
|
20
74
|
# This method returns all voteables with at least one vote.
|
data/lib/acts_as_voter.rb
CHANGED
@@ -75,7 +75,7 @@ module ThumbsUp #:nodoc:
|
|
75
75
|
end
|
76
76
|
|
77
77
|
def vote(voteable, options = {})
|
78
|
-
raise ArgumentError "you must specify :up or :down in order to vote" unless options[:direction] && [:up, :down].include?(options[:direction].to_sym)
|
78
|
+
raise ArgumentError, "you must specify :up or :down in order to vote" unless options[:direction] && [:up, :down].include?(options[:direction].to_sym)
|
79
79
|
if options[:exclusive]
|
80
80
|
self.clear_votes(voteable)
|
81
81
|
end
|
@@ -12,8 +12,10 @@ class ThumbsUpMigration < ActiveRecord::Migration
|
|
12
12
|
add_index :votes, [:voter_id, :voter_type]
|
13
13
|
add_index :votes, [:voteable_id, :voteable_type]
|
14
14
|
|
15
|
-
|
15
|
+
<% if options[:unique_voting] == true %>
|
16
|
+
# Comment out the line below to allow multiple votes per voter on a single entity.
|
16
17
|
add_index :votes, [:voter_id, :voter_type, :voteable_id, :voteable_type], :unique => true, :name => 'fk_one_vote_per_user_per_entity'
|
18
|
+
<% end %>
|
17
19
|
end
|
18
20
|
|
19
21
|
def self.down
|
@@ -10,7 +10,8 @@ class Vote < ActiveRecord::Base
|
|
10
10
|
|
11
11
|
attr_accessible :vote, :voter, :voteable
|
12
12
|
|
13
|
+
<% if options[:unique_voting] == true %>
|
13
14
|
# Comment out the line below to allow multiple votes per user.
|
14
15
|
validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]
|
15
|
-
|
16
|
+
<% end %>
|
16
17
|
end
|
@@ -5,6 +5,8 @@ class ThumbsUpGenerator < Rails::Generators::Base
|
|
5
5
|
include Rails::Generators::Migration
|
6
6
|
|
7
7
|
source_root File.expand_path('../templates', __FILE__)
|
8
|
+
|
9
|
+
class_option :unique_voting, :type => :boolean, :default => true, :desc => 'Do you want only one vote allowed per voter? (default: true)'
|
8
10
|
|
9
11
|
# Implement the required interface for Rails::Generators::Migration.
|
10
12
|
def self.next_migration_number(dirname) #:nodoc:
|
data/rails/init.rb
CHANGED
@@ -3,8 +3,8 @@ RAILS_DEFAULT_LOGGER.info "** thumbs_up: setting up load paths **"
|
|
3
3
|
%w{ models controllers helpers }.each do |dir|
|
4
4
|
path = File.join(File.dirname(__FILE__) , 'lib', dir)
|
5
5
|
$LOAD_PATH << path
|
6
|
-
ActiveSupport::Dependencies.
|
7
|
-
ActiveSupport::Dependencies.
|
6
|
+
ActiveSupport::Dependencies.autoload_paths << path
|
7
|
+
ActiveSupport::Dependencies.autoload_once_paths.delete(path)
|
8
8
|
end
|
9
9
|
|
10
|
-
require 'thumbs_up'
|
10
|
+
require 'thumbs_up'
|
metadata
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: thumbs_up
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 3
|
8
|
-
- 2
|
9
|
-
version: 0.3.2
|
4
|
+
prerelease:
|
5
|
+
version: "0.4"
|
10
6
|
platform: ruby
|
11
7
|
authors:
|
12
8
|
- Brady Bouchard
|
@@ -19,7 +15,7 @@ autorequire:
|
|
19
15
|
bindir: bin
|
20
16
|
cert_chain: []
|
21
17
|
|
22
|
-
date:
|
18
|
+
date: 2011-04-01 00:00:00 +10:00
|
23
19
|
default_executable:
|
24
20
|
dependencies: []
|
25
21
|
|
@@ -61,21 +57,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
61
57
|
requirements:
|
62
58
|
- - ">="
|
63
59
|
- !ruby/object:Gem::Version
|
64
|
-
segments:
|
65
|
-
- 0
|
66
60
|
version: "0"
|
67
61
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
62
|
none: false
|
69
63
|
requirements:
|
70
64
|
- - ">="
|
71
65
|
- !ruby/object:Gem::Version
|
72
|
-
segments:
|
73
|
-
- 0
|
74
66
|
version: "0"
|
75
67
|
requirements: []
|
76
68
|
|
77
69
|
rubyforge_project:
|
78
|
-
rubygems_version: 1.3
|
70
|
+
rubygems_version: 1.5.3
|
79
71
|
signing_key:
|
80
72
|
specification_version: 3
|
81
73
|
summary: Voting for ActiveRecord with multiple vote sources and karma calculation.
|