CarrierWave:为所有版本化文件创建相同的唯一文件名

sea*_*ugh 20 ruby filenames ruby-on-rails ruby-on-rails-3 carrierwave

在我详细介绍之前,我会明确指出:有没有人想出办法让Carrierwave保存文件,其名称为时间戳或每个文件唯一的任意字符串?

默认情况下,Carrierwave将每个文件及其备用版本保存在其自己的目录中(以型号ID号命名).我不是这个的粉丝,因为为了使用大的圆形数字而不是一个1000的目录,文件(在我的情况下是图片)中我们得到一个目录,其中有1,000个子目录,每个子目录有一个或两个文件.呸.

现在,当您覆盖Uploader的store_dir方法时,如下所示:

def store_dir
  "uploads/#{model.class.to_s.underscore}/#{mounted_as}"
end
Run Code Online (Sandbox Code Playgroud)

你最终得到了我想要的确切行为.所有文件(图片)都进入一个大的快乐文件夹.当对象被删除时,不再有子文件夹.

只有一个问题.文件冲突.如果你上传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
Run Code Online (Sandbox Code Playgroud)

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

def filename
  @name ||= "#{secure_token}.#{file.extension}" if original_filename
end
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

经过一番思考后,我决定在我的照片控制器中尝试以下操作.

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
  ...
Run Code Online (Sandbox Code Playgroud)

这会覆盖原始文件名属性,然后Carrierwave会将其作为时间戳.它完全符合我的要求.该文件的原始版本最终得到一个名称,如1312332906940106.jpg,缩略图版本(或任何其他版本)最终得到一个名称,如thumb_1312332906940106.jpg.

但是,这似乎是一个可怕的黑客.这应该是模型的一部分,或者更好的是安装到模型上的上传器的一部分.

所以,我的问题是,有没有更好的方法来实现这一目标?我是否错过了Carrierwave至关重要的一些内容?是否有一个不那么明显但更清洁的方式来解决这个问题?工作代码很好,但是工作代码没有异味更好.

iwa*_*bed 21

你可以做这样的事情在你的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
Run Code Online (Sandbox Code Playgroud)

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

  • 仅供参考..它似乎已经在CarrierWave Wiki中了:) https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Create-random-and-unique-filenames-for-all-versioned-档 (4认同)
  • 哇.你刚刚结束了我的一周!这完美无瑕.我修改了你的一点点,只是使用我已经使用过的格式(因为它在美学上让我高兴).但这确实完全符合我的需要,并且在一个有意义的地方以一种非常容易理解和优雅的Ruby方式.这真的非常真的应该在Carrierwave自述文件中.我不可能是唯一一个使用这个非常奇妙的宝石的人,他几乎拔出了头发试图找出这个看似微不足道的任务. (3认同)
  • 这段代码会破坏,因为filename不能使用model.id,因为在创建资源之前它不会被设置. (3认同)