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 }