Carrierwave 预先计算文件的 md5 校验和作为文件名

xer*_*rph 5 ruby-on-rails carrierwave ruby-on-rails-3.1

使用carrierwave上传器上传图片,尝试使用md5校验和作为文件名提供上传图片的唯一性

看起来我做错了什么

模型定义如下:

class Image < ActiveRecord::Base
  attr_accessible :description, :img
  mount_uploader :img, ImageUploader
Run Code Online (Sandbox Code Playgroud)

我的上传器代码如下:

class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file

def store_dir
  "images/#{filename[0,2]}"
end

def md5
  @md5 ||= ::Digest::MD5.file(current_path).hexdigest
end

def filename
  @name ||= "#{md5}#{::File.extname(current_path)}" if super
end
Run Code Online (Sandbox Code Playgroud)

首先,我怀疑每次查询图像条目以显示时,这种方法都会计算校验和

其次,保存图像条目后,每隔一个img.original_filename img.filename img.path img.current_path似乎都未定义,并出现以下错误:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
app/uploaders/image_uploader.rb:17:in `store_dir'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:43:in `store_path'
carrierwave (0.5.7) lib/carrierwave/storage/file.rb:41:in `retrieve!'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:95:in `block in retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/uploader/callbacks.rb:17:in `with_callbacks'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:94:in `retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/mount.rb:311:in `uploader'
Run Code Online (Sandbox Code Playgroud)

任何形式的帮助或提示表示赞赏

更新:

以这种方式更改上传器:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
app/uploaders/image_uploader.rb:17:in `store_dir'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:43:in `store_path'
carrierwave (0.5.7) lib/carrierwave/storage/file.rb:41:in `retrieve!'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:95:in `block in retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/uploader/callbacks.rb:17:in `with_callbacks'
carrierwave (0.5.7) lib/carrierwave/uploader/store.rb:94:in `retrieve_from_store!'
carrierwave (0.5.7) lib/carrierwave/mount.rb:311:in `uploader'
Run Code Online (Sandbox Code Playgroud)

current_path似乎是指表单提交的临时文件的完整路径,因此对扩展名提取有效,摘要计算 img_identifier代表持久化结果文件名,因此对前缀提取有效,因为我们store_dir 仍然不确定这种方法是否引起了任何警告

也仍然不相信应该执行文件唯一性验证的方式

更新:

before_validation在我的模型类中添加了这个回调:

def store_dir
  "images/#{model.img_identifier[0,2]}"
end

def filename
  @name ||= "#{md5}#{::File.extname(current_path)}"
end

protected
def md5
  var = :"@#{mounted_as}_md5"
  model.instance_variable_get(var) or model.instance_variable_set(var, ::Digest::MD5.file(current_path).hexdigest)
end
Run Code Online (Sandbox Code Playgroud)

这里checksum是我的模型的数据库表的独立字符串字段
因为它复制了相当多余的img一般领域,但我仍然无法找出方法来验证的独特性img本身。

更新:

以这种方式远离数据库冗余。在我的模型中:

validates_uniqueness_of :checksum
before_validation :assign_checksum

def assign_checksum
  self.checksum = img.md5 if img.present? and img_changed?
end
Run Code Online (Sandbox Code Playgroud)

现在没有必要在checksum现场

Til*_*ilo 0

1.当你定义store_dir..文件名时似乎是NIL!

这似乎是立即出现的错误 -尝试使用puts语句打印出文件名设置的内容。

如果文件名是 NIL,您将看到以下错误:

You have a nil object when you didn't expect it!
You might have expected an instance of Array.
The error occurred while evaluating nil.[]
app/uploaders/image_uploader.rb:17:in `store_dir'
Run Code Online (Sandbox Code Playgroud)

笔记:

您正在覆盖filename和 store_dir ...并在 store_dir 的定义中使用 filename ...这里可能存在“先有鸡还是先有蛋”类型的问题..最好检查一下

