diff --git a/lib/saulabs/reportable/grouping.rb b/lib/saulabs/reportable/grouping.rb index 6e5a9f7..3ea135e 100644 --- a/lib/saulabs/reportable/grouping.rb +++ b/lib/saulabs/reportable/grouping.rb @@ -41,6 +41,8 @@ def date_parts_from_db_string(db_string) from_sqlite_db_string(db_string) when /postgres/i from_postgresql_db_string(db_string) + when /mssql/i, /sqlserver/i + from_sqlserver_db_string(db_string) end end @@ -57,6 +59,8 @@ def to_sql(date_column) sqlite_format(date_column) when /postgres/i postgresql_format(date_column) + when /mssql/i, /sqlserver/i + sqlserver_format(date_column) end end @@ -94,6 +98,14 @@ def from_postgresql_db_string(db_string) end end + def from_sqlserver_db_string(db_string) + if @identifier == :week + parts = [db_string[0..3], db_string[5..6]].map(&:to_i) + else + db_string.split(/[- ]/).map(&:to_i) + end + end + def mysql_format(date_column) case @identifier when :hour @@ -133,6 +145,19 @@ def postgresql_format(date_column) end end + def sqlserver_format(date_column) + case @identifier + when :hour + "DATEADD(hh,DATEDIFF(hh,DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',#{date_column}), '1 Jan 1900'),#{date_column}), DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',#{date_column}), '1 Jan 1900'))" + when :day + "DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',#{date_column}), '1 Jan 1900')" + when :week + "LEFT(CONVERT(varchar,#{date_column},120), 4) + '-' + CAST(DATEPART(isowk,#{date_column}) AS VARCHAR)" + when :month + "DATEADD(mm,DATEDIFF(mm,'1 Jan 1900',#{date_column}), '1 Jan 1900')" + end + end + end end diff --git a/lib/saulabs/reportable/report.rb b/lib/saulabs/reportable/report.rb index 6abdd28..5e6e770 100644 --- a/lib/saulabs/reportable/report.rb +++ b/lib/saulabs/reportable/report.rb @@ -68,6 +68,7 @@ def initialize(klass, name, options = {}) @value_column = (options[:value_column] || (@aggregation == :count ? 'id' : name)).to_s @options = { :limit => options[:limit] || 100, + :distinct => options[:distinct] || false, :conditions => options[:conditions] || [], :grouping => Grouping.new(options[:grouping] || :day), :live_data => options[:live_data] || false, @@ -118,6 +119,7 @@ def read_data(begin_at, end_at, options) @klass.send(@aggregation, @value_column, :conditions => conditions, + :distinct => options[:distinct], :group => options[:grouping].to_sql(@date_column), :order => "#{options[:grouping].to_sql(@date_column)} ASC", :limit => options[:limit] @@ -145,7 +147,7 @@ def ensure_valid_options(options, context = :initialize) case context when :initialize options.each_key do |k| - raise ArgumentError.new("Invalid option #{k}!") unless [:limit, :aggregation, :grouping, :date_column, :value_column, :conditions, :live_data, :end_date].include?(k) + raise ArgumentError.new("Invalid option #{k}!") unless [:limit, :aggregation, :grouping, :distinct, :date_column, :value_column, :conditions, :live_data, :end_date].include?(k) end raise ArgumentError.new("Invalid aggregation #{options[:aggregation]}!") if options[:aggregation] && ![:count, :sum, :maximum, :minimum, :average].include?(options[:aggregation]) raise ArgumentError.new('The name of the column holding the value to sum has to be specified for aggregation :sum!') if [:sum, :maximum, :minimum, :average].include?(options[:aggregation]) && !options.key?(:value_column) diff --git a/lib/saulabs/reportable/report_cache.rb b/lib/saulabs/reportable/report_cache.rb index bd50e42..4711daf 100644 --- a/lib/saulabs/reportable/report_cache.rb +++ b/lib/saulabs/reportable/report_cache.rb @@ -40,10 +40,7 @@ class ReportCache < ActiveRecord::Base # Saulabs::Reportable::ReportCache.clear_for(User, :registrations) # def self.clear_for(klass, report) - self.delete_all(:conditions => { - :model_name => klass.name, - :report_name => report.to_s - }) + self.delete_all(:model_name => klass.name, :report_name => report.to_s) end # Processes the report using the respective cache. diff --git a/spec/classes/grouping_spec.rb b/spec/classes/grouping_spec.rb index a23e892..825aa51 100644 --- a/spec/classes/grouping_spec.rb +++ b/spec/classes/grouping_spec.rb @@ -76,6 +76,30 @@ end + describe 'for MS SQL Server' do + + before do + ActiveRecord::Base.connection.stub!(:adapter_name).and_return('sqlserver') + end + + it 'should output a format of "YYYY-MM-DD HH:00:00.0" for grouping :hour' do # string "%Y-%m-%d %h:00:00.0" + Saulabs::Reportable::Grouping.new(:hour).send(:to_sql, 'created_at').should == "DATEADD(hh,DATEDIFF(hh,DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',created_at), '1 Jan 1900'),created_at), DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',created_at), '1 Jan 1900'))" + end + + it 'should output a format of "YYYY-MM-DD" for grouping :day' do + Saulabs::Reportable::Grouping.new(:day).send(:to_sql, 'created_at').should == "DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',created_at), '1 Jan 1900')" + end + + it 'should output a format of "YYYY-WW" for grouping :week' do + Saulabs::Reportable::Grouping.new(:week).send(:to_sql, 'created_at').should == "LEFT(CONVERT(varchar,created_at,120), 4) + '-' + CAST(DATEPART(isowk,created_at) AS VARCHAR)" + end + + it 'should output a format of "YYYY-MM-01" for grouping :month' do + Saulabs::Reportable::Grouping.new(:month).send(:to_sql, 'created_at').should == "DATEADD(mm,DATEDIFF(mm,'1 Jan 1900',created_at), '1 Jan 1900')" + end + + end + end describe '#date_parts_from_db_string' do @@ -94,7 +118,7 @@ end - it 'should split the string with "-" and return teh calendar year and week for grouping :week' do + it 'should split the string with "-" and return the calendar year and week for grouping :week' do db_string = '2008-2-1' expected = [2008, 5] @@ -150,6 +174,29 @@ end + describe 'for MS SQL Server' do + + before do + ActiveRecord::Base.connection.stub!(:adapter_name).and_return('sqlserver') + end + + for grouping in [[:hour, '2008-12-31 12'], [:day, '2008-12-31'], [:month, '2008-12']] do + + it "should split the string with '-' and ' ' for grouping :#{grouping[0].to_s}" do + Saulabs::Reportable::Grouping.new(grouping[0]).date_parts_from_db_string(grouping[1]).should == grouping[1].split(/[- ]/).map(&:to_i) + end + + end + + it 'should use the first 4 numbers for the year and the last 2 numbers for the week for grouping :week' do + db_string = '2008-52' + expected = [2008, 52] + + Saulabs::Reportable::Grouping.new(:week).date_parts_from_db_string(db_string).should == expected + end + + end + end end diff --git a/spec/classes/report_cache_spec.rb b/spec/classes/report_cache_spec.rb index 39f3d66..4fa5251 100644 --- a/spec/classes/report_cache_spec.rb +++ b/spec/classes/report_cache_spec.rb @@ -94,10 +94,10 @@ describe '.clear_for' do it 'should delete all entries in the cache for the klass and report name' do - Saulabs::Reportable::ReportCache.should_receive(:delete_all).once.with(:conditions => { + Saulabs::Reportable::ReportCache.should_receive(:delete_all).once.with( :model_name => User.name, :report_name => 'registrations' - }) + ) Saulabs::Reportable::ReportCache.clear_for(User, :registrations) end diff --git a/spec/classes/report_spec.rb b/spec/classes/report_spec.rb index 479099d..a665328 100644 --- a/spec/classes/report_spec.rb +++ b/spec/classes/report_spec.rb @@ -21,7 +21,7 @@ it 'should process the data with the report cache' do Saulabs::Reportable::ReportCache.should_receive(:process).once.with( @report, - { :limit => 100, :grouping => @report.options[:grouping], :conditions => [], :live_data => false, :end_date => false } + { :limit => 100, :grouping => @report.options[:grouping], :conditions => [], :live_data => false, :end_date => false, :distinct => false } ) @report.run @@ -30,7 +30,7 @@ it 'should process the data with the report cache when custom conditions are given' do Saulabs::Reportable::ReportCache.should_receive(:process).once.with( @report, - { :limit => 100, :grouping => @report.options[:grouping], :conditions => { :some => :condition }, :live_data => false, :end_date => false } + { :limit => 100, :grouping => @report.options[:grouping], :conditions => { :some => :condition }, :live_data => false, :end_date => false, :distinct => false } ) @report.run(:conditions => { :some => :condition }) @@ -47,7 +47,7 @@ Saulabs::Reportable::Grouping.should_receive(:new).once.with(:month).and_return(grouping) Saulabs::Reportable::ReportCache.should_receive(:process).once.with( @report, - { :limit => 100, :grouping => grouping, :conditions => [], :live_data => false, :end_date => false } + { :limit => 100, :grouping => grouping, :conditions => [], :live_data => false, :end_date => false, :distinct => false } ) @report.run(:grouping => :month) @@ -71,10 +71,10 @@ describe "for grouping :#{grouping.to_s}" do before(:all) do - User.create!(:login => 'test 1', :created_at => Time.now, :profile_visits => 2) - User.create!(:login => 'test 2', :created_at => Time.now - 1.send(grouping), :profile_visits => 1) - User.create!(:login => 'test 3', :created_at => Time.now - 3.send(grouping), :profile_visits => 2) - User.create!(:login => 'test 4', :created_at => Time.now - 3.send(grouping), :profile_visits => 3) + User.create!(:login => 'test 1', :created_at => Time.now, :profile_visits => 2, :sub_type => "red") + User.create!(:login => 'test 2', :created_at => Time.now - 1.send(grouping), :profile_visits => 1, :sub_type => "red") + User.create!(:login => 'test 3', :created_at => Time.now - 3.send(grouping), :profile_visits => 2, :sub_type => "blue") + User.create!(:login => 'test 4', :created_at => Time.now - 3.send(grouping), :profile_visits => 3, :sub_type => "blue") end describe 'optimized querying with contiguously cached data' do @@ -299,6 +299,24 @@ result[6][1].should == 0.0 end + it 'should return correct data for aggregation :count with distinct: true' do + @report = Saulabs::Reportable::Report.new(User, :registrations, + :aggregation => :count, + :grouping => grouping, + :value_column => :sub_type, + :distinct => true, + :limit => 10, + :live_data => live_data + ) + result = @report.run.to_a + + result[10][1].should == 1.0 if live_data + result[9][1].should == 1.0 + result[8][1].should == 0.0 + result[7][1].should == 1.0 + result[6][1].should == 0.0 + end + it 'should return correct data for aggregation :sum' do @report = Saulabs::Reportable::Report.new(User, :registrations, :aggregation => :sum, diff --git a/spec/db/schema.rb b/spec/db/schema.rb index f1a49a2..1c2a9a3 100644 --- a/spec/db/schema.rb +++ b/spec/db/schema.rb @@ -4,6 +4,7 @@ t.string :login, :null => false t.integer :profile_visits, :null => false, :default => 0 t.string :type, :null => false, :default => 'User' + t.string :sub_type t.timestamps end