CarrierWave: Use of file name in store_dir - ruby

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.

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

How to fix a "value too long for type character varying(255)" error

I'm trying to save a file so that I can upload it to stripe using CarrierWave, but I'm getting the error:
ERROR: value too long for type character varying(255)
and don't understand why as I followed the CarrierWave usage guide on GitHub.
This is my application:
class SplitterStripeServer < Sinatra::Base
CarrierWave.configure do |config|
config.root = File.dirname(__FILE__) + "/public"
end
post "/" do
img = Image.new
img.file = params[:file] #carrierwave will upload the file automatically
img.save!
redirect to("/")
end
get "/upload" do
erb :upload
end
get "/" do
#image = Image.find(1)
erb :index
end
end
This is the uploader:
class ImagesUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
end
This is the model:
class Image
include DataMapper::Resource
property :id, Serial
mount_uploader :file, ImagesUploader
end
I feel like I'm missing something simple.
You need to decrease a length of a file name. Override filename method and cut a file's basename e.g. to 250 characters.
class ImagesUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :file
def filename
"#{file.basename[0..250]}.#{file.extension}" if original_filename.present?
end
end
check if you have additional installed gems
I had was installed gem validates_lengths_from_database and this gem doesn't understand how to work with carrierwave. I am just turn off it in model validates_lengths_from_database except: %i[file]

Including Service class in Controller

I want to include the following class from my services folder into my Controller..
Here is the Class in ..services/product_service.rb
class MyServices
class << self
def screen_print
"These are the words in screen print"
end
end
end
And all I want to do is this in my controller:
class AmazonsController < ApplicationController
def index
#joe = MyServices.screen_print
end
end
I thought I could just include it in the controller. And its not a module so include isn't working, and I tried updating my config/appliaction.rb file and that didn't work either..
Your class name needs to be the same as the name of your file, I believe. So since your file is named product_service.rb, your class should be:
class ProductService
class << self
def screen_print
"These are the words in screen print"
end
end
end
and in your controller:
class AmazonsController < ApplicationController
def index
#joe = ProductService.screen_print
end
end
In addition to the naming problems already pointed out, Rails won't automatically require arbitrary files from folders it doesn't know about.
If you want files in a new folder to be automatically required, you need to add it to Rails' autoload paths:
# config/application.rb
config.autoload_paths << Rails.root.join('services')
See Auto-loading lib files in Rails 4 for more details.
Rails does not load files from uncommon locations. You will need to tells Rails that the services folder exists and to load file from it.
Add the following to your config/application.rb:
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += [Rails.root.join('app', 'services')]

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

Resources