Duplicating carrierwave image - ruby

I have a model called Book which has a cover_image.
mount_uploader :cover_image, BookPixUploader
And the uploader is declared as follows:
class BookPixUploader < CarrierWave::Uploader::Base
# Include RMagick or ImageScience support:
include CarrierWave::RMagick
CarrierWave::Uploader::ExtensionWhitelist
# include CarrierWave::MiniMagick
# include CarrierWave::ImageScience
# Choose what kind of storage to use for this uploader:
#storage :file
storage :fog
# 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
"system/uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
....
I am trying to save a new book:
newbook = book.dup
newbook.save
But It's not uploading a new image to amazon S3.
I have tried:
newbook = book.dup
newbook.cover_image = book.cover_image
newbook.save
Any ideas?

newbook = book.dup
newbook.remote_cover_image_url = book.cover_image.url
newbook.save
If this doesn't work, you may want to look into duplicating the attributes another way, since book.dup could do weird things to the uploader.

Related

Using an existing image on s3 with Carrierwave?

I'm trying to use existing images on my s3 and relate them to a carrierwave object. These images are stored in a different location from the store_dir of my uploader.
I currently have my migration setup using remote_image_url but it is extremely slow and I don't think re-uploading them is necessary.
So, is it possible to point my uploader to an existing image that uses a folder structure different from my store_dir?
Here's an example of what I have now.
example block from migration:
ActiveRecord::Base.connection.execute(update_images)
Models::Product.where('image IS NOT NULL').select(:id, :image).find_in_batches(batch_size: 500).with_index do |group, batch|
puts "Processing group ##{batch}"
group.each do |prod|
next unless prod[:image].present?
image = Models::ProductImage.new(product_id: prod[:id])
image.remote_image_url = prod[:image]
image.save
end
sleep(7)
end
uploader:
class ImageUploader < CarrierWave::Uploader::Base
storage :fog
def filename
"#{secure_token}.#{file.extension}" if original_filename.present?
end
def store_dir
"#{ENV['RACK_ENV']}/#{sub_folder}"
end
def cache_dir
"#{ENV['RACK_ENV']}/#{sub_folder}"
end
def extension_allowlist
%w(jpg jpeg gif png)
end
def size_range
1.byte..10.megabytes
end
def sub_folder
case model
when Models::UserImage
'user_images'
when Models::ProductImage
'product_images'
else
'misplaced'
end
end
protected
def secure_token
var = :"##{mounted_as}_secure_token"
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.uuid)
end
end

Where does CarrierWave stores uploads

Please help me understand how does CarrierWave works.
I'm using minimal Sinatra/DataMapper app with following contents:
class VideoUploader < CarrierWave::Uploader::Base
storage :file
end
class Video
include DataMapper::Resource
property :id, Serial
property :name, String
property :desc, Text
mount_uploader :file, VideoUploader
end
get '/' do
slim :form
end
post '/' do
video = Video.new
video.name = params[:name]
video.desc = params[:desc]
video.file = params[:file]
video.save
redirect '/'
end
As I understood mount_uploader :file, VideoUploader string in Video definition adds .video method to Video instance, and I can store uploads assigning params[:file] to it. When I'm trying to send form from browser, the request successfully creates record in DB table, but I can't find any signs of file existence either in DB and public_directory. What I'm doing wrong?
You probably should define the store_dir method inside the VideoUploader class:
class VideoUploader < CarrierWave::Uploader::Base
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
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
....
end

Who can explain how this code block works: Carrierwave MD5 as filename

I am using the Carrierwave gem with a Sinatra app to upload and store images.
I am following How to: Use file's MD5 as filename and everything works as expected. However I do not understand how the following piece of code provided on the how-to page works:
class ImageUploader < CarrierWave::Uploader::Base
storage :file
def md5
chunk = model.send(mounted_as)
#md5 ||= Digest::MD5.hexdigest(chunk.read)
end
def filename
#name ||= "#{md5}#{File.extname(super)}" if super
end
end
I in particular do not understand what model.send(mounted_as)does, what the ||= operator means, and why the if super conditional is used (and what it does).
Could somebody please explain this to me?
Say, for example the model is Person and the ImageUploader is mounted as avatar.
class Person < ActiveRecord::Base
mount_uploader :avatar, ImageUploader
end
Then, the md5 method would be calling something to the affect of chunk = person.avatar and using this to calculate the hash of the file contents, which you want for the name.
The filename method checking to see if there is a #filename instance variable, as defined in the CarrierWave::Uploader::Store class.
class CarrierWave::Uploader::Store
def filename
#filename
end
end
Then calling this again to get the filename extension to use in the constructed filename. The #name variable is then just a temporary cache of the name, so that future calls to the method do not require the whole thing to be calculated again.
Edit:
The carrierwave uploader has methods/instance variables for the model (eg Person instance) and declared mount point in the model (eg avatar). These are from the mount_uploader declaration in your active record model.
module CarrierWave::Uploader::Mountable
attr_reader :model, :mounted_as
def initialize(model=nil, mounted_as=nil)
#model = model
#mounted_as = mounted_as
end
end
These are used for various things, as well as being made available for us to do things just such as you are trying. It is just an abstract way to call person.avatar, which returns the file (File instance not string path). This is then read into the MD5 lib, which gives the hexdigest.
Rewriting this in more plain terms
class ImageUploader < CarrierWave::Uploader::Base
def md5
uploaded_file = model.send(mounted_as) # person.avatar (File instance)
#md5 ||= Digest::MD5.hexdigest(uploaded_file.read) # hexdigest of file content
end
end

