Class: Philiprehberger::CsvBuilder::Builder

Inherits:
Object
  • Object
show all
Defined in:
lib/philiprehberger/csv_builder/builder.rb

Overview

DSL builder for constructing CSV output from records

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(records, delimiter: ',', quote_char: '"', bom: false, encoding: 'UTF-8') ⇒ Builder

Returns a new instance of Builder.

Parameters:

  • records (Array)

    the source records

  • delimiter (String) (defaults to: ',')

    the column separator (default: “,”)

  • quote_char (String) (defaults to: '"')

    the quote character (default: ‘“’)

  • bom (Boolean) (defaults to: false)

    prepend UTF-8 BOM (default: false)

  • encoding (String) (defaults to: 'UTF-8')

    output encoding name (default: “UTF-8”)



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/philiprehberger/csv_builder/builder.rb', line 21

def initialize(records, delimiter: ',', quote_char: '"', bom: false, encoding: 'UTF-8')
  @records = records
  @columns = []
  @filters = []
  @validations = []
  @row_number_header = nil
  @delimiter = delimiter
  @quote_char = quote_char
  @sort_by = nil
  @sort_direction = :asc
  @limit_count = nil
  @offset_count = nil
  @footer_block = nil
  @header_transform = nil
  @bom = bom
  @encoding = encoding
end

Instance Attribute Details

#columnsArray<Column> (readonly)

Returns the defined columns.

Returns:

  • (Array<Column>)

    the defined columns



11
12
13
# File 'lib/philiprehberger/csv_builder/builder.rb', line 11

def columns
  @columns
end

#recordsArray (readonly)

Returns the source records.

Returns:

  • (Array)

    the source records



14
15
16
# File 'lib/philiprehberger/csv_builder/builder.rb', line 14

def records
  @records
end

Instance Method Details

#column(name, header: nil) {|record| ... } ⇒ self

Define a column

Parameters:

  • name (Symbol, String)

    the column name

  • header (String, nil) (defaults to: nil)

    optional custom header label

Yields:

  • (record)

    optional block to transform the value

Yield Parameters:

  • record (Object)

    the source record

Returns:

  • (self)


131
132
133
134
# File 'lib/philiprehberger/csv_builder/builder.rb', line 131

def column(name, header: nil, &block)
  @columns << Column.new(name, header: header, &block)
  self
end

#filter {|record| ... } ⇒ self

Add a filter to exclude records

Yields:

  • (record)

    block that returns true to include the record

Yield Parameters:

  • record (Object)

    the source record

Returns:

  • (self)


141
142
143
144
# File 'lib/philiprehberger/csv_builder/builder.rb', line 141

def filter(&block)
  @filters << block
  self
end

#filtered_recordsArray

Return the filtered records

Returns:

  • (Array)


167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/philiprehberger/csv_builder/builder.rb', line 167

def filtered_records
  result = @records
  @filters.each do |f|
    result = result.select(&f)
  end
  if @sort_by
    result = result.sort_by(&@sort_by)
    result = result.reverse if @sort_direction == :desc
  end
  result = result.drop(@offset_count) if @offset_count
  result = result.first(@limit_count) if @limit_count
  result
end

Append a computed footer row after all data rows

Yields:

  • (Array)

    filtered records

Yield Returns:

  • (Array)

    footer row values

Returns:

  • (self)


79
80
81
82
# File 'lib/philiprehberger/csv_builder/builder.rb', line 79

def footer(&block)
  @footer_block = block
  self
end

#headersArray<String>

Return the header row

Returns:

  • (Array<String>)


158
159
160
161
162
# File 'lib/philiprehberger/csv_builder/builder.rb', line 158

def headers
  base = @columns.map(&:header)
  base = base.map { |h| @header_transform.call(h) } if @header_transform
  @row_number_header ? [@row_number_header] + base : base
end

#limit(n) ⇒ self

Limit the number of output rows

Parameters:

  • n (Integer)

    maximum rows

Returns:

  • (self)


60
61
62
63
# File 'lib/philiprehberger/csv_builder/builder.rb', line 60

def limit(n)
  @limit_count = n
  self
end

#offset(n) ⇒ self

Skip the first N filtered/sorted records

Parameters:

  • n (Integer)

    number of rows to skip

Returns:

  • (self)


