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 AllSeeingEye.redis.scard("allseeingeye:#{self.model_name}:all").to_i end def self.count_by_field(field, options = {}) AllSeeingEye.redis.llen("allseeingeye:#{self.model_name}:fields:#{field}:#{options[:value]}").to_i 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}:*", :limit => options[:offset] && options[:limit] ? [options[:offset], options[:limit]] : nil).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.list_by_field(field, options = {}) options = {:start => '-inf', :stop => '+inf', :page => nil}.merge(options) limit = if options[:page] [(options[:page] - 1) * 10, 10] else nil end raw_list, needs_pruning = if options[:value] [AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:#{field}:#{options[:value]}:count", '-inf', '+inf', :with_scores => true, :limit => limit), true] else if AllSeeingEye.redis.exists("allseeingeye:#{self.model_name}:fields:#{field}:count") [AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:#{field}:count", '-inf', '+inf', :with_scores => true, :limit => limit), true] else [AllSeeingEye.redis.zrevrangebyscore("allseeingeye:#{self.model_name}:fields:#{field}", options[:stop], options[:start], :with_scores => true, :limit => limit), false] end end list = [] raw_list.each_with_index do |value, index| if index % 2 == 0 list << [value] else list.last[1] = value.to_i end end if needs_pruning list = list.find_all{|i| i.first.to_i > options[:start]} if options[:start] && options[:start] != '-inf' list = list.find_all{|i| i.first.to_i < options[:stop]} if options[:stop] && options[:stop] != '+inf' end list.sort{|a, b| a.first.to_i == 0 ? b.last <=> a.last : a.first <=> b.first} 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, options = {}) if query =~ /^[\sa-zA-Z0-9,-]*? to [\sa-zA-Z0-9,-]*?$/ start = Chronic.parse(query.split(' to ').first, :context => :past).to_i stop = Chronic.parse(query.split(' to ').last, :context => :past).to_i if options[:count] return self.list_by_field('created_at', :start => start, :stop => stop) else return AllSeeingEye.redis.zrangebyscore("allseeingeye:#{self.model_name}:fields:created_at", start, stop, :with_scores => false).collect{|id| self.find(id)} end elsif query =~ /^(#{self.field_names.join('|')}):(.*)$/ if options[:count] self.list_by_field($1, :value => $2) else self.find_by_field($1, :value => $2) end end 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 timestamp = (self.created_at.to_i / AllSeeingEye.configuration[:all_seeing_eye]['round_to_seconds']) * AllSeeingEye.configuration[:all_seeing_eye]['round_to_seconds'] 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.zincrby("allseeingeye:#{self.class.model_name}:fields:#{field}:count", 1, timestamp) AllSeeingEye.redis.zadd("allseeingeye:#{self.class.model_name}:fields:#{field}", value.to_i, id) else AllSeeingEye.redis.zincrby("allseeingeye:#{self.class.model_name}:fields:#{field}:#{value}:count", 1, timestamp) 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