Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Carrierwave precalculated md5 checksum of a file as a filename

Using carrierwave uploader for images, trying to provide uniqueness of uploaded images using md5 checksum as filename

looks like I'm doing something wrong

model is defined like:

class Image < ActiveRecord::Base
  attr_accessible :description, :img
  mount_uploader :img, ImageUploader

My uploader code is as following:

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

first of all, I suspect this approach inflicts calculation of checksum each time image entry is queried to display

secondly, after image entry is saved, every other of img.original_filename img.filename img.path img.current_path seem to be undefined with following error:

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'

any kind of help or tip is appreciated

UPD:

changed uploader this way:

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

current_path seems to refer to full path of form-submitted tempfile, thus being valid for extension extraction and digest calculation img_identifier stands for persisting resulting filename and thus goes valid for prefix extraction for our store_dir still not sure if any caveat is induced with this approach

also still not convinced about the way file uniqueness validation should be performed

UPD:

I've added this before_validation callback in my model class:

validates_uniqueness_of :checksum
before_validation :assign_checksum

def assign_checksum
  self.checksum = img.md5 if img.present? and img_changed?
end

where checksum is a separate string field in my model's db table
it is quite redundant as it duplicates the img field in general, but I still can't figure out the way to validate uniqueness of img itself.

UPD:

Moved away from db redundancy this way. In my model:

validate :img_uniqueness

def img_uniqueness
  errors.add :img, "Image already exists in database" if Image.where(:img => self.img.filename).first
end

now there's no need in checksum field

like image 538
xeromorph Avatar asked Oct 10 '22 16:10

xeromorph


2 Answers

This could help: How to: Use file`s MD5 as filename https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-file%60s-MD5-as-filename

And maybe

How-to: Use file's digest (e.g. MD5, SHA-1) as file path https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-file's-digest-(e.g.-MD5,-SHA-1)-as-file-path

like image 185
Daniel Pérez Rada Avatar answered Oct 24 '22 02:10

Daniel Pérez Rada


Add a md5hash field to your model, then add the following code to your Model:

  before_validation :compute_hash
  validates_uniqueness_of :md5hash, :on => :create

  def compute_hash
    self.md5hash = Digest::MD5.hexdigest(self.file.read)
  end

That should do the trick.

like image 27
lafeber Avatar answered Oct 24 '22 02:10

lafeber