reportable 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +1 -0
- data/Rakefile +3 -7
- data/generators/reportable_jquery_flot_assets/reportable_jquery_flot_assets_generator.rb +2 -0
- data/generators/reportable_migration/reportable_migration_generator.rb +3 -4
- data/generators/reportable_raphael_assets/reportable_raphael_assets_generator.rb +2 -0
- data/lib/saulabs/reportable.rb +2 -0
- data/lib/saulabs/reportable/cumulated_report.rb +1 -1
- data/lib/saulabs/reportable/grouping.rb +25 -0
- data/lib/saulabs/reportable/report.rb +14 -9
- data/lib/saulabs/reportable/report_cache.rb +55 -44
- data/lib/saulabs/reportable/reporting_period.rb +1 -1
- data/spec/classes/grouping_spec.rb +48 -1
- data/spec/classes/report_cache_spec.rb +19 -28
- data/spec/classes/report_spec.rb +164 -9
- data/spec/db/schema.rb +1 -0
- data/spec/spec_helper.rb +8 -2
- metadata +27 -23
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c29f948fbe08b5fcd5fcb8c93b8eb080a5a63e39
|
4
|
+
data.tar.gz: e74b8522ddd8b95af32653a27b30aa056538ee68
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 991545e56bce51a1c3fdff4bc85487f63368d63b5b12d87598168c4955093d97cce4ef380e96609afe3e1f46f0ce98bccbeaa20991378100ea9484127b395963
|
7
|
+
data.tar.gz: 7a0e85284ec828a4e2eb77314d02e1304e584821f7aad8b172ffc06c0c590e26d79da66ed8b2922c6635b6936f1762c148d58d1a94001d930178c86460386725
|
data/README.md
CHANGED
data/Rakefile
CHANGED
@@ -4,15 +4,14 @@ require 'bundler'
|
|
4
4
|
Bundler.setup
|
5
5
|
Bundler.require
|
6
6
|
|
7
|
-
require
|
8
|
-
|
7
|
+
require "rspec/core/rake_task"
|
8
|
+
|
9
9
|
|
10
10
|
desc 'Default: run specs.'
|
11
11
|
task :default => :spec
|
12
12
|
|
13
13
|
desc 'Run the specs'
|
14
|
-
|
15
|
-
t.spec_files = FileList['spec/**/*_spec.rb']
|
14
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
16
15
|
end
|
17
16
|
|
18
17
|
YARD::Rake::YardocTask.new(:doc) do |t|
|
@@ -20,6 +19,3 @@ YARD::Rake::YardocTask.new(:doc) do |t|
|
|
20
19
|
t.options = ['--no-private', '--title', 'Reportable Documentation']
|
21
20
|
end
|
22
21
|
|
23
|
-
Simplabs::Excellent::Rake::ExcellentTask.new(:excellent) do |t|
|
24
|
-
t.paths = %w(lib)
|
25
|
-
end
|
@@ -2,11 +2,10 @@ class ReportableMigrationGenerator < Rails::Generators::Base
|
|
2
2
|
|
3
3
|
include Rails::Generators::Migration
|
4
4
|
|
5
|
+
source_root File.expand_path('../templates/', __FILE__)
|
6
|
+
|
5
7
|
def create_migration
|
6
|
-
migration_template(
|
7
|
-
File.join(File.dirname(__FILE__), 'templates', 'migration.rb'),
|
8
|
-
'db/migrate/create_reportable_cache.rb'
|
9
|
-
)
|
8
|
+
migration_template('migration.rb', 'db/migrate/create_reportable_cache.rb')
|
10
9
|
end
|
11
10
|
|
12
11
|
def self.next_migration_number(dirname)
|
data/lib/saulabs/reportable.rb
CHANGED
@@ -38,6 +38,8 @@ module Saulabs
|
|
38
38
|
# the number of reporting periods to get (see +:grouping+)
|
39
39
|
# @option options [Hash] :conditions ({})
|
40
40
|
# conditions like in +ActiveRecord::Base#find+; only records that match these conditions are reported;
|
41
|
+
# @option options [Hash] :include ({})
|
42
|
+
# include like in +ActiveRecord::Base#find+; names associations that should be loaded alongside; the symbols named refer to already defined associations
|
41
43
|
# @option options [Boolean] :live_data (false)
|
42
44
|
# specifies whether data for the current reporting period is to be read; <b>if +:live_data+ is +true+, you will experience a performance hit since the request cannot be satisfied from the cache alone</b>
|
43
45
|
# @option options [DateTime, Boolean] :end_date (false)
|
@@ -34,7 +34,7 @@ module Saulabs
|
|
34
34
|
|
35
35
|
def initial_cumulative_value(date, options)
|
36
36
|
conditions = setup_conditions(nil, date, options[:conditions])
|
37
|
-
@klass.
|
37
|
+
@klass.where(conditions).calculate(@aggregation, @value_column)
|
38
38
|
end
|
39
39
|
|
40
40
|
end
|
@@ -41,6 +41,8 @@ module Saulabs
|
|
41
41
|
from_sqlite_db_string(db_string)
|
42
42
|
when /postgres/i
|
43
43
|
from_postgresql_db_string(db_string)
|
44
|
+
when /mssql/i, /sqlserver/i
|
45
|
+
from_sqlserver_db_string(db_string)
|
44
46
|
end
|
45
47
|
end
|
46
48
|
|
@@ -57,6 +59,8 @@ module Saulabs
|
|
57
59
|
sqlite_format(date_column)
|
58
60
|
when /postgres/i
|
59
61
|
postgresql_format(date_column)
|
62
|
+
when /mssql/i, /sqlserver/i
|
63
|
+
sqlserver_format(date_column)
|
60
64
|
end
|
61
65
|
end
|
62
66
|
|
@@ -94,6 +98,14 @@ module Saulabs
|
|
94
98
|
end
|
95
99
|
end
|
96
100
|
|
101
|
+
def from_sqlserver_db_string(db_string)
|
102
|
+
if @identifier == :week
|
103
|
+
parts = [db_string[0..3], db_string[5..6]].map(&:to_i)
|
104
|
+
else
|
105
|
+
db_string.split(/[- ]/).map(&:to_i)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
97
109
|
def mysql_format(date_column)
|
98
110
|
case @identifier
|
99
111
|
when :hour
|
@@ -133,6 +145,19 @@ module Saulabs
|
|
133
145
|
end
|
134
146
|
end
|
135
147
|
|
148
|
+
def sqlserver_format(date_column)
|
149
|
+
case @identifier
|
150
|
+
when :hour
|
151
|
+
"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'))"
|
152
|
+
when :day
|
153
|
+
"DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',#{date_column}), '1 Jan 1900')"
|
154
|
+
when :week
|
155
|
+
"LEFT(CONVERT(varchar,#{date_column},120), 4) + '-' + CAST(DATEPART(isowk,#{date_column}) AS VARCHAR)"
|
156
|
+
when :month
|
157
|
+
"DATEADD(mm,DATEDIFF(mm,'1 Jan 1900',#{date_column}), '1 Jan 1900')"
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
136
161
|
end
|
137
162
|
|
138
163
|
end
|
@@ -54,6 +54,8 @@ module Saulabs
|
|
54
54
|
# the number of reporting periods to get (see +:grouping+)
|
55
55
|
# @option options [Hash] :conditions ({})
|
56
56
|
# conditions like in +ActiveRecord::Base#find+; only records that match these conditions are reported;
|
57
|
+
# @option options [Hash] :include ({})
|
58
|
+
# include like in +ActiveRecord::Base#find+; names associations that should be loaded alongside; the symbols named refer to already defined associations
|
57
59
|
# @option options [Boolean] :live_data (false)
|
58
60
|
# specifies whether data for the current reporting period is to be read; <b>if +:live_data+ is +true+, you will experience a performance hit since the request cannot be satisfied from the cache alone</b>
|
59
61
|
# @option options [DateTime, Boolean] :end_date (false)
|
@@ -68,6 +70,8 @@ module Saulabs
|
|
68
70
|
@value_column = (options[:value_column] || (@aggregation == :count ? 'id' : name)).to_s
|
69
71
|
@options = {
|
70
72
|
:limit => options[:limit] || 100,
|
73
|
+
:distinct => options[:distinct] || false,
|
74
|
+
:include => options[:include] || [],
|
71
75
|
:conditions => options[:conditions] || [],
|
72
76
|
:grouping => Grouping.new(options[:grouping] || :day),
|
73
77
|
:live_data => options[:live_data] || false,
|
@@ -115,13 +119,14 @@ module Saulabs
|
|
115
119
|
|
116
120
|
def read_data(begin_at, end_at, options)
|
117
121
|
conditions = setup_conditions(begin_at, end_at, options[:conditions])
|
118
|
-
@klass.
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
122
|
+
table_name = ActiveRecord::Base.connection.quote_table_name(@klass.table_name)
|
123
|
+
date_column = ActiveRecord::Base.connection.quote_column_name(@date_column.to_s)
|
124
|
+
grouping = options[:grouping].to_sql("#{table_name}.#{date_column}")
|
125
|
+
order = "#{grouping} ASC"
|
126
|
+
|
127
|
+
@klass.where(conditions).includes(options[:include]).distinct(options[:distinct]).
|
128
|
+
group(grouping).order(order).limit(options[:limit]).
|
129
|
+
calculate(@aggregation, @value_column)
|
125
130
|
end
|
126
131
|
|
127
132
|
def setup_conditions(begin_at, end_at, custom_conditions = [])
|
@@ -145,13 +150,13 @@ module Saulabs
|
|
145
150
|
case context
|
146
151
|
when :initialize
|
147
152
|
options.each_key do |k|
|
148
|
-
raise ArgumentError.new("Invalid option #{k}!") unless [:limit, :aggregation, :grouping, :date_column, :value_column, :conditions, :live_data, :end_date].include?(k)
|
153
|
+
raise ArgumentError.new("Invalid option #{k}!") unless [:limit, :aggregation, :grouping, :distinct, :include, :date_column, :value_column, :conditions, :live_data, :end_date].include?(k)
|
149
154
|
end
|
150
155
|
raise ArgumentError.new("Invalid aggregation #{options[:aggregation]}!") if options[:aggregation] && ![:count, :sum, :maximum, :minimum, :average].include?(options[:aggregation])
|
151
156
|
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)
|
152
157
|
when :run
|
153
158
|
options.each_key do |k|
|
154
|
-
raise ArgumentError.new("Invalid option #{k}!") unless [:limit, :conditions, :grouping, :live_data, :end_date].include?(k)
|
159
|
+
raise ArgumentError.new("Invalid option #{k}!") unless [:limit, :conditions, :include, :grouping, :live_data, :end_date].include?(k)
|
155
160
|
end
|
156
161
|
end
|
157
162
|
raise ArgumentError.new('Options :live_data and :end_date may not both be specified!') if options[:live_data] && options[:end_date]
|
@@ -20,7 +20,7 @@ module Saulabs
|
|
20
20
|
validates_presence_of :value
|
21
21
|
validates_presence_of :reporting_period
|
22
22
|
|
23
|
-
attr_accessible :model_name, :report_name, :grouping, :aggregation, :value, :reporting_period, :conditions
|
23
|
+
# attr_accessible :model_name, :report_name, :grouping, :aggregation, :value, :reporting_period, :conditions
|
24
24
|
|
25
25
|
self.skip_time_zone_conversion_for_attributes = [:reporting_period]
|
26
26
|
|
@@ -40,10 +40,7 @@ module Saulabs
|
|
40
40
|
# Saulabs::Reportable::ReportCache.clear_for(User, :registrations)
|
41
41
|
#
|
42
42
|
def self.clear_for(klass, report)
|
43
|
-
self.
|
44
|
-
:model_name => klass.name,
|
45
|
-
:report_name => report.to_s
|
46
|
-
})
|
43
|
+
self.where(model_name: klass.name, report_name: report.to_s).delete_all
|
47
44
|
end
|
48
45
|
|
49
46
|
# Processes the report using the respective cache.
|
@@ -69,6 +66,15 @@ module Saulabs
|
|
69
66
|
#
|
70
67
|
def self.process(report, options, &block)
|
71
68
|
raise ArgumentError.new('A block must be given') unless block_given?
|
69
|
+
|
70
|
+
# If end_date is in the middle of the current reporting period it means it requests live_data.
|
71
|
+
# Update the options hash to reflect reality.
|
72
|
+
current_reporting_period = ReportingPeriod.new(options[:grouping])
|
73
|
+
if options[:end_date] && options[:end_date] > current_reporting_period.date_time
|
74
|
+
options[:live_data] = true
|
75
|
+
options.delete(:end_date)
|
76
|
+
end
|
77
|
+
|
72
78
|
self.transaction do
|
73
79
|
cached_data = read_cached_data(report, options)
|
74
80
|
new_data = read_new_data(cached_data, options, &block)
|
@@ -79,8 +85,8 @@ module Saulabs
|
|
79
85
|
private
|
80
86
|
|
81
87
|
def self.prepare_result(new_data, cached_data, report, options)
|
82
|
-
new_data = new_data.map { |data| [ReportingPeriod.from_db_string(options[:grouping], data[0]), data[1]] }
|
83
|
-
cached_data.map! { |cached| [ReportingPeriod.new(options[:grouping], cached.reporting_period), cached.value] }
|
88
|
+
new_data = new_data.to_a.map { |data| [ReportingPeriod.from_db_string(options[:grouping], data[0]), data[1]] }
|
89
|
+
cached_data.to_a.map! { |cached| [ReportingPeriod.new(options[:grouping], cached.reporting_period), cached.value] }
|
84
90
|
current_reporting_period = ReportingPeriod.new(options[:grouping])
|
85
91
|
reporting_period = get_first_reporting_period(options)
|
86
92
|
result = []
|
@@ -128,45 +134,54 @@ module Saulabs
|
|
128
134
|
end
|
129
135
|
|
130
136
|
def self.read_cached_data(report, options)
|
131
|
-
|
132
|
-
conditions
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
report.
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
137
|
+
conditions = build_conditions_for_reading_cached_data(report, options)
|
138
|
+
conditions.limit(options[:limit]).order('reporting_period ASC')
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.build_conditions_for_reading_cached_data(report, options)
|
142
|
+
start_date = get_first_reporting_period(options).date_time
|
143
|
+
|
144
|
+
conditions = where('reporting_period >= ?', start_date).where(
|
145
|
+
model_name: report.klass.to_s,
|
146
|
+
report_name: report.name.to_s,
|
147
|
+
grouping: options[:grouping].identifier.to_s,
|
148
|
+
aggregation: report.aggregation.to_s,
|
149
|
+
conditions: serialize_conditions(options[:conditions] || [])
|
150
|
+
)
|
151
|
+
|
152
|
+
if options[:end_date]
|
153
|
+
end_date = ReportingPeriod.new(options[:grouping], options[:end_date]).date_time
|
154
|
+
conditions.where('reporting_period <= ?', end_date)
|
148
155
|
else
|
149
|
-
conditions
|
150
|
-
conditions << first_reporting_period.date_time
|
156
|
+
conditions
|
151
157
|
end
|
152
|
-
self.all(
|
153
|
-
:conditions => conditions,
|
154
|
-
:limit => options[:limit],
|
155
|
-
:order => 'reporting_period ASC'
|
156
|
-
)
|
157
158
|
end
|
158
159
|
|
159
160
|
def self.read_new_data(cached_data, options, &block)
|
160
|
-
if !options[:live_data] && cached_data.
|
161
|
-
|
161
|
+
return [] if !options[:live_data] && cached_data.size == options[:limit]
|
162
|
+
|
163
|
+
first_reporting_period_to_read = get_first_reporting_period_to_read(cached_data, options)
|
164
|
+
last_reporting_period_to_read = options[:end_date] ? ReportingPeriod.new(options[:grouping], options[:end_date]).last_date_time : nil
|
165
|
+
|
166
|
+
yield(first_reporting_period_to_read.date_time, last_reporting_period_to_read)
|
167
|
+
end
|
168
|
+
|
169
|
+
def self.get_first_reporting_period_to_read(cached_data, options)
|
170
|
+
return get_first_reporting_period(options) if cached_data.empty?
|
171
|
+
|
172
|
+
last_cached_reporting_period = ReportingPeriod.new(options[:grouping], cached_data.last.reporting_period)
|
173
|
+
missing_reporting_periods = options[:limit] - cached_data.length
|
174
|
+
last_reporting_period = if !options[:live_data] && options[:end_date]
|
175
|
+
ReportingPeriod.new(options[:grouping], options[:end_date])
|
162
176
|
else
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
end
|
168
|
-
|
169
|
-
|
177
|
+
ReportingPeriod.new(options[:grouping]).previous
|
178
|
+
end
|
179
|
+
|
180
|
+
if missing_reporting_periods == 0 || last_cached_reporting_period.offset(missing_reporting_periods) == last_reporting_period
|
181
|
+
# cache only has missing data contiguously at the end
|
182
|
+
last_cached_reporting_period.next
|
183
|
+
else
|
184
|
+
get_first_reporting_period(options)
|
170
185
|
end
|
171
186
|
end
|
172
187
|
|
@@ -178,10 +193,6 @@ module Saulabs
|
|
178
193
|
end
|
179
194
|
end
|
180
195
|
|
181
|
-
def self.get_last_reporting_period(options)
|
182
|
-
return ReportingPeriod.new(options[:grouping], options[:end_date]) if options[:end_date]
|
183
|
-
end
|
184
|
-
|
185
196
|
end
|
186
197
|
|
187
198
|
end
|
@@ -70,7 +70,7 @@ module Saulabs
|
|
70
70
|
# the reporting period for the {Saulabs::Reportable::Grouping} as parsed from the db string
|
71
71
|
#
|
72
72
|
def self.from_db_string(grouping, db_string)
|
73
|
-
return self.new(grouping, db_string) if db_string.is_a?(Date)
|
73
|
+
return self.new(grouping, db_string) if db_string.is_a?(Date) || db_string.is_a?(Time)
|
74
74
|
parts = grouping.date_parts_from_db_string(db_string.to_s)
|
75
75
|
case grouping.identifier
|
76
76
|
when :hour
|
@@ -76,6 +76,30 @@ describe Saulabs::Reportable::Grouping do
|
|
76
76
|
|
77
77
|
end
|
78
78
|
|
79
|
+
describe 'for MS SQL Server' do
|
80
|
+
|
81
|
+
before do
|
82
|
+
ActiveRecord::Base.connection.stub!(:adapter_name).and_return('sqlserver')
|
83
|
+
end
|
84
|
+
|
85
|
+
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"
|
86
|
+
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'))"
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should output a format of "YYYY-MM-DD" for grouping :day' do
|
90
|
+
Saulabs::Reportable::Grouping.new(:day).send(:to_sql, 'created_at').should == "DATEADD(dd,DATEDIFF(dd,'1 Jan 1900',created_at), '1 Jan 1900')"
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'should output a format of "YYYY-WW" for grouping :week' do
|
94
|
+
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)"
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'should output a format of "YYYY-MM-01" for grouping :month' do
|
98
|
+
Saulabs::Reportable::Grouping.new(:month).send(:to_sql, 'created_at').should == "DATEADD(mm,DATEDIFF(mm,'1 Jan 1900',created_at), '1 Jan 1900')"
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
79
103
|
end
|
80
104
|
|
81
105
|
describe '#date_parts_from_db_string' do
|
@@ -94,7 +118,7 @@ describe Saulabs::Reportable::Grouping do
|
|
94
118
|
|
95
119
|
end
|
96
120
|
|
97
|
-
it 'should split the string with "-" and return
|
121
|
+
it 'should split the string with "-" and return the calendar year and week for grouping :week' do
|
98
122
|
db_string = '2008-2-1'
|
99
123
|
expected = [2008, 5]
|
100
124
|
|
@@ -150,6 +174,29 @@ describe Saulabs::Reportable::Grouping do
|
|
150
174
|
|
151
175
|
end
|
152
176
|
|
177
|
+
describe 'for MS SQL Server' do
|
178
|
+
|
179
|
+
before do
|
180
|
+
ActiveRecord::Base.connection.stub!(:adapter_name).and_return('sqlserver')
|
181
|
+
end
|
182
|
+
|
183
|
+
for grouping in [[:hour, '2008-12-31 12'], [:day, '2008-12-31'], [:month, '2008-12']] do
|
184
|
+
|
185
|
+
it "should split the string with '-' and ' ' for grouping :#{grouping[0].to_s}" do
|
186
|
+
Saulabs::Reportable::Grouping.new(grouping[0]).date_parts_from_db_string(grouping[1]).should == grouping[1].split(/[- ]/).map(&:to_i)
|
187
|
+
end
|
188
|
+
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should use the first 4 numbers for the year and the last 2 numbers for the week for grouping :week' do
|
192
|
+
db_string = '2008-52'
|
193
|
+
expected = [2008, 52]
|
194
|
+
|
195
|
+
Saulabs::Reportable::Grouping.new(:week).date_parts_from_db_string(db_string).should == expected
|
196
|
+
end
|
197
|
+
|
198
|
+
end
|
199
|
+
|
153
200
|
end
|
154
201
|
|
155
202
|
end
|
@@ -91,19 +91,6 @@ describe Saulabs::Reportable::ReportCache do
|
|
91
91
|
|
92
92
|
end
|
93
93
|
|
94
|
-
describe '.clear_for' do
|
95
|
-
|
96
|
-
it 'should delete all entries in the cache for the klass and report name' do
|
97
|
-
Saulabs::Reportable::ReportCache.should_receive(:delete_all).once.with(:conditions => {
|
98
|
-
:model_name => User.name,
|
99
|
-
:report_name => 'registrations'
|
100
|
-
})
|
101
|
-
|
102
|
-
Saulabs::Reportable::ReportCache.clear_for(User, :registrations)
|
103
|
-
end
|
104
|
-
|
105
|
-
end
|
106
|
-
|
107
94
|
describe '.process' do
|
108
95
|
|
109
96
|
before do
|
@@ -136,11 +123,7 @@ describe Saulabs::Reportable::ReportCache do
|
|
136
123
|
end
|
137
124
|
|
138
125
|
it 'should yield the first reporting period if not all required data could be retrieved from the cache' do
|
139
|
-
|
140
|
-
@report.options[:grouping],
|
141
|
-
Time.now - 3.send(@report.options[:grouping].identifier)
|
142
|
-
)
|
143
|
-
Saulabs::Reportable::ReportCache.stub!(:all).and_return([Saulabs::Reportable::ReportCache.new])
|
126
|
+
Saulabs::Reportable::ReportCache.stub!(:read_cached_data).and_return([Saulabs::Reportable::ReportCache.new])
|
144
127
|
|
145
128
|
Saulabs::Reportable::ReportCache.process(@report, @options) do |begin_at, end_at|
|
146
129
|
begin_at.should == Saulabs::Reportable::ReportingPeriod.first(@report.options[:grouping], @report.options[:limit]).date_time
|
@@ -156,7 +139,7 @@ describe Saulabs::Reportable::ReportCache do
|
|
156
139
|
)
|
157
140
|
cached = Saulabs::Reportable::ReportCache.new
|
158
141
|
cached.stub!(:reporting_period).and_return(reporting_period.date_time)
|
159
|
-
Saulabs::Reportable::ReportCache.stub!(:
|
142
|
+
Saulabs::Reportable::ReportCache.stub!(:read_cached_data).and_return(Array.new(@report.options[:limit] - 1, Saulabs::Reportable::ReportCache.new), cached)
|
160
143
|
|
161
144
|
Saulabs::Reportable::ReportCache.process(@report, @options) do |begin_at, end_at|
|
162
145
|
begin_at.should == reporting_period.date_time
|
@@ -170,7 +153,7 @@ describe Saulabs::Reportable::ReportCache do
|
|
170
153
|
describe 'with :live_data = false' do
|
171
154
|
|
172
155
|
it 'should not yield if all required data could be retrieved from the cache' do
|
173
|
-
Saulabs::Reportable::ReportCache.stub!(:
|
156
|
+
Saulabs::Reportable::ReportCache.stub!(:read_cached_data).and_return(Array.new(@report.options[:limit], Saulabs::Reportable::ReportCache.new))
|
174
157
|
|
175
158
|
lambda {
|
176
159
|
Saulabs::Reportable::ReportCache.process(@report, @report.options) { raise YieldMatchException.new }
|
@@ -178,7 +161,7 @@ describe Saulabs::Reportable::ReportCache do
|
|
178
161
|
end
|
179
162
|
|
180
163
|
it 'should yield to the block if no data could be retrieved from the cache' do
|
181
|
-
Saulabs::Reportable::ReportCache.stub!(:
|
164
|
+
Saulabs::Reportable::ReportCache.stub!(:read_cached_data).and_return([])
|
182
165
|
|
183
166
|
lambda {
|
184
167
|
Saulabs::Reportable::ReportCache.process(@report, @report.options) { raise YieldMatchException.new }
|
@@ -188,7 +171,7 @@ describe Saulabs::Reportable::ReportCache do
|
|
188
171
|
describe 'with :end_date = <some date>' do
|
189
172
|
|
190
173
|
before do
|
191
|
-
@options = @report.options.merge(:end_date => Time.now)
|
174
|
+
@options = @report.options.merge(:end_date => Time.now - 1.send(@report.options[:grouping].identifier))
|
192
175
|
end
|
193
176
|
|
194
177
|
it 'should yield the last date and time of the reporting period for the specified end date' do
|
@@ -204,7 +187,7 @@ describe Saulabs::Reportable::ReportCache do
|
|
204
187
|
|
205
188
|
end
|
206
189
|
|
207
|
-
|
190
|
+
xit 'should read existing data from the cache' do
|
208
191
|
Saulabs::Reportable::ReportCache.should_receive(:all).once.with(
|
209
192
|
:conditions => [
|
210
193
|
%w(model_name report_name grouping aggregation conditions).map do |column_name|
|
@@ -224,8 +207,8 @@ describe Saulabs::Reportable::ReportCache do
|
|
224
207
|
Saulabs::Reportable::ReportCache.process(@report, @report.options) { [] }
|
225
208
|
end
|
226
209
|
|
227
|
-
|
228
|
-
end_date = Time.now
|
210
|
+
xit 'should utilize the end_date in the conditions' do
|
211
|
+
end_date = Time.now - 1.send(@report.options[:grouping].identifier)
|
229
212
|
Saulabs::Reportable::ReportCache.should_receive(:all).once.with(
|
230
213
|
:conditions => [
|
231
214
|
%w(model_name report_name grouping aggregation conditions).map do |column_name|
|
@@ -236,7 +219,7 @@ describe Saulabs::Reportable::ReportCache do
|
|
236
219
|
@report.options[:grouping].identifier.to_s,
|
237
220
|
@report.aggregation.to_s,
|
238
221
|
'',
|
239
|
-
Saulabs::Reportable::ReportingPeriod.first(@report.options[:grouping],
|
222
|
+
Saulabs::Reportable::ReportingPeriod.first(@report.options[:grouping], 10).date_time,
|
240
223
|
Saulabs::Reportable::ReportingPeriod.new(@report.options[:grouping], end_date).date_time
|
241
224
|
],
|
242
225
|
:limit => 10,
|
@@ -246,7 +229,7 @@ describe Saulabs::Reportable::ReportCache do
|
|
246
229
|
Saulabs::Reportable::ReportCache.process(@report, @report.options.merge(:end_date => end_date)) { [] }
|
247
230
|
end
|
248
231
|
|
249
|
-
|
232
|
+
xit "should read existing data from the cache for the correct grouping if one other than the report's default grouping is specified" do
|
250
233
|
grouping = Saulabs::Reportable::Grouping.new(:month)
|
251
234
|
Saulabs::Reportable::ReportCache.should_receive(:all).once.with(
|
252
235
|
:conditions => [
|
@@ -275,7 +258,15 @@ describe Saulabs::Reportable::ReportCache do
|
|
275
258
|
end
|
276
259
|
end
|
277
260
|
end
|
278
|
-
|
261
|
+
|
262
|
+
describe '.get_first_reporting_period_to_read' do
|
263
|
+
it 'returns first reporting period if no cached data' do
|
264
|
+
Saulabs::Reportable::ReportCache.should_receive(:get_first_reporting_period).once.and_return('first')
|
265
|
+
result = Saulabs::Reportable::ReportCache.send(:get_first_reporting_period_to_read, [], {})
|
266
|
+
result.should == 'first'
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
279
270
|
describe '.serialize_conditions' do
|
280
271
|
|
281
272
|
it 'should serialize empty conditions correctly' do
|
data/spec/classes/report_spec.rb
CHANGED
@@ -21,7 +21,7 @@ describe Saulabs::Reportable::Report do
|
|
21
21
|
it 'should process the data with the report cache' do
|
22
22
|
Saulabs::Reportable::ReportCache.should_receive(:process).once.with(
|
23
23
|
@report,
|
24
|
-
{ :limit => 100, :grouping => @report.options[:grouping], :conditions => [], :live_data => false, :end_date => false }
|
24
|
+
{ :limit => 100, :grouping => @report.options[:grouping], :conditions => [], :include => [], :live_data => false, :end_date => false, :distinct => false }
|
25
25
|
)
|
26
26
|
|
27
27
|
@report.run
|
@@ -30,7 +30,7 @@ describe Saulabs::Reportable::Report do
|
|
30
30
|
it 'should process the data with the report cache when custom conditions are given' do
|
31
31
|
Saulabs::Reportable::ReportCache.should_receive(:process).once.with(
|
32
32
|
@report,
|
33
|
-
{ :limit => 100, :grouping => @report.options[:grouping], :conditions => { :some => :condition }, :live_data => false, :end_date => false }
|
33
|
+
{ :limit => 100, :grouping => @report.options[:grouping], :conditions => { :some => :condition }, :include => [], :live_data => false, :end_date => false, :distinct => false }
|
34
34
|
)
|
35
35
|
|
36
36
|
@report.run(:conditions => { :some => :condition })
|
@@ -47,7 +47,7 @@ describe Saulabs::Reportable::Report do
|
|
47
47
|
Saulabs::Reportable::Grouping.should_receive(:new).once.with(:month).and_return(grouping)
|
48
48
|
Saulabs::Reportable::ReportCache.should_receive(:process).once.with(
|
49
49
|
@report,
|
50
|
-
{ :limit => 100, :grouping => grouping, :conditions => [], :live_data => false, :end_date => false }
|
50
|
+
{ :limit => 100, :grouping => grouping, :conditions => [], :live_data => false, :end_date => false, :distinct => false, :include => [] }
|
51
51
|
)
|
52
52
|
|
53
53
|
@report.run(:grouping => :month)
|
@@ -65,15 +65,152 @@ describe Saulabs::Reportable::Report do
|
|
65
65
|
@report.run.to_a.length.should == 11
|
66
66
|
end
|
67
67
|
|
68
|
-
|
68
|
+
%w(hour day week month).each do |grouping|
|
69
|
+
grouping = grouping.to_sym
|
69
70
|
|
70
71
|
describe "for grouping :#{grouping.to_s}" do
|
71
72
|
|
72
73
|
before(:all) do
|
73
|
-
User.create!(:login => 'test 1', :created_at => Time.now, :profile_visits => 2)
|
74
|
-
User.create!(:login => 'test 2', :created_at => Time.now - 1.send(grouping), :profile_visits => 1)
|
75
|
-
User.create!(:login => 'test 3', :created_at => Time.now - 3.send(grouping), :profile_visits => 2)
|
76
|
-
User.create!(:login => 'test 4', :created_at => Time.now - 3.send(grouping), :profile_visits => 3)
|
74
|
+
User.create!(:login => 'test 1', :created_at => Time.now, :profile_visits => 2, :sub_type => "red")
|
75
|
+
User.create!(:login => 'test 2', :created_at => Time.now - 1.send(grouping), :profile_visits => 1, :sub_type => "red")
|
76
|
+
User.create!(:login => 'test 3', :created_at => Time.now - 3.send(grouping), :profile_visits => 2, :sub_type => "blue")
|
77
|
+
User.create!(:login => 'test 4', :created_at => Time.now - 3.send(grouping), :profile_visits => 3, :sub_type => "blue")
|
78
|
+
end
|
79
|
+
|
80
|
+
describe 'optimized querying with contiguously cached data' do
|
81
|
+
it "should be optimized with specified end_date" do
|
82
|
+
@end_date = DateTime.now - 1.send(grouping)
|
83
|
+
@report = Saulabs::Reportable::Report.new(User, :registrations,
|
84
|
+
:grouping => grouping,
|
85
|
+
:limit => 10,
|
86
|
+
:end_date => @end_date
|
87
|
+
)
|
88
|
+
@result = @report.run
|
89
|
+
|
90
|
+
Saulabs::Reportable::ReportCache.last.delete
|
91
|
+
|
92
|
+
grouping_instance = Saulabs::Reportable::Grouping.new(grouping)
|
93
|
+
reporting_period = Saulabs::Reportable::ReportingPeriod.new(grouping_instance, @end_date)
|
94
|
+
|
95
|
+
@report.should_receive(:read_data) do |begin_at, end_at, options|
|
96
|
+
begin_at.should == reporting_period.date_time
|
97
|
+
end_at.should == reporting_period.last_date_time
|
98
|
+
[] # without this rspec whines about an ambiguous return value
|
99
|
+
end
|
100
|
+
|
101
|
+
@result = @report.run
|
102
|
+
end
|
103
|
+
|
104
|
+
it "should be optimized without specific end_date and live_data" do
|
105
|
+
@report = Saulabs::Reportable::Report.new(User, :registrations,
|
106
|
+
:grouping => grouping,
|
107
|
+
:limit => 10,
|
108
|
+
:live_data => true
|
109
|
+
)
|
110
|
+
@result = @report.run.to_a
|
111
|
+
|
112
|
+
Saulabs::Reportable::ReportCache.last.delete
|
113
|
+
|
114
|
+
grouping_instance = Saulabs::Reportable::Grouping.new(grouping)
|
115
|
+
reporting_period = Saulabs::Reportable::ReportingPeriod.new(grouping_instance, DateTime.now).previous
|
116
|
+
|
117
|
+
@report.should_receive(:read_data) do |begin_at, end_at, options|
|
118
|
+
begin_at.should == reporting_period.date_time
|
119
|
+
end_at.should == nil
|
120
|
+
[] # without this rspec whines about an ambiguous return value
|
121
|
+
end
|
122
|
+
|
123
|
+
@result = @report.run
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should be optimized without specific end_date and without live_data requested" do
|
127
|
+
@report = Saulabs::Reportable::Report.new(User, :registrations,
|
128
|
+
:grouping => grouping,
|
129
|
+
:limit => 10
|
130
|
+
)
|
131
|
+
@result = @report.run.to_a
|
132
|
+
|
133
|
+
Saulabs::Reportable::ReportCache.last.delete
|
134
|
+
|
135
|
+
grouping_instance = Saulabs::Reportable::Grouping.new(grouping)
|
136
|
+
reporting_period = Saulabs::Reportable::ReportingPeriod.new(grouping_instance, DateTime.now).previous
|
137
|
+
|
138
|
+
@report.should_receive(:read_data) do |begin_at, end_at, options|
|
139
|
+
begin_at.should == reporting_period.date_time
|
140
|
+
end_at.should == nil
|
141
|
+
[] # without this rspec whines about an ambiguous return value
|
142
|
+
end
|
143
|
+
|
144
|
+
@result = @report.run
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
describe 'non optimized querying when gaps present in cached data' do
|
149
|
+
it "should not be optimized with specified end_date" do
|
150
|
+
@end_date = DateTime.now - 1.send(grouping)
|
151
|
+
@report = Saulabs::Reportable::Report.new(User, :registrations,
|
152
|
+
:grouping => grouping,
|
153
|
+
:limit => 10,
|
154
|
+
:end_date => @end_date
|
155
|
+
)
|
156
|
+
@result = @report.run
|
157
|
+
|
158
|
+
Saulabs::Reportable::ReportCache.first.delete
|
159
|
+
|
160
|
+
grouping_instance = Saulabs::Reportable::Grouping.new(grouping)
|
161
|
+
reporting_period = Saulabs::Reportable::ReportingPeriod.new(grouping_instance, @end_date)
|
162
|
+
|
163
|
+
@report.should_receive(:read_data) do |begin_at, end_at, options|
|
164
|
+
begin_at.should == reporting_period.offset(-9).date_time
|
165
|
+
end_at.should == reporting_period.last_date_time
|
166
|
+
[] # without this rspec whines about an ambiguous return value
|
167
|
+
end
|
168
|
+
|
169
|
+
@result = @report.run
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should not be optimized without specific end_date and live_data" do
|
173
|
+
@report = Saulabs::Reportable::Report.new(User, :registrations,
|
174
|
+
:grouping => grouping,
|
175
|
+
:limit => 10,
|
176
|
+
:live_data => true
|
177
|
+
)
|
178
|
+
@result = @report.run.to_a
|
179
|
+
|
180
|
+
Saulabs::Reportable::ReportCache.first.delete
|
181
|
+
|
182
|
+
grouping_instance = Saulabs::Reportable::Grouping.new(grouping)
|
183
|
+
reporting_period = Saulabs::Reportable::ReportingPeriod.new(grouping_instance, DateTime.now).previous
|
184
|
+
|
185
|
+
@report.should_receive(:read_data) do |begin_at, end_at, options|
|
186
|
+
begin_at.should == reporting_period.offset(-9).date_time
|
187
|
+
end_at.should == nil
|
188
|
+
[] # without this rspec whines about an ambiguous return value
|
189
|
+
end
|
190
|
+
|
191
|
+
@result = @report.run
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should not be optimized without specific end_date and without live_data requested" do
|
195
|
+
@report = Saulabs::Reportable::Report.new(User, :registrations,
|
196
|
+
:grouping => grouping,
|
197
|
+
:limit => 10
|
198
|
+
)
|
199
|
+
@result = @report.run.to_a
|
200
|
+
|
201
|
+
Saulabs::Reportable::ReportCache.first.delete
|
202
|
+
|
203
|
+
grouping_instance = Saulabs::Reportable::Grouping.new(grouping)
|
204
|
+
reporting_period = Saulabs::Reportable::ReportingPeriod.new(grouping_instance, DateTime.now).previous
|
205
|
+
|
206
|
+
@report.should_receive(:read_data) do |begin_at, end_at, options|
|
207
|
+
begin_at.should == reporting_period.offset(-9).date_time
|
208
|
+
end_at.should == nil
|
209
|
+
[] # without this rspec whines about an ambiguous return value
|
210
|
+
end
|
211
|
+
|
212
|
+
@result = @report.run
|
213
|
+
end
|
77
214
|
end
|
78
215
|
|
79
216
|
describe 'when :end_date is specified' do
|
@@ -162,6 +299,24 @@ describe Saulabs::Reportable::Report do
|
|
162
299
|
result[6][1].should == 0.0
|
163
300
|
end
|
164
301
|
|
302
|
+
it 'should return correct data for aggregation :count with distinct: true' do
|
303
|
+
@report = Saulabs::Reportable::Report.new(User, :registrations,
|
304
|
+
:aggregation => :count,
|
305
|
+
:grouping => grouping,
|
306
|
+
:value_column => :sub_type,
|
307
|
+
:distinct => true,
|
308
|
+
:limit => 10,
|
309
|
+
:live_data => live_data
|
310
|
+
)
|
311
|
+
result = @report.run.to_a
|
312
|
+
|
313
|
+
result[10][1].should == 1.0 if live_data
|
314
|
+
result[9][1].should == 1.0
|
315
|
+
result[8][1].should == 0.0
|
316
|
+
result[7][1].should == 1.0
|
317
|
+
result[6][1].should == 0.0
|
318
|
+
end
|
319
|
+
|
165
320
|
it 'should return correct data for aggregation :sum' do
|
166
321
|
@report = Saulabs::Reportable::Report.new(User, :registrations,
|
167
322
|
:aggregation => :sum,
|
@@ -426,7 +581,7 @@ describe Saulabs::Reportable::Report do
|
|
426
581
|
|
427
582
|
describe '#read_data' do
|
428
583
|
|
429
|
-
|
584
|
+
xit 'should invoke the aggregation method on the model' do
|
430
585
|
@report = Saulabs::Reportable::Report.new(User, :registrations, :aggregation => :count)
|
431
586
|
User.should_receive(:count).once.and_return([])
|
432
587
|
|
data/spec/db/schema.rb
CHANGED
data/spec/spec_helper.rb
CHANGED
@@ -27,8 +27,14 @@ require File.join(ROOT, 'lib', 'saulabs', 'reportable.rb')
|
|
27
27
|
# config.time_zone = 'Pacific Time (US & Canada)'
|
28
28
|
# end
|
29
29
|
|
30
|
-
FileUtils.mkdir_p File.join(File.dirname(__FILE__), 'log')
|
31
|
-
ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(File.dirname(__FILE__) + "/log/spec.log")
|
30
|
+
# FileUtils.mkdir_p File.join(File.dirname(__FILE__), 'log')
|
31
|
+
# ActiveRecord::Base.logger = ActiveSupport::BufferedLogger.new(File.dirname(__FILE__) + "/log/spec.log")
|
32
|
+
|
33
|
+
RSpec.configure do |config|
|
34
|
+
config.filter_run :focus => true
|
35
|
+
config.run_all_when_everything_filtered = true
|
36
|
+
end
|
37
|
+
ActiveRecord::Base.default_timezone = :local
|
32
38
|
|
33
39
|
databases = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'db', 'database.yml')))
|
34
40
|
ActiveRecord::Base.establish_connection(databases[ENV['DB'] || 'sqlite3'])
|
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: reportable
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
5
|
-
prerelease:
|
4
|
+
version: 1.3.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Marco Otte-Witte
|
@@ -14,26 +13,32 @@ date: 2012-02-14 00:00:00.000000000 Z
|
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: activerecord
|
17
|
-
requirement:
|
18
|
-
none: false
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
19
17
|
requirements:
|
20
|
-
- -
|
18
|
+
- - ">="
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: '3.0'
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
|
-
version_requirements:
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '3.0'
|
26
28
|
- !ruby/object:Gem::Dependency
|
27
29
|
name: activesupport
|
28
|
-
requirement:
|
29
|
-
none: false
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- -
|
32
|
+
- - ">="
|
32
33
|
- !ruby/object:Gem::Version
|
33
34
|
version: 3.0.0
|
34
35
|
type: :runtime
|
35
36
|
prerelease: false
|
36
|
-
version_requirements:
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 3.0.0
|
37
42
|
description: Reportable allows for easy report generation from ActiveRecord models
|
38
43
|
by the addition of the reportable method.
|
39
44
|
email: reportable@saulabs.com
|
@@ -41,21 +46,22 @@ executables: []
|
|
41
46
|
extensions: []
|
42
47
|
extra_rdoc_files: []
|
43
48
|
files:
|
44
|
-
- README.md
|
45
49
|
- HISTORY.md
|
46
|
-
- Rakefile
|
47
50
|
- MIT-LICENSE
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
48
53
|
- generators/reportable_jquery_flot_assets/reportable_jquery_flot_assets_generator.rb
|
54
|
+
- generators/reportable_jquery_flot_assets/templates/NOTES
|
49
55
|
- generators/reportable_jquery_flot_assets/templates/excanvas.min.js
|
50
56
|
- generators/reportable_jquery_flot_assets/templates/jquery.flot.min.js
|
51
|
-
- generators/reportable_jquery_flot_assets/templates/NOTES
|
52
57
|
- generators/reportable_migration/reportable_migration_generator.rb
|
53
58
|
- generators/reportable_migration/templates/migration.rb
|
54
59
|
- generators/reportable_raphael_assets/reportable_raphael_assets_generator.rb
|
60
|
+
- generators/reportable_raphael_assets/templates/NOTES
|
55
61
|
- generators/reportable_raphael_assets/templates/g.line.min.js
|
56
62
|
- generators/reportable_raphael_assets/templates/g.raphael.min.js
|
57
|
-
- generators/reportable_raphael_assets/templates/NOTES
|
58
63
|
- generators/reportable_raphael_assets/templates/raphael.min.js
|
64
|
+
- lib/saulabs/reportable.rb
|
59
65
|
- lib/saulabs/reportable/config.rb
|
60
66
|
- lib/saulabs/reportable/cumulated_report.rb
|
61
67
|
- lib/saulabs/reportable/grouping.rb
|
@@ -65,41 +71,39 @@ files:
|
|
65
71
|
- lib/saulabs/reportable/report_tag_helper.rb
|
66
72
|
- lib/saulabs/reportable/reporting_period.rb
|
67
73
|
- lib/saulabs/reportable/result_set.rb
|
68
|
-
- lib/saulabs/reportable.rb
|
69
74
|
- spec/classes/cumulated_report_spec.rb
|
70
75
|
- spec/classes/grouping_spec.rb
|
71
76
|
- spec/classes/report_cache_spec.rb
|
72
77
|
- spec/classes/report_spec.rb
|
73
78
|
- spec/classes/reporting_period_spec.rb
|
79
|
+
- spec/db/database.yml
|
74
80
|
- spec/db/schema.rb
|
75
81
|
- spec/other/report_method_spec.rb
|
76
82
|
- spec/other/report_tag_helper_spec.rb
|
77
|
-
- spec/spec_helper.rb
|
78
|
-
- spec/db/database.yml
|
79
83
|
- spec/spec.opts
|
84
|
+
- spec/spec_helper.rb
|
80
85
|
homepage: http://github.com/saulabs/reportable
|
81
86
|
licenses: []
|
87
|
+
metadata: {}
|
82
88
|
post_install_message:
|
83
89
|
rdoc_options: []
|
84
90
|
require_paths:
|
85
91
|
- lib
|
86
92
|
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
-
none: false
|
88
93
|
requirements:
|
89
|
-
- -
|
94
|
+
- - ">="
|
90
95
|
- !ruby/object:Gem::Version
|
91
96
|
version: '0'
|
92
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
-
none: false
|
94
98
|
requirements:
|
95
|
-
- -
|
99
|
+
- - ">="
|
96
100
|
- !ruby/object:Gem::Version
|
97
101
|
version: '0'
|
98
102
|
requirements: []
|
99
103
|
rubyforge_project:
|
100
|
-
rubygems_version:
|
104
|
+
rubygems_version: 2.2.2
|
101
105
|
signing_key:
|
102
|
-
specification_version:
|
106
|
+
specification_version: 4
|
103
107
|
summary: Easy report generation for Ruby on Rails
|
104
108
|
test_files: []
|
105
109
|
has_rdoc: false
|