Carrierwave precomputed the md5 checksum of a file as a file name

Using a carrier loader for images, trying to ensure the uniqueness of downloaded images using the md5 checksum as the file name

it looks like I'm doing something wrong

Model

defined as follows:

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

My bootloader code is as follows:

 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 that this approach causes a checksum to be calculated every time an image record is requested for display

secondly, after saving the image record, all the rest img.original_filename img.filename img.path img.current_path seem undefined with the 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 help or hint is welcome

UPD:

changed the bootloader like this:

 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 the full path of the tempfile file provided on the form, so it is valid for calculating the extension and calculating the digest img_identifier means saving the current file name and therefore valid for the prefix for our store_dir are still not sure if any caution with this approach.

also still not convinced that a file uniqueness check should be performed

UPD:

I 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 db table
it is pretty redundant as it duplicates the img field in general, but I still cannot figure out a way to verify the uniqueness of img itself.

UPD:

Moved with 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 no need for checksum field

+4
source share
4 answers

This may help: How to use the MD5 file as the file name https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Use-file%60s-MD5-as-filename

And maybe,

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

+3
source

1. When you define store_dir .. filename it seems NIL !!

There seems to be an immediate error - try the puts statement to print what file name is set to.

If the file name is NIL, you will see the error message that you see:

 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' 

NOTE.

you override both file_name and store_dir ... and use the file name inside the store_dir definition ... There might be a chicken and egg problem here. Better check that

store_dir should just be a directory, for example. /somewhere/on/your/disk/images

filename should be just a filename without a path, for example. 24371592d9ea16625854ed68ac4b5846 , or 24371592d9ea16625854ed68ac4b5846.jpg

eg. check how these two are used in the code in store.rb (at the end below)

Question:

using filename[0,2] - do you use directories with a 2-letter prefix MD5-sum to store images?

2. Side note: What is current_path ? Seems wrong. There must be a path + file name, not just a path

3. Check the second code snippet (below) from store.rb

It looks like you set store_dir in a relative directory - it's very fragile and error prone .. it would be better to set it to an absolute path (starting with '/')

4. Try specifying the file name and store_dir for constants for debugging

As a health check, this works when you do this:

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

which should work before setting the file name to the amount of MD5.


From the source code: (0.5.7)

Library / carrierwave / storage / file.rb

Library / carrierwave / downloader / store.rb

You can override CarrierWave :: Uploader :: Store # file_name to specify the file name of your choice (see source code):

Redefinition in CarrierWave :: Uploader :: Base should work because "Store" is included in "Base"; this overrides the default file name.

From 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 

You can also check the cached file name, calculate the MD5 sum (or better SHA1 sum ) on it, and then use the result for the file name.


From 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 

File Equality:

 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 
0
source

Add the 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.

0
source

All Articles