【问题标题】:Paperclip renaming files after they're saved回形针在保存文件后重命名文件
【发布时间】:2010-04-25 12:02:26
【问题描述】:

上传并保存文件后如何重命名文件? 我的问题是我需要自动解析有关文件的信息以便提出文件名,该文件应与我的应用程序一起保存,但在记录之前我无法访问生成文件名所需的信息模型已保存。

【问题讨论】:

  • 生成文件名需要哪些信息?它可能在保存之前立即可用,如下面的@Voyta 示例所示,也可能不可用,这会影响解决方案。

标签: ruby-on-rails ruby paperclip


【解决方案1】:

例如,如果您的模型具有属性图像:

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

默认情况下,papeclip 文件存储在 /system/:attachment/:id/:style/:filename 中。

因此,您可以通过重命名每种样式,然后更改数据库中的 image_file_name 列来完成。

(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

【讨论】:

    【解决方案2】:

    你签出paperclip interpolations了吗?

    如果它是您可以在控制器中计算出来的东西(在它被保存之前),您可以使用控制器、模型和插值的组合来解决您的问题。

    我有这个例子,我想根据文件的 MD5 哈希来命名文件。

    在我的控制器中,我有:

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

    然后我有一个config/initializers/paperclip.rb

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

    最后,在我的模型中,我有:

    validates_attachment_presence :upload
    has_attached_file :upload,
      :path => ':rails_root/public/files/:md5.:extension',
      :url => '/files/:md5.:extension'
    

    【讨论】:

      【解决方案3】:

      添加到@Voyta 的答案,如果您使用带有回形针的 S3:

      (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)
      

      【讨论】:

      • 第 2 行应为 AWS::S3::S3Object.rename(record.image.path(style), new_file_path, record.image.bucket_name)。如果您不包含style,它将默认为:originalbucket_name 可以从附件实例中确定。
      • 现在称为“rename_to”或“move_to”
      • 不应该是record.image.s3_object.move_to而不是AWS::S3::S3Object.move_to 吗? move_to 是 AWS::S3::S3Object 的实例方法,而不是类方法。
      • 使用上述方法在 S3 中重命名文件并更新文件名为 DB 后,我无法使用新的 url 获取文件(由 Model.image.url 给出或 url 来自firefox S3Organiser),我只有在重新处理后才能得到图像?那么是否有必要重新处理图像?还是我错过了什么?
      • @Puneeth 当您将 s3 对象移动到新位置时,您还需要复制权限:record.image.s3_object(style).move_to new_file_path, acl: record.image.s3_permissions, content_type: record.image.content_type
      【解决方案4】:

      我的头像图像是用用户 slug 命名的,如果他们更改名称,我也必须重命名图像。

      这就是我使用 S3 和回形针重命名头像图像的方式。

      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
      

      【讨论】:

      • 你是怎么得到name和name_was的?
      • 名称是我的用户 (ActiveRecord::Base) 中的一个属性(数据库中的列)。 _was_changed? 来自 ActiveModel::Dirty api.rubyonrails.org/classes/ActiveModel/Dirty.html
      • 我把:extension = File.extname avatar_image_file_name改为extension = File.extname(avatar_image.path(style)).downcase,因为我的样式扩展不一定和原来的一样。
      • 谢谢!我在这里做的唯一改变是使用extension = Paperclip::Interpolations.extension(self.avatar_image, style) ...如果你需要计算其他标准回形针的东西,比如'basename'或'attachment',它会派上用场-rubydoc.info/github/thoughtbot/paperclip/Paperclip/…
      【解决方案5】:

      要添加另一个答案,这是我用于 S3 重命名的完整方法:

        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
      

      使用方法:Model.find(#).rename(:avatar, "test.jpg")

      【讨论】:

      • 很好的解决方案 - 这里还需要注意的是 s3 对象的访问策略。当您调用 move_to 时,访问策略默认为私有。有一个:acl 选项可以传递给move_to 方法。
      • 为我工作,非常干净的示例在模型上创建方法。
      【解决方案6】:

      我想捐赠我的“安全移动”解决方案,该解决方案不依赖任何私有 API,可防止因网络故障导致数据丢失:

      首先,我们获取每种样式的新旧路径:

      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/},'')]} ]
      

      然后,我们将每个文件复制到它的新路径。由于默认路径包括对象 ID 和文件名,因此它永远不会与不同文件的路径发生冲突。但是如果我们尝试重命名而不更改名称,这将失败:

      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
      

      现在我们将更新后的模型应用到数据库:

      save!
      

      在我们创建新的 S3 对象之后但在我们删除旧的 S3 对象之前执行此操作很重要。如果数据库更新失败(例如,网络拆分时间错误),此线程中的大多数其他解决方案可能会导致数据丢失,因为这样文件将位于新的 S3 位置,但数据库仍指向旧位置。这就是为什么我的解决方案在数据库更新成功之前不会删除旧的 S3 对象:

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

      就像复制一样,我们不可能不小心删除另一个数据库对象的数据,因为对象 ID 包含在路径中。因此,除非您同时重命名同一个数据库对象 A->B 和 B->A(例如 2 个线程),否则此删除将始终是安全的。

      【讨论】:

        【解决方案7】:

        添加到@Fotios 的答案:

        这是我认为制作自定义文件名的最佳方式,但如果您想要基于 md5 的文件名,您可以使用 Paperclip 中已有的指纹。

        你所要做的就是把它放到 config/initializers/paperclip_defaults.rb

        Paperclip::Attachment.default_options.update({
            # :url=>"/system/:class/:attachment/:id_partition/:style/:filename"
            :url=>"/system/:class/:attachment/:style/:fingerprint.:extension"
            })
        

        这里不需要设置 :path ,因为默认情况下是这样设置的:

        :path=>":rails_root/public:url"
        

        我没有检查是否有必要,但如果它对您不起作用,请确保您的模型能够将指纹保存在数据库中 -> here

        另一个我觉得很方便的技巧是使用 rails 控制台来检查它是如何工作的:

        $ rails c --sandbox
        > Paperclip::Attachment.default_options
        ..
        > s = User.create(:avatar => File.open('/foo/bar.jpg', 'rb'))
        ..
        > s.avatar.path
         => "/home/groovy_user/rails_projectes/funky_app/public/system/users/avatars/original/49332b697a83d53d3f3b5bebce7548ea.jpg" 
        > s.avatar.url 
         => "/system/users/avatars/original/49332b697a83d53d3f3b5bebce7548ea.jpg?1387099146" 
        

        【讨论】:

          【解决方案8】:

          以下迁移解决了我的问题。

          avatar 重命名为photo

          class RenamePhotoColumnFromUsers < ActiveRecord::Migration
            def up
              add_attachment :users, :photo
          
              # Add `avatar` method (from Paperclip) temporarily, because it has been deleted from the model
              User.has_attached_file :avatar, styles: { medium: '300x300#', thumb: '100x100#' }
              User.validates_attachment_content_type :avatar, content_type: %r{\Aimage\/.*\Z}
          
              # Copy `avatar` attachment to `photo` in S3, then delete `avatar`
              User.where.not(avatar_file_name: nil).each do |user|
                say "Updating #{user.email}..."
          
                user.update photo: user.avatar
                user.update avatar: nil
              end
          
              remove_attachment :users, :avatar
            end
          
            def down
              raise ActiveRecord::IrreversibleMigration
            end
          end
          

          希望对你有帮助:)

          【讨论】:

            【解决方案9】:

            另一个选项设置为默认值,适用于所有上传。

            此示例将名称文件更改为 web 的“名称默认值”,例如:test áé.jpgtest_ae.jpg

            helper/application_helper.rb

            def sanitize_filename(filename)
                fn = filename.split /(?<=.)\.(?=[^.])(?!.*\.[^.])/m
                fn[0] = fn[0].parameterize
                return fn.join '.'
            end
            

            创建config/initializers/paperclip_defaults.rb

            include ApplicationHelper
            
            Paperclip::Attachment.default_options.update({
                :path => ":rails_root/public/system/:class/:attachment/:id/:style/:parameterize_file_name",
                :url => "/system/:class/:attachment/:id/:style/:parameterize_file_name",
            })
            
            Paperclip.interpolates :parameterize_file_name do |attachment, style|
                sanitize_filename(attachment.original_filename)
            end
            

            输入这段代码后需要重启

            【讨论】:

              猜你喜欢
              • 2012-05-04
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-09-24
              • 2010-12-31
              • 1970-01-01
              • 1970-01-01
              • 2013-07-02
              相关资源
              最近更新 更多