diff --git a/lib/data_table.rb b/lib/data_table.rb index b2a31af..d1220d5 100644 --- a/lib/data_table.rb +++ b/lib/data_table.rb @@ -1,16 +1,16 @@ require 'fastercsv' class DataTable + class ColumnAlreadyExists < StandardError ; end - class ColumnNotFound < StandardError ; end + class ColumnNotFound < StandardError ; end - INFINITY = 1.0/0 + INFINITY = 1.0 / 0 class Aggregators - SUM = lambda do |key, rows| - result = 0.0 - rows.each { |r| result += r[key] } - result + + SUM = lambda do |key, rows| + rows.inject(0.0) { |sum, row| sum += row[key] } end MIN = lambda do |key, rows| @@ -24,26 +24,25 @@ class Aggregators rows.each { |r| result = r[key] if r[key] > result } result end + end class Column - attr_accessor :key - attr_accessor :header - attr_accessor :transformer + + attr_accessor :key, :header, :transformer def initialize(key, header, transformer) - @key = key - @header = header - @transformer = transformer + @key, @header, @transformer = key, header, transformer end + end class Row + attr_accessor :cells def initialize(table) - @table = table - @cells = {} + @table, @cells = table, {} end # get cell @@ -53,22 +52,22 @@ def [](key) # set cell def []=(key, value) - raise ColumnNotFound unless @table.columns.has_key?(key) + raise ColumnNotFound unless @table.columns.key?(key) @cells[key] = value end + end - - attr_accessor :columns - attr_accessor :rows + + + attr_accessor :rows, :columns def initialize - @columns = {} - @column_keys = [] # used to memorize the order of columns - @rows = [] + @rows, @columns = [], {} + @column_keys = [] # used to memorize the order of columns end def add_column(key, header, transformer = nil) - raise ColumnAlreadyExists if @columns.has_key?(key) + raise ColumnAlreadyExists if @columns.key?(key) @column_keys << key @columns[key] = Column.new(key, header, transformer) end @@ -76,7 +75,7 @@ def add_column(key, header, transformer = nil) def build_rows(items) items.each do |item| row = Row.new(self) - @columns.each do |key, col| + columns.each do |key, col| if col.transformer row[key] = col.transformer.call(item) elsif item.respond_to?(key) @@ -84,7 +83,7 @@ def build_rows(items) end end yield(row, item) if block_given? - @rows << row + rows << row end end @@ -103,19 +102,19 @@ def aggregate(key, rows, aggregators, group_key) # yields a grouped DataTable object def group_by(key, aggregators, group_key = lambda {|x| x }) - result = DataTable.new + result = self.class.new my_key = key groups = {} - @rows.each do |row| + rows.each do |row| idx = group_key.call(row[key]) groups[idx] = groups[idx].to_a << row end # copy implicitly selected colums from original data table - result.add_column(key, @columns[key].header) + result.add_column(key, columns[key].header) aggregators.each_key do |key| - result.add_column(key, @columns[key].header) + result.add_column(key, columns[key].header) end groups.each_value do |group| @@ -130,13 +129,13 @@ def render_csv(options) # Add headers headers = [] @column_keys.each do |key| - headers << @columns[key].header + headers << columns[key].header end csv << headers # Add data rows - @rows.each do |row| + rows.each do |row| row_data = [] @column_keys.each do |key| row_data << row[key] @@ -146,6 +145,5 @@ def render_csv(options) end end -end - +end diff --git a/test/test_data_table.rb b/test/test_data_table.rb index da8d51e..2866658 100644 --- a/test/test_data_table.rb +++ b/test/test_data_table.rb @@ -1,6 +1,7 @@ require 'helper' class TestDataTable < Test::Unit::TestCase + context "A DataTable instance" do setup do @@ -29,36 +30,42 @@ def initialize(articlenum, name, group, ordered, quantity, price) OrderPosition.new("DTJA6181", "R2DX", "BB", Date.new(2009, 10, 4), 1, 200.0 ), OrderPosition.new("DTJA6181", "R2DX", "BB", Date.new(2009, 10, 4), 1, 20.0 ) ] + end should "work properly" do t = DataTable.new - # variant 1: implicit method lookup - t.add_column(:articlenum, "Articlenum") - t.add_column(:name, "Product Name") - t.add_column(:ordered, "Datum") - t.add_column(:group, "Product Group") - # variant 2: explicit transformer function that yields an item of your collection - t.add_column(:amount, "Amount", lambda { |p| p.quantity*p.price }) - # variant 3: explicit row modification (see build_rows!) + # Variant 1: implicit method lookup + + t.add_column :articlenum, "Articlenum" + t.add_column :name, "Product Name" + t.add_column :ordered, "Datum" + t.add_column :group, "Product Group" + + # Variant 2: explicit transformer function that yields an item of your collection + + t.add_column(:amount, "Amount", lambda { |p| p.quantity*p.price }) + + # Variant 3: explicit row modification (see build_rows) + t.add_column(:doubled_price, "Doubled Price") - t.build_rows(@order_positions) do |row, p| # block can be omitted - row[:doubled_price] = p.price*2 + row[:doubled_price] = p.price * 2 end - + + # Grouping + grouped_table = t.group_by(:ordered, { :amount => DataTable::Aggregators::SUM, :doubled_price => DataTable::Aggregators::SUM - }, lambda {|x| x.year.to_s+"-"+x.month.to_s}) - + }, lambda { |x| "#{x.year}-#{x.month}" }) + # puts t.render_csv(:col_sep => ",") puts grouped_table.render_csv(:col_sep => ",") + end - - end end