Parent

Namespace

Included Modules

Files

Ruport::Data::Table

Overview

This class is one of the core classes for building and working with data in Ruport. The idea is to get your data into a standard form, regardless of its source (a database, manual arrays, ActiveRecord, CSVs, etc.).

Table is intended to be used as the data store for structured, tabular data.

Once your data is in a Table object, it can be manipulated to suit your needs, then used to build a report.

Attributes

column_names[R]

This Table's column names

data[R]

This Table's data

Public Class Methods

new(options={}) click to toggle source

Creates a new table based on the supplied options.

Valid options:

:data

An Array of Arrays representing the records in this Table.

:column_names

An Array containing the column names for this Table.

:filters

A proc or array of procs that set up conditions to filter the data being added to the table.

:transforms

A proc or array of procs that perform transformations on the data being added to the table.

:record_class

Specify the class of the table's records.

Example:

table = Table.new :data => [[1,2,3], [3,4,5]], 
                  :column_names => %w[a b c]
# File lib/ruport/data/table.rb, line 265
def initialize(options={})
  @column_names = options[:column_names] ? options[:column_names].dup : []
  @record_class = options[:record_class] &&
                  options[:record_class].name || "Ruport::Data::Record"
  @data         = []  
  
  feeder = Feeder.new(self)
 
  Array(options[:filters]).each { |f| feeder.filter(&f) }
  Array(options[:transforms]).each { |t| feeder.transform(&t) }
  
  if options[:data]
    options[:data].each do |e|
      if e.kind_of?(Record)
        e = if @column_names.empty? or 
               e.attributes.all? { |a| a.kind_of?(Numeric) }
          e.to_a
        else
          e.to_hash.values_at(*@column_names)  
        end
      end
      r = recordize(e)
                                                 
      feeder << r
    end  
  end    
  
  yield(feeder) if block_given?  
end

Public Instance Methods

+(other) click to toggle source

Used to merge two Tables by rows. Raises an ArgumentError if the Tables don't have identical columns.

Example:

inky = Table.new :data => [[1,2], [3,4]], 
                 :column_names => %w[a b]

blinky = Table.new :data => [[5,6]], 
                   :column_names => %w[a b]

sue = inky + blinky
sue.data #=> [[1,2],[3,4],[5,6]]
# File lib/ruport/data/table.rb, line 387
def +(other)
  raise ArgumentError unless other.column_names == @column_names
  self.class.new( :column_names => @column_names, 
                  :data => @data + other.data,
                  :record_class => record_class )
end
<<(row) click to toggle source

Used to add extra data to the Table. row can be an Array, Hash or Record. It also can be anything that implements a meaningful to_hash or to_ary.

Example:

data = Table.new :data => [[1,2], [3,4]], 
                 :column_names => %w[a b]
data << [8,9]
data << { :a => 4, :b => 5}
data << Record.new [5,6], :attributes => %w[a b]
# File lib/ruport/data/table.rb, line 363
def <<(row)
  @data << recordize(row)
  return self   
end
==(other) click to toggle source
Alias for: eql?
add_column(name,options={}) click to toggle source

Adds an extra column to the Table.

Available Options:

:default

The default value to use for the column in existing rows. Set to nil if not specified.

:position

Inserts the column at the indicated position number.

:before

Inserts the new column before the column indicated (by name).

:after

Inserts the new column after the column indicated (by name).

If a block is provided, it will be used to build up the column.

Example:

data = Table("a","b") { |t| t << [1,2] << [3,4] }

# basic usage, column full of 1's
data.add_column 'new_column', :default => 1

# new empty column before new_column
data.add_column 'new_col2', :before => 'new_column'

# new column placed just after column a
data.add_column 'new_col3', :position => 1

# new column built via a block, added at the end of the table
data.add_column("new_col4") { |r| r.a + r.b }
# File lib/ruport/data/table.rb, line 457
def add_column(name,options={})
  if pos = options[:position]
    column_names.insert(pos,name)   
  elsif pos = options[:after]
    column_names.insert(column_names.index(pos)+1,name)   
  elsif pos = options[:before]
    column_names.insert(column_names.index(pos),name)
  else
    column_names << name
  end 

  if block_given?
    each { |r| r[name] = yield(r) || options[:default] }
  else
    each { |r| r[name] = options[:default] }
  end; self
