Skip to content

Commit

Permalink
Updates to the README. Renamed rank_tally to the more intuitive plusm…
Browse files Browse the repository at this point in the history
…inus_tally.
  • Loading branch information
bouchard committed Aug 31, 2011
1 parent fab18fa commit 785c1c4
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 54 deletions.
15 changes: 7 additions & 8 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ You can easily retrieve voteable object collections based on the properties of t
:order => "items.name DESC"
})

This will select the Items with between 1 and 10,000 votes, the votes having been cast within the last two weeks (not including today), then display the 10 last items in an alphabetical list.
This will select the Items with between 1 and 10,000 votes, the votes having been cast within the last two weeks (not including today), then display the 10 last items in an alphabetical list. This tallies all votes, regardless of whether they are +1 (up) or -1 (down).

##### Tally Options:
:start_at - Restrict the votes to those created after a certain time
Expand All @@ -98,13 +98,12 @@ This will select the Items with between 1 and 10,000 votes, the votes having bee
:at_least - Item must have at least X votes
:at_most - Item may not have more than X votes

##### Tallying Rank
##### Tallying Rank ("Plusminus")

Similar to tallying votes, but this will actually return voteable object collections based on a rating
system where up votes and down votes get equal rating. For Instance, a voteable with 3 upvotes and 2
downvotes will have a rating in this instance of 1.
This is similar to tallying votes, but this will return voteable object collections based on the sum of the differences between up and down votes (ups are +1, downs are -1). For Instance, a voteable with 3 upvotes and 2
downvotes will have a plusminus of 1.

##### Rank_Tally Options:
##### Plusminus Tally Options:
:start_at - Restrict the votes to those created after a certain time
:end_at - Restrict the votes to those created before a certain time
:conditions - A piece of SQL conditions to add to the query
Expand All @@ -117,7 +116,7 @@ downvotes will have a rating in this instance of 1.

positiveVoteCount = voteable.votes_for
negativeVoteCount = voteable.votes_against
plusminus = voteable.plusminus # Votes for minus votes against.
plusminus = voteable.plusminus # Votes for, minus votes against.

voter.voted_for?(voteable) # True if the voter voted for this object.
voter.vote_count(:up | :down | :all) # returns the count of +1, -1, or all votes
Expand All @@ -134,7 +133,7 @@ ThumbsUp by default only allows one vote per user. This can be changed by removi

validates_uniqueness_of :voteable_id, :scope => [:voteable_type, :voter_type, :voter_id]

#### In the migration:
#### In the migration, the unique index:

add_index :votes, ["voter_id", "voter_type", "voteable_id", "voteable_type"], :unique => true, :name => "uniq_one_vote_only"

Expand Down
94 changes: 49 additions & 45 deletions lib/acts_as_voteable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,59 +15,63 @@ def acts_as_voteable
end

module SingletonMethods

# The point of this function is to return rankings based on the difference between up and down votes
# assuming equal weighting (i.e. a user with 1 up vote and 1 down vote has a Vote_Total of 0.
# First the votes table is joined twiced so that the Vote_Total can be calculated for every ID
# Then this table is joined against the specific table passed to this function to allow for
# ranking of the items within that table based on the difference between up and down votes.
# Options:
# :start_at - Restrict the votes to those created after a certain time
# :end_at - Restrict the votes to those created before a certain time
# :conditions - A piece of SQL conditions to add to the query
# :limit - The maximum number of voteables to return
# :ascending - Default false - normal order DESC (i.e. highest rank to lowest)
# :at_least - Item must have at least X votes
# :at_most - Item may not have more than X votes
def rank_tally(*args)
options = args.extract_options!

tsub0 = Vote
tsub0 = tsub0.where("vote = ?", false)
tsub0 = tsub0.where("voteable_type = ?", self.name)
tsub0 = tsub0.group("voteable_id")
tsub0 = tsub0.select("DISTINCT voteable_id, COUNT(vote) as Votes_Against")

tsub1 = Vote
tsub1 = tsub1.where("vote = ?", true)
tsub1 = tsub1.where("voteable_type = ?", self.name)
tsub1 = tsub1.group("voteable_id")
tsub1 = tsub1.select("DISTINCT voteable_id, COUNT(vote) as Votes_For")

