Search code examples
ruby-on-railsamazon-s3rails-activestorage

"path_for" ActiveStorage attachments on S3


I have an application hosted on Heroku, where one user can answer a quiz and submit some photos (via Active Storage, and the files are sent to a S3 bucket), and another user can see the retrieve the answers and photos.

I used a method for the user download all photos of a quiz on a .zip file, but I keep getting this error message on the live application:

NoMethodError (undefined method `path_for' for #ActiveStorage::Service::S3Service:0x0000000006b47368>):

The method used to create the .zip file is:

def quiz_photos_download
  @quiz = Quiz.find(params[:quiz_id])
  @project = Project.find(@quiz.project_id)
  @photos = @quiz.room_photos

  arquivo = "#{Rails.root}/tmp/quiz_photos.zip"

  Zip::File.open(arquivo, Zip::File::CREATE) do |zipfile|
    @photos.each do |photo|
      zipfile.add(photo.filename, ActiveStorage::Blob.service.send(:path_for, photo.key))
    end
  end
      send_file(arquivo, :type => 'application/zip', :filename => "Fotos "[email protected]+".zip")
end

I can display all the images sent by the user on the projects views, and the bulk download works just fine when the code is run locally.

Any ideas on this?


Solution

  • You're using the S3 backend to store your Active Storage attachments so the files are off in some S3 bucket rather than anywhere in your file system. There is no path to use like there is with the disk service.

    Instead of adding files to the zip file by their paths, you'll have to download the images from S3 and add the data directly to the zip file. Something like this:

    # The streaming API is a bit easier for this.
    Zip::OutputStream.open(arquivo) do |zip|
      @photos.each do |photo|
        zip.put_next_entry(photo.filename)
        zip.print(photo.download)
      end
    end
    

    The photo.download call will download the file from S3 into an in-memory string. This can chew up more memory than you'd like so you might want to use the ActiveStorage::Downloading module to help download the attachments to temp files, see the Active Storage Overview Guide for some information on using ActiveStorage::Downloading.

    This process could get slow and resource intensive so you might want to push the zip creation off to background job and then notify the person when the job is finished.