Rails Carrierwave & Imagemagick, using conditions to resize image - ruby-on-rails

I have been struggling to find any tutorial or question that explains how to upload image and resize it according to certain conditions provided by user.
I am easily able to upload and resize image using hard coded values, however I am stuck at using user provided parameters to be accessed from the Uploader.
I want the image to be resized to either 800x600 or 300x300 based on whether the user checks the image as Large or Small.
For that I have a boolean column named "large" in the model structure.
In the Uploader I am able to access model and its values easily in the store_dir block, but anywhere outside this block any model attribute returns as nil.
This is what I want to do:-
class BannerUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
resize_to_fit(800,600) if model.large==true
resize_to_fit(300,300) if model.large!=true
end
However this returns the error
undefined local variable or method `model' for BannerUploader:Class
How to go about this issue.

To process an original file you can specify custom method:
class BannerUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
process :process_original_version
def process_original_version
if model.large
resize_to_fit(800,600)
else
resize_to_fit(300,300)
end
end
end
For a specific version:
class BannerUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
version :normal do
if model.large
process resize_to_fit: [800,600]
else
process resize_to_fit: [300,300]
end
end
end

Ok so #Alex Kojin has the right answer. However I was faced with another problem as well. When the user submitted the form, the image would be resized always as small (300x300) because for some reason the image resize process would be executed first and then the "large" attribute would be set as true. Therefore the Uploader would always get model.large as false.
So this is how I had to change my action controller
def create
#banner=Banner.new
#banner.large=params[:large]
#banner.update_attributes(banner_params)
#banner.save
redirect_to :back
end
Not sure if this is the right approach but does work for me.

Related

How do you pass additional variables to a CarrierWave uploader in Rails?

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

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 - Save nil if the image is not uploaded

i am trying to migrate images from local file system to dropbox, so i am using carrierwave dropbox gem to move all images to dropbox. i am able to store new images which is uploaded from my application. I am trying to move the existing images.
i am using Article.first.avatar? method to check whether the image exists or not, i have used this method in many places for different sizes of images in my application.
when i use the above method to find out whether the image exists or not, it says true always when the image is not present in dropbox. Look at my console output(2),
My uploader:
class Avatar < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage: file
def store_dir
if model
"uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}"
else
"uploads/#{mounted_as}/"
end
end
end
console output (1)
>Article.first.avatar?
>false
#<AvatarUploader:0x007f9813f1fe70
#file=
#<CarrierWave::SanitizedFile:0x007f9813f1e688
#content_type=nil,
#file="/Users/work/project/app1/public/uploads/370/avatar/avatar.png",
#original_filename=nil>,
#model=
##Article model
#mounted_as=:avatar,
#storage=#<CarrierWave::Storage::File:0x007f9813f1fad8 #uploader=#<AvatarUploader:0x007f9813f1fe70 ...>>,
#versions={}>
I changed uploader as follows:
class Avatar < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage: dropbox
def store_dir
if model
"uploads/#{model.class.to_s.underscore}/#{model.id}/#{mounted_as}"
else
"uploads/#{mounted_as}/"
end
end
end
console output(2)
>Article.first.avatar?
>true
#<AvatarUploader:0x007f8574143ee8
#file=
#<CarrierWave::Storage::Dropbox::File:0x007f8574143308
#client=
#<DropboxClient:0x007f8574143420
#root="dropbox",
#session=
#<DropboxSession:0x007f8574143498
#access_token=#<OAuthToken:0x007f8574143470 #key="123453333", #secret="22222222222">,
#consumer_key="abcdeafs",
#consumer_secret="asdfasfj",
#locale=nil,
#request_token=nil>>,
#config=
{:app_key=>"asdfasfasf",
:app_secret=>"asdfkasfksf",
:access_token=>"adfkjasfkhs",
:access_token_secret=>"aksdfkhsfksf",
:access_type=>"dropbox",
:user_id=>"292929292"},
#path="uploads/images/370/avatar.png",
#uploader=#<AvatarUploader:0x007f8574143ee8 ...>>,
#model=
#Artcle Model>,
#mounted_as=:image,
#storage=
#<CarrierWave::Storage::Dropbox:0x007f8574143c90
#config=
{:app_key=>"asdfasfasf",
:app_secret=>"asdfkasfksf",
:access_token=>"adfkjasfkhs",
:access_token_secret=>"aksdfkhsfksf",
:access_type=>"dropbox",
:user_id=>"292929292"},
#dropbox_client=
#<DropboxClient:0x007f8574143420
#root="dropbox",
#session=
why does it show "true" when the image is not present.
how can i get "false" when the image is not present in dropbox.
My stab in the dark is that inside Carrierwave, the normal file class it returns (sanitized file) responds to empty by checking if the file exists:
https://github.com/carrierwaveuploader/carrierwave/blob/master/lib/carrierwave/sanitized_file.rb#L144
Inside the carrierwave dropbox gem, the File class (https://github.com/robin850/carrierwave-dropbox/blob/master/lib/carrierwave/storage/dropbox.rb#L45) doesn't implement this. I'm guessing if you reopened the class and added
module CarrierWave
module Storage
class Dropbox < Abstract
class File
def empty?
# use dropbox API to get the file and check the presence here
end
end
end
end
end
It would probably work?

Upload image default image to s3 with carrierwave

We are using carrierwave gem to upload our images on a rails project.
We need to support the creation of the object without an image, but the application will run on several platforms and we don't want to support objects without images in everyone o that platforms, so we want to have a default image.
So this was the first solution
class IconUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
def store_dir
"uploads/#{model.id}"
end
def default_url
"/images/icons/" + [version_name, "default.png"].compact.join('_')
end
version :thumbnail do
process resize_to_fill: [200, 200]
end
version :high_resolution do
process resize_to_fill: [400, 400]
end
end
The problem with this is that all the devices will come to the server (not to S3) looking for that default image.
At the same time its a problem with the multiple versions of the image.
So this second solution came:
class CategoryController < ApplicationController
...
def create
#category = Category.new(category_params)
#uploader = IconUploader.new(#category, :icon)
uploader.cache!(File.open("app/assets/images/photos/picture.jpg"))
#category.image=#uploader
#object.save
end
...
end
This solves the problem of the multiple resolutions and it always goes to S3 to get the image. But the problem here is that it will duplicate the default image on S3 many times.
Our third solution could be a to use an already created image on Amazon as default URL, but it has the same problem with the version so we don't even implement it.
Can any one think of a better solution for this problem?

CarrierWave not overriding previous pages

I am currently using carrierwave, and i am wondering if the following his possible. If so how!. Thanks in advance
I have a user and wants to upload an avatar then the following folder would be created
public/image/avatar/customer.id/image01/small
public/image/avatar/customer.id/image01/normal
public/image/avatar/customer.id/image01/big
public/image/avatar/customer.id/image02/small
public/image/avatar/customer.id/image02/normal
public/image/avatar/customer.id/image02/big
Basically, I do not want to overide the previous image has i want to keep them, but create a folder for the newest picture to have it there. Also want the customer id has a path.
Thanks.
PS: if its possible, please provide a tutorial or somesort, if not possible would paperclip allow it? Thanks i can't seem to find anything about it.
uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
include CarrierWave::RMagick
# include CarrierWave::MiniMagick
# Choose what kind of storage to use for this uploader:
storage :file
# 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
"public/image/avatar/#{current_user.id}"
end
version :small do
end
version :normal do
end
version :big do
end
end
Your image uploader should look something like that.

Resources