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