Why does the default CarrierWave uploader's `filename` method have a condition on original_filename? - carrierwave

I would like to understand why the default CarrierWave uploader has the condition if original_filename.
# 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
Why isn't it just:
def filename
"something.jpg"
end

I didn't check in details the source code, but if you don't specify this condition, carrierwave will somehow always consider that the image "exists" even there is none (ie if it's not on the filesystem).
So your "non existing image" will have a url, and the default_url will never be used (=> 404 on these images)

Related

Carrierwave doesn't recreate versions after the model update

I've introduced a new version on my Carrierwave Uploader. When I create a new Event it creates both versions correctly. But when I update it, only the file I attached gets uploaded, but versions do not get recreated.
I am using CarrierWave 1.2.2, and looking at the changelog, it doesn't seem to have been a bug that got fixed in the newer versions
class CoverUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
if Rails.env.development? || Rails.env.test?
storage :file
elsif Rails.env.production?
storage :fog
end
# Override the directory where uploaded files will be stored.
# This is a sensible default for uploaders that are meant to be mounted:
def store_dir
if ENV['HEROKU_APP_NAME'].to_s.include?('-pr-')
"review_apps/#{model.class.to_s.underscore}/#{model.id}"
else
"#{Rails.env}/#{model.class.to_s.underscore}/#{model.id}"
end
end
# Provide a default URL as a default if there hasn't been a file uploaded:
def default_url(*args)
ActionController::Base.helpers.asset_path('test.jpg')
end
# Create different versions of your uploaded files:
version :optimised do
process convert: 'webp'
process :set_content_type_to_webp
def full_filename(_for_file = model.cover.file)
"cover_#{model.id}.webp"
end
def exists?
file&.exists?
end
end
def extension_blacklist
%w(webp)
end
private
# Required to actually force Amazon S3 to treat it like an image
def set_content_type_to_webp
file.instance_variable_set(:#content_type, 'image/webp')
end
end
#ogelacinyc was partly correct when he found the bug in full_filename. I went back to test normal functionality with creating another version, with a simple dimension change. I could then see that update would recreate the versions by itself, just like I expected.
That made me think that maybe there is something wrong with my version :optimised block. So after commenting one by one, I found that full_filename was the culprit. It could have been model.cover.file failing silently, but I think it was model.id, as can be seen in the description for filename method in Carrierwave
So instead, I grab the filename directly, extract extension and substitute it with webp:
def full_filename(for_file = model.file_name.file)
extension = File.extname(for_file)
"cover_#{for_file.sub(extension, '.webp')}"
end
Which works without problems!
You need to add an after_save callback to Event and then call recreate_versions! on your mounted uploader.
Assuming you have an Event model with the following, this would solve your problem.
class Event < ApplicationRecord
mount_uploader :cover_image, CoverUploader
after_save :recreate_versions!
delegate :recreate_versions!, to: :cover_image, allow_nil: true
end
See CarrierWave's README also.

carrierwave default_url how to display in views?

Carrierwave supports the default_url for if there is no image attached,
I have this set like:
def default_url
"/assets/avatars/" + [version_name, "_default.png"].compact.join('_')
end
Using a helper method like:
def show_avatar(id)
#profile = User.find(id).profile rescue nil
image_tag #profile.assets.first.file_url(:search) rescue nil
end
How would this to be refactored, so that it displays the default_url image when there is no image? I could not find this, now using a rescue nil because if there is no image attached it would crash.
I may be wrong but I'm sure in this case you would just do
image_tag model.mount.version.url
If there is no version or file then carrierwave uses the default url
I worked around it by doing this:
Image.new.file.url(:version)
Where Image is my model where I'm attaching uploader to the file field. Hope it helps.

How to prevent carrierwave from adding the filename string to the mounted column when file is not uploaded?

I am using Rails and ActiveRecord.
I have carrierwave mounted on one of the columns(:logo) of a model(Listing). My default filename is "disp_logo". Let's say I just do Listing.create! In this case, I haven't really uploaded any file. I did not do Listing.logo=<some file> or Listing.remote_logo_url=<some url>. But, carrierwave still inserts the string "disp_logo" in the :logo column. Why does it do that? How can I prevent carrierwave from doing so?
My uploader class has the following methods:
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def filename
"disp_logo"
end
That's what I mean by default filename.
I want the column :logo to have NULL if image is not uploaded. Instead it has "disp_logo".
It would seem you created your migration with "disp_logo" as the default value for your column. You should set the default image in your uploader instead, like this:
# Provide a default URL as a default if there hasn't been a file uploaded:
def default_url
"/" + [version_name, "disp_logo.jpg"].compact.join('_')
end
Or if you use rails 3.1 and the assets pipeline:
# Include the Sprokets helpers for Rails 3.1+ asset pipeline compatibility:
include Sprockets::Helpers::RailsHelper
include Sprockets::Helpers::IsolatedHelper
# Provide a default URL as a default if there hasn't been a file uploaded:
def default_url
asset_path [version_name, "disp_logo.jpg"].compact.join('_')
end
Edit:
Modify your filename method as follows:
def filename
"something.jpg" unless original_filename.nil?
end
I came up with a solution that fits my use-case better.
class LogoUploader < CarrierWave::Uploader::Base
attr_accessor :upload_failed
def filename
upload_failed ? "failed" : "disp_logo"
end
end
class SomeModel < ActiveRecord::Base
mount_uploader :logo, LogoUploader
def some_method_that_assigns_a_file_and_uploads_it
self.remote_logo_url = <some_url>
OR
self.logo = <some file>
<some_other_logic>
save!
rescue
logo.upload_failed = true
save!
end
end
This way, I know which records were processed:logo #=> "disp_logo", which records failed:logo #=> "failed" and which are yet to be processed:logo #=> nil

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
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
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.
1. When you define store_dir .. filename seems to be NIL!!
That seems to be the immediate error -- try a puts statement to print out what filename is set to..
If the filename is NIL, you'll see the error you're seeing:
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're overriding both filename and store_dir ... and using filename inside the definition of store_dir...
There could be a "chicken and the egg" type of problem here.. better check on that
store_dir should just be a directory, e.g. /somewhere/on/your/disk/images
filename should just be a filename without path, e.g. 24371592d9ea16625854ed68ac4b5846 , or 24371592d9ea16625854ed68ac4b5846.jpg
e.g. check how those two are used in the code in store.rb (at end below)
Question:
using filename[0,2] - you're using directories with 2-letter prefix of the MD5-sum to store the images?
2. Side-note: What is current_path?? Seems a misnomer. Should be a path+filename, not just a path
3. Check the second code snippet (below) from store.rb
Seems like you set store_dir to a relative directory -- that's very fragile and error prone.. it might be a better idea to set it to an absolute path (starting with '/')
4. Try setting filename and store_dir to constants for debugging
Just as a sanity check, does it work 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
that should work before setting filename to MD5 sum..
From the source code: (0.5.7)
lib/carrierwave/storage/file.rb
lib/carrierwave/uploader/store.rb
You can override CarrierWave::Uploader::Store#filename to point to a filename of your choice (see source code):
Overriding it in CarrierWave::Uploader::Base should work, as 'Store' is included in 'Base' ; this overrides the default filename.
Fromstore.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 could also check for the cached filename, compute the MD5-sum (or better SHA1 sum) on it, and then use the result to name the file.
Fromstore.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
I would consider the wiki way for file name uniquess,
https://github.com/jnicklas/carrierwave/wiki/How-to%3A-Create-random-and-unique-filenames-for-all-versioned-files

Renaming uploaded files with Carrierwave

I'm using Carrierwave to upload files, and I have it working.
My issue is attempting to change the name of the uploaded file.
In the generated uploader.rb there is a method I think I should be using
def filename
"something.jpg" if original_filename
basename = "what"+orginal_filename if original_filename, works
basename = (0...8).map{65.+(rand(25)).chr}.join if original_filename # will create a random name for each version, e.g. the orginal, the thumb, and the filename in the db, useless
end
I can't seem to access items like 'extension' or 'content_type' in sanitized_file.rb, so this is a bit beyond my current skill level right now.
Any suggestions or exercises for doing this, i.e. generate filename for an uploaded file that works as well as the carrierwave default (do nothing, but does carry on to each version)? Seems like it should be simple enough but I've stumbled over this.
Well, another problem with your random filename generator is that it's possible to have collisions isn't it? You could possibly generate a filename that was already generated.
One way to go about it would be to somehow generate a hash based on unique properties of the image, like file path. An example, from the carrierwave group:
def filename
if original_filename
#name ||= Digest::MD5.hexdigest(File.dirname(current_path))
"#{#name}.#{file.extension}"
end
end
This will create an MD5 hash based on the current path and then append the original file's extension to it.
Edit: The carrierwave wiki added an entry with a few methods on how to create random and unique filenames for all versioned files.
To have a real unique filename (not almost unique) I recommend to use the uuid gem.
in Gemfile add:
gem 'uuid'
in file_uploader.rb:
def filename
if original_filename
if model && model.read_attribute(mounted_as).present?
model.read_attribute(mounted_as)
else
#name ||= "#{mounted_as}-#{uuid}.#{file.extension}"
end
end
end
protected
def uuid
UUID.state_file = false
uuid = UUID.new
uuid.generate
end
From the Google Group:
def filename
#name ||= "#{secure_token}.#{file.extension}" if original_filename
end
private
def secure_token
ivar = "##{mounted_as}_secure_token"
token = model.instance_variable_get(ivar)
token ||= model.instance_variable_set(ivar, ActiveSupport::SecureRandom.hex(4))
end
To just make the record.id prefix the filename you can do the following:
class MyUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
model.class.to_s.underscore.pluralize
end
def filename
model.id ? "#{model.id}-#{original_filename}" : original_filename
end
def url
"/#{store_dir}/#{model.id}-#{model.file_before_type_cast}"
end
end
The other solution looks good, but how I did it then was to have a hook that created a random string for a new name on instance creation, then:
def filename
"#{model.randomstring}.#{model.image.file.extension}"
end
in the uploader.
That worked, putting the random name generation as part of the model, then having carrierwave use that.
I am curious which is faster, more effective, reasonable, sound, etc.
Here is the solution, how to change the name of the file, if store_dir already contains the file with the exact name:
if File.exists?(Rails.root.join("documents/" + "#{file.filename}")) && !path.to_s.eql?(Rails.root.join("documents/" + original_filename).to_s)
#name ||= File.basename(original_filename, '.*') + Digest::MD5.hexdigest(File.dirname(current_path)).from(25)
"#{#name}.#{file.extension}"
else
"#{original_filename}"
end
Note: Rails.root.join("documents/") is defined as my store_dir.
Hope it helps someone.

Resources