module ReallySimple #:nodoc: module Is #:nodoc: module Popularity #:nodoc: def self.append_features(base) super base.extend(ClassMethods) end #==== Popularity Generator # # script/generate popularity post # #==== Usage # # class PostPopularity < ActiveRecord::Base # is_popularity :posts # end # #Accepts the belongs_to_model, which is the association whose popularity this stores. #options is an hash of popularity options, including: # #:day, which is what defines a day, default 1 #:week, which is what defines a week, default 7 #:month, which is what defines a month, default 30 #:yearh, which is what defines a year, default 365 # #These options define when the popularity for that time is recycled. module ClassMethods #Add this to a ActiveRecord Model to define it as the popularity of a model. def is_popularity(belongs_to_model, options = {}) belongs_to belongs_to_model class_eval { cattr_accessor :score, :is_popularity_options } options[:day] ||= 1 options[:week] ||= 7 options[:month] ||= 30 options[:year] ||= 365 self.is_popularity_options = options include InstanceMethods end end module InstanceMethods #Updates the popularity of the associated instance. #Accepts an options hash of :now and :new_score #returns the popularity instance. def update_score(options = {}) t = options[:now] || Time.now s = options[:new_score] || self.score update_daily_score(:now => t, :new_score => s) update_weekly_score(:now => t, :new_score => s) update_monthly_score(:now => t, :new_score => s) update_yearly_score(:now => t, :new_score => s) update_total_score(:now => t, :new_score => s) self.score = s self.save return self end #Before creating a popularity, fill in all the current data with defaults. def before_create t = Time.now self.daily_score = self.score self.weekly_score = self.score self.monthly_score = self.score self.yearly_score = self.score self.total_score = self.score self.year_created_at_score = self.score self.month_created_at_score = self.score self.week_created_at_score = self.score self.total_created_at = t self.year_created_at = t self.month_created_at = t self.week_created_at = t self.today_created_at = t return true end #Valid options are :now and :new_score, defaulted to self.score, Time.now #Updates self.daily_score and self.today_created if popularity is at least a day old. def update_daily_score(options = {}) t = options[:now] || Time.now s = options[:new_score] || self.score return false unless s && t if ((t-(self.today_created_at || t))/(60*60*24)) >= self.is_popularity_options[:day] self.daily_score = s self.today_created_at = t end return self end #Valid options are :now and :new_score, defaulted to self.score, Time.now #Updates self.weekly_score and self.weekly_created_at and self.weekly_created_at_score if popularity is at least a week old #The weekly_score is always the current score + the old weekly score - the weekly score on the last cycle. #The time_score will always reflect the last time of scores. #Otherwise, it adds the current score to the weekly_score def update_weekly_score(options = {}) t = options[:now] || Time.now s = options[:new_score] || self.score return false unless s && t if ((t-(self.week_created_at || t))/(60*60*24)) >= self.is_popularity_options[:week] #Popularity is at least a week old, must be recycled #Weekly Score + Score Today - Preview Week's First Day Score (which is now more then a week old) old_weekly_score = self.weekly_score old_week_created_at_score = self.week_created_at_score self.weekly_score = (old_weekly_score-old_week_created_at_score)+s self.week_created_at_score = s # Score to Drop Next Week self.week_created_at = t else old_weekly_score = self.weekly_score self.weekly_score+=s end return self end #Valid options are :now and :new_score, defaulted to self.score, Time.now #Updates self.monthly_score and self.monthly_created_at and self.monthly_created_at_score if popularity is at least a month old #The monthly_score is always the current score + the old monthly score - the monthly score on the last cycle. #The time_score will always reflect the last time of scores. #Otherwise, it adds the current score to the monthly_score def update_monthly_score(options = {}) t = options[:now] || Time.now s = options[:new_score] || self.score return false unless s && t if ((t-(self.month_created_at || t))/(60*60*24)) >= self.is_popularity_options[:month] old_monthly_score = self.monthly_score old_month_created_at_score = self.month_created_at_score self.monthly_score = (old_monthly_score-old_month_created_at_score)+s self.month_created_at_score = s self.month_created_at = t else self.monthly_score+=s end return self end #Valid options are :now and :new_score, defaulted to self.score, Time.now #Updates self.yearly_score and self.yearly_created_at and self.yearly_created_at_score if popularity is at least a year old #The yearly_score is always the current score + the old yearly score - the yearly score on the last cycle. #The time_score will always reflect the last time of scores. #Otherwise, it adds the current score to the yearly_score def update_yearly_score(options = {}) t = options[:now] || Time.now s = options[:new_score] || self.score return false unless s && t if ((t-(self.year_created_at || t))/(60*60*24)) >= self.is_popularity_options[:year] old_yearly_score = self.yearly_score old_year_created_at_score = self.year_created_at_score self.yearly_score = (old_yearly_score-old_year_created_at_score)+s self.year_created_at_score = s self.year_created_at = t else self.yearly_score+=s end return self end #Valid options are :now and :new_score, defaulted to self.score, Time.now #Updates self.total_score def update_total_score(options = {}) t = options[:now] || Time.now s = options[:new_score] || self.score return false unless s && t self.total_score+=s return self end end end end end ActiveRecord::Base.class_eval { include ReallySimple::Is::Popularity }