Active Storage with Amazon S3 not saving with file

2020-07-18 05:09发布

问题:

I am having an issue with Active Storage. When I upload to Amazon S3, instead of saving the file inside the bucket with the original name like myfile.zip it is saving it as the key which is associated with that file. So in Cyberduck I am seeing something like this: 5YE1aJQuFYyWNr6BSHxhQ48t. Without any file extension.

I am not sure if there is some setting in Rails 5 or whether it is within Amazon S3 but I have spent hours Googling around to figure out why this is happening.

Any pointers would be really appreciated!

Best regards, Andrew

回答1:

This is by design, from ActiveStorage. The file is stored by it's key and without extension on S3, but when the URL is generated by ActiveStorage, the disposition and filename are set.

def url(key, expires_in:, filename:, disposition:, content_type:)
  instrument :url, key: key do |payload|
    generated_url = object_for(key).presigned_url :get, expires_in: expires_in.to_i,
      response_content_disposition: content_disposition_with(type: disposition, filename: filename),
      response_content_type: content_type

    payload[:url] = generated_url

    generated_url
  end

end

This is probably done to avoid filename escaping issues that you'd run into otherwise.

You can read more about the Content-Disposition headers here.



回答2:

You can still reference the name with filename accessor.

class User < ApplicationRecord
  has_one_attached :photo
  ...
end

filename = User.first.photo.filename


回答3:

In order to have a custom filename on S3, you should update both blob.key and the name on S3.

Active storage uploads images on S3 using blob.key as remote image path and name.

For my usage, I only changed the name for 'images variants' with a Monkey Patch that allows to generate a key terminating by the filename :

config/initializers/active_storate_variant.rb :

ActiveStorage::Variant.class_eval do
  def key
    "variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}/#{filename}"
  end
end

So when I need the public url for an image variant, I just call image.url('400x400')

This is how my Image model is customized :

class Image < ApplicationRecord
  belongs_to :imageable, polymorphic: true
  has_one_attached :picture

  SIZES = { '400x400' => '400x400' }

  def url(size)
    return "https://placehold.it/#{size}" unless picture.attached?

    'https://my_s3_subdomain.amazonaws.com/' +
        picture.variant(resize: SIZES[size]).processed.key
  end

  ...

end

If someone has a better way to do that, I would be happy to see it :)