ruby-on-rails - CarrierWave:为所有版本化文件创建相同、唯一的文件名

标签 ruby-on-rails ruby ruby-on-rails-3 filenames carrierwave

在详细介绍之前,我先开门见山:有没有人想出一种方法让 Carrierwave 将文件的名称保存为时间戳或每个文件唯一的任意字符串?

默认情况下,Carrierwave 将每个文件及其替代版本保存在自己的目录中(以型号 ID 号命名)。我不喜欢这个,因为不是一个目录有 1,000 个,为了使用大的整数,文件(在我的例子中是图片),我们得到一个目录有 1,000 个子目录,每个子目录有一个或两个文件。呸。

现在,当您将 uploader 的 store_dir 方法重写为如下内容时:

def store_dir
  "uploads/#{model.class.to_s.underscore}/#{mounted_as}"
end

你最终得到了我想要的确切行为。所有文件(图片)都放在一个大的 happy 文件夹中。当对象被删除时,不再有子文件夹残留。

只有一个问题。文件冲突。如果你上传 delicious_cake.jpg 两次,即使它们是两张不同的美味蛋糕图片,第二次也会覆盖第一次!这就是为什么 store_dir 方法在它返回的值末尾附加了额外的 /#{model.id} 的原因。

那么,怎么办?仔细阅读后,我发现在生成的上传文件中有一个明显的解决方案被注释掉了。

# Override the filename of the uploaded files:
# Avoid using model.id or version_name here, see uploader/store.rb for details.
# def filename
#   "something.jpg" if original_filename
# end

经过一番搜索,我发现有人做了以下事情

def filename
  @name ||= "#{secure_token}.#{file.extension}" if original_filename
end

这让我开始思考,为什么不这样做

def filename
  @name ||= "#{(Time.now.to_i.to_s + Time.now.usec.to_s).ljust(16, '0')}#{File.extname(original_filename)}"
end

就在那时,事情变得非常糟糕。这样做的问题是 filename 显然会为文件的每个版本调用,因此我们最终得到的文件名为 1312335603175322.jpg 和 thumb_1312335603195323.jpg。注意到细微的差别了吗?每个文件名都基于为特定版本调用 filename 的时间。那根本不行。

接下来我厌倦了使用 model.created_at 作为时间戳的基础。只有一个问题,第一个版本返回 nil,因为它还没有被放入数据库。

经过进一步思考,我决定在我的图片 Controller 中尝试以下操作。

def create
  if params[:picture] and params[:picture][:image]
    params[:picture][:image].original_filename = "#{(Time.now.to_i.to_s + Time.now.usec.to_s).ljust(16, '0')}#{File.extname(params[:picture][:image].original_filename)}"
  end
  ...

这会在 Carrierwave 到达它之前覆盖 original_filename 属性,使其成为时间戳。它完全符合我的要求。文件的原始版本以 1312332906940106.jpg 之类的名称结尾,缩略图版本(或任何其他版本)以 thumb_1312332906940106.jpg 之类的名称结尾。

但是,这似乎是一个糟糕的黑客攻击。这应该是模型的一部分,或者更好的是安装到模型上的 uploader 的一部分。

那么,我的问题是,是否有更好的方法来实现这一点?我是不是错过了 Carrierwave 的一些重要的东西,让这一切变得简单?有没有一种不那么明显但更简洁的方法来解决这个问题?工作代码很好,但没有异味的工作代码更好。

最佳答案

你可以在你的 uploader 文件中做这样的事情,它也适用于版本文件(即如果你有一个图像然后创建同一文件的其他 3 个缩略图版本,它们将都具有相同的名称,只是在名称上附加了尺寸信息):

  # Set the filename for versioned files
  def filename
    random_token = Digest::SHA2.hexdigest("#{Time.now.utc}--#{model.id.to_s}").first(20)
    ivar = "@#{mounted_as}_secure_token"    
    token = model.instance_variable_get(ivar)
    token ||= model.instance_variable_set(ivar, random_token)
    "#{model.id}_#{token}.jpg" if original_filename
  end

这将创建一个类似这样的文件名,例如:76_a9snx8b81js8kx81kx92.jpg,其中 76 是模型的 ID,另一位是随机的 SHA 十六进制数。

关于ruby-on-rails - CarrierWave:为所有版本化文件创建相同、唯一的文件名,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6920926/

相关文章:

ruby-on-rails - 为什么 Rails 4 测试不重置数据库

ruby - 为什么 RVM 卡在 "#importing default gemsets, this may take time"上?

ruby - 从 sinatra 返回 base64 图像

ruby-on-rails - 在使用 ActiveAdmin 进行验证之前,使用 HABTM 关联编辑对象修改数据库

ruby-on-rails - Rails 4.2 语法错误,意外 ':',期待 =>

ruby-on-rails - 使用@import 或 require 将 css 和 js 导入 Rails 4.2.4 应用程序

ruby-on-rails - 在 Rails 3.1 中隐藏语言环境参数?

ruby-on-rails - 获取未定义的方法 `empty?' for nil :NilClass in my test suite when I try to call a Rails path helper (*_path) using Rails, Rspec

ruby-on-rails - rails structure.sql 和 schema.rb 有什么区别

ruby-on-rails - Rails - 如何保护外键并仍然允许关联选择