From 785c1c4986f2e9db01bddadc5e2597ceed1a9d2a Mon Sep 17 00:00:00 2001 From: Brady Bouchard Date: Thu, 1 Sep 2011 00:10:55 +1000 Subject: [PATCH] Updates to the README. Renamed rank_tally to the more intuitive plusminus_tally. --- README.markdown | 15 +++---- lib/acts_as_voteable.rb | 94 +++++++++++++++++++++-------------------- lib/has_karma.rb | 2 +- 3 files changed, 57 insertions(+), 54 deletions(-) diff --git a/README.markdown b/README.markdown index 1a7272f..74d1806 100644 --- a/README.markdown +++ b/README.markdown @@ -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 @@ -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 @@ -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 @@ -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" diff --git a/lib/acts_as_voteable.rb b/lib/acts_as_voteable.rb index bebcd74..d684455 100644 --- a/lib/acts_as_voteable.rb +++ b/lib/acts_as_voteable.rb @@ -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. diff --git a/lib/has_karma.rb b/lib/has_karma.rb index b593dae..bc41460 100644 --- a/lib/has_karma.rb +++ b/lib/has_karma.rb @@ -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