Listing urls which can be downoaded

HI folks

I have a Message.rb record which has many Documents.

class Document < ApplicationRecord
  belongs_to :message
  include FileUploader::Attachment(:file)
end

I want to list the files associated with each message to enable users to click on them and to download those files. I’m finding that it’s taking quite a little bit of time to generate the presigned urls for these files.

<%= message.documents.map{ |d| link_to(d.file.metadata["filename"], d.file_url(expires: 6.days.to_i) + %Q(&response-content-disposition=attachment; filename="#{d.file.metadata["filename"]}")) }.join(", ") %>

It seems that to generating these file_urls (which make presigned url calls) are an expensive operation and it is taking up to 400ms for each message (which has many documents) to load on the page. Was wondering if any of you would have a solution to this problem?

Please let me know if you do.

regards

Ben

There’s another problem waiting for you here, too: if the visitor leaves this page open long enough, eventually the presign keys will stale out, and the links won’t work. Rather than just exposing the presign link, try adding your own route to the attached document, and within that, lazily compute the presign. Something like this:

#documents_controller.rb

def show
  redirect_to @document.file.url
end

now just link_to @document.name, @document in your index view, These will not take any time at all to render, and they will delay computing the key until you actually need it.

Walter

1 Like

@walterdavis that’s an interesting solution! I like it.

@BKSpurgeon, when I read your question, I immediately wondered if you might be having a N+1 query problem as well. If so, you can try prefetching all the documents using eager loading like Message.where(...).includes(:documents). If a message has 100’s of documents, then maybe you load a fraction of it like a “pagination” strategy. If you don’t have N+1 problem and the time is coming from the presign generation, then the above solution will be better.

1 Like

Sounds good. This is what I implemented. This is now many orders of magnitudes faster.

def show
     # works with Google.
     # this retains the file name stored in the record's database column when downloading
     # As far as I know, google does not easily allow for content dispositions to be set when creating a  presigned URL, nor does it allow for object meta data to be set with a presigned url (when uploading files) - this must be done after the file has already been uploaded (i could be wrong). The below strategy allows you to download the file name set via the database column rather than the content disposition field which may be stored by Google:
     redirect_to @document.file.url(expires: 1.year)+ %Q(&response-content-disposition=attachment; filename="#{@document.file.metadata["filename"]}")
 end

@walterdavis @hmistry thank you for the pointers.