end
add_columns(names,options={}) click to toggle source

Add multiple extra columns to the Table. See add_column for a list of available options.

Example:

data = Table("a","b") { |t| t << [1,2] << [3,4] }

data.add_columns ['new_column_1','new_column_2'], :default => 1
# File lib/ruport/data/table.rb, line 484
def add_columns(names,options={})     
  raise "Greg isn't smart enough to figure this out.\n"+
        "Send ideas in at http://list.rubyreports.org" if block_given?
  need_reverse = !!(options[:after] || options[:position])
  names = names.reverse if need_reverse
  names.each { |n| add_column(n,options) } 
  self
end
column(name) click to toggle source

Returns an array of values for the given column name.

Example:

table = [[1,2],[3,4],[5,6]].to_table(%w[col1 col2])
table.column("col1")   #=> [1,3,5]
# File lib/ruport/data/table.rb, line 688
def column(name)
  case(name)
  when Integer
    unless column_names.empty?
      raise ArgumentError if name > column_names.length         
    end
  else
    raise ArgumentError unless column_names.include?(name)
  end
     
  map { |r| r[name] }
end
column_names=(new_column_names) click to toggle source

Sets the column names for this table. new_column_names should be an array listing the names of the columns.

Example:

table = Table.new :data => [[1,2,3], [3,4,5]], 
                  :column_names => %w[a b c]

table.column_names = %w[e f g]
# File lib/ruport/data/table.rb, line 315
def column_names=(new_column_names)
  columns = new_column_names.zip(@column_names)
  @column_names.replace(new_column_names.dup)
  unless @data.empty?
    each { |r|
      columns.each_with_index { |x,i|
        if x[1].nil?
          r.rename_attribute(i,x[0])
        elsif x[1] != x[0]
          r.rename_attribute(x[1],x[0],false)
        end
      }
      r.send(:reindex, @column_names)
    }
  end
end
eql?(other) click to toggle source

Compares this Table to another Table and returns true if both the data and column_names are equal.

Example:

one = Table.new :data => [[1,2], [3,4]], 
                :column_names => %w[a b]

two = Table.new :data => [[1,2], [3,4]], 
                :column_names => %w[a b]

one.eql?(two) #=> true
# File lib/ruport/data/table.rb, line 345
def eql?(other)
  data.eql?(other.data) && column_names.eql?(other.column_names) 
end
Also aliased as: ==
feed_element(row) click to toggle source
# File lib/ruport/data/table.rb, line 874
def feed_element(row)
   recordize(row)
end
initialize_copy(from) click to toggle source

Create a copy of the Table. Records will be copied as well.

Example:

one = Table.new :data => [[1,2], [3,4]], 
                :column_names => %w[a b]
two = one.dup
# File lib/ruport/data/table.rb, line 827
def initialize_copy(from)
  @record_class = from.record_class.name
  @column_names = from.column_names.dup
  @data = []
  from.data.each { |r| self << r.dup }
end
method_missing(id,*args,&block) click to toggle source

Provides a shortcut for the as() method by converting a call to as(:format_name) into a call to to_format_name

Also converts a call to rows_with_columnname to a call to rows_with(:columnname => args[0]).

# File lib/ruport/data/table.rb, line 868
def method_missing(id,*args,&block)
 return as($1.to_sym,*args,&block) if id.to_s =~ /^to_(.*)/ 
 return rows_with($1.to_sym => args[0]) if id.to_s =~ /^rows_with_(.*)/
 super
end
pivot(pivot_column, options = {}) click to toggle source

Creates a new table with values from the specified pivot column transformed into columns.

Required options:

:group_by

The name of a column whose unique values should become rows in the new table.

:values

The name of a column that should supply the values for the pivoted columns.

Optional:

:pivot_order

An ordering specification for the pivoted columns, in terms of the source rows. If this is a Proc there is an optional second argument that receives the name of the pivot column, which due to implementation oddity currently is removed from the row provided in the first argument. This wart will likely be fixed in a future version.

Example:

Given a table my_table:

