reporter 0.0.1
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.
- data/Gemfile +4 -0
- data/MIT-LICENSE +21 -0
- data/README.markdown +310 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/lib/reporter/data_set.rb +53 -0
- data/lib/reporter/data_source/active_record_source.rb +136 -0
- data/lib/reporter/data_source/scoping.rb +153 -0
- data/lib/reporter/data_source.rb +30 -0
- data/lib/reporter/data_structure.rb +41 -0
- data/lib/reporter/field/average_field.rb +7 -0
- data/lib/reporter/field/base.rb +21 -0
- data/lib/reporter/field/calculation_field.rb +32 -0
- data/lib/reporter/field/count_field.rb +9 -0
- data/lib/reporter/field/field.rb +25 -0
- data/lib/reporter/field/formula_field.rb +24 -0
- data/lib/reporter/field/sum_field.rb +7 -0
- data/lib/reporter/formula.rb +371 -0
- data/lib/reporter/result_row.rb +37 -0
- data/lib/reporter/scope/base.rb +37 -0
- data/lib/reporter/scope/date_scope.rb +109 -0
- data/lib/reporter/scope/reference_scope.rb +154 -0
- data/lib/reporter/support/time_range.rb +62 -0
- data/lib/reporter/time_iterator.rb +85 -0
- data/lib/reporter/time_optimized_result_row.rb +39 -0
- data/lib/reporter/value.rb +36 -0
- metadata +139 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
module Reporter::Support::TimeRange
|
2
|
+
|
3
|
+
def time_range?
|
4
|
+
valid_types = [Date, DateTime, Time, ActiveSupport::TimeWithZone]
|
5
|
+
return false unless valid_types.include?(self.begin.class)
|
6
|
+
return false unless valid_types.include?(self.end.class)
|
7
|
+
true
|
8
|
+
end
|
9
|
+
|
10
|
+
def human_name
|
11
|
+
return inspect unless time_range?
|
12
|
+
b = test_begin_ends(self.begin)
|
13
|
+
e = test_begin_ends(self.end)
|
14
|
+
|
15
|
+
if self.begin.year == self.end.year
|
16
|
+
if b[:by] and e[:ey]
|
17
|
+
return self.begin.strftime("%Y")
|
18
|
+
elsif b[:bq] and e[:eq]
|
19
|
+
bq = get_quarter(self.begin)
|
20
|
+
eq = get_quarter(self.end)
|
21
|
+
if bq == eq
|
22
|
+
return I18n.t("time_range.quarter", :year => self.begin.year, :quarter => bq, :default => "q%{quarter} %{year}")
|
23
|
+
else
|
24
|
+
return I18n.t("time_range.multi_quarter", :year => self.begin.year,
|
25
|
+
:begin_quarter => bq, :end_quarter => eq, :default => "q%{begin_quarter} .. q%{end_quarter} %{year}")
|
26
|
+
end
|
27
|
+
elsif b[:bm] and e[:em]
|
28
|
+
if self.begin.month == self.end.month
|
29
|
+
return self.begin.strftime("%b '%y")
|
30
|
+
else
|
31
|
+
return "#{self.begin.strftime("%b")} .. #{self.end.strftime("%b '%y")}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
else # multi year
|
35
|
+
if b[:by] and e[:ey]
|
36
|
+
return "#{self.begin.strftime("'%y")} .. #{self.end.strftime("'%y")}"
|
37
|
+
else
|
38
|
+
return inspect
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def get_quarter(date)
|
46
|
+
qs = [3, 6, 9, 12]
|
47
|
+
q = qs.detect { |q| date.month <= q }
|
48
|
+
(qs.index q) + 1
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_begin_ends(date)
|
52
|
+
r = {}
|
53
|
+
["year", "quarter", "month", "week"].each do |element|
|
54
|
+
r["b#{element.first}".to_sym] = (date.send("at_beginning_of_#{element}".to_sym) == date)
|
55
|
+
r["e#{element.first}".to_sym] = (date.send("at_end_of_#{element}".to_sym) == date)
|
56
|
+
end
|
57
|
+
r
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
Range.send(:include, Reporter::Support::TimeRange)
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Reporter::TimeIterator
|
2
|
+
|
3
|
+
public
|
4
|
+
|
5
|
+
VALID_TIME_STEP_SIZES = :total, :year, :quarter, :month, :week, :day
|
6
|
+
|
7
|
+
def iterate_time axis, *steps, &block
|
8
|
+
options = steps.extract_options!
|
9
|
+
scope = data_source.scopes.get axis
|
10
|
+
raise "Scope is not of Date type" unless scope.is_a? Reporter::Scope::DateScope
|
11
|
+
steps.each do |step|
|
12
|
+
raise "invalid stepsize: #{step}. must be one of #{VALID_TIME_STEP_SIZES.inspect}" unless VALID_TIME_STEP_SIZES.include? step
|
13
|
+
end
|
14
|
+
date_tree = build_date_tree steps
|
15
|
+
iterate_date_tree scope, date_tree, scope.limit, &block
|
16
|
+
scope.change nil
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
DATE_PART_VALUES = {
|
22
|
+
:total => 6,
|
23
|
+
:year => 5,
|
24
|
+
:quarter => 4,
|
25
|
+
:month => 3,
|
26
|
+
:week => 2,
|
27
|
+
:day => 1
|
28
|
+
}
|
29
|
+
|
30
|
+
def build_date_tree date_parts
|
31
|
+
date_parts = date_parts.dup
|
32
|
+
return nil if date_parts.empty?
|
33
|
+
# [6, 4, 3, 5]
|
34
|
+
# 2010 - 2011, q1, 1,2,3, q2, 4,5,6, q3, 7,8,9, q4, 10,11,12, 2010, q1, 1,2,3, q2, 4,5,6, q3, 7,8,9, q4, 10,11,12, 2011
|
35
|
+
date_coded = date_parts.collect { |part| DATE_PART_VALUES[part] }
|
36
|
+
if date_coded.first == date_coded.max
|
37
|
+
parent = date_parts.shift
|
38
|
+
{:children_first => false, :name => parent, :children => build_date_tree(date_parts)}
|
39
|
+
elsif date_coded.last == date_coded.max
|
40
|
+
parent = date_parts.pop
|
41
|
+
{:children_first => true, :name => parent, :children => build_date_tree(date_parts)}
|
42
|
+
else
|
43
|
+
raise "invalid sequence: #{date_parts.inspect}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def iterate_date_tree scope, tree, time_frame, &block
|
48
|
+
iterate_time_periods time_frame, tree[:name] do |new_time_frame, optimization|
|
49
|
+
iterate_date_tree scope, tree[:children], time_frame, &block if tree[:children_first] and tree[:children]
|
50
|
+
scope.change new_time_frame
|
51
|
+
@time_iteration_row ||= Reporter::TimeOptimizedResultRow.new(self, nil, scope, time_frame)
|
52
|
+
@time_iteration_row.current_iteration = optimization
|
53
|
+
@time_iteration_row.scope = data_source.scopes.current_scope
|
54
|
+
yield @time_iteration_row
|
55
|
+
iterate_date_tree scope, tree[:children], time_frame, &block if !tree[:children_first] and tree[:children]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def iterate_time_periods period, block_type, &block
|
60
|
+
if block_type == :total
|
61
|
+
yield period
|
62
|
+
return
|
63
|
+
end
|
64
|
+
advancement, filter = case block_type
|
65
|
+
when :year : [{ :years => 1 }, [:year]]
|
66
|
+
when :quarter : [{ :months => 3 }, [:quarter, :year]]
|
67
|
+
when :month : [{ :months => 1 }, [:month, :year]]
|
68
|
+
when :week : [{ :weeks => 1 }, [:week, :year]]
|
69
|
+
when :day : [{ :days => 1 }, [:day, :month, :year]]
|
70
|
+
else raise "Unsupported type: #{block_type}"
|
71
|
+
end
|
72
|
+
iterate_period = period.begin.send("beginning_of_#{block_type}".to_sym) .. period.begin.send("end_of_#{block_type}".to_sym)
|
73
|
+
optimization = { :type => block_type, :filter => filter, :period => {} }
|
74
|
+
|
75
|
+
while iterate_period.begin < period.end
|
76
|
+
optimization[:period] = {}
|
77
|
+
optimization[:filter].each { |field| optimization[:period][field] = iterate_period.begin.send(field) }
|
78
|
+
|
79
|
+
yield iterate_period, optimization
|
80
|
+
iterate_period = iterate_period.begin.advance(advancement).send("beginning_of_#{block_type}".to_sym) ..
|
81
|
+
iterate_period.end.advance(advancement).send("end_of_#{block_type}".to_sym)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Reporter::TimeOptimizedResultRow < Reporter::ResultRow
|
2
|
+
|
3
|
+
def initialize(data_set, scope_serialization, scope, period)
|
4
|
+
super data_set, scope_serialization
|
5
|
+
@scope = scope
|
6
|
+
@period = period
|
7
|
+
@active_scope = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :current_iteration
|
11
|
+
|
12
|
+
def scope= scope
|
13
|
+
@scope_serialization = scope
|
14
|
+
end
|
15
|
+
|
16
|
+
def [] field
|
17
|
+
field_cache[field] ||= {}
|
18
|
+
preload_time_period_values_for field unless field_cache[field].has_key? current_iteration[:type]
|
19
|
+
field_cache[field][current_iteration[:type]] ||= {}
|
20
|
+
field_cache[field][current_iteration[:type]][current_iteration[:period]] ||= load_field_values(field)[field]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_reader :data_set, :scope_serialization, :period
|
26
|
+
|
27
|
+
def preload_time_period_values_for(field_name)
|
28
|
+
#Rails.logger.info "Trying to pre-load data for #{field_name} for the period of #{period} in chunks of #{current_iteration[:filter].to_sentence}"
|
29
|
+
|
30
|
+
field = data_set.data_structure.fields[field_name]
|
31
|
+
if field.respond_to? :preload_for_period
|
32
|
+
#Rails.logger.info "Preloading possible for #{field_name}!"
|
33
|
+
field_cache[field_name] ||= {}
|
34
|
+
field_cache[field_name][current_iteration[:type]] = \
|
35
|
+
field.preload_for_period data_set.data_source, {}, period, current_iteration[:filter], @scope
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Reporter::Value
|
2
|
+
|
3
|
+
def initialize(field_alias, field_human_name, value, human_value, description, source_link)
|
4
|
+
@field_alias = field_alias
|
5
|
+
@field_human_name = field_human_name || field_alias
|
6
|
+
@value = value
|
7
|
+
@human_value = human_value
|
8
|
+
@description = description
|
9
|
+
@source_link = source_link
|
10
|
+
end
|
11
|
+
|
12
|
+
attr_reader :field_alias, :field_human_name
|
13
|
+
attr_accessor :value, :description, :source_link
|
14
|
+
attr_writer :human_value
|
15
|
+
|
16
|
+
def human_value
|
17
|
+
@human_value || value
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_s
|
21
|
+
human_value
|
22
|
+
end
|
23
|
+
|
24
|
+
def as_percentage
|
25
|
+
if @value.is_a? Numeric
|
26
|
+
"%.2f %%" % (@value * 100.0)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def round(precision = 2)
|
31
|
+
if @value.is_a? Numeric
|
32
|
+
"%.#{precision}f" % @value
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reporter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matthijs Groen
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-09-16 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activerecord
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 3
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 3.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: activesupport
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 3
|
48
|
+
- 0
|
49
|
+
- 0
|
50
|
+
version: 3.0.0
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
name: arel
|
55
|
+
prerelease: false
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 21
|
62
|
+
segments:
|
63
|
+
- 1
|
64
|
+
- 0
|
65
|
+
- 1
|
66
|
+
version: 1.0.1
|
67
|
+
type: :runtime
|
68
|
+
version_requirements: *id003
|
69
|
+
description: "\n Reporter adds a consistent way to build reports.\n "
|
70
|
+
email: matthijs.groen@gmail.com
|
71
|
+
executables: []
|
72
|
+
|
73
|
+
extensions: []
|
74
|
+
|
75
|
+
extra_rdoc_files:
|
76
|
+
- README.markdown
|
77
|
+
files:
|
78
|
+
- Gemfile
|
79
|
+
- MIT-LICENSE
|
80
|
+
- README.markdown
|
81
|
+
- Rakefile
|
82
|
+
- VERSION
|
83
|
+
- lib/reporter/data_set.rb
|
84
|
+
- lib/reporter/data_source.rb
|
85
|
+
- lib/reporter/data_source/active_record_source.rb
|
86
|
+
- lib/reporter/data_source/scoping.rb
|
87
|
+
- lib/reporter/data_structure.rb
|
88
|
+
- lib/reporter/field/average_field.rb
|
89
|
+
- lib/reporter/field/base.rb
|
90
|
+
- lib/reporter/field/calculation_field.rb
|
91
|
+
- lib/reporter/field/count_field.rb
|
92
|
+
- lib/reporter/field/field.rb
|
93
|
+
- lib/reporter/field/formula_field.rb
|
94
|
+
- lib/reporter/field/sum_field.rb
|
95
|
+
- lib/reporter/formula.rb
|
96
|
+
- lib/reporter/result_row.rb
|
97
|
+
- lib/reporter/scope/base.rb
|
98
|
+
- lib/reporter/scope/date_scope.rb
|
99
|
+
- lib/reporter/scope/reference_scope.rb
|
100
|
+
- lib/reporter/support/time_range.rb
|
101
|
+
- lib/reporter/time_iterator.rb
|
102
|
+
- lib/reporter/time_optimized_result_row.rb
|
103
|
+
- lib/reporter/value.rb
|
104
|
+
has_rdoc: true
|
105
|
+
homepage: http://github.com/matthijsgroen/reporter
|
106
|
+
licenses: []
|
107
|
+
|
108
|
+
post_install_message:
|
109
|
+
rdoc_options:
|
110
|
+
- --charset=UTF-8
|
111
|
+
require_paths:
|
112
|
+
- lib
|
113
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
115
|
+
requirements:
|
116
|
+
- - ">="
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
hash: 3
|
119
|
+
segments:
|
120
|
+
- 0
|
121
|
+
version: "0"
|
122
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
|
+
none: false
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
hash: 3
|
128
|
+
segments:
|
129
|
+
- 0
|
130
|
+
version: "0"
|
131
|
+
requirements: []
|
132
|
+
|
133
|
+
rubyforge_project:
|
134
|
+
rubygems_version: 1.3.7
|
135
|
+
signing_key:
|
136
|
+
specification_version: 3
|
137
|
+
summary: Report builder.
|
138
|
+
test_files: []
|
139
|
+
|