文件保存后Paperclip重命名文件

19

如何在文件上传并保存后重命名文件? 我的问题是,为了使用我的应用程序,我需要自动解析有关文件的信息,以便确定文件应该保存的文件名,但是我无法访问生成文件名所需的信息,直到该模型的记录被保存。


你需要哪些信息来生成文件名?它可能会在保存之前立即可用,就像@Voyta的示例一样,或者可能不可用,这会影响解决方案。 - Tim Snowhite
9个回答

24
如果您的模型有一个名为image的属性,例如:
has_attached_file :image, :styles => { ...... }

默认情况下,papepclip文件存储在/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

23

你有没有查看paperclip插值

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

我有一个例子,我想根据文件的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'

18

补充@Voyta的回答,如果您正在使用S3和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)

1
第二行应该写成 AWS::S3::S3Object.rename(record.image.path(style), new_file_path, record.image.bucket_name)。如果您不包括 style,它将默认为 :originalbucket_name 可以从附件实例中确定。 - Zubin
2
现在被称为“rename_to”或“move_to”。 - digitalWestie
3
应该使用record.image.s3_object.move_to而不是AWS::S3::S3Object.move_to,因为move_toAWS::S3::S3Object的实例方法而不是类方法。 - evanrmurphy
在使用上述方法重命名 S3 中的文件并更新数据库中的文件名后,我无法使用新的 URL(由 Model.image.url 或从 Firefox S3Organiser 获取的 URL)获取该文件,只有重新处理后才能获得图像? 因此,是否需要重新处理图像?还是我漏了什么? - Puneeth
确认了@evanrmurphy的建议:必须使用record.image.s3_object(style).move_to new_file_path才能使其正常工作。 - Jason Rust
1
@Puneeth 当您将S3对象移动到新位置时,您需要同时复制权限:record.image.s3_object(style).move_to new_file_path, acl: record.image.s3_permissions, content_type: record.image.content_type - Jason Rust

6

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

这是我使用 S3 和 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

你是如何获得name和name_was的名称的? - evanrmurphy
名字是我的用户(ActiveRecord :: Base)中的属性(数据库列)。 _was_changed? 来自 ActiveModel :: Dirty http://api.rubyonrails.org/classes/ActiveModel/Dirty.html - Pablo Cantero
我将代码从 extension = File.extname avatar_image_file_name 改为了 extension = File.extname(avatar_image.path(style)).downcase,因为我的样式扩展名不一定与原始文件相同。 - Dustin M.
1
谢谢!我唯一要做的更改是使用 extension = Paperclip::Interpolations.extension(self.avatar_image, style) ... 这对于计算其他标准的Paperclip事物(如“basename”或“attachment”)非常有用 - http://www.rubydoc.info/github/thoughtbot/paperclip/Paperclip/Interpolations - Subimage

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时,访问策略默认为私有。但是,您可以通过move_to方法传递:acl选项来更改它。 - digitalWestie
对我很有用,非常干净的示例来创建模型上的方法。 - Sami Birnbaum

3

我希望能够捐赠我的“安全移动”解决方案,该解决方案不依赖于任何私有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位置,但数据库仍然指向旧位置。这就是为什么我的解决方案直到DB更新成功后才会删除旧的S3对象:

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

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


0

关于@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"

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

我发现另一个方便的提示是使用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" 

0

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

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

希望能有所帮助 :)

0

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

这个例子将文件名更改为“name default”用于网络,例如:test áé.jpg 变成 test_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

在放置此代码后,需要重新启动


这会打破现有的记录吗? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接