+-------------------------+
| Group | Segment | Value |
+-------------------------+
|   A   |    1    |   0   |
|   A   |    2    |   1   |
|   B   |    1    |   2   |
|   B   |    2    |   3   |
+-------------------------+

Pivoting the table on the Segment column:

my_table.pivot('Segment', :group_by => 'Group', :values => 'Value',
  :pivot_order => proc {|row, name| name})

Yields a new table like this:

+---------------+
| Group | 1 | 2 |
+---------------+
|   A   | 0 | 1 |
|   B   | 2 | 3 |
+---------------+
# File lib/ruport/data/table.rb, line 141
def pivot(pivot_column, options = {})
  group_column = options[:group_by] || 
    raise(ArgumentError, ":group_by option required")
  value_column = options[:values]   || 
    raise(ArgumentError, ":values option required")
  Pivot.new(
    self, group_column, pivot_column, value_column, options
  ).to_table
end
record_class() click to toggle source

Returns the record class constant being used by the table.

# File lib/ruport/data/table.rb, line 369
def record_class
  @record_class.split("::").inject(Class) { |c,el| c.send(:const_get,el) }
end
reduce(columns=column_names,range=nil,&block) click to toggle source

Generates a sub table in place, modifying the receiver. See documentation for sub_table.

# File lib/ruport/data/table.rb, line 672
def reduce(columns=column_names,range=nil,&block)
  t = sub_table(columns,range,&block)
  @data = t.data
  @column_names = t.column_names
  self
end
Also aliased as: sub_table!
remove_column(col) click to toggle source

Removes the given column from the table. May use name or position.

Example:

table.remove_column(0) #=> removes the first column
table.remove_column("apple") #=> removes column named apple
# File lib/ruport/data/table.rb, line 500
def remove_column(col)
  col = column_names[col] if col.kind_of? Fixnum
  column_names.delete(col)
  each { |r| r.send(:delete,col) }
end
remove_columns(*cols) click to toggle source

Removes multiple columns from the table. May use name or position Will autosplat arrays.

Example: table.remove_columns('a','b','c') table.remove_columns()

# File lib/ruport/data/table.rb, line 513
def remove_columns(*cols)
  cols = cols[0] if cols[0].kind_of? Array
  cols.each { |col| remove_column(col) }
end
rename_column(old_name,new_name) click to toggle source

Renames a column. Will update Record attributes as well.

Example:

old_values = table.map { |r| r.a }
table.rename_column("a","zanzibar")
new_values = table.map { |r| r.zanzibar }
old_values == new_values #=> true
table.column_names.include?("a") #=> false
# File lib/ruport/data/table.rb, line 528
def rename_column(old_name,new_name)
  index = column_names.index(old_name) or return
  self.column_names[index] = new_name
  each { |r| r.rename_attribute(old_name,new_name,false)} 
end
rename_columns(old_cols=nil,new_cols=nil) click to toggle source

Renames multiple columns. Takes either a hash of "old" => "new" names or two arrays of names %w[old names],%w[new names].

Example:

table.column_names #=> ["a", "b"]
table.rename_columns ["a", "b"], ["c", "d"]
table.column_names #=> ["c", "d"]

table.column_names #=> ["a", "b"]
table.rename_columns {"a" => "c", "b" => "d"}
table.column_names #=> ["c", "d"]
# File lib/ruport/data/table.rb, line 547
def rename_columns(old_cols=nil,new_cols=nil)
  if block_given?
    if old_cols
      old_cols.each { |c| rename_column(c,yield(c)) }
    else
      column_names.each { |c| rename_column(c,yield(c)) }
    end
    return
  end
  
  raise ArgumentError unless old_cols

  if new_cols
    raise ArgumentError,
      "odd number of arguments" unless old_cols.size == new_cols.size
    h = Hash[*old_cols.zip(new_cols).flatten]
  else
    h = old_cols
  end
  h.each {|old,new| rename_column(old,new) }
end
reorder(*indices) click to toggle source

Allows you to change the order of, or reduce the number of columns in a Table.

Example:

a = Table.new :data => [[1,2,3],[4,5,6]], :column_names => %w[a b c]
a.reorder("b","c","a")
a.column_names #=> ["b","c","a"]

