kbaum-munger 0.1.4
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/.gitignore +5 -0
- data/README +90 -0
- data/Rakefile +21 -0
- data/VERSION +1 -0
- data/examples/column_add.rb +30 -0
- data/examples/development.log +2 -0
- data/examples/example_helper.rb +23 -0
- data/examples/sinatra_app.rb +100 -0
- data/examples/test.html +0 -0
- data/lib/munger.rb +16 -0
- data/lib/munger/data.rb +232 -0
- data/lib/munger/item.rb +50 -0
- data/lib/munger/render.rb +22 -0
- data/lib/munger/render/csv.rb +31 -0
- data/lib/munger/render/html.rb +89 -0
- data/lib/munger/render/sortable_html.rb +133 -0
- data/lib/munger/render/text.rb +54 -0
- data/lib/munger/report.rb +349 -0
- data/spec/munger/data/new_spec.rb +18 -0
- data/spec/munger/data_spec.rb +140 -0
- data/spec/munger/item_spec.rb +37 -0
- data/spec/munger/render/csv_spec.rb +21 -0
- data/spec/munger/render/html_spec.rb +75 -0
- data/spec/munger/render/text_spec.rb +22 -0
- data/spec/munger/render_spec.rb +28 -0
- data/spec/munger/report_spec.rb +148 -0
- data/spec/spec_helper.rb +76 -0
- metadata +92 -0
data/lib/munger/item.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
module Munger #:nodoc:
|
2
|
+
|
3
|
+
class Item
|
4
|
+
|
5
|
+
attr_reader :data
|
6
|
+
|
7
|
+
def initialize(data)
|
8
|
+
@data = data
|
9
|
+
end
|
10
|
+
|
11
|
+
def [](key)
|
12
|
+
return @data[key] if @data[key]
|
13
|
+
if key.is_a? Symbol
|
14
|
+
return @data[key.to_s] if @data[key.to_s]
|
15
|
+
elsif key.is_a? String
|
16
|
+
return @data[key.to_sym] if @data[key.to_sym]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def []=(key, value)
|
21
|
+
@data[key] = value
|
22
|
+
end
|
23
|
+
|
24
|
+
def method_missing( id, *args )
|
25
|
+
if @data[id].nil?
|
26
|
+
m = id.to_s
|
27
|
+
if /=$/ =~ m
|
28
|
+
@data[m.chomp!] = (args.length < 2 ? args[0] : args)
|
29
|
+
else
|
30
|
+
@data[m]
|
31
|
+
end
|
32
|
+
else
|
33
|
+
@data[id]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.ensure(item)
|
38
|
+
if item.is_a? Munger::Item
|
39
|
+
return item
|
40
|
+
else
|
41
|
+
return Item.new(item)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_hash
|
46
|
+
@data
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Munger::Render.to_html(report)
|
2
|
+
|
3
|
+
module Munger #:nodoc:
|
4
|
+
module Render
|
5
|
+
|
6
|
+
def self.to_html(report, options = {})
|
7
|
+
Html::new(report, options).render
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.to_sortable_html(report, options ={})
|
11
|
+
SortableHtml::new(report, options).render
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.to_text(report)
|
15
|
+
Text::new(report).render
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.to_csv(report)
|
19
|
+
CSV::new(report).render
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Munger #:nodoc:
|
2
|
+
module Render #:nodoc:
|
3
|
+
class CSV #:nodoc:
|
4
|
+
|
5
|
+
attr_reader :report
|
6
|
+
|
7
|
+
def initialize(report)
|
8
|
+
@report = report
|
9
|
+
end
|
10
|
+
|
11
|
+
def render
|
12
|
+
output = []
|
13
|
+
|
14
|
+
# header
|
15
|
+
output << @report.columns.collect { |col| @report.column_title(col).to_s }.join(',')
|
16
|
+
|
17
|
+
# body
|
18
|
+
@report.process_data.each do |row|
|
19
|
+
output << @report.columns.collect { |col| row[:data][col].to_s }.join(',')
|
20
|
+
end
|
21
|
+
|
22
|
+
output.join("\n")
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid?
|
26
|
+
@report.is_a? Munger::Report
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
begin
|
2
|
+
require 'builder'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
require 'builder'
|
6
|
+
end
|
7
|
+
|
8
|
+
module Munger #:nodoc:
|
9
|
+
module Render #:nodoc:
|
10
|
+
class Html
|
11
|
+
|
12
|
+
attr_reader :report, :classes
|
13
|
+
|
14
|
+
def initialize(report, options = {})
|
15
|
+
@report = report
|
16
|
+
set_classes(options[:classes])
|
17
|
+
end
|
18
|
+
|
19
|
+
def set_classes(options = nil)
|
20
|
+
options = {} if !options
|
21
|
+
default = {:table => 'report-table'}
|
22
|
+
@classes = default.merge(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def render
|
26
|
+
x = Builder::XmlMarkup.new
|
27
|
+
|
28
|
+
x.table(:class => @classes[:table]) do
|
29
|
+
|
30
|
+
x.tr do
|
31
|
+
@report.columns.each do |column|
|
32
|
+
x.th(:class => 'columnTitle') { x << @report.column_title(column) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@report.process_data.each do |row|
|
37
|
+
|
38
|
+
classes = []
|
39
|
+
classes << row[:meta][:row_styles]
|
40
|
+
classes << 'group' + row[:meta][:group].to_s if row[:meta][:group]
|
41
|
+
classes << cycle('even', 'odd')
|
42
|
+
classes.compact!
|
43
|
+
|
44
|
+
if row[:meta][:group_header]
|
45
|
+
classes << 'groupHeader' + row[:meta][:group_header].to_s
|
46
|
+
end
|
47
|
+
|
48
|
+
row_attrib = {}
|
49
|
+
row_attrib = {:class => classes.join(' ')} if classes.size > 0
|
50
|
+
|
51
|
+
x.tr(row_attrib) do
|
52
|
+
if row[:meta][:group_header]
|
53
|
+
header = row[:meta][:group_value].to_s
|
54
|
+
x.th(:colspan => @report.columns.size) { x << header }
|
55
|
+
else
|
56
|
+
@report.columns.each do |column|
|
57
|
+
|
58
|
+
cell_attrib = {}
|
59
|
+
if cst = row[:meta][:cell_styles]
|
60
|
+
cst = Item.ensure(cst)
|
61
|
+
if cell_styles = cst[column]
|
62
|
+
cell_attrib = {:class => cell_styles.join(' ')}
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
x.td(cell_attrib) { x << row[:data][column].to_s }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def cycle(one, two)
|
76
|
+
if @current == one
|
77
|
+
@current = two
|
78
|
+
else
|
79
|
+
@current = one
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def valid?
|
84
|
+
@report.is_a? Munger::Report
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'builder'
|
2
|
+
module Munger #:nodoc:
|
3
|
+
module Render #:nodoc:
|
4
|
+
# Render a table that lets the user sort the columns
|
5
|
+
class SortableHtml
|
6
|
+
|
7
|
+
attr_reader :report, :classes
|
8
|
+
|
9
|
+
# options:
|
10
|
+
# :url => /some/url/for/link # link to put on column
|
11
|
+
# :params => {:url_params} # parameters from url if any
|
12
|
+
# :sort => 'column' # column that is currently sorted
|
13
|
+
# :order => 'asc' || 'desc' # order of the currently sorted field
|
14
|
+
def initialize(report, options = {})
|
15
|
+
@report = report
|
16
|
+
@options = options
|
17
|
+
# default url and params options
|
18
|
+
@options[:url] ||= '/'
|
19
|
+
@options[:params] ||= {}
|
20
|
+
set_classes(options[:classes])
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_classes(options = nil)
|
24
|
+
options = {} if !options
|
25
|
+
default = {:table => 'report-table'}
|
26
|
+
@classes = default.merge(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def render
|
30
|
+
x = Builder::XmlMarkup.new
|
31
|
+
|
32
|
+
x.table(:class => @classes[:table]) do
|
33
|
+
|
34
|
+
x.tr do
|
35
|
+
@report.columns.each do |column|
|
36
|
+
# TODO: Should be able to see if a column is 'sortable'
|
37
|
+
# Assume all columns are sortable here - for now.
|
38
|
+
sorted_state = 'unsorted'
|
39
|
+
direction = 'asc'
|
40
|
+
if [column.to_s, @report.column_data_field(column)].include?(@options[:sort])
|
41
|
+
sorted_state = "sorted"
|
42
|
+
direction = @options[:order] == 'asc' ? 'desc' : 'asc'
|
43
|
+
direction_class = "sorted-#{direction}"
|
44
|
+
end
|
45
|
+
new_params = @options[:params].merge({'sort' => @report.column_data_field(column),'order' => direction})
|
46
|
+
x.th(:class => "columnTitle #{sorted_state} #{direction_class}" ) do
|
47
|
+
# x << @report.column_title(column)
|
48
|
+
x << "<a href=\"#{@options[:url]}?#{create_querystring(new_params)}\">#{@report.column_title(column)}</a>"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@report.process_data.each do |row|
|
54
|
+
|
55
|
+
classes = []
|
56
|
+
classes << row[:meta][:row_styles]
|
57
|
+
classes << 'group' + row[:meta][:group].to_s if row[:meta][:group]
|
58
|
+
classes << cycle('even', 'odd')
|
59
|
+
classes.compact!
|
60
|
+
|
61
|
+
if row[:meta][:group_header]
|
62
|
+
classes << 'groupHeader' + row[:meta][:group_header].to_s
|
63
|
+
end
|
64
|
+
|
65
|
+
row_attrib = {}
|
66
|
+
row_attrib = {:class => classes.join(' ')} if classes.size > 0
|
67
|
+
|
68
|
+
x.tr(row_attrib) do
|
69
|
+
if row[:meta][:group_header]
|
70
|
+
header = @report.column_title(row[:meta][:group_name]) + ' : ' + row[:meta][:group_value].to_s
|
71
|
+
x.th(:colspan => @report.columns.size) { x << header }
|
72
|
+
else
|
73
|
+
@report.columns.each do |column|
|
74
|
+
|
75
|
+
cell_attrib = {}
|
76
|
+
if cst = row[:meta][:cell_styles]
|
77
|
+
cst = Item.ensure(cst)
|
78
|
+
if cell_styles = cst[column]
|
79
|
+
cell_attrib = {:class => cell_styles.join(' ')}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
# TODO: Clean this up, I don't like it but it's working
|
83
|
+
# output the cell
|
84
|
+
# x.td(cell_attrib) { x << row[:data][column].to_s }
|
85
|
+
x.td(cell_attrib) do
|
86
|
+
formatter,*args = *@report.column_formatter(column)
|
87
|
+
col_data = row[:data] #[column]
|
88
|
+
if formatter && col_data[column]
|
89
|
+
formatted = if formatter.class == Proc
|
90
|
+
formatter.call(col_data.data)
|
91
|
+
elsif col_data[column].respond_to? formatter
|
92
|
+
col_data[column].send(formatter, *args)
|
93
|
+
elsif
|
94
|
+
col_data[column].to_s
|
95
|
+
end
|
96
|
+
else
|
97
|
+
formatted = col_data[column].to_s
|
98
|
+
end
|
99
|
+
x << formatted.to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def cycle(one, two)
|
111
|
+
if @current == one
|
112
|
+
@current = two
|
113
|
+
else
|
114
|
+
@current = one
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def valid?
|
119
|
+
@report.is_a? Munger::Report
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def create_querystring(params={})
|
124
|
+
qs = []
|
125
|
+
params.each do |k,v|
|
126
|
+
qs << "#{k}=#{v}"
|
127
|
+
end
|
128
|
+
qs.join("&")
|
129
|
+
end
|
130
|
+
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Munger #:nodoc:
|
2
|
+
module Render #:nodoc:
|
3
|
+
class Text
|
4
|
+
|
5
|
+
attr_reader :report
|
6
|
+
|
7
|
+
def initialize(report)
|
8
|
+
@report = report
|
9
|
+
end
|
10
|
+
|
11
|
+
def render
|
12
|
+
output = ''
|
13
|
+
depth = {}
|
14
|
+
|
15
|
+
# find depth
|
16
|
+
@report.process_data.each do |row|
|
17
|
+
@report.columns.each do |column|
|
18
|
+
i = row[:data][column].to_s.size
|
19
|
+
depth[column] ||= @report.column_title(column).to_s.size
|
20
|
+
depth[column] = (depth[column] < i) ? i : depth[column]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# header
|
25
|
+
output += '|'
|
26
|
+
@report.columns.each do |column|
|
27
|
+
output += @report.column_title(column).to_s.ljust(depth[column] + 1) + '| '
|
28
|
+
end
|
29
|
+
output += "\n"
|
30
|
+
|
31
|
+
total = depth.values.inject { |sum, i| sum + i } + (depth.size * 3)
|
32
|
+
0.upto(total) { |i| output += '-' }
|
33
|
+
output += "\n"
|
34
|
+
|
35
|
+
# body
|
36
|
+
@report.process_data.each do |row|
|
37
|
+
(row[:meta][:group]) ? sep = ':' : sep = '|'
|
38
|
+
output += sep
|
39
|
+
@report.columns.each do |column|
|
40
|
+
output += row[:data][column].to_s.ljust(depth[column] + 1) + sep + ' '
|
41
|
+
end
|
42
|
+
output += "\n"
|
43
|
+
end
|
44
|
+
|
45
|
+
output
|
46
|
+
end
|
47
|
+
|
48
|
+
def valid?
|
49
|
+
@report.is_a? Munger::Report
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,349 @@
|
|
1
|
+
module Munger #:nodoc:
|
2
|
+
|
3
|
+
class Report
|
4
|
+
|
5
|
+
attr_writer :data, :sort, :columns, :subgroup, :subgroup_options, :aggregate
|
6
|
+
attr_accessor :column_titles, :column_data_fields, :column_formatters
|
7
|
+
attr_reader :process_data, :grouping_level
|
8
|
+
|
9
|
+
# r = Munger::Report.new ( :data => data,
|
10
|
+
# :columns => [:collect_date, :spot_name, :airings, :display_name],
|
11
|
+
# :sort => [:collect_date, :spot_name]
|
12
|
+
# :subgroup => @group_list,
|
13
|
+
# :aggregate => {:sum => new_columns} )
|
14
|
+
# report = r.highlight
|
15
|
+
def initialize(options = {})
|
16
|
+
@grouping_level = 0
|
17
|
+
@column_titles = {}
|
18
|
+
@column_data_fields = {}
|
19
|
+
@column_formatters = {}
|
20
|
+
set_options(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.from_data(data)
|
24
|
+
Report.new(:data => data)
|
25
|
+
end
|
26
|
+
|
27
|
+
def set_options(options)
|
28
|
+
if d = options[:data]
|
29
|
+
if d.is_a? Munger::Data
|
30
|
+
@data = d
|
31
|
+
else
|
32
|
+
@data = Munger::Data.new(:data => d)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
self.sort(options[:sort]) if options[:sort]
|
36
|
+
self.columns(options[:columns]) if options[:columns]
|
37
|
+
self.subgroup(options[:subgroup]) if options[:subgroup]
|
38
|
+
self.aggregate(options[:aggregate]) if options[:aggregate]
|
39
|
+
end
|
40
|
+
|
41
|
+
def processed?
|
42
|
+
if @process_data
|
43
|
+
true
|
44
|
+
else
|
45
|
+
false
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# returns ReportTable
|
50
|
+
def process(options = {})
|
51
|
+
set_options(options)
|
52
|
+
|
53
|
+
# sorts and fills NativeReport
|
54
|
+
@report = translate_native(do_field_sort(@data.data))
|
55
|
+
|
56
|
+
do_add_groupings
|
57
|
+
do_add_aggregate_rows
|
58
|
+
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def sort(values = nil)
|
63
|
+
if values
|
64
|
+
@sort = values
|
65
|
+
self
|
66
|
+
else
|
67
|
+
@sort
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def subgroup(values = nil, options = {})
|
72
|
+
if values
|
73
|
+
@subgroup = values
|
74
|
+
@subgroup_options = options
|
75
|
+
self
|
76
|
+
else
|
77
|
+
@subgroup
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def columns(values = nil)
|
82
|
+
if values
|
83
|
+
if values.is_a? Hash
|
84
|
+
@columns = values.keys
|
85
|
+
@column_titles = values
|
86
|
+
else
|
87
|
+
@columns = Data.array(values)
|
88
|
+
end
|
89
|
+
self
|
90
|
+
else
|
91
|
+
@columns ||= @data.columns
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def column_title(column)
|
96
|
+
if c = @column_titles[column]
|
97
|
+
return c.to_s
|
98
|
+
else
|
99
|
+
return column.to_s
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def column_data_field(column)
|
104
|
+
@column_data_fields[column] || column.to_s
|
105
|
+
end
|
106
|
+
|
107
|
+
def column_formatter(column)
|
108
|
+
@column_formatters[column]
|
109
|
+
end
|
110
|
+
|
111
|
+
def aggregate(values = nil)
|
112
|
+
if values
|
113
|
+
@aggregate = values
|
114
|
+
self
|
115
|
+
else
|
116
|
+
@aggregate
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def rows
|
121
|
+
@process_data.size
|
122
|
+
end
|
123
|
+
|
124
|
+
def valid?
|
125
|
+
(@data.is_a? Munger::Data) && (@data.valid?)
|
126
|
+
end
|
127
|
+
|
128
|
+
# @report.style_cells('highlight') { |cell, row| cell > 32 }
|
129
|
+
def style_cells(style, options = {})
|
130
|
+
@process_data.each_with_index do |row, index|
|
131
|
+
|
132
|
+
# filter columns to look at
|
133
|
+
if options[:only]
|
134
|
+
cols = Data.array(options[:only])
|
135
|
+
elsif options [:except]
|
136
|
+
cols = columns - Data.array(options[:except])
|
137
|
+
else
|
138
|
+
cols = columns
|
139
|
+
end
|
140
|
+
|
141
|
+
if options[:no_groups] && row[:meta][:group]
|
142
|
+
next
|
143
|
+
end
|
144
|
+
|
145
|
+
cols.each do |col|
|
146
|
+
if yield(row[:data][col], row[:data])
|
147
|
+
@process_data[index][:meta][:cell_styles] ||= {}
|
148
|
+
@process_data[index][:meta][:cell_styles][col] ||= []
|
149
|
+
@process_data[index][:meta][:cell_styles][col] << style
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# @report.style_rows('highlight') { |row| row.age > 32 }
|
156
|
+
def style_rows(style, options = {})
|
157
|
+
@process_data.each_with_index do |row, index|
|
158
|
+
if yield(row[:data])
|
159
|
+
@process_data[index][:meta][:row_styles] ||= []
|
160
|
+
@process_data[index][:meta][:row_styles] << style
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# post-processing calls
|
166
|
+
|
167
|
+
def get_subgroup_rows(group_level = nil)
|
168
|
+
data = @process_data.select { |r| r[:meta][:group] }
|
169
|
+
data = data.select { |r| r[:meta][:group] == group_level } if group_level
|
170
|
+
data
|
171
|
+
end
|
172
|
+
|
173
|
+
def to_s
|
174
|
+
pp @process_data
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def translate_native(array_of_hashes)
|
180
|
+
@process_data = []
|
181
|
+
array_of_hashes.each do |row|
|
182
|
+
@process_data << {:data => Item.ensure(row), :meta => {:data => true}}
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def do_add_aggregate_rows
|
187
|
+
return false if !@aggregate
|
188
|
+
return false if !@aggregate.is_a? Hash
|
189
|
+
|
190
|
+
totals = {}
|
191
|
+
|
192
|
+
@process_data.each_with_index do |row, index|
|
193
|
+
if row[:meta][:data]
|
194
|
+
@aggregate.each do |type, columns|
|
195
|
+
Data.array(columns).each do |column|
|
196
|
+
value = row[:data][column]
|
197
|
+
@grouping_level.downto(0) do |level|
|
198
|
+
totals[column] ||= {}
|
199
|
+
totals[column][level] ||= []
|
200
|
+
totals[column][level] << value
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
elsif level = row[:meta][:group]
|
205
|
+
# write the totals and reset level
|
206
|
+
@aggregate.each do |type, columns|
|
207
|
+
Data.array(columns).each do |column|
|
208
|
+
data = totals[column][level]
|
209
|
+
@process_data[index][:data][column] = calculate_aggregate(type, data)
|
210
|
+
totals[column][level] = []
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
total_row = {:data => {}, :meta => {:group => 0}}
|
217
|
+
# write one row at the end with the totals
|
218
|
+
@aggregate.each do |type, columns|
|
219
|
+
Data.array(columns).each do |column|
|
220
|
+
data = totals[column][0]
|
221
|
+
total_row[:data][column] = calculate_aggregate(type, data)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
@process_data << total_row
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
def calculate_aggregate(type, data)
|
229
|
+
return 0 if !data
|
230
|
+
if type.is_a? Proc
|
231
|
+
type.call(data)
|
232
|
+
else
|
233
|
+
case type
|
234
|
+
when :count
|
235
|
+
data.size
|
236
|
+
when :average
|
237
|
+
sum = data.inject {|sum, n| sum + n }
|
238
|
+
(sum / data.size) rescue 0
|
239
|
+
else
|
240
|
+
data.inject {|sum, n| sum + n }
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def do_add_groupings
|
246
|
+
return false if !@subgroup
|
247
|
+
sub = Data.array(@subgroup)
|
248
|
+
@grouping_level = sub.size
|
249
|
+
|
250
|
+
current = {}
|
251
|
+
new_data = []
|
252
|
+
|
253
|
+
first_row = @process_data.first
|
254
|
+
sub.reverse.each do |group|
|
255
|
+
current[group] = first_row[:data][group]
|
256
|
+
end
|
257
|
+
prev_row = {:data => {}}
|
258
|
+
|
259
|
+
@process_data.each_with_index do |row, index|
|
260
|
+
# insert header title rows
|
261
|
+
next_row = @process_data[index + 1]
|
262
|
+
|
263
|
+
if next_row
|
264
|
+
|
265
|
+
# insert header rows
|
266
|
+
if @subgroup_options[:with_headers]
|
267
|
+
level = 1
|
268
|
+
sub.each do |group|
|
269
|
+
if (prev_row[:data][group] != current[group]) && current[group]
|
270
|
+
group_row = {:data => {}, :meta => {:group_header => level,
|
271
|
+
:group_name => group, :group_value => row[:data][group]}}
|
272
|
+
new_data << group_row
|
273
|
+
end
|
274
|
+
level += 1
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# insert current row
|
279
|
+
new_data << row
|
280
|
+
|
281
|
+
|
282
|
+
# insert footer rows
|
283
|
+
level = @grouping_level
|
284
|
+
sub.reverse.each do |group|
|
285
|
+
if (next_row[:data][group] != current[group]) && current[group]
|
286
|
+
group_row = {:data => {}, :meta => {:group => level, :group_name => group}}
|
287
|
+
new_data << group_row
|
288
|
+
end
|
289
|
+
current[group] = next_row[:data][group]
|
290
|
+
level -= 1
|
291
|
+
end
|
292
|
+
|
293
|
+
prev_row = row
|
294
|
+
|
295
|
+
else # last row
|
296
|
+
level = @grouping_level
|
297
|
+
|
298
|
+
# insert header rows
|
299
|
+
sub.each do |group|
|
300
|
+
if (prev_row[:data][group] != current[group]) && current[group]
|
301
|
+
group_row = {:data => {}, :meta => {:group_header => level,
|
302
|
+
:group_name => group, :group_value => row[:data][group]}}
|
303
|
+
new_data << group_row
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
new_data << row
|
308
|
+
|
309
|
+
sub.reverse.each do |group|
|
310
|
+
group_row = {:data => {}, :meta => {:group => level, :group_name => group}}
|
311
|
+
new_data << group_row
|
312
|
+
level -= 1
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
@process_data = new_data
|
318
|
+
end
|
319
|
+
|
320
|
+
def do_field_sort(data)
|
321
|
+
data.sort do |a, b|
|
322
|
+
compare = 0
|
323
|
+
a = Item.ensure(a)
|
324
|
+
b = Item.ensure(b)
|
325
|
+
|
326
|
+
Data.array(@sort).each do |sorting|
|
327
|
+
if sorting.is_a?(String) || sorting.is_a?(Symbol)
|
328
|
+
compare = a[sorting.to_s] <=> b[sorting.to_s] rescue 0
|
329
|
+
break if compare != 0
|
330
|
+
elsif sorting.is_a? Array
|
331
|
+
key = sorting[0]
|
332
|
+
func = sorting[1]
|
333
|
+
if func == :asc
|
334
|
+
compare = a[key] <=> b[key]
|
335
|
+
elsif func == :desc
|
336
|
+
compare = b[key] <=> a[key]
|
337
|
+
elsif func.is_a? Proc
|
338
|
+
compare = func.call(a[key], b[key])
|
339
|
+
end
|
340
|
+
break if compare != 0
|
341
|
+
end
|
342
|
+
end
|
343
|
+
compare
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
end
|
348
|
+
|
349
|
+
end
|