diff --git a/lib/munger/data.rb b/lib/munger/data.rb index 01bf585..26d4bf4 100644 --- a/lib/munger/data.rb +++ b/lib/munger/data.rb @@ -1,31 +1,31 @@ module Munger #:nodoc: - + # this class is a data munger - # it takes raw data (arrays of hashes, basically) + # it takes raw data (arrays of hashes, basically) # and can manipulate it in various interesting ways class Data - + attr_accessor :data - + # will accept active record collection or array of hashes def initialize(options = {}) @data = options[:data] if options[:data] yield self if block_given? end - + def <<(data) add_data(data) end - + def add_data(data) if @data - @data = @data + data + @data = @data + data else @data = data end @data end - + #-- # NOTE: @@ -37,14 +37,14 @@ def add_data(data) def self.load_data(data, options = {}) Data.new(:data => data) end - + def columns @columns ||= clean_data(@data.first).to_hash.keys rescue puts clean_data(@data.first).to_hash.inspect end - - # :default: The default value to use for the column in existing rows. + + # :default: The default value to use for the column in existing rows. # Set to nil if not specified. # if a block is passed, you can set the values manually def add_column(names, options = {}) @@ -55,7 +55,7 @@ def add_column(names, options = {}) else col_data = default end - + if names.is_a? Array names.each_with_index do |col, i| row[col] = col_data[i] @@ -69,7 +69,7 @@ def add_column(names, options = {}) alias :add_columns :add_column alias :transform_column :add_column alias :transform_columns :add_column - + def clean_data(hash_or_ar) if hash_or_ar.is_a? Hash return Item.ensure(hash_or_ar) @@ -78,24 +78,24 @@ def clean_data(hash_or_ar) end hash_or_ar end - + def filter_rows new_data = [] - + @data.each do |row| row = Item.ensure(row) if (yield row) new_data << row end end - + @data = new_data end - + # group the data like sql def group(groups, agg_hash = {}) data_hash = {} - + agg_columns = [] agg_hash.each do |key, columns| Data.array(columns).each do |col| # column name @@ -103,23 +103,23 @@ def group(groups, agg_hash = {}) end end agg_columns = agg_columns.uniq.compact - + @data.each do |row| - row_key = Data.array(groups).map { |rk| row[rk] } + row_key = Data.array(groups).map { |rk| row[rk] || row.send(rk) } data_hash[row_key] ||= {:cells => {}, :data => {}, :count => 0} focus = data_hash[row_key] focus[:data] = clean_data(row) - + agg_columns.each do |col| focus[:cells][col] ||= [] - focus[:cells][col] << row[col] + focus[:cells][col] << (row[col] || row.send(col)) end focus[:count] += 1 end - + new_data = [] new_keys = [] - + data_hash.each do |row_key, data| new_row = data[:data] agg_hash.each do |key, columns| @@ -128,15 +128,15 @@ def group(groups, agg_hash = {}) if key.is_a?(Array) && key[1].is_a?(Proc) newcol = key[0].to_s + '_' + col.to_s new_row[newcol] = key[1].call(data[:cells][col]) - else + else newcol = key.to_s + '_' + col.to_s case key when :average sum = data[:cells][col].inject { |sum, a| sum + a } - new_row[newcol] = (sum / data[:count]) + new_row[newcol] = (sum / data[:count]) when :count - new_row[newcol] = data[:count] - else + new_row[newcol] = data[:count] + else new_row[newcol] = data[:cells][col].inject { |sum, a| sum + a } end end @@ -145,14 +145,14 @@ def group(groups, agg_hash = {}) end new_data << Item.ensure(new_row) end - + @data = new_data new_keys.compact end - + def pivot(columns, rows, value, aggregation = :sum) data_hash = {} - + @data.each do |row| column_key = Data.array(columns).map { |rk| row[rk] } row_key = Data.array(rows).map { |rk| row[rk] } @@ -163,10 +163,10 @@ def pivot(columns, rows, value, aggregation = :sum) focus[:count] += 1 focus[:sum] += row[value] end - + new_data = [] new_keys = {} - + data_hash.each do |row_key, row_hash| new_row = {} row_hash.each do |column_key, data| @@ -174,22 +174,22 @@ def pivot(columns, rows, value, aggregation = :sum) new_row.merge!(data[:data]) case aggregation when :average - new_row[ckey] = (data[:sum] / data[:count]) + new_row[ckey] = (data[:sum] / data[:count]) when :count - new_row[ckey] = data[:count] - else - new_row[ckey] = data[:sum] + new_row[ckey] = data[:count] + else + new_row[ckey] = data[:sum] end new_keys[ckey] = true end end new_data << Item.ensure(new_row) end - + @data = new_data new_keys.keys end - + def self.array(string_or_array) if string_or_array.is_a? Array return string_or_array @@ -197,12 +197,12 @@ def self.array(string_or_array) return [string_or_array] end end - + def size @data.size end alias :length :size - + def valid? if ((@data.size > 0) && (@data.respond_to? :each_with_index) && @@ -225,8 +225,8 @@ def to_a(cols=nil) end array end - + end - + end diff --git a/lib/munger/render/html.rb b/lib/munger/render/html.rb index 65c9f28..f639cc5 100644 --- a/lib/munger/render/html.rb +++ b/lib/munger/render/html.rb @@ -8,33 +8,36 @@ module Munger #:nodoc: module Render #:nodoc: class Html - + attr_reader :report, :classes - + def initialize(report, options = {}) @report = report + @skip_data = options[:skip_data] set_classes(options[:classes]) end - + def set_classes(options = nil) options = {} if !options default = {:table => 'report-table'} @classes = default.merge(options) end - + def render x = Builder::XmlMarkup.new - + x.table(:class => @classes[:table]) do - + x.tr do @report.columns.each do |column| x.th(:class => 'columnTitle') { x << @report.column_title(column) } end end - - @report.process_data.each do |row| - + + process_data = @report.process_data + process_data = process_data.reject{|p| p[:meta][:data]} if @skip_data + process_data.each do |row| + classes = [] classes << row[:meta][:row_styles] classes << 'group' + row[:meta][:group].to_s if row[:meta][:group] @@ -42,19 +45,19 @@ def render classes.compact! if row[:meta][:group_header] - classes << 'groupHeader' + row[:meta][:group_header].to_s + classes << 'groupHeader' + row[:meta][:group_header].to_s end - + row_attrib = {} row_attrib = {:class => classes.join(' ')} if classes.size > 0 - + x.tr(row_attrib) do if row[:meta][:group_header] header = row[:meta][:group_value].to_s x.th(:colspan => @report.columns.size) { x << header } - else + else @report.columns.each do |column| - + cell_attrib = {} if cst = row[:meta][:cell_styles] cst = Item.ensure(cst) @@ -62,16 +65,16 @@ def render cell_attrib = {:class => cell_styles.join(' ')} end end - + x.td(cell_attrib) { x << row[:data][column].to_s } end end end end - + end end - + def cycle(one, two) if @current == one @current = two @@ -79,11 +82,11 @@ def cycle(one, two) @current = one end end - + def valid? @report.is_a? Munger::Report end - + end end end diff --git a/lib/munger/report.rb b/lib/munger/report.rb index 2ad1096..439ffbd 100644 --- a/lib/munger/report.rb +++ b/lib/munger/report.rb @@ -1,12 +1,12 @@ module Munger #:nodoc: - + class Report - + attr_writer :data, :sort, :columns, :subgroup, :subgroup_options, :aggregate attr_accessor :column_titles, :column_data_fields, :column_formatters attr_reader :process_data, :grouping_level - - # r = Munger::Report.new ( :data => data, + + # r = Munger::Report.new ( :data => data, # :columns => [:collect_date, :spot_name, :airings, :display_name], # :sort => [:collect_date, :spot_name] # :subgroup => @group_list, @@ -19,11 +19,11 @@ def initialize(options = {}) @column_formatters = {} set_options(options) end - + def self.from_data(data) Report.new(:data => data) end - + def set_options(options) if d = options[:data] if d.is_a? Munger::Data @@ -37,7 +37,7 @@ def set_options(options) self.subgroup(options[:subgroup]) if options[:subgroup] self.aggregate(options[:aggregate]) if options[:aggregate] end - + def processed? if @process_data true @@ -45,39 +45,40 @@ def processed? false end end - + # returns ReportTable def process(options = {}) set_options(options) - - # sorts and fills NativeReport + + # sorts and fills NativeReport + @only_totals = options[:only_totals] @report = translate_native(do_field_sort(@data.data)) - + do_add_groupings do_add_aggregate_rows - + self end - + def sort(values = nil) if values - @sort = values + @sort = values self else @sort end end - + def subgroup(values = nil, options = {}) if values - @subgroup = values + @subgroup = values @subgroup_options = options self else @subgroup end end - + def columns(values = nil) if values if values.is_a? Hash @@ -99,28 +100,28 @@ def column_title(column) return column.to_s end end - + def column_data_field(column) @column_data_fields[column] || column.to_s end - + def column_formatter(column) @column_formatters[column] end - + def aggregate(values = nil) if values - @aggregate = values + @aggregate = values self else @aggregate end end - + def rows @process_data.size end - + def valid? (@data.is_a? Munger::Data) && (@data.valid?) end @@ -128,7 +129,7 @@ def valid? # @report.style_cells('highlight') { |cell, row| cell > 32 } def style_cells(style, options = {}) @process_data.each_with_index do |row, index| - + # filter columns to look at if options[:only] cols = Data.array(options[:only]) @@ -141,7 +142,7 @@ def style_cells(style, options = {}) if options[:no_groups] && row[:meta][:group] next end - + cols.each do |col| if yield(row[:data][col], row[:data]) @process_data[index][:meta][:cell_styles] ||= {} @@ -151,7 +152,7 @@ def style_cells(style, options = {}) end end end - + # @report.style_rows('highlight') { |row| row.age > 32 } def style_rows(style, options = {}) @process_data.each_with_index do |row, index| @@ -169,26 +170,26 @@ def get_subgroup_rows(group_level = nil) data = data.select { |r| r[:meta][:group] == group_level } if group_level data end - + def to_s pp @process_data end - - private - + + private + def translate_native(array_of_hashes) @process_data = [] array_of_hashes.each do |row| @process_data << {:data => Item.ensure(row), :meta => {:data => true}} end end - + def do_add_aggregate_rows return false if !@aggregate return false if !@aggregate.is_a? Hash - - totals = {} - + + totals = {} + @process_data.each_with_index do |row, index| if row[:meta][:data] @aggregate.each do |type, columns| @@ -201,7 +202,7 @@ def do_add_aggregate_rows end end end - elsif level = row[:meta][:group] + elsif level = row[:meta][:group] # write the totals and reset level @aggregate.each do |type, columns| Data.array(columns).each do |column| @@ -212,7 +213,7 @@ def do_add_aggregate_rows end end end - + total_row = {:data => {}, :meta => {:group => 0}} # write one row at the end with the totals @aggregate.each do |type, columns| @@ -222,9 +223,9 @@ def do_add_aggregate_rows end end @process_data << total_row - + end - + def calculate_aggregate(type, data) return 0 if !data if type.is_a? Proc @@ -241,40 +242,40 @@ def calculate_aggregate(type, data) end end end - + def do_add_groupings return false if !@subgroup sub = Data.array(@subgroup) @grouping_level = sub.size - + current = {} new_data = [] - + first_row = @process_data.first sub.reverse.each do |group| current[group] = first_row[:data][group] end prev_row = {:data => {}} - + @process_data.each_with_index do |row, index| # insert header title rows next_row = @process_data[index + 1] - + if next_row - + # insert header rows if @subgroup_options[:with_headers] level = 1 sub.each do |group| if (prev_row[:data][group] != current[group]) && current[group] - group_row = {:data => {}, :meta => {:group_header => level, + group_row = {:data => {}, :meta => {:group_header => level, :group_name => group, :group_value => row[:data][group]}} new_data << group_row end level += 1 end end - + # insert current row new_data << row @@ -283,31 +284,33 @@ def do_add_groupings level = @grouping_level sub.reverse.each do |group| if (next_row[:data][group] != current[group]) && current[group] - group_row = {:data => {}, :meta => {:group => level, :group_name => group}} + data = @only_totals ? {group => row[:data][group]} : {} + group_row = {:data => data, :meta => {:group => level, :group_name => group}} new_data << group_row end current[group] = next_row[:data][group] level -= 1 - end - + end + prev_row = row - + else # last row level = @grouping_level - + # insert header rows sub.each do |group| if (prev_row[:data][group] != current[group]) && current[group] - group_row = {:data => {}, :meta => {:group_header => level, + group_row = {:data => {}, :meta => {:group_header => level, :group_name => group, :group_value => row[:data][group]}} new_data << group_row end end - + new_data << row - + sub.reverse.each do |group| - group_row = {:data => {}, :meta => {:group => level, :group_name => group}} + data = @only_totals ? {group => row[:data][group]} : {} + group_row = {:data => data, :meta => {:group => level, :group_name => group}} new_data << group_row level -= 1 end @@ -316,13 +319,13 @@ def do_add_groupings @process_data = new_data end - + def do_field_sort(data) data.sort do |a, b| compare = 0 a = Item.ensure(a) b = Item.ensure(b) - + Data.array(@sort).each do |sorting| if sorting.is_a?(String) || sorting.is_a?(Symbol) compare = a[sorting.to_s] <=> b[sorting.to_s] rescue 0 @@ -343,7 +346,7 @@ def do_field_sort(data) compare end end - + end - + end