ref: master
vendor/plugins/kandadaboggu-vote_fu/lib/acts_as_voteable.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 |
# ActsAsVoteable module Juixe module Acts #:nodoc: module Voteable #:nodoc: def self.included(base) base.extend ClassMethods end module ClassMethods # # Options: # :vote_counter # Model stores the sum of votes in the vote counter column when the value is true. This requires a column named `vote_total` in the table corresponding to `voteable` model. # You can also specify a custom vote counter column by providing a column name instead of a true/false value to this option (e.g., :vote_counter => :my_custom_counter.) # Note: Specifying a counter will add it to that model‘s list of readonly attributes using attr_readonly. # def acts_as_voteable options={} has_many :votes, :as => :voteable, :dependent => :destroy include Juixe::Acts::Voteable::InstanceMethods extend Juixe::Acts::Voteable::SingletonMethods if (options[:vote_counter]) Vote.send(:include, Juixe::Acts::Voteable::VoteCounterClassMethods) unless Vote.respond_to?(:vote_counters) Vote.vote_counters = [self] # define vote_counter_column instance method on voteable counter_column_name = (options[:vote_counter] == true) ? :vote_total : options[:vote_counter] class_eval <<-EOS def self.vote_counter_column # def self.vote_counter_column :"#{counter_column_name}" # :vote_total end # end def vote_counter_column self.class.vote_counter_column end EOS define_method(:reload_vote_counter) {reload(:select => vote_counter_column.to_s)} attr_readonly counter_column_name end end end # This module contains class methods Vote class module VoteCounterClassMethods def self.included(base) base.class_inheritable_array(:vote_counters) base.after_create { |record| record.update_vote_counters(1) } base.before_destroy { |record| record.update_vote_counters(-1) } end def update_vote_counters direction klass, vtbl = self.voteable.class, self.voteable klass.update_counters(vtbl.id, vtbl.vote_counter_column.to_sym => (self.vote * direction) ) if self.vote_counters.any?{|c| c == klass} end end # This module contains class methods module SingletonMethods # Calculate the vote counts for all voteables of my type. # 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 # :order - A piece of SQL to order by. Two calculated columns `count`, and `total` # are available for sorting apart from other columns. Defaults to `total DESC`. # Eg: :order => 'count desc' # :order => 'total desc' # :order => 'post.created_at desc' # :at_least - Item must have at least X votes count # :at_most - Item may not have more than X votes count # :at_least_total - Item must have at least X votes total # :at_most_total - Item may not have more than X votes total def tally(options = {}) order("total DESC").all options_for_tally(options) end def options_for_tally (options = {}) options.assert_valid_keys :start_at, :end_at, :conditions, :at_least, :at_most, :order, :limit, :at_least_total, :at_most_total scope = scope(:find) start_at = sanitize_sql(["#{Vote.table_name}.created_at >= ?", options.delete(:start_at)]) if options[:start_at] end_at = sanitize_sql(["#{Vote.table_name}.created_at <= ?", options.delete(:end_at)]) if options[:end_at] if respond_to?(:vote_counter_column) # use the counter cache column if present. total_col = "#{table_name}.#{vote_counter_column}" at_least_total = sanitize_sql(["#{total_col} >= ?", options.delete(:at_least_total)]) if options[:at_least_total] at_most_total = sanitize_sql(["#{total_col} <= ?", options.delete(:at_most_total)]) if options[:at_most_total] end conditions = [ options[:conditions], at_least_total, at_most_total, start_at, end_at ] conditions = conditions.compact.join(' AND ') conditions = merge_conditions(conditions, scope[:conditions]) if scope type_and_context = "#{Vote.table_name}.voteable_type = #{quote_value(base_class.name)}" joins = ["LEFT OUTER JOIN #{Vote.table_name} ON #{table_name}.#{primary_key} = #{Vote.table_name}.voteable_id AND #{type_and_context}"] joins << scope[:joins] if scope && scope[:joins] at_least = sanitize_sql(["COUNT(#{Vote.table_name}.id) >= ?", options.delete(:at_least)]) if options[:at_least] at_most = sanitize_sql(["COUNT(#{Vote.table_name}.id) <= ?", options.delete(:at_most)]) if options[:at_most] at_least_total = at_most_total = nil # reset the values unless respond_to?(:vote_counter_column) # aggregate the votes when counter cache is absent. total_col = "SUM(#{Vote.table_name}.vote)" at_least_total = sanitize_sql(["#{total_col} >= ?", options.delete(:at_least_total)]) if options[:at_least_total] at_most_total = sanitize_sql(["#{total_col} <= ?", options.delete(:at_most_total)]) if options[:at_most_total] end having = [at_least, at_most, at_least_total, at_most_total].compact.join(' AND ') group_by = "#{Vote.table_name}.voteable_id HAVING COUNT(#{Vote.table_name}.id) > 0" group_by << " AND #{having}" unless having.blank? { :select => "#{table_name}.*, COUNT(#{Vote.table_name}.id) AS count, #{total_col} AS total", :joins => joins.join(" "), :conditions => conditions, :group => group_by }.update(options) end end # This module contains instance methods module InstanceMethods def votes_for self.votes.where(vote: 1).count end def votes_against self.votes.where(vote: -1).count end # Same as voteable.votes.size def votes_count self.votes.size end def votes_total respond_to?(:vote_counter_column) ? send(self.vote_counter_column) : self.votes.sum(:vote) end def voters_who_voted self.votes.collect(&:voter) end def voted_by?(voter, for_or_against = "all") options = (for_or_against == "all") ? {} : {:vote => (for_or_against ? 1 : -1)} self.votes.exists?({:voter_id => voter.id, :voter_type => voter.class.base_class.name}.merge(options)) end end end end end |