store_dir应该只是一个目录,例如/somewhere/on/your/disk/images

文件名应该只是一个没有路径的文件名,例如24371592d9ea16625854ed68ac4b5846,或 24371592d9ea16625854ed68ac4b5846.jpg

例如检查这两个在代码中如何使用store.rb (在下面的末尾)

问题:

使用filename[0,2]- 您正在使用带有 MD5-sum 2 字母前缀的目录来存储图像?

2. 旁注:什么是current_path似乎用词不当。应该是路径+文件名,而不仅仅是路径

3. 检查第二个代码片段(如下)store.rb

似乎您设置store_dir为相对目录——这非常脆弱且容易出错..将其设置为绝对路径(以“/”开头)可能是一个更好的主意

4. 尝试将 filename 和 store_dir 设置为常量以进行调试

就像健全性检查一样,当您执行以下操作时它是否有效:

def store_dir
   '/tmp/'      # from the source code, it looks like this needs to start with '/' !!
end

def filename
   'my_uploaded_file'
end
Run Code Online (Sandbox Code Playgroud)

在将文件名设置为 MD5 总和之前应该可以工作。


来自源代码: (0.5.7)

lib/carrierwave/storage/file.rb

lib/carrierwave/uploader/store.rb

您可以覆盖 CarrierWave::Uploader::Store#filename以指向您选择的文件名(请参阅源代码):

在 CarrierWave::Uploader::Base 中覆盖它应该可以工作,因为 'Store' 包含在 'Base' 中;这会覆盖默认文件名。

store.rb

  # Override this in your Uploader to change the filename.                                                                                                                                                                                                                   
  #                                                                                                                                                                                                                                                                          
  # Be careful using record ids as filenames. If the filename is stored in the database                                                                                                                                                                                      
  # the record id will be nil when the filename is set. Don't use record ids unless you                                                                                                                                                                                      
  # understand this limitation.                                                                                                                                                                                                                                              
  #                                                                                                                                                                                                                                                                          
  # Do not use the version_name in the filename, as it will prevent versions from being                                                                                                                                                                                      
  # loaded correctly.                                                                                                                                                                                                                                                        
  #                                                                                                                                                                                                                                                                          
  # === Returns                                                                                                                                                                                                                                                              
  #                                                                                                                                                                                                                                                                          
  # [String] a filename                                                                                                                                                                                                                                                      
  #                                                                                                                                                                                                                                                                          
  def filename
    @filename
  end
Run Code Online (Sandbox Code Playgroud)

您还可以检查缓存的文件名,计算其 MD5-sum (或更好的 SHA1 sum),然后使用结果来命名文件。


store.rb

  # Calculates the path where the file should be stored. If +for_file+ is given, it will be                                                                                                                                                                                 
  # used as the filename, otherwise +CarrierWave::Uploader#filename+ is assumed.                                                                                                                                                                                            
  #                                                                                                                                                                                                                                                                         
  # === Parameters                                                                                                                                                                                                                                                          
  #                                                                                                                                                                                                                                                                         
  # [for_file (String)] name of the file <optional>                                                                                                                                                                                                                         
  #                                                                                                                                                                                                                                                                         
  # === Returns                                                                                                                                                                                                                                                             
  #                                                                                                                                                                                                                                                                         
  # [String] the store path                                                                                                                                                                                                                                                 
  #                                                                                                                                                                                                                                                                         
  def store_path(for_file=filename)   # DEFAULT argument is filename - you don't need to override both
    File.join([store_dir, full_filename(for_file)].compact)
  end
Run Code Online (Sandbox Code Playgroud)

文件平等:

files_equal?( filename1, filename2 )

      return true if File.size(filename1) == File.size(filename2)

      # return MD5(filename1) == MD5(filename2)

      # as we assume that the filename == MD5 + some suffix :

      return File.basename(filename1) == File.basename(filename2)   # if names == MD5 are the same

end
Run Code Online (Sandbox Code Playgroud)