a = Table.new :data => [[1,2,3],[4,5,6]], :column_names => %w[a b c]
a.reorder(1,2,0)
a.column_names #=> ["b","c","a"]

a = Table.new :data => [[1,2,3],[4,5,6]], :column_names => %w[a b c]
a.reorder(0,2)
a.column_names #=> ["a","c"]
# File lib/ruport/data/table.rb, line 411
def reorder(*indices)
  raise(ArgumentError,"Can't reorder without column names set!") if
    @column_names.empty?
  
  indices = indices[0] if indices[0].kind_of? Array
  
  if indices.all? { |i| i.kind_of? Integer }  
    indices.map! { |i| @column_names[i] }  
  end
  
  reduce(indices)
end
replace_column(old_col,new_col=nil,&block) click to toggle source

Allows you to specify a new column to replace an existing column in your table via a block.

Example:

>> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] } >> a.replace_column("c","c2") { |r| r.c * 2 + r.a }

>> puts a

+------------+
| a | b | c2 |
+------------+
| 1 | 2 |  7 |
| 4 | 5 | 16 |
+------------+
# File lib/ruport/data/table.rb, line 618
def replace_column(old_col,new_col=nil,&block)
  if new_col
    add_column(new_col,:after => old_col,&block)
    remove_column(old_col)
  else
    each { |r| r[old_col] = yield(r) }
  end
end
rows_with(columns,&block) click to toggle source

Get an array of records from the Table limited by the criteria specified.

Example:

table = Table.new :data => [[1,2,3], [1,4,6], [4,5,6]], 
                  :column_names => %w[a b c]
table.rows_with(:a => 1)           #=> [[1,2,3], [1,4,6]]
table.rows_with(:a => 1, :b => 4)  #=> [[1,4,6]]
table.rows_with_a(1)               #=> [[1,2,3], [1,4,6]]
table.rows_with(%w[a b]) {|a,b| [a,b] == [1,4] }  #=> [[1,4,6]]
# File lib/ruport/data/table.rb, line 809
def rows_with(columns,&block) 
  select { |r|
    if block
      block[*(columns.map { |c| r.get(c) })]
    else
      columns.all? { |k,v| r.get(k) == v }
    end
  }
end
sigma(column=nil) click to toggle source

Calculates sums. If a column name or index is given, it will try to convert each element of that column to an integer or float and add them together.

If a block is given, it yields each Record so that you can do your own calculation.

Example:

table = [[1,2],[3,4],[5,6]].to_table(%w[col1 col2])
table.sigma("col1") #=> 9
table.sigma(0)      #=> 9
table.sigma { |r| r.col1 + r.col2 } #=> 21
table.sigma { |r| r.col2 + 1 } #=> 15
# File lib/ruport/data/table.rb, line 716
def sigma(column=nil)
  inject(0) { |s,r| 
    if column
      s + if r.get(column).kind_of? Numeric
        r.get(column)
      else
        r.get(column) =~ /\./ ? r.get(column).to_f : r.get(column).to_i
      end
    else
      s + yield(r)
    end
  }      
end
Also aliased as: sum
sort_rows_by(col_names=nil, options={}, &block) click to toggle source

Returns a sorted table. If col_names is specified, the block is ignored and the table is sorted by the named columns.

The second argument specifies sorting options. Currently only :order is supported. Default order is ascending, to sort decending use :order => :descending

Example:

table = [[4, 3], [2, 5], [7, 1]].to_table(%w[col1 col2 ])

# returns a new table sorted by col1
table.sort_rows_by {|r| r["col1"]}

# returns a new table sorted by col1, in descending order
table.sort_rows_by(nil, :order => :descending) {|r| r["col1"]}

# returns a new table sorted by col2
table.sort_rows_by(["col2"])

# returns a new table sorted by col2, descending order
table.sort_rows_by("col2", :order => :descending)

# returns a new table sorted by col1, then col2
table.sort_rows_by(["col1", "col2"])