t = self.joins("LEFT OUTER JOIN (SELECT DISTINCT #{Vote.table_name}.*,
(COALESCE(vfor.Votes_For, 0)-COALESCE(against.Votes_Against, 0)) AS Vote_Total
FROM (#{Vote.table_name} LEFT JOIN
(#{tsub0.to_sql}) AS against ON #{Vote.table_name}.voteable_id = against.voteable_id)
LEFT JOIN
(#{tsub1.to_sql}) as vfor ON #{Vote.table_name}.voteable_id = vfor.voteable_id)
AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} =
joined_#{Vote.table_name}.voteable_id")

t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'")
t = t.group("joined_#{Vote.table_name}.voteable_id, joined_#{Vote.table_name}.Vote_Total, #{column_names_for_tally}")
t = t.limit(options[:limit]) if options[:limit]
t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at]
t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at]
t = t.where(options[:conditions]) if options[:conditions]
t = options[:ascending] ? t.order("joined_#{Vote.table_name}.Vote_Total") : t.order("joined_#{Vote.table_name}.Vote_Total DESC")

t = t.having(["COUNT(joined_#{Vote.table_name}.voteable_id) > 0",
(options[:at_least] ? "joined_votes.Vote_Total >= #{sanitize(options[:at_least])}" : nil),
(options[:at_most] ? "joined_votes.Vote_Total <= #{sanitize(options[:at_most])}" : nil)
].compact.join(' AND '))
# :start_at - Restrict the votes to those created after a certain time
# :end_at - Restrict the votes to those created before a certain time
# :ascending - Default false - normal order DESC (i.e. highest rank to lowest)
# :at_least - Default 1 - Item must have at least X votes
# :at_most - Item may not have more than X votes
def plusminus_tally(*args)
options = args.extract_options!

t.select("#{self.table_name}.*, joined_#{Vote.table_name}.Vote_Total")
tsub0 = Vote
tsub0 = tsub0.where("vote = ?", false)
tsub0 = tsub0.where("voteable_type = ?", self.name)
tsub0 = tsub0.group("voteable_id")
tsub0 = tsub0.select("DISTINCT voteable_id, COUNT(vote) as votes_against")

tsub1 = Vote
tsub1 = tsub1.where("vote = ?", true)
tsub1 = tsub1.where("voteable_type = ?", self.name)
tsub1 = tsub1.group("voteable_id")
tsub1 = tsub1.select("DISTINCT voteable_id, COUNT(vote) as votes_for")

t = self.joins("LEFT OUTER JOIN (SELECT DISTINCT #{Vote.table_name}.*,
(COALESCE(vfor.votes_for, 0)-COALESCE(against.votes_against, 0)) AS vote_total
FROM (#{Vote.table_name} LEFT JOIN
(#{tsub0.to_sql}) AS against ON #{Vote.table_name}.voteable_id = against.voteable_id)
LEFT JOIN
(#{tsub1.to_sql}) as vfor ON #{Vote.table_name}.voteable_id = vfor.voteable_id)
AS joined_#{Vote.table_name} ON #{self.table_name}.#{self.primary_key} =
joined_#{Vote.table_name}.voteable_id")

t = t.where("joined_#{Vote.table_name}.voteable_type = '#{self.name}'")
t = t.group("joined_#{Vote.table_name}.voteable_id, joined_#{Vote.table_name}.vote_total, #{column_names_for_tally}")
t = t.where("joined_#{Vote.table_name}.created_at >= ?", options[:start_at]) if options[:start_at]
t = t.where("joined_#{Vote.table_name}.created_at <= ?", options[:end_at]) if options[:end_at]
t = options[:ascending] ? t.order("joined_#{Vote.table_name}.vote_total") : t.order("joined_#{Vote.table_name}.vote_total DESC")

t = t.having([
"COUNT(joined_#{Vote.table_name}.voteable_id) > 0",
(options[:at_least] ?
"joined_#{Vote.table_name}.vote_total >= #{sanitize(options[:at_least])}" : nil
),
(options[:at_most] ?
"joined_#{Vote.table_name}.vote_total <= #{sanitize(options[:at_most])}" : nil
)
].compact.join(' AND '))

t.select("#{self.table_name}.*, joined_#{Vote.table_name}.vote_total")
end

# #rank_tally is depreciated.
alias_method :rank_tally, :plusminus_tally

# Calculate the vote counts for all voteables of my type.
# This method returns all voteables with at least one vote.
Expand Down
2 changes: 1 addition & 1 deletion lib/has_karma.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module SingletonMethods
## Not yet implemented. Don't use it!
# Find the most popular users
def find_most_karmic
find(:all)
self.all
end

end
Expand Down

0 comments on commit 785c1c4

Please sign in to comment.