Paperclip renaming files after they're saved

前端 未结 9 1420
情书的邮戳
情书的邮戳 2020-12-07 23:34

How do I rename a file after is has been uploaded and saved? My problem is that I need to parse information about the files automatically in order to come up with the file

相关标签:
9条回答
  • 2020-12-07 23:54

    To add to @Voyta's answer, if you're using S3 with paperclip:

    (record.image.styles.keys+[:original]).each do |style|
      AWS::S3::S3Object.move_to record.image.path(style), new_file_path, record.image.bucket_name
    end
    
    record.update_attribute(:image_file_name, new_file_name)
    
    0 讨论(0)
  • 2020-12-07 23:55

    Have you checked out paperclip interpolations?

    If it is something that you can figure out in the controller (before it gets saved), you can use a combination of the controller, model, and interpolation to solve your problem.

    I have this example where I want to name a file based on it's MD5 hash.

    In my controller I have:

    params[:upload][:md5] = Digest::MD5.file(file.path).hexdigest
    

    I then have a config/initializers/paperclip.rb with:

    Paperclip.interpolates :md5 do|attachment,style| 
      attachment.instance.md5
    end
    

    Finally, in my model I have:

    validates_attachment_presence :upload
    has_attached_file :upload,
      :path => ':rails_root/public/files/:md5.:extension',
      :url => '/files/:md5.:extension'
    
    0 讨论(0)
  • 2020-12-07 23:56

    I'd like to donate my "safe move" solution that doesn't rely on any private API and protects against data loss due to network failure:

    First, we get the old and new paths for every style:

    styles = file.styles.keys+[:original]
    old_style2key = Hash[ styles.collect{|s| [s,file.path(s).sub(%r{\A/},'')]} ]
    self.file_file_name = new_filename
    new_style2key = Hash[ styles.collect{|s| [s,file.path(s).sub(%r{\A/},'')]} ]
    

    Then, we copy every file to it's new path. Since the default path includes both object ID and filename, this can never collide with the path for a different file. But this will fail if we try to rename without changing the name:

    styles.each do |style|
      raise "same key" if old_style2key[style] == new_style2key[style]
      file.s3_bucket.objects[old_style2key[style]].copy_to(new_style2key[style])
    end
    

    Now we apply the updated model to the DB:

    save!
    

    It is important to do this after we create the new S3 objects but before we delete the old S3 objects. Most of the other solutions in this thread can lead to a loss of data if the database update fails (e.g. network split with bad timing), because then the file would be at a new S3 location but the DB still points to the old location. That's why my solution doesn't delete the old S3 objects until after the DB update succeeded:

    styles.each do |style|
      file.s3_bucket.objects[old_style2key[style]].delete
    end
    

    Just like with the copy, there's no chance that we accidentally delete another database object's data, because the object ID is included in the path. So unless you rename the same database object A->B and B->A at the same time (e.g. 2 threads), this delete will always be safe.

    0 讨论(0)
  • 2020-12-08 00:03

    If, for example, your model has attribute image:

    has_attached_file :image, :styles => { ...... }
    

    By default papepclip files are stored in /system/:attachment/:id/:style/:filename.

    So, You can accomplish it by renaming every style and then changing image_file_name column in database.

    (record.image.styles.keys+[:original]).each do |style|
        path = record.image.path(style)
        FileUtils.move(path, File.join(File.dirname(path), new_file_name))
    end
    
    record.image_file_name = new_file_name
    record.save
    
    0 讨论(0)
  • 2020-12-08 00:05

    My avatar images are named with the user slug, if they change their names I have to rename images too.

    That's how I rename my avatar images using S3 and paperclip.

    class User < ActiveRecord::Base
      after_update :rename_attached_files_if_needed
    
      has_attached_file :avatar_image,
        :storage        => :s3,
        :s3_credentials => "#{Rails.root}/config/s3.yml",
        :path           => "/users/:id/:style/:slug.:extension",
        :default_url    => "/images/users_default.gif",
        :styles         => { mini: "50x50>", normal: "100x100>", bigger: "150x150>" }
    
      def slug
        return name.parameterize if name
        "unknown"
      end
    
    
      def rename_attached_files_if_needed
        return if !name_changed? || avatar_image_updated_at_changed?
        (avatar_image.styles.keys+[:original]).each do |style|
          extension = Paperclip::Interpolations.extension(self.avatar_image, style)
          old_path = "users/#{id}/#{style}/#{name_was.parameterize}#{extension}"
          new_path = "users/#{id}/#{style}/#{name.parameterize}#{extension}"
          avatar_image.s3_bucket.objects[old_path].move_to new_path, acl: :public_read
        end
      end
    end
    
    0 讨论(0)
  • 2020-12-08 00:05

    And to add yet another answer, here is the full method I'm using for S3 renaming :

      def rename(key, new_name)
        file_name = (key.to_s+"_file_name").to_sym
        old_name = self.send(file_name)
        (self.send(key).styles.keys+[:original]).each do |style|
          path = self.send(key).path(style)
          self[file_name] = new_name
          new_path = self.send(key).path(style)
          new_path[0] = ""
          self[file_name] = old_name
          old_obj = self.send(key).s3_object(style.to_sym)
          new_obj = old_obj.move_to(new_path)
        end
        self.update_attribute(file_name, new_name)
      end
    

    To use : Model.find(#).rename(:avatar, "test.jpg")

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