class AllSeeingEye::Model attr_accessor :id, :score def self.model_name self.to_s.split('::').last.downcase end def self.next_id AllSeeingEye.redis.incr("allseeingeye:#{self.model_name}:id") end def self.fields AllSeeingEye.configuration[:all_seeing_eye][self.model_name] end def self.field_keys self.fields.keys + ['time_spent'] end def self.field_names (self.field_keys + ['created_at']).flatten end def self.new(fields = {}) object = super() fields.each {|key, value| object.send("#{key}=".to_sym, value)} object end def self.create(fields) self.new(fields).save end def self.load(object) object = Marshal.load(object) object.send(:initialize) end def self.find(id) self.load(AllSeeingEye.redis["allseeingeye:#{self.model_name}:#{id}"]) end def self.all(options = {}) all = AllSeeingEye.redis.sort("allseeingeye:#{self.model_name}:all", :by => 'nosort', :get => "allseeingeye:#{self.model_name}:*").collect{|o| self.load(o)} all = all.find_all{|o| o.created_at > options[:start]} if options[:start] all = all.find_all{|o| o.created_at < options[:stop]} if options[:stop] all end def self.count(options = {}) options[:start] = options[:start].to_i if options[:start] && (options[:start].is_a?(Time) || options[:start].is_a?(DateTime) || options[:start].is_a?(Date)) options[:stop] = options[:stop].to_i if options[:stop] && (options[:stop].is_a?(Time) || options[:stop].is_a?(DateTime) || options[:stop].is_a?(Date)) options = {:start => '-inf', :stop => '+inf'}.merge(options) AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:created_at", options[:start], options[:stop], :with_scores => false).collect{|id| self.find(id)}.size end def self.find_by_field(field, options = {}) if options[:value] all = AllSeeingEye.redis.sort("allseeingeye:#{self.model_name}:fields:#{field}:#{options[:value]}", :by => 'nosort', :get => "allseeingeye:#{self.model_name}:*").collect{|o| self.load(o)} all = all.find_all{|o| o.created_at > options[:start]} if options[:start] all = all.find_all{|o| o.created_at < options[:stop]} if options[:stop] all else options[:start] = options[:start].to_i options[:stop] = options[:stop].to_i options = {:start => '-inf', :stop => '+inf'}.merge(options) AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:#{field}", options[:start], options[:stop], :with_scores => false).collect{|id| self.find(id)} end end def self.count_by_field(field, options = {}) options = {:start => '-inf', :stop => '+inf'}.merge(options) raw_list = AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:#{field}", options[:start], options[:stop], :with_scores => true) list = [] raw_list.each_with_index do |value, index| if index % 2 == 0 list << [value] else list.last[1] = value.to_i end end list.reverse end def self.first self.find(AllSeeingEye.redis.zrange("allseeingeye:#{self.model_name}:fields:created_at", 0, 0)) end def self.last self.find(AllSeeingEye.redis.zrange("allseeingeye:#{self.model_name}:fields:created_at", -1, -1)) end def self.search(query) if query =~ /^[\sa-zA-Z0-9,-]*? to [\sa-zA-Z0-9,-]*?$/ start = Chronic.parse(query.split(' to ').first, :context => :past) stop = Chronic.parse(query.split(' to ').last, :context => :past) AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:created_at", start.to_i, stop.to_i, :with_scores => false).collect{|id| self.find(id)} elsif query =~ /^(#{self.field_names.join('|')}):(.*)$/ self.find_by_field($1, :value => $2) else results = [] self.field_names.each {|f| results << self.find_by_field(f, :value => query)} results.flatten.uniq end end def self.conglomerate(array, options = {}) return [] if array.empty? options = {:granularity => 500}.merge(options) if !array.first.score.nil? array = array.sort{|a, b| a.score <=> b.score} else array = array.sort{|a, b| a.created_at <=> b.created_at} end start = array.first.created_at.utc.to_i stop = array.last.created_at.utc.to_i interval = (stop - start) / options[:granularity] interval = 1 if interval == 0 tree = RBTree.new options[:granularity].times {|n| tree[start + (interval * n)] = 0} array.each do |o| val = o.created_at.utc.to_i bound = tree.lower_bound(val) bound = tree.upper_bound(val) if bound.nil? tree[bound.first] += 1 end tree.to_a end def initialize self.class.field_names.each do |key| self.class.send(:attr_accessor, key.to_sym) end self end def id @id ||= self.class.next_id end def save AllSeeingEye.redis["allseeingeye:#{self.class.model_name}:#{id}"] = Marshal.dump(self) AllSeeingEye.redis.sadd("allseeingeye:#{self.class.model_name}:all", id) self.class.field_names.each do |field| value = self.send(field.to_sym) next if value.nil? || (value.respond_to?(:empty?) && value.empty?) if value.is_a?(Time) || value.is_a?(DateTime) || value.is_a?(Date) AllSeeingEye.redis.zadd("allseeingeye:#{self.class.model_name}:fields:#{field}", value.to_i, id) else AllSeeingEye.redis.lpush("allseeingeye:#{self.class.model_name}:fields:#{field}:#{value}", id) AllSeeingEye.redis.zincrby("allseeingeye:#{self.class.model_name}:fields:#{field}", 1, value) end end self end end