Paperclip doesn't move files - ruby-on-rails

I'm trying to do somethinkg I've done hundered of times, and today it will not work... And I have no idea why.
I try to create an object with an attached file, and when object is created, do some treatment on the attached file. Or, the file is not present at the specified place !
I have an other model in the same application, doing Imagemagick treatments, and files are in the right directories. Here is no image treatment.
Here is some code
class Test
has_attached_file :file
after_create :do_some_stuff
def do_some_stuff
raise "File not found" if !File.exists?(self.file.path)
end
end
I obtain my File not found exception.
I tried to see if PostProcessing were executed, like this :
class Test
has_attached_file :file
after_post_process :print_log
after_create :print_created
def print_log
$stderr.puts "Processed."
end
def print_created
$stderr.puts "Created."
end
end
The "Processed" is correctly printed before the after_create method...
Do you have any idea ?
My configuration :
Rails 2.3.18
Ruby 1.8.6
Rspec 1.3.2
Paperclip 2.3.0

OK, my fault.
I read the code, and Paperclip call the save method with an after_save. And after_save is called after after_create.

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.

File does not get stored (mounted) if processing of versions fails

I upgraded carrierwave from 0.11.0 to 1.2.3 and realised that a, for me crucial, behaviour has changed and broke my logic. Here is the example of my uploader.
class FileUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :fog
version :thumb do
process :convert => :jpg
def default_url
'/assets/document_thumb.png'
end
end
end
And the model that it's mounted too:
class Material < ActiveRecord::Base
attr_accessible :name, :file
mount_uploader :file, FileUploader, validate_processing: false
before_create :create_file_hash
def create_file_hash
self.hash_digest = Digest::MD5.hexdigest(file.read)
end
end
In the old carrierwave, even if the version processing (e.g. in this case the convert) failed the main version of the file would still be uploaded and stored. However, now in cases when processing fails (not always, but I can't do conditional processing as my case is more complex then here illustrated) nothing gets stored. The file attribute remains an empty (blank) uploader and nothing is uploaded to the fog storage.
Any idea on how to get back the old behaviour?
In other words, how to ignore any errors with processing of versions. Or not trigger processing of versions in the after_cache callback but rather some later time down the line?
I think I've tracked down this issue to the following change in Mounter#cache method:
def cache(new_files)
return if not new_files or new_files == ""
#uploaders = new_files.map do |new_file|
uploader = blank_uploader
uploader.cache!(new_file)
uploader
end
#integrity_error = nil
#processing_error = nil
rescue CarrierWave::IntegrityError => e
#integrity_error = e
raise e unless option(:ignore_integrity_errors)
rescue CarrierWave::ProcessingError => e
#processing_error = e
raise e unless option(:ignore_processing_errors)
end
Which used to just do the uploader.cache!(new_file) directly (not in map) and then uploader got updated along the way and returned to the model when needed. However, now the processing error causes the map block to exit and #uploaders array never gets updated with the uploader that worked (i.e. for the original file).
One possible solution would be overriding the cache! method in you uploader instead:
class FileUploader < CarrierWave::Uploader::Base
def cache!(*)
super
rescue CarrierWave::ProcessingError => e
Rails.logger.debug "FileUploader: Error creating thumbnail: #{e}"
nil
end
...
end
That way, it works for every model
Half a day of effort later here is the solution I've come up with that doesn't involve monkey patching carrierwave.
class Material < ActiveRecord::Base
attr_accessible :name, :file
mount_uploader :file, FileUploader, validate_processing: false
#----> This is to manually trigger thumbnail creation <----
before_create :create_thumbnail
def create_thumbnail
file.thumb.cache!(file.file)
rescue CarrierWave::ProcessingError => e
Rails.logger.debug "FileUploader: Error creating thumbnail: #{e}"
end
# rest of the model code
end
So here we have create_thumbnail method triggered in before_create callback that manually calls the cache! method on the thumb uploader. The file.file is at this moment (i.e. before create, so before the file has been uploaded to the storage) pointing to the temporary cached file. Which is exactly what we want (we don't want to re-download the file from the storage just to create thumbnails.
class FileUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :fog
#----> Add the if condition to versions <----
version :thumb, if: :has_versions? do
process :convert => :jpg
#----> This is needed to trigger processing later <----
after :cache, :process!
def default_url
'/assets/document_thumb.png'
end
end
#---> This is to avoid versions before the main version is fully processed and cached <---
def has_versions?
!(model.new_record? && model[:file].nil?)
end
end
Now this is the tricky party. We need to initially disable the version creations and for that reason we have the has_versions? method that checks if the file is a new record. Now that check is not enough, because in our before_create callback the model is still new record (i.e. it hasn't yet been persisted).
However, what's the difference between the first time the uploader tries to create the versions (and which, if it fails, prevents original file from caching as described in the question) and the moment we call it in our before_create callback is that in the second case the file attribute of the model will be set.
Be careful, however, because you cannot do model.file since that points to the uploader (and if called here where I'm calling it it would actually cause a stack overflow). You need to access it as model[:file].
The final trick is that for some reason just calling cache! in the model would not actually trigger the processing. The processing was supposed to be triggered during the initial run (which we prevented for other versions) and since the original file is cached, carrierwave expects the versions are as well, so they don't need processing. But adding the after :cache, :process! ensures that it's triggered.
Don't know if anybody will find this useful or if I've gone about the problem the wrong way.
Either way, I'd love to hear comments.
Happy I made it work for my case and that I can continue using latest gem version.

recreate versions - carrierwave-fog-aws

I'm trying to recreate the images that I have uploaded using the following in my model...
Post.all.each do |ym|
ym.avatar.cache_stored_file!
ym.avatar.retrieve_from_cache!(ym.avatar.cache_name)
ym.avatar.recreate_versions!
ym.save!
end
Unfortunately, I get the following error....
(undefined method `body' for nil:NilClass):
My uploader is named AvatarUploader and is for my Post model. Any advice on how to fix this?
I've ran with a similar problem when using async processing on a read-only server(Heroku). In my case there was a problem when defining file size which I resolved monkey-patching def size from fog file class:
module CarrierWave
module Storage
class Fog < Abstract
class File
def size
file.nil? ? 0 : file.content_length
end
end
end
end
end
I could help you more if the issue still persists and when you post more details from your backtrace and gems configuration.

callback issue with carrierwave and mongoid

I am using carrierwave and mongoid on a rails 3 application and am having an issue with an after_save callback. Consider the following
class Video
include Mongoid::Document
field :name
mount_uploader :file, VideoUploader
after_create :enqueue_for_encoding
protected
def enqueue_for_encoding
// point your encoding service to where it expects the permanent file to reside
// in my case on s3
end
end
My issue is that in my enqueue_for_encoding method, file.url points to the local tmp directory not the s3 directory.
How do I get my enqueue_for_encoding method to be called when file.url points to s3?
Thanks!
Jonathan
Check out carrierwave's howto page on Callbacks
https://github.com/jnicklas/carrierwave/wiki/How-to%3A-use-callbacks
It worked for me
Okay, I figured it out. To took a bit of hacking. So currently carrierwave does not expose an after_create hook, all of it persisting and processing happens in the after_save callback. Here is the code I used to work around it:
# Video.rb
mount_uploader :file, VideoUploader
# overwrite the file setting to flag the model that we are creating rather than saving
def file=(obj)
#new_file = true
super(obj)
end
# chain the store_file! method to enqueue_for_encoding after storing the file AND
# if the file is new
alias_method :orig_store_file!, :store_file!
def store_file!
orig_store_file!
if #new_file #means dirty
#new_file = false
enqueue_for_encoding
end
true
end
UPDATE
Woops -- that didn't work. It almost did -- the url is correct, but it is being fired permanently. Meaning the file is still in process of being loaded, and is not fully stored when enqueue_for_encoding is called
It is possible to set your enqueue_for_encoding callback on the uploader itself. But I prefer to do it this way:
class Video
# mount the uploader first:
mount_uploader :file, VideoUploader
# then add the callback:
after_save :enqueue_for_encoding, on: :create
end
You could try removing your after_create callback in the model and add the following to your uploader:
# video_uploader.rb
process :encode
def encode
model.enqueue_for_encoding
end
The process callbacks are called after the file is saved (I think) which should allow you to hook in once your file is up on S3.

Overriding content_type for Rails Paperclip plugin

I think I have a bit of a chicken and egg problem. I would like to set the content_type of a file uploaded via Paperclip. The problem is that the default content_type is only based on extension, but I'd like to base it on another module.
I seem to be able to set the content_type with the before_post_process
class Upload < ActiveRecord::Base
has_attached_file :upload
before_post_process :foo
def foo
logger.debug "Changing content_type"
#This works
self.upload.instance_write(:content_type,"foobar")
# This fails because the file does not actually exist yet
self.upload.instance_write(:content_type,file_type(self.upload.path)
end
# Returns the filetype based on file command (assume it works)
def file_type(path)
return `file -ib '#{path}'`.split(/;/)[0]
end
end
But...I cannot base the content type on the file because Paperclip doesn't write the file until after_create.
And I cannot seem to set the content_type after it has been saved or with the after_create callback (even back in the controller)
So I would like to know if I can somehow get access to the actual file object (assume there are no processors doing anything to the original file) before it is saved, so that I can run the file_type command on that. Or is there a way to modify the content_type after the objects have been created.
Probably you could use upload.to_file. It gives you paperclip temporary file (Paperclip::Tempfile). It has path property, so you can use
self.upload.instance_write(:content_type,file_type(self.upload.to_file.path)
You can get Tempfile using upload.to_file.to_tempfile

Resources