| allowing to specify grouping on report execution (overrides report's default gr… | |
| Log | |
| Files | |
| Refs | |
| README | |
| --- | |
| commit ae05149c92108118879b867ae6c51f1b72113ed4 | |
| parent e97c9a62834f5d6a6cea1ff387784c386ba7abb6 | |
| Author: Marco Otte-Witte <[email protected]> | |
| Date: Wed, 14 Jan 2009 01:38:19 +0800 | |
| allowing to specify grouping on report execution (overrides report's default gr… | |
| Signed-off-by: Marco Otte-Witte <[email protected]> | |
| Diffstat: | |
| M lib/kvlr/reports_as_sparkline/repo… | 25 ++++++++++++++----------- | |
| M lib/kvlr/reports_as_sparkline/repo… | 22 +++++++++++----------- | |
| M spec/classes/report_cache_spec.rb | 76 ++++++++++++++++++++---------… | |
| M spec/classes/report_spec.rb | 26 +++++++++++++++++--------- | |
| 4 files changed, 91 insertions(+), 58 deletions(-) | |
| --- | |
| diff --git a/lib/kvlr/reports_as_sparkline/report.rb b/lib/kvlr/reports_as_spar… | |
| @@ -5,7 +5,7 @@ module Kvlr #:nodoc: | |
| # The Report class that does all the data retrieval and calculations | |
| class Report | |
| - attr_reader :klass, :name, :date_column, :value_column, :grouping, :aggr… | |
| + attr_reader :klass, :name, :date_column, :value_column, :aggregation, :o… | |
| # ==== Parameters | |
| # * <tt>klass</tt> - The model the report works on (This is the class yo… | |
| @@ -26,12 +26,13 @@ module Kvlr #:nodoc: | |
| @date_column = (options[:date_column] || 'created_at').to_s | |
| @value_column = (options[:value_column] || (options[:aggregation] != :… | |
| @aggregation = options[:aggregation] || :count | |
| - @grouping = Grouping.new(options[:grouping] || :day) | |
| @options = { | |
| - :limit => options[:limit] || 100, | |
| - :conditions => options[:conditions] || [] | |
| + :limit => options[:limit] || 100, | |
| + :conditions => options[:conditions] || [], | |
| + :grouping => Grouping.new(options[:grouping] || :day) | |
| } | |
| @options.merge!(options) | |
| + @options.freeze | |
| end | |
| # Runs the report and returns an array of array of DateTimes and Floats | |
| @@ -39,24 +40,26 @@ module Kvlr #:nodoc: | |
| # ==== Options | |
| # * <tt>:limit</tt> - The number of periods to get | |
| # * <tt>:conditions</tt> - Conditions like in ActiveRecord::Base#find; o… | |
| + # * <tt>:grouping</tt> - The period records are grouped on (:hour, :day,… | |
| def run(options = {}) | |
| ensure_valid_options(options, :run) | |
| custom_conditions = options.key?(:conditions) | |
| options.reverse_merge!(@options) | |
| - ReportCache.process(self, options[:limit], custom_conditions) do |begi… | |
| - read_data(begin_at, options[:conditions]) | |
| + options[:grouping] = Grouping.new(options[:grouping]) unless options[:… | |
| + ReportCache.process(self, options[:limit], options[:grouping], custom_… | |
| + read_data(begin_at, options[:grouping], options[:conditions]) | |
| end | |
| end | |
| private | |
| - def read_data(begin_at, conditions = []) #:nodoc: | |
| + def read_data(begin_at, grouping, conditions = []) #:nodoc: | |
| conditions = setup_conditions(begin_at, conditions) | |
| @klass.send(@aggregation, | |
| @value_column, | |
| :conditions => conditions, | |
| - :group => @grouping.to_sql(@date_column), | |
| - :order => "#{@grouping.to_sql(@date_column)} ASC" | |
| + :group => grouping.to_sql(@date_column), | |
| + :order => "#{grouping.to_sql(@date_column)} ASC" | |
| ) | |
| end | |
| @@ -78,13 +81,13 @@ module Kvlr #:nodoc: | |
| raise ArgumentError.new("Invalid option #{k}") unless [:limit,… | |
| end | |
| raise ArgumentError.new("Invalid aggregation #{options[:aggregat… | |
| - raise ArgumentError.new("Invalid grouping #{options[:grouping]}"… | |
| raise ArgumentError.new('The name of the column holding the valu… | |
| when :run | |
| options.each_key do |k| | |
| - raise ArgumentError.new("Invalid option #{k}") unless [:limit,… | |
| + raise ArgumentError.new("Invalid option #{k}") unless [:limit,… | |
| end | |
| end | |
| + raise ArgumentError.new("Invalid grouping #{options[:grouping]}") if… | |
| raise ArgumentError.new("Invalid conditions: #{options[:conditions].… | |
| end | |
| diff --git a/lib/kvlr/reports_as_sparkline/report_cache.rb b/lib/kvlr/reports_a… | |
| @@ -4,11 +4,11 @@ module Kvlr #:nodoc: | |
| class ReportCache < ActiveRecord::Base #:nodoc: | |
| - def self.process(report, limit, no_cache = false, &block) | |
| + def self.process(report, limit, grouping, no_cache = false, &block) | |
| raise ArgumentError.new('A block must be given') unless block_given? | |
| self.transaction do | |
| cached_data = [] | |
| - last_reporting_period_to_read = ReportingPeriod.first(report.groupin… | |
| + last_reporting_period_to_read = ReportingPeriod.first(grouping, limi… | |
| unless no_cache | |
| cached_data = self.find( | |
| :all, | |
| @@ -16,29 +16,29 @@ module Kvlr #:nodoc: | |
| 'model_name = ? AND report_name = ? AND grouping = ? AND aggre… | |
| report.klass.to_s, | |
| report.name.to_s, | |
| - report.grouping.identifier.to_s, | |
| + grouping.identifier.to_s, | |
| report.aggregation.to_s, | |
| last_reporting_period_to_read.date_time | |
| ], | |
| :limit => limit, | |
| :order => 'reporting_period ASC' | |
| ) | |
| - last_reporting_period_to_read = ReportingPeriod.new(report.groupin… | |
| + last_reporting_period_to_read = ReportingPeriod.new(grouping, cach… | |
| end | |
| new_data = yield(last_reporting_period_to_read.date_time) | |
| - prepare_result(new_data, cached_data, last_reporting_period_to_read,… | |
| + prepare_result(new_data, cached_data, last_reporting_period_to_read,… | |
| end | |
| end | |
| private | |
| - def self.prepare_result(new_data, cached_data, last_reporting_period_t… | |
| - new_data.map! { |data| [ReportingPeriod.from_db_string(report.groupi… | |
| + def self.prepare_result(new_data, cached_data, last_reporting_period_t… | |
| + new_data.map! { |data| [ReportingPeriod.from_db_string(grouping, dat… | |
| result = cached_data.map { |cached| [cached.reporting_period, cached… | |
| - current_reporting_period = ReportingPeriod.new(report.grouping) | |
| + current_reporting_period = ReportingPeriod.new(grouping) | |
| reporting_period = last_reporting_period_to_read | |
| while reporting_period < current_reporting_period | |
| - cached = build_cached_data(report, reporting_period, find_value(ne… | |
| + cached = build_cached_data(report, grouping, reporting_period, fin… | |
| cached.save! unless no_cache | |
| result << [reporting_period.date_time, cached.value] | |
| reporting_period = reporting_period.next | |
| @@ -52,11 +52,11 @@ module Kvlr #:nodoc: | |
| data ? data[1] : 0.0 | |
| end | |
| - def self.build_cached_data(report, reporting_period, value) | |
| + def self.build_cached_data(report, grouping, reporting_period, value) | |
| self.new( | |
| :model_name => report.klass.to_s, | |
| :report_name => report.name.to_s, | |
| - :grouping => report.grouping.identifier.to_s, | |
| + :grouping => grouping.identifier.to_s, | |
| :aggregation => report.aggregation.to_s, | |
| :reporting_period => reporting_period.date_time, | |
| :value => value | |
| diff --git a/spec/classes/report_cache_spec.rb b/spec/classes/report_cache_spec… | |
| @@ -15,70 +15,89 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| it 'should raise an ArgumentError if no block is given' do | |
| lambda do | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.opt… | |
| end.should raise_error(ArgumentError) | |
| end | |
| it 'sould start a transaction' do | |
| Kvlr::ReportsAsSparkline::ReportCache.should_receive(:transaction) | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) {} | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.optio… | |
| end | |
| it 'should yield to the given block' do | |
| lambda { | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) { raise Yie… | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.opt… | |
| }.should raise_error(YieldMatchException) | |
| end | |
| - it 'should read existing data for the report from cache' do | |
| + it 'should read existing data from the cache' do | |
| Kvlr::ReportsAsSparkline::ReportCache.should_receive(:find).once.with( | |
| :all, | |
| :conditions => [ | |
| 'model_name = ? AND report_name = ? AND grouping = ? AND aggregation… | |
| @report.klass.to_s, | |
| @report.name.to_s, | |
| - @report.grouping.identifier.to_s, | |
| + @report.options[:grouping].identifier.to_s, | |
| @report.aggregation.to_s, | |
| - Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.grouping, 10… | |
| + Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.options[:gro… | |
| ], | |
| :limit => 10, | |
| :order => 'reporting_period ASC' | |
| ) | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) { [] } | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.optio… | |
| + end | |
| + | |
| + it "should read existing data from the cache for the correct grouping if o… | |
| + grouping = Kvlr::ReportsAsSparkline::Grouping.new(:month) | |
| + Kvlr::ReportsAsSparkline::ReportCache.should_receive(:find).once.with( | |
| + :all, | |
| + :conditions => [ | |
| + 'model_name = ? AND report_name = ? AND grouping = ? AND aggregation… | |
| + @report.klass.to_s, | |
| + @report.name.to_s, | |
| + grouping.identifier.to_s, | |
| + @report.aggregation.to_s, | |
| + Kvlr::ReportsAsSparkline::ReportingPeriod.first(grouping, 10).date_t… | |
| + ], | |
| + :limit => 10, | |
| + :order => 'reporting_period ASC' | |
| + ) | |
| + | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, grouping) { [… | |
| end | |
| it 'should prepare the results before it returns them' do | |
| new_data = [] | |
| cached_data = [] | |
| Kvlr::ReportsAsSparkline::ReportCache.stub!(:find).and_return(cached_dat… | |
| - last_reporting_period_to_read = Kvlr::ReportsAsSparkline::ReportingPerio… | |
| - Kvlr::ReportsAsSparkline::ReportCache.should_receive(:prepare_result).on… | |
| + last_reporting_period_to_read = Kvlr::ReportsAsSparkline::ReportingPerio… | |
| + Kvlr::ReportsAsSparkline::ReportCache.should_receive(:prepare_result).on… | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) { new_data } | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.optio… | |
| end | |
| it 'should yield the first reporting period if the cache is empty' do | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) do |begin_at| | |
| - begin_at.should == Kvlr::ReportsAsSparkline::ReportingPeriod.first(@re… | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.optio… | |
| + begin_at.should == Kvlr::ReportsAsSparkline::ReportingPeriod.first(@re… | |
| [] | |
| end | |
| end | |
| it 'should yield the reporting period after the last one in the cache if t… | |
| - reporting_period = Kvlr::ReportsAsSparkline::ReportingPeriod.new(@report… | |
| + reporting_period = Kvlr::ReportsAsSparkline::ReportingPeriod.new(@report… | |
| cached = Kvlr::ReportsAsSparkline::ReportCache.new({ | |
| :model_name => @report.klass, | |
| :report_name => @report.name, | |
| - :grouping => @report.grouping.identifier.to_s, | |
| + :grouping => @report.options[:grouping].identifier.to_s, | |
| :aggregation => @report.aggregation.to_s, | |
| :value => 1, | |
| :reporting_period => reporting_period.date_time | |
| }) | |
| Kvlr::ReportsAsSparkline::ReportCache.stub!(:find).and_return([cached]) | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) do |begin_at| | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.optio… | |
| begin_at.should == reporting_period.next.date_time | |
| [] | |
| end | |
| @@ -89,12 +108,12 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| it 'should not read any data from cache' do | |
| Kvlr::ReportsAsSparkline::ReportCache.should_not_receive(:find) | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, true) {} | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.opt… | |
| end | |
| it 'should yield the first reporting period' do | |
| - Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, true) do |b… | |
| - begin_at.should == Kvlr::ReportsAsSparkline::ReportingPeriod.first(@… | |
| + Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.opt… | |
| + begin_at.should == Kvlr::ReportsAsSparkline::ReportingPeriod.first(@… | |
| [] | |
| end | |
| end | |
| @@ -106,7 +125,7 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| describe '.prepare_result' do | |
| before do | |
| - @last_reporting_period_to_read = Kvlr::ReportsAsSparkline::ReportingPeri… | |
| + @last_reporting_period_to_read = Kvlr::ReportsAsSparkline::ReportingPeri… | |
| @new_data = [[@last_reporting_period_to_read.date_time, 1.0]] | |
| Kvlr::ReportsAsSparkline::ReportingPeriod.stub!(:from_db_string).and_ret… | |
| @cached = Kvlr::ReportsAsSparkline::ReportCache.new | |
| @@ -115,44 +134,47 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| end | |
| it 'should convert the date strings from the newly read data to reporting … | |
| - Kvlr::ReportsAsSparkline::ReportingPeriod.should_receive(:from_db_string… | |
| + Kvlr::ReportsAsSparkline::ReportingPeriod.should_receive(:from_db_string… | |
| - Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [… | |
| + Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [… | |
| end | |
| it 'should create (:limit - 1) instances of Kvlr::ReportsAsSparkline::Repo… | |
| Kvlr::ReportsAsSparkline::ReportCache.should_receive(:build_cached_data)… | |
| @report, | |
| + @report.options[:grouping], | |
| anything(), | |
| 0.0 | |
| ).and_return(@cached) | |
| - Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, [], [], @las… | |
| + Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, [], [], @las… | |
| end | |
| it 'should create a new Kvlr::ReportsAsSparkline::ReportCache with the cor… | |
| Kvlr::ReportsAsSparkline::ReportCache.should_receive(:build_cached_data)… | |
| @report, | |
| + @report.options[:grouping], | |
| anything(), | |
| 0.0 | |
| ).and_return(@cached) | |
| Kvlr::ReportsAsSparkline::ReportCache.should_receive(:build_cached_data)… | |
| @report, | |
| + @report.options[:grouping], | |
| @last_reporting_period_to_read, | |
| 1.0 | |
| ).and_return(@cached) | |
| - Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [… | |
| + Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [… | |
| end | |
| it 'should save the created Kvlr::ReportsAsSparkline::ReportCache if no_ca… | |
| @cached.should_receive(:save!).once | |
| - Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [… | |
| + Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [… | |
| end | |
| it 'should return an array of arrays of Dates and Floats' do | |
| - result = Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @ne… | |
| + result = Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @ne… | |
| result.should be_kind_of(Array) | |
| result[0].should be_kind_of(Array) | |
| @@ -165,14 +187,14 @@ describe Kvlr::ReportsAsSparkline::ReportCache do | |
| it 'should not save the created Kvlr::ReportsAsSparkline::ReportCache' do | |
| @cached.should_not_receive(:save!) | |
| - Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data,… | |
| + Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data,… | |
| end | |
| it 'should not update the last cached record if new data has been read f… | |
| Kvlr::ReportsAsSparkline::ReportingPeriod.stub!(:from_db_string).and_r… | |
| @cached.should_not_receive(:update_attributes!) | |
| - Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data,… | |
| + Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data,… | |
| end | |
| end | |
| diff --git a/spec/classes/report_spec.rb b/spec/classes/report_spec.rb | |
| @@ -9,13 +9,13 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| describe '#run' do | |
| it 'should process the data with the report cache' do | |
| - Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with… | |
| + Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with… | |
| @report.run | |
| end | |
| it 'should process the data with the report cache and specify no_cache whe… | |
| - Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with… | |
| + Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with… | |
| @report.run(:conditions => { :some => :condition }) | |
| end | |
| @@ -26,6 +26,14 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| result = @report.run(:limit => 3) | |
| end | |
| + it 'should use a custom grouping if one is specified' do | |
| + grouping = Kvlr::ReportsAsSparkline::Grouping.new(:month) | |
| + Kvlr::ReportsAsSparkline::Grouping.should_receive(:new).once.with(:month… | |
| + Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with… | |
| + | |
| + @report.run(:conditions => { :some => :condition }, :grouping => :month) | |
| + end | |
| + | |
| for grouping in [:hour, :day, :week, :month] do | |
| describe "for grouping #{grouping.to_s}" do | |
| @@ -114,13 +122,13 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| @report = Kvlr::ReportsAsSparkline::Report.new(User, :registrations, :ag… | |
| User.should_receive(:count).once.and_return([]) | |
| - @report.send(:read_data, Time.now) | |
| + @report.send(:read_data, Time.now, @report.options[:grouping]) | |
| end | |
| it 'should setup the conditions' do | |
| @report.should_receive(:setup_conditions).once.and_return([]) | |
| - @report.send(:read_data, Time.now) | |
| + @report.send(:read_data, Time.now, @report.options[:grouping]) | |
| end | |
| end | |
| @@ -187,6 +195,10 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| lambda { @report.send(:ensure_valid_options, { :conditions => { :first_n… | |
| end | |
| + it 'should raise an error if an invalid grouping is specified' do | |
| + lambda { @report.send(:ensure_valid_options, { :grouping => :decade }) }… | |
| + end | |
| + | |
| describe 'for context :initialize' do | |
| it 'should not raise an error if valid options are specified' do | |
| @@ -208,10 +220,6 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| it 'should raise an error if an invalid aggregation is specified' do | |
| lambda { @report.send(:ensure_valid_options, { :aggregation => :invali… | |
| end | |
| - | |
| - it 'should raise an error if an invalid grouping is specified' do | |
| - lambda { @report.send(:ensure_valid_options, { :grouping => :decade })… | |
| - end | |
| it 'should raise an error if aggregation :sum is spesicied but no :value… | |
| lambda { @report.send(:ensure_valid_options, { :aggregation => :sum })… | |
| @@ -222,7 +230,7 @@ describe Kvlr::ReportsAsSparkline::Report do | |
| describe 'for context :run' do | |
| it 'should not raise an error if valid options are specified' do | |
| - lambda { @report.send(:ensure_valid_options, { :limit => 100, :conditi… | |
| + lambda { @report.send(:ensure_valid_options, { :limit => 100, :conditi… | |
| }.should_not raise_error(ArgumentError) | |
| end | |