# ActsAsHotRecord # a simple plugin module that flags a record as hot # and get the records base on its hotness # @author: Marjun Pagalan # module June module Acts # ---------------------- # acts_as_hot_record # flags the record as a hot_record for site_owners/site_admin to show on their home page # # only one record will be hot with a max_level of hotness # ---------------------- # example usage # # class Book < ActiveRecord::Base # acts_as_hot_record # end # # book = Book.find(:first) # book.set_hotness(:level => 1 ) # this sets the hotness of book to the level given # # book.set_hotness(:level => 1, expiration_date => Date.today + 1 ) #this sets the hotness of book to the level given and the expiration date of hotness # # Book.hot_records #returns all hot records of the AR Class # Book.hot_records( :level => 1..2 , :limit => 5) #returns all hot records of the AR Class # module HotRecord class HotnessLevelError < RuntimeError; end class HotnessMaxDuplicateError < RuntimeError; end DATE_NOW = Date.today #const for the current date def self.included(base) #:nodoc: base.extend(ClassMethods) end # This acts as allows a polymorphic link between arbitrary objects. module ClassMethods def acts_as_hot_record(options={}) # This is more for the future if we want to have more functions include HotRecord::InstanceMethods extend HotRecord::SingletonMethods # this allows you to redefine the acts' options for each subclass, however options[:min_level] ||= 1 options[:max_level] ||= options[:min_level].to_i + 1 options[:level] ||= (options[:min_level]..options[:max_level]) options[:force] = true if options[:force].nil? # reset the min_level with the given level range if options[:level].class == Range options[:min_level] = options[:level].first options[:max_level] = options[:level].last end #hot_record_options is available to processors and backends class_inheritable_accessor :hot_record_options self.hot_record_options = options end end # Class level methods module SingletonMethods # Generate the hot columns on a table, to be used when creating the table # in a migration. This is the preferred way to do in a migration that creates # new tables as it will make it as part of the table creation, and not generate def generate_hot_columns table table.column :hotness_level, :integer table.column :hotness_expired_at, :datetime end # Create the needed columns for acts_as_hot_record. # To be used during migration, but can also be used in other places. def add_hot_columns if !self.content_columns.find { |c| 'hotness_level' == c.name } self.connection.add_column table_name, :hotness_level, :integer self.connection.add_column table_name, :hotness_expired_at, :datetime self.reset_column_information end end # Remove the acts_as_hot_record specific columns added with add_hot_columns # To be used during migration, but can also be used in other places def remove_hot_columns if self.content_columns.find { |c| 'hotness_level' == c.name } self.connection.remove_column table_name, :hotness_level self.connection.remove_column table_name, :hotness_expired_at self.reset_column_information end end # return hot records # accepts :level => nil, :limit => nil , :order => "hotness_level DESC" || "hotness_level ASC" def hot_records(options={}) options[:level] ||= (hot_record_options[:min_level]..hot_record_options[:max_level]) options[:limit] ||= 10 options[:order] ||= "hotness_level DESC" lvl = hot_record_options[:level].map{|i| i} level = options[:level] # does all necessary sanitazation of level options case level.class when Range level = level.map{|i| i} raise HotnessLevelError , "Level is not in range" if level.map{ |o| lvl.include?(o) } when Integer level = level.to_a raise HotnessLevelError , "Level is not in range" if level.map{ |o| lvl.include?(o) } when Array level raise HotnessLevelError , "Level is not in range" if level.map{ |o| lvl.include?(o) } end date_now = HotRecord::DATE_NOW # finds our hot records based on the conditions we set hot_records = self.find( :all, :conditions => ['hotness_level IN (?) AND hotness_expired_at > ? ', level , date_now ], :limit => options[:limit], :order => options[:order] ) hot_records end end # This are the methods that are instance level module InstanceMethods # setter for the hotness attribute of the object # only one object of the same class will have the max hotness level def set_hotness(options ={}) date_now = HotRecord::DATE_NOW options[:level] ||= hot_record_options[:min_level] options[:expiry_date] ||= date_now + 1 #set our default expiry date ~TODO: could be configurable # we check if the level is the max_level for the hotness of the record # since we have a special handler for this kind of hotness_level if options[:level] == hot_record_options[:max_level] hot_record = self.class.find( :first , :conditions => [" hotness_level = ? AND id <> ? AND hotness_expired_at > ? ", options[:level] , self.id, date_now]) if hot_record raise HotnessMaxDuplicateError , "Another record is on the max level of hotness" unless hot_record_options[:force] hot_record.hotness_level = hot_record_options[:min_level] hot_record.save end end self.hotness_level = options[:level] self.hotness_expired_at = options[:expiry_date] self.save end # we just un_hot our hot record by setting the hot attributes to nil def un_hot! self.hotness_level = nil self.hotness_expired_at = nil end end end end end ActiveRecord::Base.send :include, June::Acts::HotRecord