Skip to content

Instantly share code, notes, and snippets.

@scottserok
Created January 31, 2019 23:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scottserok/b1cb43166ebee062bae5a79f9abde00a to your computer and use it in GitHub Desktop.
Save scottserok/b1cb43166ebee062bae5a79f9abde00a to your computer and use it in GitHub Desktop.
Simple class to easily build CSV outputs for your POROs or ActiveRecord models.
# CsvBuilder configures a model to generate a resource. Inspired by ActiveAdmin.
#
# Usage example:
#
# csv_builder = CSVBuilder.new User, col_sep: ';'
# csv_builder.column :id
# csv_builder.column :email
# csv_builder.column("Name") { |resource| resource.display_name }
# csv_builder.headers %w[ID Email Name]
# csv_builder.scope { |scope| scope.where('created_at > ?', 7.days.ago) } # unavailable for POROs
# csv_builder.generate
#
class CsvBuilder
attr_reader :columns, :options
def initialize(klass, options = {})
fail ArgumentError, 'Must provide a Class' unless klass.is_a?(Class)
@klass = klass
@options = options.reverse_merge(row_sep: "\n")
@columns = []
@headers = []
end
def column(str, &block)
columns << Column.new(str, block)
end
def header(str)
@headers << str
end
def headers(arr)
@headers = arr
end
def scope(&block)
if @klass.ancestors.include?(ActiveRecord::Base) # naive approach
@scope = block if block.is_a?(Proc)
else
raise StandardError.new(@klass.to_s + ' is not a decendant of ActiveRecord::Base')
end
end
def generate(io = '')
io << CSV.generate_line(generate_header_row, @options)
resources.each do |resource|
io << CSV.generate_line(generate_data_row(resource), @options)
end
io
end
class Column
attr_reader :name, :data
def initialize(str, block = nil)
@name = str
@data = block || str.to_sym
end
end
private
def generate_header_row
if @headers.empty?
columns.map do |column|
if column.name.is_a?(Symbol)
@klass.human_attribute_name(column.name)
else
column.name
end
end
else
@headers
end
end
def generate_data_row(resource)
columns.map do |column|
if column.data.is_a?(Proc)
column.data.call resource
else
resource.public_send(column.data)
end
end
end
def resources
if @scope
@scope.call(@klass).find_each.lazy
else
@klass.all.find_each.lazy
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment