Files
2023-11-28 10:38:36 -05:00

124 lines
4.0 KiB
Ruby

require "csv"
module Dunlop::CsvBuilder
extend ActiveSupport::Concern
included do
attr_reader :resource, :options, :csv_options, :controller
class_attribute :default_csv_options
self.default_csv_options = {}
end
def initialize(resource, options = {})
@resource, @options = resource, options
@csv_options = default_csv_options.merge(options.slice(:col_sep, :row_sep, :encoding, :field_size_limit, :quote_char, :force_quotes))
@controller = options[:controller]
end
def file_name
options[:file_name] || "#{Rails.application.config.application_name}-#{date_number}-data.csv"
end
def headers
[]
end
def for_each_serialized_row(&block)
iterate_over = iteration_resource
iterate_over = Array.wrap(iterate_over) unless iterate_over.respond_to?(:find_each_ordered) || iterate_over.respond_to?(:each) # allow resource to be a record using the index's csv builder
iteration_method = iterate_over.respond_to?(:find_each_ordered) ? :find_each_ordered : :each
iterate_over.public_send(iteration_method) do |*args|
block.call serialize_row(*args)
end
end
def serialize_row(record)
[record.id]
end
def data
# use #to_csv for base_class builders if present
# in custom class implement"
# def data
# resource.to_csv
# end
# since Arrays implement #to_csv, but we do not want to use it in this case we explicitly exclude it
return resource.to_csv if self.class == CsvBuilder and resource.present? and resource.respond_to?(:to_csv) and !resource.is_a?(Array)
# try iteration otherwise
str = CSV.generate **csv_options do |csv|
csv << headers if headers.present?
for_each_serialized_row do |serialized_row|
csv << serialized_row
end
end
str
end
def date_number
Date.current.to_s(:number)
end
# This method can be overridden in order to use another iteration source than the resource eg:
# resouce = batch
# def iteration_resource
# resource.workflow_instances
# end
def iteration_resource
resource
end
#TODO: this is a separate include, not part of the core CsvBuilder object
def workflow_instances_in_batch
resource.workflow_instances.for_filtered_export
end
# Do a lookup on a record of an attribute and map the value to the
# csv required version if appliccable. At the moment booleans are
# converted to zeros and ones
def record_attribute(record, attribute_name)
result = record.public_send(attribute_name)
case result
when TrueClass then 1
when FalseClass then 0
when String then result.strip.gsub(/\r\n?|\n/, " ")
when DayTimeBase then result.number
else result
end
end
# Make some options available as methods
%i[action_name controller_path].each do |option_method|
define_method option_method do
options[option_method]
end
end
module ClassMethods
# find the csv builder for the given route spec
def find_for_route(route_spec)
find_for_route_lookup_order(route_spec).lazy.map(&:safe_constantize).find(&:itself)
end
# separate method since this is the important method to test, so extracted from the main method
def find_for_route_lookup_order(route_spec)
controller_path, action_name = route_spec.split('#')
controller_parts = controller_path.split('/')
if controller_parts.size < 2
parts = controller_parts.map(&:classify)
else
parts = controller_parts[0..-2].map(&:camelize)
parts << controller_parts.last.classify
end
# Create a lookup tree that uses all the namespaces, followed by the actoin name
# and ending with CsvBuilder for consitancy. Aka: if the route is:
# workflow_instances/scenario1s#show
# the lookup_order should be:
# ["WorkflowInstanceBatch::Scenario1::ShowCsvBuilder", "WorkflowInstanceBatch::ShowCsvBuilder"]
action_postfix = "::#{action_name.camelize}CsvBuilder"
parts.map.with_index{|p, i| parts[0..(i - 1)].join('::') + action_postfix}
end
end
end