CarrierWave: Use of file name in store_dir

I'm saving images to S3 using CarrierWave. Before uploading the images are renamed to a random string. Now for faster lookups I wanted to put the files in directories named after the two first letters of the filenames. But doing so, Rails aborts with stack level too deep.
So I assume calling #{model.image[0, 2] leads to an infinite recursion. Is there a way to access just the file name as a string? Or an even simpler solution?
This is the uploader:
class ImageUploader < CarrierWave::Uploader::Base
storage :fog
def store_dir
"images/#{model.image[0, 2]}/"
end
def filename
"#{secure_token}.#{file.extension}" if original_filename.present?
end
protected
def secure_token
var = :"##{mounted_as}_secure_token"
model.instance_variable_get(var) or model.instance_variable_set(var, SecureRandom.hex(8))
end
end
try the :file_identifier method
class ImageUploader < CarrierWave::Uploader::Base
...
def store_dir
"images/#{model.file_identifier[0, 2]}/#{model.id}"
end
...
end
The easy answer is to add a proxy attribute such as filename in the Image model and access that using read_attribute:
class Image < ApplicationRecord
mount_uploader :image, ImageUploader
def filename
read_attribute(:image)
end
...
end
Then in your ImageUploader#store_dir method make reference to the proxy attribute filename:
def store_dir
"images/#{model.filename}"
end
This worked for me and eliminated the recursive error problem.

carrierwave be_identical_to helper not working in rspec

I am having some issues with an rspec test I am trying to run on Carrierwave uploads. Basically, I am trying to test processing to make sure if images are uploaded and processed. I have created a post-processed example file that should be identical to the post-uploaded-and-processed test file. However, I am getting the following warning:
ImageUploader the greyscale version should remove color from the image and make it greyscale
Failure/Error: #uploader.should be_identical_to(#pregreyed_image)
TypeError:
can't convert ImageUploader into String
# ./spec/uploaders/image_uploader_spec.rb:24:in `block (3 levels) in <top (required)>'
Here is my test file:
image_uploader_spec.rb
require File.dirname(__FILE__) + '/../spec_helper'
require 'carrierwave/test/matchers'
describe ImageUploader do
include CarrierWave::Test::Matchers
include ActionDispatch::TestProcess
before do
ImageUploader.enable_processing = true
#uploader_attr = fixture_file_upload('/test_images/testimage.jpg', 'image/jpeg')
#uploader = ImageUploader.new(#uploader_attr)
#uploader.store!
#pregreyed_image = fixture_file_upload('/test_images/testimage_GREY.jpg', 'image/jpeg')
end
after do
#uploader.remove!
ImageUploader.enable_processing = false
end
context 'the greyscale version' do
it "should remove color from the image and make it greyscale" do
#uploader.should be_identical_to(#pregreyed_image)
end
end
end
image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
# Include RMagick or MiniMagick support:
include CarrierWave::RMagick
# include CarrierWave::MiniMagick
# Include the Sprockets helpers for Rails 3.1+ asset pipeline compatibility:
include Sprockets::Helpers::RailsHelper
include Sprockets::Helpers::IsolatedHelper
# 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
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
# Process files as they are uploaded:
process :convert_to_grayscale
def convert_to_grayscale
manipulate! do |img|
img.colorspace = Magick::GRAYColorspace
img.quantize(256, Magick::GRAYColorspace)
img = yield(img) if block_given?
img
end
end
Underneath the covers be_identical_to uses FileUtils.identical? on the two arguments. So your expectation:
#uploader.should be_identical_to(#pregreyed_image)
is actually calling:
FileUtils.identcal?(#uploader, #pregreyed_image)
Since in my test environment I'm using a file storage system, I got around this by passing in the #current_path rather than the uploader itself like this:
#uploader.current_path.should be_identical_to(#pregreyed_image)
I actually ended up needing to compare uploaders directly and implemented == on my uploader:
class MyUploader < CarrierWave::Uploader::Base
...
def ==(other)
return true if !present? && !other.present?
FileUtils.identical?(current_path, other.current_path)
end
end

Resources