69
70
71
72
# File 'lib/philiprehberger/csv_builder/builder.rb', line 69

def offset(n)
  @offset_count = n
  self
end

#row_number(header: '#') ⇒ self

Add an auto-incrementing row number as the first column

Parameters:

  • header (String) (defaults to: '#')

    the header label for the row number column

Returns:

  • (self)


150
151
152
153
# File 'lib/philiprehberger/csv_builder/builder.rb', line 150

def row_number(header: '#')
  @row_number_header = header
  self
end

#sort_by(direction: :asc) {|record| ... } ⇒ self

Sort records before CSV output

Parameters:

  • direction (Symbol) (defaults to: :asc)

    :asc (default) or :desc

Yields:

  • (record)

    block returning the sort key

Yield Parameters:

  • record (Object)

    the source record

Returns:

  • (self)

Raises:

  • (Error)

    if direction is not :asc or :desc



46
47
48
49
50
51
52
53
54
# File 'lib/philiprehberger/csv_builder/builder.rb', line 46

def sort_by(direction: :asc, &block)
  raise Error, 'A block is required for sort_by' unless block
  raise Error, "direction must be :asc or :desc (got #{direction.inspect})" unless %i[asc
                                                                                      desc].include?(direction)

  @sort_by = block
  @sort_direction = direction
  self
end

#to_csvString

Generate the CSV as a string

Returns:

  • (String)

Raises:



185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/philiprehberger/csv_builder/builder.rb', line 185

def to_csv
  recs = filtered_records
  validate_rows!(recs) unless @validations.empty?
  csv_string = CSV.generate(**csv_options) do |csv|
    csv << headers
    recs.each_with_index do |record, index|
      csv << build_row(record, index)
    end
    csv << @footer_block.call(recs) if @footer_block
  end
  csv_string = csv_string.encode(@encoding) unless @encoding == 'UTF-8'
  @bom ? "\xEF\xBB\xBF#{csv_string}" : csv_string
end

#to_file(path) ⇒ void

This method returns an undefined value.

Write the CSV to a file

Parameters:

  • path (String)

    the output file path



203
204
205
# File 'lib/philiprehberger/csv_builder/builder.rb', line 203

def to_file(path)
  File.binwrite(path, to_csv)
end

#to_io(io) ⇒ void

This method returns an undefined value.

Stream CSV to any IO object

Parameters:

  • io (IO, StringIO)

    the IO object to write to

Raises:



212
213
214
215
216
217
218
219
220
221
222
# File 'lib/philiprehberger/csv_builder/builder.rb', line 212

def to_io(io)
  io.write("\xEF\xBB\xBF") if @bom
  recs = filtered_records
  validate_rows!(recs) unless @validations.empty?
  csv = CSV.new(io, **csv_options)
  csv << headers
  recs.each_with_index do |record, index|
    csv << build_row(record, index)
  end
  csv << @footer_block.call(recs) if @footer_block
end

#total(column_name) {|values| ... } ⇒ self

Shorthand for adding a footer row with a computed total for the named column

Parameters:

  • column_name (Symbol, String)

    the column to total

Yields:

  • (values)

    optional block to compute the total (receives array of numeric values)

Returns:

  • (self)


109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/philiprehberger/csv_builder/builder.rb', line 109

def total(column_name, &block)
  col_name = column_name.to_sym
  @footer_block = lambda do |recs|
    columns.map do |col|
      if col.name == col_name
        values = recs.map { |r| col.extract(r).to_f }
        block ? block.call(values) : values.sum
      else
        ''
      end
    end
  end
  self
end

#transform_header {|name| ... } ⇒ self

Register a proc applied to all column headers during rendering

Yields:

  • (name)

    block that transforms a header name

Yield Parameters:

  • name (String)

    the original header label

Returns:

  • (self)


99
100
101
102
# File 'lib/philiprehberger/csv_builder/builder.rb', line 99

def transform_header(&block)
  @header_transform = block
  self
end

#validate {|row| ... } ⇒ self

Register a validation block for rows

Yields:

  • (row)

    block that validates the row hash

Yield Parameters:

  • row (Hash)

    column-name to value mapping

Returns:

  • (self)


89
90
91
92
# File 'lib/philiprehberger/csv_builder/builder.rb', line 89

def validate(&block)
  @validations << block
  self
end