For my Ruby on Rails project (Rails version 5.1.2), I'm generating image files (png) and downloading them as a zipfile using RubyZip gem.
The image files are not stored in any directory. I have a model called Attachment. Each attachment has an attribute image_string that is a base64 string for an image. You can show the images using a tag like image_tag(src = "data:image/jpeg;base64, #{attachment.image_string}", style: "border-radius: 0;")
For multiple images, I want to create temporary file for each of them without storing them anywhere and download those images as a zip file.
The code I have now:
def bulk_download
require('zip')
::Zip::File.open("/tmp/mms.zip", Zip::File::CREATE) do |zipfile|
Attachment.all.each do |attachment|
image_file = Tempfile.new("#{attachment.created_at.in_time_zone}.png")
image_file.write(attachment.image_string)
zipfile.add("#{attachment.created_at.in_time_zone}.png", image_file.path)
end
end
send_file "/tmp/mms.zip", type: 'application/zip', disposition: 'attachment', filename: "my_archive.zip"
respond_to do |format |
format.all { head :ok, content_type: "text/html" }
end
end
But the downloaded zipfile has no files in it and the size of it is 0 bytes. Thanks in advance.
You should need to close and unlink the zip file like so:
require('zip')
class SomeController < ApplicationController
# ...
def bulk_download
filename = 'my_archive.zip'
temp_file = Tempfile.new(filename)
begin
Zip::OutputStream.open(temp_file) { |zos| }
Zip::File.open(temp_file.path, Zip::File::CREATE) do |zip|
Attachment.all.each do |attachment|
image_file = Tempfile.new("#{attachment.created_at.in_time_zone}.png")
image_file.write(attachment.image_string)
zipfile.add("#{attachment.created_at.in_time_zone}.png", image_file.path)
end
end
zip_data = File.read(temp_file.path)
send_data(zip_data, type: 'application/zip', disposition: 'attachment', filename: filename)
ensure # important steps below
temp_file.close
temp_file.unlink
end
end
end
Here is a good blog post that I used as the source for this code: https://thinkingeek.com/2013/11/15/create-temporary-zip-file-send-response-rails/
Also, it's good practice to keep all your library requirements at the top of the file (i.e. require('zip')).
The accepted solution is indeed correct. However, I'm going to extend the already provided solution to get it working with ActiveStorage attachments.
While using the accepted solution I found that the image_string method does not work for ActiveStorage attachment and throws an error like this
NoMethodError - undefined method `image_string' for #<ActiveStorage::Attached::One:0x00007f78cc686298>
Suppose we have a rails model called Product with an ActiveStorage attribute called attachment
class Product < ApplicationRecord
has_one_attached :attachment
end
In order to get this working for ActiveStorage attachments, we need to update the code as follows
begin
Zip::OutputStream.open(temp_file) { |zos| }
Zip::File.open(temp_file.path, Zip::File::CREATE) do |zipfile|
Product.all.each do |product|
image_file = Tempfile.new("#{product.attachment.created_at.in_time_zone}.png")
# image_file.write(product.attachment.image_string) #this does not work for ActiveStorage attachments
# use this instead
File.open(image_file.path, 'w', encoding: 'ASCII-8BIT') do |file|
product.attachment.download do |chunk|
file.write(chunk)
end
end
zipfile.add("#{product.attachment.created_at.in_time_zone}.png", image_file.path)
end
end
zip_data = File.read(temp_file.path)
send_data(zip_data, type: 'application/zip', disposition: 'attachment', filename: filename)
ensure # important steps below
temp_file.close
temp_file.unlink
end
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With