# returns a new table sorted by col1, then col2, in descending order
table.sort_rows_by(["col1", "col2"], :order => descending)
# File lib/ruport/data/table.rb, line 761
def sort_rows_by(col_names=nil, options={}, &block)
  # stabilizer is needed because of 
  # http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/170565
  stabilizer = 0
  
  nil_rows, sortable = partition do |r| 
    Array(col_names).any? { |c| r[c].nil? } 
  end

  data_array =
    if col_names
      sortable.sort_by do |r| 
        stabilizer += 1
        [Array(col_names).map {|col| r[col]}, stabilizer] 
      end
    else
      sortable.sort_by(&block)
    end                 
                                                           
  data_array += nil_rows
  data_array.reverse! if options[:order] == :descending    

  table = self.class.new( :data => data_array, 
                          :column_names => @column_names,
                          :record_class => record_class )

  return table
end
sort_rows_by!(col_names=nil,options={},&block) click to toggle source

Same as Table#sort_rows_by, but self modifying. See sort_rows_by for documentation.

# File lib/ruport/data/table.rb, line 793
def sort_rows_by!(col_names=nil,options={},&block)
  table = sort_rows_by(col_names,options,&block) 
  @data = table.data
end
sub_table(cor=column_names,range=nil,&block) click to toggle source

Generates a sub table

Examples:

table = [[1,2,3,4],[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d])

Using column_names and a range:

sub_table = table.sub_table(%w[a b],1..-1)
sub_table == [[5,6],[9,10]].to_table(%w[a b]) #=> true

Using just column_names:

sub_table = table.sub_table(%w[a d])
sub_table == [[1,4],[5,8],[9,12]].to_table(%w[a d]) #=> true

Using column_names and a block:

sub_table = table.sub_table(%w[d b]) { |r| r.a < 6 } 
sub_table == [[4,2],[8,6]].to_table(%w[d b]) #=> true

Using a range for row reduction:

sub_table = table.sub_table(1..-1)
sub_table == [[5,6,7,8],[9,10,11,12]].to_table(%w[a b c d]) #=> true

Using just a block:

sub_table = table.sub_table { |r| r.c > 10 }
sub_table == [[9,10,11,12]].to_table(%w[a b c d]) #=> true
# File lib/ruport/data/table.rb, line 657
def sub_table(cor=column_names,range=nil,&block)
  if range
    self.class.new(:column_names => cor,:data => data[range])
  elsif cor.kind_of?(Range)
    self.class.new(:column_names => column_names,:data => data[cor])
  elsif block
    self.class.new( :column_names => cor, :data => data.select(&block))
  else
    self.class.new( :column_names => cor, :data => data)  
  end 
end
sub_table!(columns=column_names,range=nil,&block) click to toggle source
Alias for: reduce
sum(column=nil) click to toggle source
Alias for: sigma
swap_column(a,b) click to toggle source

Exchanges one column with another.

Example:

>> a = Table(%w[a b c]) { |t| t << [1,2,3] << [4,5,6] } 
>> puts a
   +-----------+
   | a | b | c |
   +-----------+
   | 1 | 2 | 3 |
   | 4 | 5 | 6 |
   +-----------+
>> a.swap_column("a","c")
>> puts a
   +-----------+
   | c | b | a |
   +-----------+
   | 3 | 2 | 1 |
   | 6 | 5 | 4 |
   +-----------+
# File lib/ruport/data/table.rb, line 590
def swap_column(a,b)    
  if [a,b].all? { |r| r.kind_of? Fixnum }
   col_a,col_b = column_names[a],column_names[b]
   column_names[a] = col_b
   column_names[b] = col_a
  else
    a_ind, b_ind = [column_names.index(a), column_names.index(b)] 
    column_names[b_ind] = a
    column_names[a_ind] = b
  end
end
to_group(name=nil) click to toggle source

Convert the Table into a Group using the supplied group name.

data = Table.new :data => [[1,2], [3,4]], 
                 :column_names => %w[a b]
group = data.to_group("my_group")
# File lib/ruport/data/table.rb, line 852
def to_group(name=nil)
  Group.new( :data => data, 
             :column_names => column_names,
             :name => name,
             :record_class => record_class )
end
to_s() click to toggle source

Uses Ruport's built-in text formatter to render this Table into a String.

Example:

data = Table.new :data => [[1,2], [3,4]], 
                 :column_names => %w[a b]
puts data.to_s
# File lib/ruport/data/table.rb, line 842
def to_s
  as(:text)
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.