diff --git a/lib/redistat/finder.rb b/lib/redistat/finder.rb index c0ee779..494206b 100644 --- a/lib/redistat/finder.rb +++ b/lib/redistat/finder.rb @@ -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 \ No newline at end of file diff --git a/lib/redistat/model.rb b/lib/redistat/model.rb index 8d9059b..1f6c90d 100644 --- a/lib/redistat/model.rb +++ b/lib/redistat/model.rb @@ -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 \ No newline at end of file diff --git a/spec/finder_spec.rb b/spec/finder_spec.rb index aaa0fec..f87e8ff 100644 --- a/spec/finder_spec.rb +++ b/spec/finder_spec.rb @@ -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) diff --git a/spec/model_spec.rb b/spec/model_spec.rb index 19755bd..3398bdf 100644 --- a/spec/model_spec.rb +++ b/spec/model_spec.rb @@ -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