Merge branch 'feature/lazy-loading' into dev

This commit is contained in:
2011-03-09 01:31:34 +00:00
4 changed files with 239 additions and 110 deletions

View File

@@ -2,97 +2,7 @@ module Redistat
class Finder
include Database
attr_reader :options
def initialize(options = {})
@options = options
end
def db
super(@options[:connection_ref])
end
def valid_options?
return true if !@options[:scope].blank? && !@options[:label].blank? && !@options[:from].blank? && !@options[:till].blank?
false
end
def find(options = {})
@options.merge!(options)
raise InvalidOptions.new if !valid_options?
if @options[:interval].nil? || !@options[:interval]
find_by_magic
else
find_by_interval
end
end
def find_by_interval(options = {})
@options.merge!(options)
raise InvalidOptions.new if !valid_options?
key = build_key
col = Collection.new(@options)
col.total = Result.new(@options)
build_date_sets.each do |set|
set[:add].each do |date|
result = Result.new
result.date = Date.new(date).to_time
db.hgetall("#{key.prefix}#{date}").each do |k, v|
result[k] = v
col.total.set_or_incr(k, v.to_i)
end
col << result
end
end
col
end
def find_by_magic(options = {})
@options.merge!(options)
raise InvalidOptions.new if !valid_options?
key = Key.new(@options[:scope], @options[:label])
col = Collection.new(@options)
col.total = Result.new(@options)
col << col.total
build_date_sets.each do |set|
sum = Result.new
sum = summarize_add_keys(set[:add], key, sum)
sum = summarize_rem_keys(set[:rem], key, sum)
sum.each do |k, v|
col.total.set_or_incr(k, v.to_i)
end
end
col
end
def build_date_sets
Finder::DateSet.new(@options[:from], @options[:till], @options[:depth], @options[:interval])
end
def build_key
Key.new(@options[:scope], @options[:label])
end
def summarize_add_keys(sets, key, sum)
sets.each do |date|
db.hgetall("#{key.prefix}#{date}").each do |k, v|
sum.set_or_incr(k, v.to_i)
end
end
sum
end
def summarize_rem_keys(sets, key, sum)
sets.each do |date|
db.hgetall("#{key.prefix}#{date}").each do |k, v|
sum.set_or_incr(k, -v.to_i)
end
end
sum
end
class << self
def find(*args)
new.find(*args)
end
@@ -126,15 +36,49 @@ module Redistat
def interval(unit)
new.interval(unit)
end
end
attr_reader :options
def initialize(options = {})
@options = options
end
def all(reload = false)
@result = nil if reload
@result ||= find
end
def total
all.total
end
def each(&block)
all.each(&block)
end
def map(&block)
all.map(&block)
end
def each_with_index(&block)
all.each_with_index(&block)
end
def connection_ref(ref)
reset! if @options[:connection_ref] != ref
@options[:connection_ref] = ref
self
end
def scope(scope)
reset! if @options[:scope] != scope
@options[:scope] = scope
self
end
def label(label)
reset! if @options[:label] != label
@options[:label] = label
self
end
@@ -145,25 +89,124 @@ module Redistat
alias :date :dates
def from(date)
reset! if @options[:from] != date
@options[:from] = date
self
end
def till(date)
reset! if @options[:till] != date
@options[:till] = date
self
end
alias :until :till
def depth(unit)
reset! if @options[:depth] != unit
@options[:depth] = unit
self
end
def interval(unit)
reset! if @options[:interval] != unit
@options[:interval] = unit
self
end
def find(options = {})
set_options(options)
raise InvalidOptions.new if !valid_options?
if @options[:interval].nil? || !@options[:interval]
find_by_magic
else
find_by_interval
end
end
private
def set_options(opts = {})
opts = opts.clone
opts.each do |key, value|
self.send(key, opts.delete(key)) if self.respond_to?(key)
end
@options.merge!(opts)
end
def find_by_interval(options = {})
raise InvalidOptions.new if !valid_options?
key = build_key
col = Collection.new(@options)
col.total = Result.new(@options)
build_date_sets.each do |set|
set[:add].each do |date|
result = Result.new
result.date = Date.new(date).to_time
db.hgetall("#{key.prefix}#{date}").each do |k, v|
result[k] = v
col.total.set_or_incr(k, v.to_i)
end
col << result
end
end
col
end
def find_by_magic(options = {})
raise InvalidOptions.new if !valid_options?
key = Key.new(@options[:scope], @options[:label])
col = Collection.new(@options)
col.total = Result.new(@options)
col << col.total
build_date_sets.each do |set|
sum = Result.new
sum = summarize_add_keys(set[:add], key, sum)
sum = summarize_rem_keys(set[:rem], key, sum)
sum.each do |k, v|
col.total.set_or_incr(k, v.to_i)
end
end
col
end
def reset!
@result = nil
end
def valid_options?
return true if !@options[:scope].blank? && !@options[:label].blank? && !@options[:from].blank? && !@options[:till].blank?
false
end
def build_date_sets
Finder::DateSet.new(@options[:from], @options[:till], @options[:depth], @options[:interval])
end
def build_key
Key.new(@options[:scope], @options[:label])
end
def summarize_add_keys(sets, key, sum)
sets.each do |date|
db.hgetall("#{key.prefix}#{date}").each do |k, v|
sum.set_or_incr(k, v.to_i)
end
end
sum
end
def summarize_rem_keys(sets, key, sum)
sets.each do |date|
db.hgetall("#{key.prefix}#{date}").each do |k, v|
sum.set_or_incr(k, -v.to_i)
end
end
sum
end
def db
super(@options[:connection_ref])
end
end
end

