Skip to content

Instantly share code, notes, and snippets.

@gagoit
Created January 25, 2019 07:35
Show Gist options
  • Save gagoit/9a77f1aa00cdd20f239de74a0ae6dfae to your computer and use it in GitHub Desktop.
Save gagoit/9a77f1aa00cdd20f239de74a0ae6dfae to your computer and use it in GitHub Desktop.
Faster CSV downloads using Enumerator
Faster CSV downloads using Enumerator
https://medium.com/reflektive-engineering/faster-csv-downloads-using-enumerator-7e9b94b870d3
We do not have to download the full movie first to start watching it thanks to the concept of ‘media streaming’. In simple terms, we can download chunks of the movie in sequence while we’re watching the downloaded chunks.
Similarly, we can stream the CSV to the customer, rather than make them wait for the complete CSV to be generated.
This is possible because Rack, the middleware that Rails uses, requires the response object to support the #each method. Rack uses #each to send the response back in chunks.
In the current scenario, the CSV file that’s generated (Refer to Step#2 in the process) is a String. String does not support #each, and Rack is forced to send all of it in one shot, after it’s ready.
So, Rack requires the response object to support #each and ruby’s Enumerator fit this requirement.
def generate_csv(column_names, records)
Enumerator.new do |csv|
csv << column_names.to_csv # add headers to the CSV
records.each do |record|
csv << record.attributes.values_at(*column_names).to_csv
end
end
end
# In the controller#action
respond_to do |format|
format.csv { render_streaming_csv(generate_csv(column_names, records)) }
end
def render_streaming_csv(csv_enumerator)
# Delete this header so that Rack knows to stream the content.
headers.delete("Content-Length")
# Do not cache results from this action.
headers["Cache-Control"] = "no-cache"
# Let the browser know that this file is a CSV.
headers['Content-Type'] = 'text/csv'
# Do not buffer the result when using proxy servers.
headers['X-Accel-Buffering'] = 'no'
# Set the filename
headers['Content-Disposition'] = "attachment; filename=\"report.csv\""
# Set the response body as the enumerator
self.response_body = csv_enumerator
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment