ruport 0.5.4 → 0.6.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.
- data/AUTHORS +13 -4
- data/CHANGELOG +15 -1
- data/Rakefile +1 -1
- data/bin/rope +5 -1
- data/examples/basic_grouping.rb +13 -3
- data/examples/latex_table.rb +17 -0
- data/examples/line_graph_report.rb +23 -0
- data/examples/line_graph_report.rb.rej +15 -0
- data/examples/line_graph_report.rb~ +23 -0
- data/examples/line_plotter.rb +1 -0
- data/examples/sql_erb.rb +20 -0
- data/examples/template.rb +1 -1
- data/examples/text_processors.rb +3 -9
- data/lib/ruport.rb +2 -3
- data/lib/ruport.rb~ +69 -0
- data/lib/ruport/attempt.rb +63 -0
- data/lib/ruport/data.rb +1 -1
- data/lib/ruport/data.rb.rej +5 -0
- data/lib/ruport/data.rb~ +1 -0
- data/lib/ruport/data/groupable.rb +20 -0
- data/lib/ruport/data/record.rb +18 -13
- data/lib/ruport/data/table.rb +92 -26
- data/lib/ruport/data/table.rb~ +329 -0
- data/lib/ruport/format.rb +7 -1
- data/lib/ruport/format/engine/table.rb +2 -1
- data/lib/ruport/format/plugin.rb +8 -8
- data/lib/ruport/format/plugin/csv_plugin.rb +5 -1
- data/lib/ruport/format/plugin/html_plugin.rb +12 -8
- data/lib/ruport/format/plugin/latex_plugin.rb +50 -0
- data/lib/ruport/format/plugin/text_plugin.rb +38 -33
- data/lib/ruport/meta_tools.rb +3 -3
- data/lib/ruport/query.rb +21 -9
- data/lib/ruport/report.rb +35 -20
- data/lib/ruport/report/graph.rb +14 -0
- data/lib/ruport/system_extensions.rb +9 -8
- data/test/_test_groupable.rb +0 -0
- data/test/samples/data.tsv +3 -0
- data/test/samples/erb_test.sql +1 -0
- data/test/samples/query_test.sql +1 -0
- data/test/test_collection.rb +1 -1
- data/test/test_format.rb +1 -1
- data/test/test_format_engine.rb +17 -0
- data/test/test_groupable.rb +41 -0
- data/test/test_invoice.rb +1 -1
- data/test/test_latex.rb +20 -0
- data/test/test_plugin.rb +59 -29
- data/test/test_query.rb +12 -6
- data/test/test_record.rb +23 -4
- data/test/test_record.rb.rej +46 -0
- data/test/test_report.rb +32 -7
- data/test/test_table.rb +152 -4
- data/test/ts_all.rb +21 -0
- data/test/unit.log +61 -154
- metadata +32 -12
- data/lib/ruport/rails.rb +0 -2
- data/lib/ruport/rails/reportable.rb +0 -58
data/lib/ruport/data/table.rb
CHANGED
@@ -33,33 +33,33 @@ module Ruport::Data
|
|
33
33
|
#
|
34
34
|
# For building a table using ActiveRecord, have a look at Ruport::Reportable.
|
35
35
|
class Table < Collection
|
36
|
-
|
36
|
+
include Groupable
|
37
37
|
# Creates a new table based on the supplied options.
|
38
38
|
# Valid options are :data and :column_names.
|
39
39
|
#
|
40
40
|
# table = Table.new({:data => [1,2,3], [3,4,5],
|
41
41
|
# :column_names => %w[a b c]})
|
42
42
|
def initialize(options={})
|
43
|
-
@column_names = options[:column_names]
|
43
|
+
@column_names = options[:column_names] ? options[:column_names].dup : []
|
44
44
|
@data = []
|
45
|
-
|
45
|
+
if options[:data]
|
46
|
+
if options[:data].all? { |r| r.kind_of? Record }
|
47
|
+
record_tags = options[:data].map { |r| r.tags }
|
48
|
+
options[:data] = options[:data].map { |r| r.to_a }
|
49
|
+
end
|
50
|
+
options[:data].each { |e| self << e }
|
51
|
+
each { |r| r.tags = record_tags.shift } if record_tags
|
52
|
+
end
|
46
53
|
end
|
47
54
|
|
48
55
|
attr_reader :column_names
|
49
|
-
|
50
56
|
def_delegator :@data, :[]
|
51
|
-
|
52
57
|
# Sets the column names for this table. The single parameter should be
|
53
58
|
# an array listing the names of the columns.
|
54
59
|
#
|
55
60
|
# tbl = Table.new({:data => [1,2,3], [3,4,5], :column_names => %w[a b c]})
|
56
61
|
# tbl.column_names = %w[e f g]
|
57
62
|
def column_names=(other)
|
58
|
-
#FIXME: when column_names changes, we'll need to remove this
|
59
|
-
unless @column_names
|
60
|
-
@column_names = other.dup
|
61
|
-
each { |r| r.attributes = @column_names }
|
62
|
-
end
|
63
63
|
@column_names.replace(other.dup)
|
64
64
|
end
|
65
65
|
|
@@ -72,6 +72,7 @@ module Ruport::Data
|
|
72
72
|
def eql?(other)
|
73
73
|
data.eql?(other.data) && column_names.eql?(other.column_names)
|
74
74
|
end
|
75
|
+
|
75
76
|
alias_method :==, :eql?
|
76
77
|
|
77
78
|
# Uses Ruport's built-in text plugin to render this table into a string
|
@@ -107,22 +108,32 @@ module Ruport::Data
|
|
107
108
|
self
|
108
109
|
end
|
109
110
|
|
111
|
+
# Used to combine two tables. Throws an ArgumentError if the tables don't
|
112
|
+
# have identical columns.
|
113
|
+
#
|
114
|
+
# inky = Table.new(:data => [[1,2], [3,4]], :column_names => %w[a b])
|
115
|
+
# blinky = Table.new(:data => [[5,6]], :column_names => %w[a b])
|
116
|
+
# sue = inky + blinky
|
117
|
+
# sue.data #=> [[1,2],[3,4],[5,6]]
|
118
|
+
|
119
|
+
def +(other)
|
120
|
+
raise ArgumentError unless other.column_names == @column_names
|
121
|
+
Table.new(:column_names => @column_names, :data => @data + other.data)
|
122
|
+
end
|
123
|
+
|
110
124
|
# Reorders the columns that exist in the table. Operates directly
|
111
125
|
# on this table.
|
112
126
|
#
|
113
127
|
# data = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
114
128
|
# data.reorder!([1,0])
|
115
|
-
#
|
116
129
|
def reorder!(*indices)
|
117
130
|
indices = indices[0] if indices[0].kind_of? Array
|
118
|
-
if @column_names
|
131
|
+
if @column_names && !@column_names.empty?
|
119
132
|
x = if indices.all? { |i| i.kind_of? Integer }
|
120
133
|
indices.map { |i| @column_names[i] }
|
121
134
|
else
|
122
135
|
indices
|
123
136
|
end
|
124
|
-
# FIXME: @column_names.replace should and allow us to avoid the
|
125
|
-
# r.attributes hack below. This means this might be buggy.
|
126
137
|
@column_names = x
|
127
138
|
end
|
128
139
|
@data.each { |r|
|
@@ -145,14 +156,14 @@ module Ruport::Data
|
|
145
156
|
# use for the column in existing rows.
|
146
157
|
#
|
147
158
|
# data = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
148
|
-
# data.
|
159
|
+
# data.append_column({:name => 'new_column', :fill => 1)
|
149
160
|
def append_column(options={})
|
150
161
|
self.column_names += [options[:name]] if options[:name]
|
151
162
|
if block_given?
|
152
163
|
each { |r| r.data << yield(r) || options[:fill] }
|
153
164
|
else
|
154
165
|
each { |r| r.data << options[:fill] }
|
155
|
-
end
|
166
|
+
end; self
|
156
167
|
end
|
157
168
|
|
158
169
|
# Removes a column from the table. Any values in the specified column are
|
@@ -168,11 +179,15 @@ module Ruport::Data
|
|
168
179
|
# data.eql? [[1],[3]].to_table %w[a] #=> true
|
169
180
|
def remove_column(options={})
|
170
181
|
if options.kind_of? Integer
|
171
|
-
reorder!((0...data[0].length).to_a - [options])
|
182
|
+
return reorder!((0...data[0].length).to_a - [options])
|
183
|
+
elsif options.kind_of? Hash
|
184
|
+
name = options[:name]
|
172
185
|
else
|
173
|
-
|
174
|
-
|
175
|
-
|
186
|
+
name = options
|
187
|
+
end
|
188
|
+
|
189
|
+
raise ArgumentError unless column_names.include? name
|
190
|
+
reorder! column_names - [name]
|
176
191
|
end
|
177
192
|
|
178
193
|
# Create a shallow copy of the table: the same data elements are referenced
|
@@ -189,13 +204,22 @@ module Ruport::Data
|
|
189
204
|
# Loads a CSV file directly into a table using the fasterCSV library.
|
190
205
|
#
|
191
206
|
# data = Table.load('mydata.csv')
|
192
|
-
def self.load(csv_file, options
|
193
|
-
|
207
|
+
def self.load(csv_file, options={})
|
208
|
+
get_table_from_csv(:foreach, csv_file, options)
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.parse(string, options={})
|
212
|
+
get_table_from_csv(:parse,string,options)
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.get_table_from_csv(msg,param,options={})
|
216
|
+
options = {:has_names => true,
|
217
|
+
:csv_options => {} }.merge(options)
|
194
218
|
require "fastercsv"
|
195
219
|
loaded_data = self.new
|
196
220
|
|
197
221
|
first_line = true
|
198
|
-
FasterCSV.
|
222
|
+
FasterCSV.send(msg,param,options[:csv_options]) do |row|
|
199
223
|
if first_line && options[:has_names]
|
200
224
|
loaded_data.column_names = row
|
201
225
|
first_line = false
|
@@ -207,8 +231,6 @@ module Ruport::Data
|
|
207
231
|
end ; loaded_data
|
208
232
|
end
|
209
233
|
|
210
|
-
|
211
|
-
|
212
234
|
# Allows you to split tables into multiple tables for grouping.
|
213
235
|
#
|
214
236
|
# Example:
|
@@ -259,13 +281,57 @@ module Ruport::Data
|
|
259
281
|
:column_names => c
|
260
282
|
)
|
261
283
|
}
|
262
|
-
if options[:group].kind_of? Array
|
284
|
+
rec = if options[:group].kind_of? Array
|
263
285
|
Ruport::Data::Record.new(data,
|
264
286
|
:attributes => group.map { |e| e.join("_") } )
|
265
287
|
else
|
266
288
|
Ruport::Data::Record.new data, :attributes => group
|
267
289
|
end
|
290
|
+
class << rec
|
291
|
+
def each_group; attributes.each { |a| yield(a) }; end
|
292
|
+
end; rec
|
293
|
+
end
|
294
|
+
|
295
|
+
# Calculates sums. If a column name or index is given, it will try to
|
296
|
+
# convert each element of that column to an integer or float
|
297
|
+
# and add it together
|
298
|
+
#
|
299
|
+
# If a block is given, yields each Record so that you can do a calculation.
|
300
|
+
#
|
301
|
+
#
|
302
|
+
# Example:
|
303
|
+
#
|
304
|
+
#
|
305
|
+
# table = [[1,2],[3,4],[5,6]].to_table(%w[col1 col2])
|
306
|
+
# table.sigma("col1") #=> 9
|
307
|
+
# table.sigma(0) #=> 9
|
308
|
+
# table.sigma { |r| r.col1 + r.col2 } #=> 21
|
309
|
+
# table.sigma { |r| r.col2 + 1 } #=> 15
|
310
|
+
#
|
311
|
+
# For the non-mathy, this has been aliased as Table#sum
|
312
|
+
def sigma(column=nil)
|
313
|
+
inject(0) { |s,r|
|
314
|
+
if column
|
315
|
+
s + if r[column].kind_of? Numeric
|
316
|
+
r[column]
|
317
|
+
else
|
318
|
+
r[column] =~ /\./ ? r[column].to_f : r[column].to_i
|
319
|
+
end
|
320
|
+
else
|
321
|
+
s + yield(r)
|
322
|
+
end
|
323
|
+
}
|
268
324
|
end
|
325
|
+
|
326
|
+
alias_method :sum, :sigma
|
269
327
|
|
270
328
|
end
|
329
|
+
|
330
|
+
end
|
331
|
+
|
332
|
+
module Ruport::Data::TableHelper
|
333
|
+
def table(names=[])
|
334
|
+
t = [].to_table(names)
|
335
|
+
yield(t) if block_given?; t
|
336
|
+
end
|
271
337
|
end
|
@@ -0,0 +1,329 @@
|
|
1
|
+
# The Ruport Data Collections.
|
2
|
+
# Authors: Gregory Brown / Dudley Flanders
|
3
|
+
#
|
4
|
+
# This is Free Software. For details, see LICENSE and COPYING
|
5
|
+
# Copyright 2006 by respective content owners, all rights reserved.
|
6
|
+
|
7
|
+
class Array
|
8
|
+
# Converts an array to a Ruport::Data::Table object, ready to
|
9
|
+
# use in your reports.
|
10
|
+
#
|
11
|
+
# [[1,2],[3,4]].to_table(%w[a b])
|
12
|
+
def to_table(options={})
|
13
|
+
options = { :column_names => options } if options.kind_of? Array
|
14
|
+
Ruport::Data::Table.new({:data => self}.merge(options))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Ruport::Data
|
19
|
+
|
20
|
+
# This class is one of the core classes for building and working with data
|
21
|
+
# in Ruport. The idea is to get your data into a standard form, regardless
|
22
|
+
# of its source (a database, manual arrays, ActiveRecord, CSVs, etc.).
|
23
|
+
#
|
24
|
+
# Table is intended to be used as the data store for structured, tabular
|
25
|
+
# data - Ruport::Data::Set is an alternate data store intended for less
|
26
|
+
# structured data.
|
27
|
+
#
|
28
|
+
# Once your data is in a Ruport::Data::Table object, it can be manipulated
|
29
|
+
# to suit your needs, then used to build a report.
|
30
|
+
#
|
31
|
+
# Included in this class are methods to create Tables manually and from CSV
|
32
|
+
# files.
|
33
|
+
#
|
34
|
+
# For building a table using ActiveRecord, have a look at Ruport::Reportable.
|
35
|
+
class Table < Collection
|
36
|
+
|
37
|
+
# Creates a new table based on the supplied options.
|
38
|
+
# Valid options are :data and :column_names.
|
39
|
+
#
|
40
|
+
# table = Table.new({:data => [1,2,3], [3,4,5],
|
41
|
+
# :column_names => %w[a b c]})
|
42
|
+
def initialize(options={})
|
43
|
+
@column_names = options[:column_names] ? options[:column_names].dup : []
|
44
|
+
@data = []
|
45
|
+
if options[:data]
|
46
|
+
if options[:data].all? { |r| r.kind_of? Record }
|
47
|
+
record_tags = options[:data].map { |r| r.tags }
|
48
|
+
options[:data] = options[:data].map { |r| r.to_a }
|
49
|
+
end
|
50
|
+
options[:data].each { |e| self << e }
|
51
|
+
each { |r| r.tags = record_tags.shift } if record_tags
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_reader :column_names
|
56
|
+
def_delegator :@data, :[]
|
57
|
+
# Sets the column names for this table. The single parameter should be
|
58
|
+
# an array listing the names of the columns.
|
59
|
+
#
|
60
|
+
# tbl = Table.new({:data => [1,2,3], [3,4,5], :column_names => %w[a b c]})
|
61
|
+
# tbl.column_names = %w[e f g]
|
62
|
+
def column_names=(other)
|
63
|
+
@column_names.replace(other.dup)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Compares this table to another table and returns true if
|
67
|
+
# both the data and column names are equal
|
68
|
+
#
|
69
|
+
# one = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
70
|
+
# two = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
71
|
+
# one.eql?(two) #=> true
|
72
|
+
def eql?(other)
|
73
|
+
data.eql?(other.data) && column_names.eql?(other.column_names)
|
74
|
+
end
|
75
|
+
|
76
|
+
alias_method :==, :eql?
|
77
|
+
|
78
|
+
# Uses Ruport's built-in text plugin to render this table into a string
|
79
|
+
#
|
80
|
+
# data = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
81
|
+
# puts data.to_s
|
82
|
+
def to_s
|
83
|
+
as(:text)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Used to add extra data to the table. The single parameter can be an
|
87
|
+
# Array, Hash or Ruport::Data::Record.
|
88
|
+
#
|
89
|
+
# data = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
90
|
+
# data << [8,9]
|
91
|
+
# data << { :a => 4, :b => 5}
|
92
|
+
# data << Ruport::Data::Record.new [5,6], :attributes => %w[a b]
|
93
|
+
def <<(other)
|
94
|
+
case other
|
95
|
+
when Array
|
96
|
+
@data << Record.new(other, :attributes => @column_names)
|
97
|
+
when Hash
|
98
|
+
raise ArgumentError unless @column_names
|
99
|
+
arr = @column_names.map { |k| other[k] }
|
100
|
+
@data << Record.new(arr, :attributes => @column_names)
|
101
|
+
when Record
|
102
|
+
raise ArgumentError unless column_names.eql? other.attributes
|
103
|
+
@data << Record.new(other.data, :attributes => @column_names)
|
104
|
+
@data.last.tags = other.tags.dup
|
105
|
+
else
|
106
|
+
raise ArgumentError
|
107
|
+
end
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# Used to combine two tables. Throws an ArgumentError if the tables don't
|
112
|
+
# have identical columns.
|
113
|
+
#
|
114
|
+
# inky = Table.new(:data => [[1,2], [3,4]], :column_names => %w[a b])
|
115
|
+
# blinky = Table.new(:data => [[5,6]], :column_names => %w[a b])
|
116
|
+
# sue = inky + blinky
|
117
|
+
# sue.data #=> [[1,2],[3,4],[5,6]]
|
118
|
+
|
119
|
+
def +(other)
|
120
|
+
raise ArgumentError unless other.column_names == @column_names
|
121
|
+
Table.new(:column_names => @column_names, :data => @data + other.data)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Reorders the columns that exist in the table. Operates directly
|
125
|
+
# on this table.
|
126
|
+
#
|
127
|
+
# data = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
128
|
+
# data.reorder!([1,0])
|
129
|
+
def reorder!(*indices)
|
130
|
+
indices = indices[0] if indices[0].kind_of? Array
|
131
|
+
if @column_names && !@column_names.empty?
|
132
|
+
x = if indices.all? { |i| i.kind_of? Integer }
|
133
|
+
indices.map { |i| @column_names[i] }
|
134
|
+
else
|
135
|
+
indices
|
136
|
+
end
|
137
|
+
@column_names = x
|
138
|
+
end
|
139
|
+
@data.each { |r|
|
140
|
+
r.reorder_data!(*indices)
|
141
|
+
r.attributes = @column_names
|
142
|
+
}; self
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns a copy of the table with its columns in the requested order.
|
146
|
+
#
|
147
|
+
# one = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
148
|
+
# two = one.reorder!([1,0])
|
149
|
+
def reorder(*indices)
|
150
|
+
dup.reorder!(*indices)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Adds an extra column to the table. Accepts an options Hash as its
|
154
|
+
# only parameter which should contain 2 keys - :name and :fill.
|
155
|
+
# :name specifies the new columns name, and :fill the default value to
|
156
|
+
# use for the column in existing rows.
|
157
|
+
#
|
158
|
+
# data = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
159
|
+
# data.append_column({:name => 'new_column', :fill => 1)
|
160
|
+
def append_column(options={})
|
161
|
+
self.column_names += [options[:name]] if options[:name]
|
162
|
+
if block_given?
|
163
|
+
each { |r| r.data << yield(r) || options[:fill] }
|
164
|
+
else
|
165
|
+
each { |r| r.data << options[:fill] }
|
166
|
+
end; self
|
167
|
+
end
|
168
|
+
|
169
|
+
# Removes a column from the table. Any values in the specified column are
|
170
|
+
# lost.
|
171
|
+
# data = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
172
|
+
# data.append_column({:name => 'new_column', :fill => 1)
|
173
|
+
# data.remove_column({:name => 'new_column')
|
174
|
+
# data == Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
175
|
+
# #=> true
|
176
|
+
#
|
177
|
+
# data = [[1,2],[3,4]].to_table
|
178
|
+
# data.remove_column(1)
|
179
|
+
# data.eql? [[1],[3]].to_table %w[a] #=> true
|
180
|
+
def remove_column(options={})
|
181
|
+
if options.kind_of? Integer
|
182
|
+
return reorder!((0...data[0].length).to_a - [options])
|
183
|
+
elsif options.kind_of? Hash
|
184
|
+
name = options[:name]
|
185
|
+
else
|
186
|
+
name = options
|
187
|
+
end
|
188
|
+
|
189
|
+
raise ArgumentError unless column_names.include? name
|
190
|
+
reorder! column_names - [name]
|
191
|
+
end
|
192
|
+
|
193
|
+
# Create a shallow copy of the table: the same data elements are referenced
|
194
|
+
# by both the old and new table.
|
195
|
+
#
|
196
|
+
# one = Table.new({:data => [1,2], [3,4], :column_names => %w[a b]})
|
197
|
+
# two = one.dup
|
198
|
+
def dup
|
199
|
+
a = self.class.new(:data => @data, :column_names => @column_names)
|
200
|
+
a.tags = tags.dup
|
201
|
+
return a
|
202
|
+
end
|
203
|
+
|
204
|
+
# Loads a CSV file directly into a table using the fasterCSV library.
|
205
|
+
#
|
206
|
+
# data = Table.load('mydata.csv')
|
207
|
+
def self.load(csv_file, options={})
|
208
|
+
get_table_from_csv(:foreach, csv_file, options)
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.parse(string, options={})
|
212
|
+
get_table_from_csv(:parse,string,options)
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.get_table_from_csv(msg,param,options={})
|
216
|
+
options = {:has_names => true,
|
217
|
+
:csv_options => {} }.merge(options)
|
218
|
+
require "fastercsv"
|
219
|
+
loaded_data = self.new
|
220
|
+
|
221
|
+
first_line = true
|
222
|
+
FasterCSV.send(msg,param,options[:csv_options]) do |row|
|
223
|
+
if first_line && options[:has_names]
|
224
|
+
loaded_data.column_names = row
|
225
|
+
first_line = false
|
226
|
+
elsif !block_given?
|
227
|
+
loaded_data << row
|
228
|
+
else
|
229
|
+
yield(loaded_data,row)
|
230
|
+
end
|
231
|
+
end ; loaded_data
|
232
|
+
end
|
233
|
+
|
234
|
+
# Allows you to split tables into multiple tables for grouping.
|
235
|
+
#
|
236
|
+
# Example:
|
237
|
+
#
|
238
|
+
# a = Table.new(:column_name => %w[name a b c])
|
239
|
+
# a << ["greg",1,2,3]
|
240
|
+
# a << ["joe", 2,3,4]
|
241
|
+
# a << ["greg",7,8,9]
|
242
|
+
# a << ["joe", 1,2,3]
|
243
|
+
#
|
244
|
+
# b = a.split :group => "name"
|
245
|
+
#
|
246
|
+
# b.greg.eql? [[1,2,3],[7,8,9]].to_table(%w[a b c]) #=> true
|
247
|
+
# b["joe"].eql? [[2,3,4],[1,2,3]].to_table(%w[a b c]) #=> true
|
248
|
+
#
|
249
|
+
# You can also pass an array to :group, and the resulting attributes in
|
250
|
+
# the group will be joined by an underscore.
|
251
|
+
#
|
252
|
+
# Example:
|
253
|
+
#
|
254
|
+
# a = Table.new(:column_names => %w[first_name last_name x]
|
255
|
+
# a << %w[greg brown foo]
|
256
|
+
# a << %w[greg gibson bar]
|
257
|
+
# a << %w[greg brown baz]
|
258
|
+
#
|
259
|
+
# b = a.split :group => %w[first_name last_name]
|
260
|
+
# a.greg_brown.length #=> 2
|
261
|
+
# a["greg_gibson"].length #=> 1
|
262
|
+
# a.greg_brown[0].x #=> "foo"
|
263
|
+
def split(options={})
|
264
|
+
if options[:group].kind_of? Array
|
265
|
+
group = map { |r| options[:group].map { |e| r[e] } }.uniq
|
266
|
+
data = group.inject([]) { |s,g|
|
267
|
+
s + [select { |r| options[:group].map { |e| r[e] }.eql?(g) }]
|
268
|
+
}
|
269
|
+
c = column_names - options[:group]
|
270
|
+
else
|
271
|
+
group = map { |r| r[options[:group]] }.uniq
|
272
|
+
data = group.inject([]) { |s,g|
|
273
|
+
s + [select { |r| r[options[:group]].eql?(g) }]
|
274
|
+
}
|
275
|
+
c = column_names - [options[:group]]
|
276
|
+
|
277
|
+
end
|
278
|
+
data.map! { |g|
|
279
|
+
Ruport::Data::Table.new(
|
280
|
+
:data => g.map { |x| x.reorder(*c) },
|
281
|
+
:column_names => c
|
282
|
+
)
|
283
|
+
}
|
284
|
+
rec = if options[:group].kind_of? Array
|
285
|
+
Ruport::Data::Record.new(data,
|
286
|
+
:attributes => group.map { |e| e.join("_") } )
|
287
|
+
else
|
288
|
+
Ruport::Data::Record.new data, :attributes => group
|
289
|
+
end
|
290
|
+
class << rec
|
291
|
+
def each_group; attributes.each { |a| yield(a) }; end
|
292
|
+
end; rec
|
293
|
+
end
|
294
|
+
|
295
|
+
# Calculates sums. If a column name or index is given, it will try to
|
296
|
+
# convert each element of that column to an integer or float
|
297
|
+
# and add it together
|
298
|
+
#
|
299
|
+
# If a block is given, yields each Record so that you can do a calculation.
|
300
|
+
#
|
301
|
+
#
|
302
|
+
# Example:
|
303
|
+
#
|
304
|
+
#
|
305
|
+
# table = [[1,2],[3,4],[5,6]].to_table(%w[col1 col2])
|
306
|
+
# table.sigma("col1") #=> 9
|
307
|
+
# table.sigma(0) #=> 9
|
308
|
+
# table.sigma { |r| r.col1 + r.col2 } #=> 21
|
309
|
+
# table.sigma { |r| r.col2 + 1 } #=> 15
|
310
|
+
#
|
311
|
+
# For the non-mathy, this has been aliased as Table#sum
|
312
|
+
def sigma(column=nil)
|
313
|
+
inject(0) { |s,r|
|
314
|
+
if column
|
315
|
+
s + if r[column].kind_of? Numeric
|
316
|
+
r[column]
|
317
|
+
else
|
318
|
+
r[column] =~ /\./ ? r[column].to_f : r[column].to_i
|
319
|
+
end
|
320
|
+
else
|
321
|
+
s + yield(r)
|
322
|
+
end
|
323
|
+
}
|
324
|
+
end
|
325
|
+
|
326
|
+
alias_method :sum, :sigma
|
327
|
+
|
328
|
+
end
|
329
|
+
end
|