52

I need to get the path to the file on disk which is using ActiveStorage. The file is stored locally.

When I was using paperclip, I used the path method on the attachment which returned the full path.

Example:

user.avatar.path

While looking at the Active Storage Docs, it looked like rails_blob_path would do the trick. After looking at what it returned though, it does not provide the path to the document. Thus, it returns this error:

No such file or directory @ rb_sysopen -

Background

I need the path to the document because I am using the combine_pdf gem in order to combine multiple pdfs into a single pdf.

For the paperclip implementation, I iterated through the full_paths of the selected pdf attachments and load them into the combined pdf:

attachment_paths.each {|att_path| report << CombinePDF.load(att_path)}
Neil
  • 4,578
  • 14
  • 70
  • 155
  • 1
    The docs indicate that for `blob_path` "upon access, a redirect to the actual service endpoint is returned. This indirection decouples the public URL from the actual one" so by design this will foil what you are doing. Perhaps investigate using the download option. – memoht May 15 '18 at 04:15
  • 1
    The [disk service implementation](https://github.com/rails/rails/blob/master/activestorage/lib/active_storage/service/disk_service.rb) has a method called `path_for` that does what you're looking for but it is private. So using `#send` to get the paths or going through the download-to-temp-files process seem to be the options. – mu is too short May 15 '18 at 05:03

4 Answers4

65

Use:

ActiveStorage::Blob.service.path_for(user.avatar.key)

You can do something like this on your model:

class User < ApplicationRecord
  has_one_attached :avatar

  def avatar_on_disk
    ActiveStorage::Blob.service.path_for(avatar.key)
  end
end
Jared Beck
  • 16,796
  • 9
  • 72
  • 97
vitormil
  • 854
  • 1
  • 6
  • 6
42

I'm not sure why all the other answers use send(:url_for, key). I'm using Rails 5.2.2 and path_for is a public method, therefore, it's way better to avoid send, or simply call path_for:

class User < ApplicationRecord
  has_one_attached :avatar

  def avatar_path
    ActiveStorage::Blob.service.path_for(avatar.key)
  end
end

Worth noting that in the view you can do things like this:

<p>
  <%= image_tag url_for(@user.avatar) %>
  <br>
  <%= link_to 'View', polymorphic_url(@user.avatar) %>
  <br>
  Stored at <%= @user.image_path %>
  <br>
  <%= link_to 'Download', rails_blob_path(@user.avatar, disposition: :attachment) %>
  <br>
  <%= f.file_field :avatar %>
</p>
Christopher Oezbek
  • 23,994
  • 6
  • 61
  • 85
ecoologic
  • 10,202
  • 3
  • 62
  • 66
11

Thanks to the help of @muistooshort in the comments, after looking at the Active Storage Code, this works:

active_storage_disk_service = ActiveStorage::Service::DiskService.new(root: Rails.root.to_s + '/storage/')
active_storage_disk_service.send(:path_for, user.avatar.blob.key)
  # => returns full path to the document stored locally on disk

This solution feels a bit hacky to me. I'd love to hear of other solutions. This does work for me though.

Neil
  • 4,578
  • 14
  • 70
  • 155
8

You can download the attachment to a local dir and then process it.

Supposing you have in your model:

has_one_attached :pdf_attachment

You can define:

def process_attachment      
   # Download the attached file in temp dir
   pdf_attachment_path = "#{Dir.tmpdir}/#{pdf_attachment.filename}"
   File.open(pdf_attachment_path, 'wb') do |file|
       file.write(pdf_attachment.download)
   end   

   # process the downloaded file
   # ...
end
ldl
  • 503
  • 4
  • 6