I see this has been asked a few times over the years (eg Upload path based on a record value for Carrier wave Direct, Passing a parameter to the uploader / accessing a model's attribute from within the uploader / letting the user pick the thumbnail size), but I'm convinced I must be overcomplicating this, as it seems like a very simple problem...
I have a very straightforward Video model that mounts an uploader:
class Video < ApplicationRecord
mount_uploader :file, VideoUploader
end
In the controller, I allow two parameters:
def video_params
params.require(:video).permit(:title, :file)
end
In the actual VideoUploader, I seem to have access to a number of variables derived from the :file column using class builtins (eg original_filename), and I can process the file using ffmpeg parameters. However, I want the parameters to be conditional based on the :title string, and I have no idea how to scope it or access it. What is the absolute simplest way to make sure this variable is accessible to those methods?
Edit: here's the uploader code:
class VideoUploader < CarrierWave::Uploader::Base
require 'streamio-ffmpeg'
include CarrierWave::Video
case #title # not working
when "tblend_glitch"
process encode_video: [:mp4,
resolution: "1280x960",
custom: %w(-to 5 -vf scale=-2:720,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference)]
...
end
def full_filename(for_file)
super.chomp(File.extname(super)) + '.mp4'
end
def filename
original_filename.chomp(File.extname(original_filename)) + '.mp4'
end
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
Thanks!
You should be able to access the instance in the uploader using the model method.
You haven't defined #title — it's nil. You can create a conditional version with the following code.
class VideoUploader < CarrierWave::Uploader::Base
version :tblend, if: :tblend_glitch? do # use a better version name
process encode_video: [:mp4,
resolution: "1280x960",
custom: %w(-to 5 -vf scale=-2:720,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference,spp=4:10,tblend=all_mode=average,tblend=all_mode=difference,tblend=all_mode=difference,tblend=all_mode=difference)]
end
# rest of the code
private
def tblend_glitch?
model.title == 'tblend_glitch'
end
end
Ref: https://github.com/carrierwaveuploader/carrierwave#conditional-versions
In theory you can access the model and its attributes from the uploader. However, it looks like the mounted uploader gets invoked before the other attributes are assigned.
For me it worked to create the model with the regular parameters first, and assign the attribute that has the uploader mounted (in your case :file) in a second step. Then I could read all model attributes correctly from within the uploader.
In your case that would be something like this in the controller:
#video = Video.new(video_params.except(:file))
#video.file = video_params[:file] # Invoke uploader last to access the other attributes
Related
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.
I am trying to add additional fields to the CarrierWave Uploader so that they are stored as part of the Uploader itself and together with the CarrierWave fields, such as #file, #model, #storage etc.
The fields are also version-specific, which is why I'd prefer to be able to access them via <my_model>.<my_uploader>.attribute and<my_model>.<my_uploader>.versions[:<the_version>] instead of additional columns in the model.
I did try the carrierwave-meta gem, but ran into an error with it ( NoMethodError: undefined method \'original_filename' for #<CarrierWave::Storage::Fog::File:0xab4134c> )
that seems to not have been fixed yet.
Any ideas or suggestions on how to best accomplish this?
I'm not 100% clear what you are trying to do.
when I use carrierwave gem, I do create a path that holds some of that information. In my applaications I normally have a file app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
def store_dir
# "uploads/image/file/187/"
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
...
end
from this I always know the model, what type of file, and the id.
All other info about this model I normally save in the database.
I hope this helps and sets you in the right direction
your error is connected with fog
In my Picture Uploader I can set an attribute reader and writer
class PictureUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
def field
#field
end
def field=(field)
#field = field
end
# attr_accessor :field # for an even shorter way
end
The I open the rails console to test the model:
picture = PictureUploader.new
=> #<PictureUploader:0x0055804db336e8 #model=nil, #mounted_as=nil>
picture.field=('your text')
=> "your text"
picture.field
"your text"
About the versioning and error you are having 'NoMethodError: undefined method \'original_filename' for #<CarrierWave::Storage::Fog::File:0xab4134c>' I agree with MZaragoza
CarrierWave::Storage::Fog::File.new takes three parameters
def store!(file)
f = CarrierWave::Storage::Fog::File.new(uploader, self, uploader.store_path)
f.store(file)
f
end
uploader, self and uploader.store_path so to help us solve this problem you should include your CarrierwaveUploader model code and the output of uploader.store_path
Thanks a lot
I'm using Carrierwave and Fog gems to store a file to my Amazon S3 bucket (to /files/file_id.txt). I need to store a slightly different version of the file to a different location in the bucket (/files/file_id_processed.txt) at the same time (right after the original is stored). I don't want to create a separate uploader attribute for it on the model - is there any other way?
This my current method that stores the file:
def store_file(document)
file_name = "tmp/#{document.id}.txt"
File.open(file_name, 'w') do |f|
document_content = document.content
f.puts document_content
document.raw_export.store!(f)
document.save
end
# I need to store the document.processed_content
File.delete(file_name) if File.exist?(file_name)
end
This is the Document model:
class Document < ActiveRecord::Base
mount_uploader :raw_export, DocumentUploader
# here I want to avoid adding something like:
# mount_uploader :processed_export, DocumentUploader
end
This is my Uploader class:
class DocumentUploader < CarrierWave::Uploader::Base
storage :fog
def store_dir
"files/"
end
def extension_white_list
%w(txt)
end
end
This is how my final solution looks like (kinda) - based on Nitin Verma's answer:
I had to add a custom processor method for the version to the Uploader class:
# in document_uploader.rb
...
version :processed do
process :do_the_replacements
end
def do_the_replacements
original_content = #file.read
File.open(current_path, 'w') do |f|
f.puts original_content.gsub('Apples','Pears')
end
end
considering that you need similar file but with different name.
for this you need to create a version for file in uploader.
version :processed do
process
end
and now second file name will be processed_{origional_file}.extension. if you want to change file name of second file you can use this link https://github.com/carrierwaveuploader/carrierwave/wiki/How-to:-Customize-your-version-file-names
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.
I am trying to display the filename of a Carrierwave attachment in a Rails erb template. The following does not work:
<%= #page.form.filename %>
This seems in line with the documentation. Is some additional step needed?
My page model looks like this:
class Page < ActiveRecord::Base
mount_uploader :form, FormUploader
end
The form uploader looks like this:
class FormUploader < CarrierWave::Uploader::Base
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
def extension_white_list
%w(pdf)
end
end
I have been able to get the filename via the file internal parameter:
<%= #page.form.file.filename %>
The documentation you're looking at is the sanitized file, it's what it uses for actually storing a file. The part you're looking for is FormUploader, which is an Uploader, and part of http://rubydoc.info/gems/carrierwave/0.5.2/CarrierWave/Uploader
If you want to get the file name, you could either read it from the database column directly, or use File.basename(#page.form.path) to extract it easily.
The Carrierwave docs might be a bit off, but recommended way seems to be:
#page.form.file.identifier
#adamonduty's solution is great. Another solution I used before, just create a method on the model:
def name
file.path.split("/").last
end
You're right #epylinkn. Documentation points towards using:
#page.form.file.identifier
But when I use that, I always get nil (just as #Cheng commented).
I then inspected my objects methods (#page.form.file.methods.inspect), and found the following to work:
#page.form.file_identifier
In your model's associated uploader class, define a filename method.
def filename
File.basename(path)
end
You can then call
model_instance.file.filename
Works as of CarrierWave 1.1.0. This is a succinct restatement/amalgamation of kikito and Chris Alley's responses above.
If you're using ActiveRecord, you can directly access the field named form in two ways:
def my_method
self[:form]
end
or
def my_method
form_before_type_cast
end
The second method is read-only.
CarrierWave::SanitizedFile has a private original_filename method containing the filename of the uploaded file. (docs: http://rdoc.info/github/jnicklas/carrierwave/master/CarrierWave/SanitizedFile:original_filename)
After reading through this thread from the CarrierWave mailing list, none seemed to fit my needs. With something like
class Upload < ActiveRecord::Base
mount_uploader :file, FileUploader
# ...
I heavily modify the :file column value from the original filename. Due to this I decided to track the original filename in a separate column from the one bound to CarrierWave. In my FileUploader I simply added a reader that wraps the private original_filename method:
def original_file
original_filename
end
I then added a before_create event to the Upload class (my Upload records are never modified, so a before_create is acceptable for my needs)
before_create do
self.original_file = self.file.original_file
end
I'm assuming you've got models like this?
class Page
mount_uploader :form, FormUploader
end
If so you should be able to call:
#page.form.url
#page.form.filename
Are you sure you've uploaded/attached the file correctly? What do you see when you inspect #page.form? Remember, the attachment will not be saved until you've fully processed the upload.
This is my solution:
before_save :update_file_attributes
def update_file_attributes
if file.present? && file_changed?
self.content_type = file.file.content_type
self.file_size = file.file.size
self.file_name = read_attribute(:file)
end
end