allowing to specify grouping on report execution (overrides report's default grouping) - reportable - Fork of reportable required by WarVox, from hdm/reportable.
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
---
(DIR) commit ae05149c92108118879b867ae6c51f1b72113ed4
(DIR) parent e97c9a62834f5d6a6cea1ff387784c386ba7abb6
(HTM) Author: Marco Otte-Witte <marco.otte-witte@simplabs.com>
Date: Wed, 14 Jan 2009 01:38:19 +0800
allowing to specify grouping on report execution (overrides report's default grouping)
Signed-off-by: Marco Otte-Witte <marco.otte-witte@simplabs.com>
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(-)
---
(DIR) diff --git a/lib/kvlr/reports_as_sparkline/report.rb b/lib/kvlr/reports_as_sparkline/report.rb
@@ -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, :aggregation
+ attr_reader :klass, :name, :date_column, :value_column, :aggregation, :options
# ==== Parameters
# * <tt>klass</tt> - The model the report works on (This is the class you invoke Kvlr::ReportsAsSparkline::ClassMethods#reports_as_sparkline on)
@@ -26,12 +26,13 @@ module Kvlr #:nodoc:
@date_column = (options[:date_column] || 'created_at').to_s
@value_column = (options[:value_column] || (options[:aggregation] != :sum ? 'id' : name)).to_s
@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; only records that match there conditions are reported on (<b>Beware that when you specify conditions here, caching will be disabled</b>)
+ # * <tt>:grouping</tt> - The period records are grouped on (:hour, :day, :week, :month)
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 |begin_at|
- read_data(begin_at, options[:conditions])
+ options[:grouping] = Grouping.new(options[:grouping]) unless options[:grouping].is_a?(Grouping)
+ ReportCache.process(self, options[:limit], options[:grouping], custom_conditions) do |begin_at|
+ 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, :aggregation, :grouping, :date_column, :value_column, :conditions].include?(k)
end
raise ArgumentError.new("Invalid aggregation #{options[:aggregation]}") if options[:aggregation] && ![:count, :sum].include?(options[:aggregation])
- raise ArgumentError.new("Invalid grouping #{options[:grouping]}") if options[:grouping] && ![:hour, :day, :week, :month].include?(options[:grouping])
raise ArgumentError.new('The name of the column holding the value to sum has to be specified for aggregation :sum') if options[:aggregation] == :sum && !options.key?(:value_column)
when :run
options.each_key do |k|
- raise ArgumentError.new("Invalid option #{k}") unless [:limit, :conditions].include?(k)
+ raise ArgumentError.new("Invalid option #{k}") unless [:limit, :conditions, :grouping].include?(k)
end
end
+ raise ArgumentError.new("Invalid grouping #{options[:grouping]}") if options[:grouping] && ![:hour, :day, :week, :month].include?(options[:grouping])
raise ArgumentError.new("Invalid conditions: #{options[:conditions].inspect}") if options[:conditions] && !options[:conditions].is_a?(Array) && !options[:conditions].is_a?(Hash)
end
(DIR) diff --git a/lib/kvlr/reports_as_sparkline/report_cache.rb b/lib/kvlr/reports_as_sparkline/report_cache.rb
@@ -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.grouping, limit)
+ last_reporting_period_to_read = ReportingPeriod.first(grouping, limit)
unless no_cache
cached_data = self.find(
:all,
@@ -16,29 +16,29 @@ module Kvlr #:nodoc:
'model_name = ? AND report_name = ? AND grouping = ? AND aggregation = ? AND reporting_period >= ?',
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.grouping, cached_data.last.reporting_period).next unless cached_data.empty?
+ last_reporting_period_to_read = ReportingPeriod.new(grouping, cached_data.last.reporting_period).next unless cached_data.empty?
end
new_data = yield(last_reporting_period_to_read.date_time)
- prepare_result(new_data, cached_data, last_reporting_period_to_read, report, no_cache)[0..(limit - 1)]
+ prepare_result(new_data, cached_data, last_reporting_period_to_read, report, grouping, no_cache)[0..(limit - 1)]
end
end
private
- def self.prepare_result(new_data, cached_data, last_reporting_period_to_read, report, no_cache = false)
- new_data.map! { |data| [ReportingPeriod.from_db_string(report.grouping, data[0]), data[1]] }
+ def self.prepare_result(new_data, cached_data, last_reporting_period_to_read, report, grouping, no_cache = false)
+ new_data.map! { |data| [ReportingPeriod.from_db_string(grouping, data[0]), data[1]] }
result = cached_data.map { |cached| [cached.reporting_period, cached.value] }
- 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(new_data, reporting_period))
+ cached = build_cached_data(report, grouping, reporting_period, find_value(new_data, reporting_period))
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
(DIR) diff --git a/spec/classes/report_cache_spec.rb b/spec/classes/report_cache_spec.rb
@@ -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.options[:grouping])
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.options[:grouping]) {}
end
it 'should yield to the given block' do
lambda {
- Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) { raise YieldMatchException.new }
+ Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.options[:grouping]) { raise YieldMatchException.new }
}.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 = ? AND reporting_period >= ?',
@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).date_time
+ Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.options[:grouping], 10).date_time
],
:limit => 10,
:order => 'reporting_period ASC'
)
- Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) { [] }
+ Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.options[:grouping]) { [] }
+ end
+
+ it "should read existing data from the cache for the correct grouping if one other than the report's default grouping is specified" do
+ 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 = ? AND reporting_period >= ?',
+ @report.klass.to_s,
+ @report.name.to_s,
+ grouping.identifier.to_s,
+ @report.aggregation.to_s,
+ Kvlr::ReportsAsSparkline::ReportingPeriod.first(grouping, 10).date_time
+ ],
+ :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_data)
- last_reporting_period_to_read = Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.grouping, 10)
- Kvlr::ReportsAsSparkline::ReportCache.should_receive(:prepare_result).once.with(new_data, cached_data, last_reporting_period_to_read, @report, false)
+ last_reporting_period_to_read = Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.options[:grouping], 10)
+ Kvlr::ReportsAsSparkline::ReportCache.should_receive(:prepare_result).once.with(new_data, cached_data, last_reporting_period_to_read, @report, @report.options[:grouping], false)
- Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10) { new_data }
+ Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.options[:grouping]) { new_data }
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(@report.grouping, 10).date_time
+ Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.options[:grouping]) do |begin_at|
+ begin_at.should == Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.options[:grouping], 10).date_time
[]
end
end
it 'should yield the reporting period after the last one in the cache if the cache is not empty' do
- reporting_period = Kvlr::ReportsAsSparkline::ReportingPeriod.new(@report.grouping)
+ reporting_period = Kvlr::ReportsAsSparkline::ReportingPeriod.new(@report.options[:grouping])
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.options[:grouping]) do |begin_at|
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.options[:grouping], true) {}
end
it 'should yield the first reporting period' do
- Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, true) do |begin_at|
- begin_at.should == Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.grouping, 10).date_time
+ Kvlr::ReportsAsSparkline::ReportCache.process(@report, 10, @report.options[:grouping], true) do |begin_at|
+ begin_at.should == Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.options[:grouping], 10).date_time
[]
end
end
@@ -106,7 +125,7 @@ describe Kvlr::ReportsAsSparkline::ReportCache do
describe '.prepare_result' do
before do
- @last_reporting_period_to_read = Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.grouping, 10)
+ @last_reporting_period_to_read = Kvlr::ReportsAsSparkline::ReportingPeriod.first(@report.options[:grouping], 10)
@new_data = [[@last_reporting_period_to_read.date_time, 1.0]]
Kvlr::ReportsAsSparkline::ReportingPeriod.stub!(:from_db_string).and_return(@last_reporting_period_to_read)
@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 periods' do
- Kvlr::ReportsAsSparkline::ReportingPeriod.should_receive(:from_db_string).once.with(@report.grouping, @new_data[0][0]).and_return(Kvlr::ReportsAsSparkline::ReportingPeriod.new(@report.grouping))
+ Kvlr::ReportsAsSparkline::ReportingPeriod.should_receive(:from_db_string).once.with(@report.options[:grouping], @new_data[0][0]).and_return(Kvlr::ReportsAsSparkline::ReportingPeriod.new(@report.options[:grouping]))
- Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [], @last_reporting_period_to_read, @report)
+ Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [], @last_reporting_period_to_read, @report, @report.options[:grouping])
end
it 'should create (:limit - 1) instances of Kvlr::ReportsAsSparkline::ReportCache with value 0.0 if no new data has been read and nothing was cached' do
Kvlr::ReportsAsSparkline::ReportCache.should_receive(:build_cached_data).exactly(9).times.with(
@report,
+ @report.options[:grouping],
anything(),
0.0
).and_return(@cached)
- Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, [], [], @last_reporting_period_to_read, @report)
+ Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, [], [], @last_reporting_period_to_read, @report, @report.options[:grouping])
end
it 'should create a new Kvlr::ReportsAsSparkline::ReportCache with the correct value if new data has been read' do
Kvlr::ReportsAsSparkline::ReportCache.should_receive(:build_cached_data).exactly(8).times.with(
@report,
+ @report.options[:grouping],
anything(),
0.0
).and_return(@cached)
Kvlr::ReportsAsSparkline::ReportCache.should_receive(:build_cached_data).once.with(
@report,
+ @report.options[:grouping],
@last_reporting_period_to_read,
1.0
).and_return(@cached)
- Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [], @last_reporting_period_to_read, @report)
+ Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [], @last_reporting_period_to_read, @report, @report.options[:grouping])
end
it 'should save the created Kvlr::ReportsAsSparkline::ReportCache if no_cache is not specified' do
@cached.should_receive(:save!).once
- Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [], @last_reporting_period_to_read, @report)
+ Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [], @last_reporting_period_to_read, @report, @report.options[:grouping])
end
it 'should return an array of arrays of Dates and Floats' do
- result = Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [], @last_reporting_period_to_read, @report, true)
+ result = Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [], @last_reporting_period_to_read, @report, @report.options[:grouping], true)
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, [], @last_reporting_period_to_read, @report, true)
+ Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [], @last_reporting_period_to_read, @report, @report.options[:grouping], true)
end
it 'should not update the last cached record if new data has been read for the last reporting period to read' do
Kvlr::ReportsAsSparkline::ReportingPeriod.stub!(:from_db_string).and_return(@last_reporting_period_to_read)
@cached.should_not_receive(:update_attributes!)
- Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [@cached], @last_reporting_period_to_read, @report, true)
+ Kvlr::ReportsAsSparkline::ReportCache.send(:prepare_result, @new_data, [@cached], @last_reporting_period_to_read, @report, @report.options[:grouping], true)
end
end
(DIR) 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(@report, 100, false)
+ Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with(@report, 100, @report.options[:grouping], false)
@report.run
end
it 'should process the data with the report cache and specify no_cache when custom conditions are given' do
- Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with(@report, 100, true)
+ Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with(@report, 100, @report.options[:grouping], true)
@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).and_return(grouping)
+ Kvlr::ReportsAsSparkline::ReportCache.should_receive(:process).once.with(@report, 100, grouping, true)
+
+ @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, :aggregation => :count)
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_name => 'first name' } }) }.should_not raise_error(ArgumentError)
end
+ it 'should raise an error if an invalid grouping is specified' do
+ lambda { @report.send(:ensure_valid_options, { :grouping => :decade }) }.should raise_error(ArgumentError)
+ 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 => :invalid }) }.should raise_error(ArgumentError)
end
-
- it 'should raise an error if an invalid grouping is specified' do
- lambda { @report.send(:ensure_valid_options, { :grouping => :decade }) }.should raise_error(ArgumentError)
- end
it 'should raise an error if aggregation :sum is spesicied but no :value_column' do
lambda { @report.send(:ensure_valid_options, { :aggregation => :sum }) }.should raise_error(ArgumentError)
@@ -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, :conditions => [] }, :run)
+ lambda { @report.send(:ensure_valid_options, { :limit => 100, :conditions => [], :grouping => :week }, :run)
}.should_not raise_error(ArgumentError)
end