jsanders-ruport 1.7.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/AUTHORS +48 -0
- data/LICENSE +59 -0
- data/README +114 -0
- data/Rakefile +93 -0
- data/examples/RWEmerson.jpg +0 -0
- data/examples/anon.rb +43 -0
- data/examples/btree/commaleon/commaleon.rb +263 -0
- data/examples/btree/commaleon/sample_data/ticket_count.csv +124 -0
- data/examples/btree/commaleon/sample_data/ticket_count2.csv +119 -0
- data/examples/centered_pdf_text_box.rb +83 -0
- data/examples/data/tattle.dump +82 -0
- data/examples/example.csv +3 -0
- data/examples/line_plotter.rb +61 -0
- data/examples/pdf_report_with_common_base.rb +72 -0
- data/examples/png_embed.rb +54 -0
- data/examples/roadmap.png +0 -0
- data/examples/row_renderer.rb +39 -0
- data/examples/simple_pdf_lines.rb +25 -0
- data/examples/simple_templating_example.rb +34 -0
- data/examples/tattle_ruby_version.rb +39 -0
- data/examples/tattle_rubygems_version.rb +37 -0
- data/examples/trac_ticket_status.rb +59 -0
- data/lib/ruport.rb +127 -0
- data/lib/ruport/controller.rb +616 -0
- data/lib/ruport/controller/grouping.rb +71 -0
- data/lib/ruport/controller/table.rb +54 -0
- data/lib/ruport/data.rb +4 -0
- data/lib/ruport/data/feeder.rb +111 -0
- data/lib/ruport/data/grouping.rb +399 -0
- data/lib/ruport/data/record.rb +297 -0
- data/lib/ruport/data/table.rb +950 -0
- data/lib/ruport/extensions.rb +4 -0
- data/lib/ruport/formatter.rb +254 -0
- data/lib/ruport/formatter/csv.rb +149 -0
- data/lib/ruport/formatter/html.rb +161 -0
- data/lib/ruport/formatter/pdf.rb +591 -0
- data/lib/ruport/formatter/template.rb +187 -0
- data/lib/ruport/formatter/text.rb +231 -0
- data/lib/uport.rb +1 -0
- data/test/controller_test.rb +743 -0
- data/test/csv_formatter_test.rb +164 -0
- data/test/data_feeder_test.rb +88 -0
- data/test/grouping_test.rb +410 -0
- data/test/helpers.rb +11 -0
- data/test/html_formatter_test.rb +201 -0
- data/test/pdf_formatter_test.rb +354 -0
- data/test/record_test.rb +332 -0
- data/test/samples/addressbook.csv +6 -0
- data/test/samples/data.csv +3 -0
- data/test/samples/data.tsv +3 -0
- data/test/samples/dates.csv +1409 -0
- data/test/samples/erb_test.sql +1 -0
- data/test/samples/query_test.sql +1 -0
- data/test/samples/ruport_test.sql +8 -0
- data/test/samples/test.sql +2 -0
- data/test/samples/test.yaml +3 -0
- data/test/samples/ticket_count.csv +124 -0
- data/test/table_pivot_test.rb +134 -0
- data/test/table_test.rb +838 -0
- data/test/template_test.rb +48 -0
- data/test/text_formatter_test.rb +258 -0
- data/util/bench/data/record/bench_as_vs_to.rb +18 -0
- data/util/bench/data/record/bench_constructor.rb +46 -0
- data/util/bench/data/record/bench_indexing.rb +65 -0
- data/util/bench/data/record/bench_reorder.rb +35 -0
- data/util/bench/data/record/bench_to_a.rb +19 -0
- data/util/bench/data/table/bench_column_manip.rb +103 -0
- data/util/bench/data/table/bench_dup.rb +24 -0
- data/util/bench/data/table/bench_init.rb +67 -0
- data/util/bench/data/table/bench_manip.rb +125 -0
- data/util/bench/formatter/bench_csv.rb +14 -0
- data/util/bench/formatter/bench_html.rb +14 -0
- data/util/bench/formatter/bench_pdf.rb +14 -0
- data/util/bench/formatter/bench_text.rb +14 -0
- data/util/bench/samples/tattle.csv +1237 -0
- metadata +176 -0
@@ -0,0 +1,71 @@
|
|
1
|
+
# Ruport : Extensible Reporting System
|
2
|
+
#
|
3
|
+
# controller/grouping.rb : Group data controller for Ruby Reports
|
4
|
+
#
|
5
|
+
# Written by Michael Milner, 2007.
|
6
|
+
# Copyright (C) 2007, All Rights Reserved
|
7
|
+
#
|
8
|
+
# This is free software distributed under the same terms as Ruby 1.8
|
9
|
+
# See LICENSE and COPYING for details.
|
10
|
+
#
|
11
|
+
module Ruport
|
12
|
+
|
13
|
+
# This class implements the basic controller for a single group of data.
|
14
|
+
#
|
15
|
+
# == Supported Formatters
|
16
|
+
#
|
17
|
+
# * Formatter::CSV
|
18
|
+
# * Formatter::Text
|
19
|
+
# * Formatter::HTML
|
20
|
+
# * Formatter::PDF
|
21
|
+
#
|
22
|
+
# == Default layout options
|
23
|
+
#
|
24
|
+
# * <tt>show_table_headers</tt> #=> true
|
25
|
+
#
|
26
|
+
# == Formatter hooks called (in order)
|
27
|
+
#
|
28
|
+
# * build_group_header
|
29
|
+
# * build_group_body
|
30
|
+
# * build_group_footer
|
31
|
+
#
|
32
|
+
class Controller::Group < Controller
|
33
|
+
options { |o| o.show_table_headers = true }
|
34
|
+
|
35
|
+
stage :group_header, :group_body, :group_footer
|
36
|
+
end
|
37
|
+
|
38
|
+
# This class implements the basic controller for data groupings in Ruport
|
39
|
+
# (a collection of Groups).
|
40
|
+
#
|
41
|
+
# == Supported Formatters
|
42
|
+
#
|
43
|
+
# * Formatter::CSV
|
44
|
+
# * Formatter::Text
|
45
|
+
# * Formatter::HTML
|
46
|
+
# * Formatter::PDF
|
47
|
+
#
|
48
|
+
# == Default layout options
|
49
|
+
#
|
50
|
+
# * <tt>show_group_headers</tt> #=> true
|
51
|
+
# * <tt>style</tt> #=> :inline
|
52
|
+
#
|
53
|
+
# == Formatter hooks called (in order)
|
54
|
+
#
|
55
|
+
# * build_grouping_header
|
56
|
+
# * build_grouping_body
|
57
|
+
# * build_grouping_footer
|
58
|
+
# * finalize_grouping
|
59
|
+
#
|
60
|
+
class Controller::Grouping < Controller
|
61
|
+
options do |o|
|
62
|
+
o.show_group_headers = true
|
63
|
+
o.style = :inline
|
64
|
+
end
|
65
|
+
|
66
|
+
stage :grouping_header, :grouping_body, :grouping_footer
|
67
|
+
|
68
|
+
finalize :grouping
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# controller/table.rb : Tabular data controller for Ruby Reports
|
2
|
+
#
|
3
|
+
# Written by Gregory Brown, December 2006. Copyright 2006, All Rights Reserved
|
4
|
+
# This is Free Software, please see LICENSE and COPYING for details.
|
5
|
+
|
6
|
+
module Ruport
|
7
|
+
|
8
|
+
# This class implements the basic controller for table rows.
|
9
|
+
#
|
10
|
+
# == Supported Formatters
|
11
|
+
#
|
12
|
+
# * Formatter::CSV
|
13
|
+
# * Formatter::Text
|
14
|
+
# * Formatter::HTML
|
15
|
+
#
|
16
|
+
# == Formatter hooks called (in order)
|
17
|
+
#
|
18
|
+
# * build_row
|
19
|
+
#
|
20
|
+
class Controller::Row < Controller
|
21
|
+
stage :row
|
22
|
+
end
|
23
|
+
|
24
|
+
# This class implements the basic tabular data controller for Ruport.
|
25
|
+
#
|
26
|
+
# == Supported Formatters
|
27
|
+
#
|
28
|
+
# * Formatter::CSV
|
29
|
+
# * Formatter::Text
|
30
|
+
# * Formatter::HTML
|
31
|
+
# * Formatter::PDF
|
32
|
+
#
|
33
|
+
# == Default layout options
|
34
|
+
#
|
35
|
+
# * <tt>show_table_headers</tt> #=> true
|
36
|
+
#
|
37
|
+
# == Formatter hooks called (in order)
|
38
|
+
#
|
39
|
+
# * prepare_table
|
40
|
+
# * build_table_header
|
41
|
+
# * build_table_body
|
42
|
+
# * build_table_footer
|
43
|
+
# * finalize_table
|
44
|
+
#
|
45
|
+
class Controller::Table < Controller
|
46
|
+
options { |o| o.show_table_headers = true }
|
47
|
+
|
48
|
+
prepare :table
|
49
|
+
|
50
|
+
stage :table_header, :table_body, :table_footer
|
51
|
+
|
52
|
+
finalize :table
|
53
|
+
end
|
54
|
+
end
|
data/lib/ruport/data.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Ruport : Extensible Reporting System
|
2
|
+
#
|
3
|
+
# data/feeder.rb provides a data transformation and filtering proxy for ruport
|
4
|
+
#
|
5
|
+
# Copyright August 2007, Gregory Brown / Michael Milner. All Rights Reserved.
|
6
|
+
#
|
7
|
+
# This is free software. Please see the LICENSE and COPYING files for details.
|
8
|
+
|
9
|
+
|
10
|
+
# This class provides a simple way to apply transformations and filters that
|
11
|
+
# get run while you are aggregating data. This is used primarily to build
|
12
|
+
# constrained wrappers to Ruport::Data::Table, but can be used with abstract
|
13
|
+
# data structures as well.
|
14
|
+
#
|
15
|
+
# Table Example:
|
16
|
+
#
|
17
|
+
# t = Table(%w[a b c]) do |feeder|
|
18
|
+
# feeder.filter { |r| r.a < 5 }
|
19
|
+
# feeder.transform { |r| r.b = "B: #{r.b}"}
|
20
|
+
# feeder << [1,2,3]
|
21
|
+
# feeder << [7,1,2]
|
22
|
+
# feeder << { "a" => 3, "b" => 6, "c" => 7 }
|
23
|
+
# end
|
24
|
+
# t.length #=> 2
|
25
|
+
# t.column("b") #=> ["B: 2","B: 6"]
|
26
|
+
#
|
27
|
+
# Filters and transforms are added in a sequential order to a single list of
|
28
|
+
# constraints. You could add some constraints and then append some data, then
|
29
|
+
# add additional constraints, or even build up dynamic constraints if you'd
|
30
|
+
# like.
|
31
|
+
#
|
32
|
+
# Wrapping an arbitrary data object:
|
33
|
+
#
|
34
|
+
# In order to make Data::Feeder work with an object other than Data::Table, it
|
35
|
+
# must implement two things:
|
36
|
+
#
|
37
|
+
# * A method called feed_element that accepts a single argument.
|
38
|
+
# When Feeder#<< is called, the object to be appended is converted by this
|
39
|
+
# method, and then yielded to the filters / transforms.
|
40
|
+
#
|
41
|
+
# * A meaningful #<< method. Feeder#<< simply delegates this to the wrapped
|
42
|
+
# object once the filters and transforms have been applied, so be sure that
|
43
|
+
# the object returned by feed_element is one that can be used by your #<<
|
44
|
+
# method.
|
45
|
+
#
|
46
|
+
# Here is a sample implementation of wrapping a feeder around an Array.
|
47
|
+
#
|
48
|
+
# class Array
|
49
|
+
# def feed_element(element)
|
50
|
+
# element
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# int_array = []
|
55
|
+
# feeder = Ruport::Data::Feeder.new(int_array)
|
56
|
+
# feeder.filter { |r| r.kind_of?(Integer) }
|
57
|
+
#
|
58
|
+
# feeder << 1 << "5" << 4.7 << "kitten" << 4
|
59
|
+
# int_array #=> [1, 4]
|
60
|
+
#
|
61
|
+
class Ruport::Data::Feeder
|
62
|
+
|
63
|
+
# Creates a new Data::Feeder, wrapping the data object provided.
|
64
|
+
def initialize(data)
|
65
|
+
@data = data
|
66
|
+
@constraints = []
|
67
|
+
end
|
68
|
+
|
69
|
+
# Accesses the underlying data object directly
|
70
|
+
attr_reader :data
|
71
|
+
|
72
|
+
# Constrained append operation.
|
73
|
+
#
|
74
|
+
# Before filters and transforms are run, the element to be appended is first
|
75
|
+
# converted by data.feed_element(some_element)
|
76
|
+
#
|
77
|
+
# Filters and transforms are then run sequentially, and if the constraints
|
78
|
+
# are met, it is appended using data << some_element.
|
79
|
+
#
|
80
|
+
def <<(element)
|
81
|
+
feed_element = data.feed_element(element)
|
82
|
+
|
83
|
+
@constraints.each do |type,block|
|
84
|
+
if type == :filter
|
85
|
+
return self unless block[feed_element]
|
86
|
+
else
|
87
|
+
block[feed_element]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
data << feed_element
|
92
|
+
return self
|
93
|
+
end
|
94
|
+
|
95
|
+
# Creates a filter which must be satisfied for an object to be appended via
|
96
|
+
# the feeder.
|
97
|
+
#
|
98
|
+
# feeder.filter { |r| r.length < 4 }
|
99
|
+
#
|
100
|
+
def filter(&block)
|
101
|
+
@constraints << [:filter,block]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Creates a transformation which may change the object as it is appended.
|
105
|
+
#
|
106
|
+
# feeder.transform { |r| r.a += 10 }
|
107
|
+
def transform(&block)
|
108
|
+
@constraints << [:transform,block]
|
109
|
+
end
|
110
|
+
|
111
|
+
end
|
@@ -0,0 +1,399 @@
|
|
1
|
+
# Ruport : Extensible Reporting System
|
2
|
+
#
|
3
|
+
# data/grouping.rb provides group and grouping data structures for Ruport.
|
4
|
+
#
|
5
|
+
# Created by Michael Milner / Gregory Brown, 2007
|
6
|
+
# Copyright (C) 2007 Michael Milner / Gregory Brown, All Rights Reserved.
|
7
|
+
#
|
8
|
+
# This is free software distributed under the same terms as Ruby 1.8
|
9
|
+
# See LICENSE and COPYING for details.
|
10
|
+
#
|
11
|
+
module Ruport::Data
|
12
|
+
|
13
|
+
# === Overview
|
14
|
+
#
|
15
|
+
# This class implements a group data structure for Ruport. Group is
|
16
|
+
# simply a subclass of Table that adds a <tt>:name</tt> attribute.
|
17
|
+
#
|
18
|
+
class Group < Table
|
19
|
+
|
20
|
+
# The name of the group
|
21
|
+
attr_reader :name
|
22
|
+
|
23
|
+
# A hash of subgroups
|
24
|
+
attr_reader :subgroups
|
25
|
+
|
26
|
+
# Creates a new Group based on the supplied options.
|
27
|
+
#
|
28
|
+
# Valid options:
|
29
|
+
# <b><tt>:name</tt></b>:: The name of the Group
|
30
|
+
# <b><tt>:data</tt></b>:: An Array of Arrays representing the
|
31
|
+
# records in this Group
|
32
|
+
# <b><tt>:column_names</tt></b>:: An Array containing the column names
|
33
|
+
# for this Group.
|
34
|
+
#
|
35
|
+
# Example:
|
36
|
+
#
|
37
|
+
# group = Group.new :name => 'My Group',
|
38
|
+
# :data => [[1,2,3], [3,4,5]],
|
39
|
+
# :column_names => %w[a b c]
|
40
|
+
#
|
41
|
+
def initialize(options={})
|
42
|
+
@name = options.delete(:name)
|
43
|
+
@subgroups = {}
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
include Ruport::Controller::Hooks
|
48
|
+
renders_as_group
|
49
|
+
|
50
|
+
def self.inherited(base) #:nodoc:
|
51
|
+
base.renders_as_group
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize_copy(from) #:nodoc:
|
55
|
+
super
|
56
|
+
@name = from.name
|
57
|
+
@subgroups = from.subgroups.inject({}) { |h,d|
|
58
|
+
h.merge!({ d[0] => d[1].dup }) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Compares this Group to another Group and returns <tt>true</tt> if
|
62
|
+
# the <tt>name</tt>, <tt>data</tt>, and <tt>column_names</tt> are equal.
|
63
|
+
#
|
64
|
+
# Example:
|
65
|
+
#
|
66
|
+
# one = Group.new :name => 'test',
|
67
|
+
# :data => [[1,2], [3,4]],
|
68
|
+
# :column_names => %w[a b]
|
69
|
+
#
|
70
|
+
# two = Group.new :name => 'test',
|
71
|
+
# :data => [[1,2], [3,4]],
|
72
|
+
# :column_names => %w[a b]
|
73
|
+
#
|
74
|
+
# one.eql?(two) #=> true
|
75
|
+
#
|
76
|
+
def eql?(other)
|
77
|
+
name.eql?(other.name) && super
|
78
|
+
end
|
79
|
+
|
80
|
+
alias_method :==, :eql?
|
81
|
+
|
82
|
+
protected
|
83
|
+
|
84
|
+
attr_writer :name, :subgroups #:nodoc:
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
# Creates subgroups for the group based on the supplied column name. Each
|
89
|
+
# subgroup is a hash whose keys are the unique values in the column.
|
90
|
+
#
|
91
|
+
# Example:
|
92
|
+
#
|
93
|
+
# main_group = Group.new :name => 'test',
|
94
|
+
# :data => [[1,2,3,4,5], [3,4,5,6,7]],
|
95
|
+
# :column_names => %w[a b c d e]
|
96
|
+
# main_group.create_subgroups("a")
|
97
|
+
#
|
98
|
+
def create_subgroups(group_column)
|
99
|
+
if @subgroups.empty?
|
100
|
+
@subgroups = grouped_data(group_column)
|
101
|
+
else
|
102
|
+
@subgroups.each {|name,group|
|
103
|
+
group.send(:create_subgroups, group_column)
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def grouped_data(group_column) #:nodoc:
|
109
|
+
data = {}
|
110
|
+
group_names = column(group_column).uniq
|
111
|
+
columns = column_names.dup
|
112
|
+
columns.delete(group_column)
|
113
|
+
group_names.each do |name|
|
114
|
+
group_data = sub_table(columns) {|r|
|
115
|
+
r.send(group_column) == name
|
116
|
+
}
|
117
|
+
data[name] = Group.new(:name => name, :data => group_data,
|
118
|
+
:column_names => columns,
|
119
|
+
:record_class => record_class)
|
120
|
+
end
|
121
|
+
data
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
# === Overview
|
128
|
+
#
|
129
|
+
# This class implements a grouping data structure for Ruport. A grouping is
|
130
|
+
# a collection of groups. It allows you to group the data in a table by one
|
131
|
+
# or more columns that you specify.
|
132
|
+
#
|
133
|
+
# The data for a grouping is a hash of groups, keyed on each unique data
|
134
|
+
# point from the grouping column.
|
135
|
+
#
|
136
|
+
class Grouping
|
137
|
+
|
138
|
+
include Enumerable
|
139
|
+
|
140
|
+
# Creates a new Grouping based on the supplied options.
|
141
|
+
#
|
142
|
+
# Valid options:
|
143
|
+
# <b><tt>:by</tt></b>:: A column name or array of column names that the
|
144
|
+
# data will be grouped on.
|
145
|
+
# <b><tt>:order</tt></b>:: Determines the iteration and presentation order
|
146
|
+
# of a Grouping object. Set to :name to order by
|
147
|
+
# Group names. You can also provide a lambda which
|
148
|
+
# will be passed Group objects, and use semantics
|
149
|
+
# similar to Enumerable#group_by
|
150
|
+
#
|
151
|
+
# Examples:
|
152
|
+
#
|
153
|
+
# table = [[1,2,3],[4,5,6],[1,1,2]].to_table(%w[a b c])
|
154
|
+
#
|
155
|
+
# # unordered
|
156
|
+
# grouping = Grouping.new(table, :by => "a")
|
157
|
+
#
|
158
|
+
# # ordered by group name
|
159
|
+
# grouping = Grouping.new(table, :by => "a", :order => :name)
|
160
|
+
#
|
161
|
+
# # ordered by group size
|
162
|
+
# grouping = Grouping.new(table, :by => "a",
|
163
|
+
# :order => lambda { |g| g.size } )
|
164
|
+
def initialize(data={},options={})
|
165
|
+
if data.kind_of?(Hash)
|
166
|
+
@grouped_by = data[:by]
|
167
|
+
@order = data[:order]
|
168
|
+
@data = {}
|
169
|
+
else
|
170
|
+
@grouped_by = options[:by]
|
171
|
+
@order = options[:order]
|
172
|
+
cols = Array(options[:by]).dup
|
173
|
+
@data = data.to_group.send(:grouped_data, cols.shift)
|
174
|
+
cols.each do |col|
|
175
|
+
@data.each do |name,group|
|
176
|
+
group.send(:create_subgroups, col)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# The grouping's data
|
183
|
+
attr_accessor :data
|
184
|
+
|
185
|
+
# The name of the column used to group the data
|
186
|
+
attr_reader :grouped_by
|
187
|
+
|
188
|
+
# Allows Hash-like indexing of the grouping data.
|
189
|
+
#
|
190
|
+
# Examples:
|
191
|
+
#
|
192
|
+
# my_grouping["foo"]
|
193
|
+
#
|
194
|
+
def [](name)
|
195
|
+
@data[name] or
|
196
|
+
raise(IndexError,"Group Not Found")
|
197
|
+
end
|
198
|
+
|
199
|
+
# Iterates through the Grouping, yielding each group name and Group object
|
200
|
+
#
|
201
|
+
def each
|
202
|
+
if @order.respond_to?(:call)
|
203
|
+
@data.sort_by { |n,g| @order[g] }.each { |n,g| yield(n,g) }
|
204
|
+
elsif @order == :name
|
205
|
+
@data.sort_by { |n,g| n }.each { |name,group| yield(name,group) }
|
206
|
+
else
|
207
|
+
@data.each { |name,group| yield(name,group) }
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
# Returns a new grouping with the specified sort order.
|
213
|
+
# You can sort by Group name or an arbitrary block
|
214
|
+
#
|
215
|
+
# by_name = grouping.sort_grouping_by(:name)
|
216
|
+
# by_size = grouping.sort_grouping_by { |g| g.size }
|
217
|
+
def sort_grouping_by(type=nil,&block)
|
218
|
+
a = Grouping.new(:by => @grouped_by, :order => type || block)
|
219
|
+
each { |n,g| a << g }
|
220
|
+
return a
|
221
|
+
end
|
222
|
+
|
223
|
+
# Applies the specified sort order to an existing Grouping object.
|
224
|
+
#
|
225
|
+
# grouping.sort_grouping_by!(:name)
|
226
|
+
# grouping.sort_grouping_by! { |g| g.size }
|
227
|
+
def sort_grouping_by!(type=nil,&block)
|
228
|
+
@order = type || block
|
229
|
+
end
|
230
|
+
|
231
|
+
# Used to add extra data to the Grouping. <tt>group</tt> should be a Group.
|
232
|
+
#
|
233
|
+
# Example:
|
234
|
+
#
|
235
|
+
# table = [[1,2,3],[4,5,6]].to_table(%w[a b c])
|
236
|
+
#
|
237
|
+
# grouping = Grouping.new(table, :by => "a")
|
238
|
+
#
|
239
|
+
# group = Group.new :name => 7,
|
240
|
+
# :data => [[8,9]],
|
241
|
+
# :column_names => %w[b c]
|
242
|
+
#
|
243
|
+
# grouping << group
|
244
|
+
#
|
245
|
+
def <<(group)
|
246
|
+
if data.has_key? group.name
|
247
|
+
raise(ArgumentError, "Group '#{group.name}' exists!")
|
248
|
+
end
|
249
|
+
@data.merge!({ group.name => group })
|
250
|
+
end
|
251
|
+
|
252
|
+
alias_method :append, :<<
|
253
|
+
|
254
|
+
# Provides access to the subgroups of a particular group in the Grouping.
|
255
|
+
# Supply the name of a group and it returns a Grouping created from the
|
256
|
+
# subgroups of the group.
|
257
|
+
#
|
258
|
+
def subgrouping(name)
|
259
|
+
grouping = dup
|
260
|
+
grouping.send(:data=, @data[name].subgroups)
|
261
|
+
return grouping
|
262
|
+
end
|
263
|
+
|
264
|
+
alias_method :/, :subgrouping
|
265
|
+
|
266
|
+
# Useful for creating basic summaries from Grouping objects.
|
267
|
+
# Takes a field to summarize on, and then for each group,
|
268
|
+
# runs the specified procs and returns the results as a Table.
|
269
|
+
#
|
270
|
+
# The following example would show for each date group,
|
271
|
+
# the sum for the attributes or methods <tt>:opened</tt> and
|
272
|
+
# <tt>:closed</tt> and order them by the <tt>:order</tt> array.
|
273
|
+
#
|
274
|
+
# If <tt>:order</tt> is not specified, you cannot depend on predictable
|
275
|
+
# column order.
|
276
|
+
#
|
277
|
+
# grouping.summary :date,
|
278
|
+
# :opened => lambda { |g| g.sigma(:opened) },
|
279
|
+
# :closed => lambda { |g| g.sigma(:closed) },
|
280
|
+
# :order => [:date,:opened,:closed]
|
281
|
+
#
|
282
|
+
def summary(field,procs)
|
283
|
+
if procs[:order].kind_of?(Array)
|
284
|
+
cols = procs.delete(:order)
|
285
|
+
else
|
286
|
+
cols = procs.keys + [field]
|
287
|
+
end
|
288
|
+
expected = Table(cols) { |t|
|
289
|
+
each do |name,group|
|
290
|
+
t << procs.inject({field => name}) do |s,r|
|
291
|
+
s.merge(r[0] => r[1].call(group))
|
292
|
+
end
|
293
|
+
end
|
294
|
+
t.data.reorder(cols)
|
295
|
+
}
|
296
|
+
end
|
297
|
+
|
298
|
+
# Uses Ruport's built-in text formatter to render this Grouping
|
299
|
+
#
|
300
|
+
# Example:
|
301
|
+
#
|
302
|
+
# table = [[1,2,3],[4,5,6]].to_table(%w[a b c])
|
303
|
+
#
|
304
|
+
# grouping = Grouping.new(table, :by => "a")
|
305
|
+
#
|
306
|
+
# puts grouping.to_s
|
307
|
+
#
|
308
|
+
def to_s
|
309
|
+
as(:text)
|
310
|
+
end
|
311
|
+
|
312
|
+
# Calculates sums. If a column name or index is given, it will try to
|
313
|
+
# convert each element of that column to an integer or float
|
314
|
+
# and add them together. The sum is calculated across all groups in
|
315
|
+
# the grouping.
|
316
|
+
#
|
317
|
+
# If a block is given, it yields each Record in each Group so that you can
|
318
|
+
# do your own calculation.
|
319
|
+
#
|
320
|
+
# Example:
|
321
|
+
#
|
322
|
+
# table = [[1,2,3],[3,4,5],[5,6,7]].to_table(%w[col1 col2 col3])
|
323
|
+
# grouping = Grouping(table, :by => "col1")
|
324
|
+
# grouping.sigma("col2") #=> 12
|
325
|
+
# grouping.sigma(0) #=> 12
|
326
|
+
# grouping.sigma { |r| r.col2 + r.col3 } #=> 27
|
327
|
+
# grouping.sigma { |r| r.col2 + 1 } #=> 15
|
328
|
+
#
|
329
|
+
def sigma(column=nil)
|
330
|
+
inject(0) do |s, (group_name, group)|
|
331
|
+
if column
|
332
|
+
s + group.sigma(column)
|
333
|
+
else
|
334
|
+
s + group.sigma do |r|
|
335
|
+
yield(r)
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
alias_method :sum, :sigma
|
342
|
+
|
343
|
+
include Ruport::Controller::Hooks
|
344
|
+
renders_as_grouping
|
345
|
+
|
346
|
+
def self.inherited(base) #:nodoc:
|
347
|
+
base.renders_as_grouping
|
348
|
+
end
|
349
|
+
|
350
|
+
# Create a copy of the Grouping. Groups will be copied as well.
|
351
|
+
#
|
352
|
+
# Example:
|
353
|
+
#
|
354
|
+
# table = [[1,2,3],[4,5,6]].to_table(%w[a b c])
|
355
|
+
# one = Ruport::Data::Grouping.new(a, :by => "a")
|
356
|
+
#
|
357
|
+
# two = one.dup
|
358
|
+
#
|
359
|
+
def initialize_copy(from) #:nodoc:
|
360
|
+
@grouped_by = from.grouped_by
|
361
|
+
@data = from.data.inject({}) { |h,d| h.merge!({ d[0] => d[1].dup }) }
|
362
|
+
end
|
363
|
+
|
364
|
+
# Provides a shortcut for the <tt>as()</tt> method by converting a call to
|
365
|
+
# <tt>to_format_name</tt> into a call to <tt>as(:format_name)</tt>.
|
366
|
+
#
|
367
|
+
def method_missing(id,*args)
|
368
|
+
return as($1.to_sym,*args) if id.to_s =~ /^to_(.*)/
|
369
|
+
super
|
370
|
+
end
|
371
|
+
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
375
|
+
|
376
|
+
module Kernel
|
377
|
+
|
378
|
+
# Shortcut interface for creating Data::Grouping
|
379
|
+
#
|
380
|
+
# Example:
|
381
|
+
#
|
382
|
+
# a = Table(%w[a b c], :data => [[1,2,3],[4,5,6]])
|
383
|
+
# b = Grouping(a, :by => "a") #=> creates a new grouping on column "a"
|
384
|
+
#
|
385
|
+
def Grouping(*args)
|
386
|
+
Ruport::Data::Grouping.new(*args)
|
387
|
+
end
|
388
|
+
|
389
|
+
# Shortcut interface for creating Data::Group
|
390
|
+
#
|
391
|
+
# Example:
|
392
|
+
#
|
393
|
+
# g = Group('mygroup', :data => [[1,2,3],[4,5,6]],
|
394
|
+
# :column_names => %w[a b c]) #=> creates a new group named mygroup
|
395
|
+
#
|
396
|
+
def Group(name,opts={})
|
397
|
+
Ruport::Data::Group.new(opts.merge(:name => name))
|
398
|
+
end
|
399
|
+
end
|