View File

@@ -6,31 +6,36 @@ module Redistat
base.extend(self)
end
#
# statistics store/fetch methods
#
def store(label, stats = {}, date = nil, meta = {}, opts = {})
Event.new(name, label, date, stats, options.merge(opts), meta).save
end
alias :event :store
def fetch(label, from, till, opts = {})
find(label, from, till, opts).all
end
alias :lookup :fetch
def find(label, from, till, opts = {})
Finder.new( { :scope => name,
:label => label,
:from => from,
:till => till }.merge(options.merge(opts)) )
end
#
# options methods
#
def connect_to(opts = {})
Connection.create(opts.merge(:ref => name))
options[:connection_ref] = name
end
def connection
db(options[:connection_ref])
end
alias :redis :connection
def fetch(label, from, till, opts = {})
Finder.find({
:scope => name,
:label => label,
:from => from,
:till => till
}.merge(options.merge(opts)))
end
alias :lookup :fetch
def hashed_label(boolean = nil)
if !boolean.nil?
options[:hashed_label] = boolean
@@ -64,15 +69,22 @@ module Redistat
end
end
#
# resource access methods
#
def connection
db(options[:connection_ref])
end
alias :redis :connection
def options
@options ||= {}
end
private
def name
options[:class_name] || (@name ||= self.to_s)
end
end
end

View File

@@ -19,6 +19,10 @@ describe Redistat::Finder do
finder = Redistat::Finder.new(options)
finder.options.should == options
finder = Redistat::Finder.new
finder.send(:set_options, options)
finder.options.should == options
finder = Redistat::Finder.dates(two_hours_ago, one_hour_ago).scope("PageViews").label("Label").depth(:hour).interval(:hour)
finder.options.should == options
@@ -85,6 +89,65 @@ describe Redistat::Finder do
lambda { Redistat::Finder.find(:from => 3.hours.ago) }.should raise_error(Redistat::InvalidOptions)
end
describe "Lazy-Loading" do
before(:each) do
@first_stat, @last_stat = create_example_stats
@finder = Redistat::Finder.new
@finder.from(@first_stat).till(@last_stat).scope(@scope).label(@label).depth(:hour)
@match = [{}, {"visitors"=>"4", "views"=>"6"},
{"visitors"=>"2", "views"=>"3"},
{"visitors"=>"2", "views"=>"3"}, {}]
end
it "should lazy-load" do
@finder.instance_variable_get("@result").should be_nil
stats = @finder.all
@finder.instance_variable_get("@result").should_not be_nil
stats.should == @finder.find # find method directly fetches results
stats.total.should == @finder.total
stats.total.should == { "views" => 12, "visitors" => 8 }
stats.total.from.should == @first_stat
stats.total.till.should == @last_stat
stats.first.should == stats.total
@finder.all.object_id.should == stats.object_id
@finder.from(@first_stat + 2.hours)
@finder.instance_variable_get("@result").should be_nil
@finder.all.object_id.should_not == stats.object_id
stats = @finder.all
stats.total.should == { "views" => 6, "visitors" => 4 }
end
it "should handle #map" do
@finder.interval(:hour)
@finder.map { |r| r }.should == @match
end
it "should handle #each" do
@finder.interval(:hour)
res = []
@finder.each { |r| res << r }
res.should == @match
end
it "should handle #each_with_index" do
@finder.interval(:hour)
res = {}
match = {}
@finder.each_with_index { |r, i| res[i] = r }
@match.each_with_index { |r, i| match[i] = r }
res.should == match
end
end
# helper methods
@@ -93,7 +156,7 @@ describe Redistat::Finder do
Redistat::Summary.update(key, @stats, :hour)
key = Redistat::Key.new(@scope, @label, Time.parse("2010-05-14 13:53"))
Redistat::Summary.update(key, @stats, :hour)
key = Redistat::Key.new(@scope, @label, Time.parse("2010-05-14 14:32"))
key = Redistat::Key.new(@scope, @label, Time.parse("2010-05-14 14:52"))
Redistat::Summary.update(key, @stats, :hour)
key = Redistat::Key.new(@scope, @label, (last = Time.parse("2010-05-14 15:02")))
Redistat::Summary.update(key, @stats, :hour)

View File

@@ -17,6 +17,17 @@ describe Redistat::Model do
ModelHelper2.send(:name).should == "ModelHelper2"
end
it "should return a Finder" do
two_hours_ago = 2.hours.ago
one_hour_ago = 1.hour.ago
finder = ModelHelper1.find('label', two_hours_ago, one_hour_ago)
finder.should be_a(Redistat::Finder)
finder.options[:scope].should == 'ModelHelper1'
finder.options[:label].should == 'label'
finder.options[:from].should == two_hours_ago
finder.options[:till].should == one_hour_ago
end
it "should listen to model-defined options" do
ModelHelper2.depth.should == :day
ModelHelper2.store_event.should == true