How to specify a prefix when uploading to S3 using activestorage's direct upload?

后端 未结 4 811
渐次进展
渐次进展 2020-12-03 05:42

With a standard S3 configuration:

AWS_ACCESS_KEY_ID:        [AWS ID]
AWS_BUCKET:               [bucket name]
AWS_REGION:               [region]
AWS_SECRET_AC         


        
相关标签:
4条回答
  • 2020-12-03 06:19

    The above solution will still give IntegrityError, need to use File.open(file). Thank Though for idea.

    class History < ApplicationRecord
      has_one_attached :gs_history_file
    
      def attach(file) # method for attaching in the model
        blob_key = destination_pathname(file)
        blob = ActiveStorage::Blob.find_by(key: blob_key.to_s)
        unless blob
          blob = ActiveStorage::Blob.new.tap do |blob|
            blob.filename = blob_key.to_s
            blob.key = blob_key
            #blob.byte_size = 123123
            #blob.checksum = Time.new.strftime("%Y%m%d-") + Faker::Alphanumeric.alpha(6)
            blob.upload File.open(file)
            blob.save!
          end
        end
    
        # Attach method from ActiveStorage
        self.gs_history_file.attach blob
      end
    
      def destination_pathname(file)
        "testing/filename-#{Time.now}.xlsx"
      end
    end
    
    0 讨论(0)
  • 2020-12-03 06:24

    Thank you, Sonia, for your answer.

    I tried your solution and it works great, but I encountered problems with overwriting attachments. I often got IntegrityError while doing it. I think, that this and checksum handling may be the reason why the Rails core team don't want to add passing pathname feature. It would require changing the entire logic of the upload method.

    ActiveStorage::Attached#create_from_blob method, could also accepts an ActiveStorage::Blob object. So I tried a different approach:

    1. Create a Blob manually with a key that represents desired file structure and uploaded attachment.
    2. Attach created Blob with the ActiveStorage method.

    In my usage, the solution was something like that:

    def attach file # method for attaching in the model
      blob_key = destination_pathname(file)
      blob = ActiveStorage::Blob.find_by(key: blob_key.to_s)
    
      unless blob
        blob = ActiveStorage::Blob.new.tap do |blob|
          blob.filename = blob_key.basename.to_s
          blob.key = blob_key
          blob.upload file
          blob.save!
        end
      end
    
      # Attach method from ActiveStorage
      self.file.attach blob
    end
    

    Thanks to passing a full pathname to Blob's key I received desired file structure on a server.

    0 讨论(0)
  • 2020-12-03 06:33

    My current workaround (at least until ActiveStorage introduces the option to pass a path for the has_one_attached and has_many_attached macros) on S3 is to implement the move_to method.

    So I'm letting ActiveStorage save the image to S3 as it normally does right now (at the top of the bucket), then moving the file into a folder structure.

    The move_to method basically copies the file into the folder structure you pass then deletes the file that was put at the root of the bucket. This way your file ends up where you want it.

    So for instance if we were storing driver details: name and drivers_license, save them as you're already doing it so that it's at the top of the bucket.

    Then implement the following (I put mine in a helper):

            module DriversHelper
    
              def restructure_attachment(driver_object, new_structure)
    
              old_key = driver_object.image.key
    
              begin
                # Passing S3 Configs
                config = YAML.load_file(Rails.root.join('config', 'storage.yml'))
    
                s3 = Aws::S3::Resource.new(region: config['amazon']['region'],
                                           credentials: Aws::Credentials.new(config['amazon']['access_key_id'], config['amazon']['secret_access_key']))
    
                # Fetching the licence's Aws::S3::Object
                old_obj = s3.bucket(config['amazon']['bucket']).object(old_key)
    
                # Moving the license into the new folder structure
                old_obj.move_to(bucket: config['amazon']['bucket'], key: "#{new_structure}")
    
    
                update_blob_key(driver_object, new_structure)
              rescue => ex
                driver_helper_logger.error("Error restructuring license belonging to driver with id #{driver_object.id}: #{ex.full_message}")
              end
              end
    
              private
    
              # The new structure becomes the new ActiveStorage Blob key
              def update_blob_key(driver_object, new_key)
                blob = driver_object.image_attachment.blob
                begin
                  blob.key = new_key
                  blob.save!
                rescue => ex
                  driver_helper_logger.error("Error reassigning the new key to the blob object of the driver with id #{driver_object.id}: #{ex.full_message}")
                end
              end
    
              def driver_helper_logger
                @driver_helper_logger ||= Logger.new("#{Rails.root}/log/driver_helper.log")
              end
            end
    

    It's important to update the blob key so that references to the key don't return errors.

    If the key is not updated any function attempting to reference the image will look for it in it's former location (at the top of the bucket) rather than in it's new location.

    I'm calling this function from my controller as soon as the file is saved (that is, in the create action) so that it looks seamless even though it isn't.

    While this may not be the best way, it works for now.

    FYI: Based on the example you gave, the new_structure variable would be new_structure = "development/#{driver_object.image.key}".

    I hope this helps! :)

    0 讨论(0)
  • 2020-12-03 06:34

    Sorry, that’s not currently possible. I’d suggest creating a bucket for Active Storage to use exclusively.

    0 讨论(0)
提交回复
热议问题