124 lines
4.0 KiB
Ruby
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
|