CarrierWave - delete version only, not original - ruby-on-rails

Is it possible to ONLY delete versions and keep the original file?
Uploading an image creates the original file and the :product_thumb version (Works).
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
storage :fog # Amazon S3
version :product_thumb, if: :create_thumb? do
process :resize_to_fit => [15, 15]
end
protected
def create_thumb?
model.make_thumbnail
end
end
When i run:
prod = Product.first
prod.remove_image!
prod.save
Both the original and :product_thumb get deleted. But i only want to delete the version :product_thumb. I looked through the API and didn't see anything that would help.
I've been trying to get this to work in the console.
prod.remove_product_thumb_image! --> # NoMethodError: undefined method
prod.remove_image(:product_thumb) --> # ArgumentError: wrong number of arguments (1for0)
# one idea was to get the file path of the version and delete it manually
prod.image.url(:product_thumb) --> # path/to/file/product_thumb_test.png
Is there a way to delete the version through CarrierWave?

To delete all versions use:
prod.image.remove_versions!
To delete a specific version use:
prod.image.versions[:product_thumb].remove!

As far as I could find out, there's no short command to delete an image version. However, you can build a little Ruby script that will do it for you, i.e. delete the respective files.
Step 1: Remove the image files
Use a script like this:
Product.all.each do |p|
path = File.delete(p.image.product_thumb.current_path)
File.delete(path) if File.exists?(path.to_s)
end
Step 2: Remove the image version
Remove the product_thumb version from your ImageUploader.
Done!
It will be like there had never been an image version.

Related

Rails Active Storage set folder to store files

I'm using Active Storage to store files in a Rails 5.2 project. I've got files saving to S3, but they save with random string filenames and directly to the root of the bucket. I don't mind the random filenames (I actually prefer it for my use case) but would like to keep different attachments organized into folders in the bucket.
My model uses has_one_attached :file. I would like to specify to store all these files within a /downloads folder within S3 for example. I can't find any documentation regarding how to set these paths.
Something like has_one_attached :file, folder: '/downloads' would be great if that's possible...
The ultimate solution is to add an initializer. You can add a prefix based on an environment variable or your Rails.env :
# config/initializer/active_storage.rb
Rails.configuration.to_prepare do
ActiveStorage::Blob.class_eval do
before_create :generate_key_with_prefix
def generate_key_with_prefix
self.key = if prefix
File.join prefix, self.class.generate_unique_secure_token
else
self.class.generate_unique_secure_token
end
end
def prefix
ENV["SPACES_ROOT_FOLDER"]
end
end
end
It works perfectly with this. Other people suggest using Shrine.
Credit to for this great workaround : https://dev.to/drnic/how-to-isolate-your-rails-blobs-in-subfolders-1n0c
As of now ActiveStorage doesn't support that kind of functionality. Refer to this link. has_one_attached just accepts name and dependent.
Also in one of the GitHub issues, the maintainer clearly mentioned that they have clearly no idea of implementing something like this.
The workaround that I can imagine is, uploading the file from the front-end and then write a service that updates key field in active_storage_blob_statement
There is no official way to change the path which is determined by ActiveStorage::Blob#key and the source code is:
def key
self[:key] ||= self.class.generate_unique_secure_token
end
And ActieStorage::Blog.generate_unique_secure_token is
def generate_unique_secure_token
SecureRandom.base36(28)
end
So a workaround is to override the key method like the following:
# config/initializers/active_storage.rb
ActiveSupport.on_load(:active_storage_blob) do
def key
self[:key] ||= "my_folder/#{self.class.generate_unique_secure_token}"
end
end
Don't worry, this will not affect existing files. But you must be careful ActiveStorage is very new stuff, its source code is variant. When upgrading Rails version, remind yourself to take look whether this patch causes something wrong.
You can read ActiveStorage source code from here: https://github.com/rails/rails/tree/master/activestorage
Solution using Cloudinary service
If you're using Cloudinary you can set the folder on storage.yml:
cloudinary:
service: Cloudinary
folder: <%= Rails.env %>
With that, Cloudinary will automatically create folders based on your Rails env:
This is a long due issue with Active Storage that seems to have been worked around by the Cloudinary team. Thanks for the amazing work ❤️
# config/initializers/active_storage.rb
ActiveSupport.on_load(:active_storage_blob) do
def key
sql_find_order_id = "select * from active_storage_attachments where blob_id = #{self.id}"
active_storage_attachment = ActiveRecord::Base.connection.select_one(sql_find_order_id)
# this variable record_id contains the id of object association in has_one_attached
record_id = active_storage_attachment['record_id']
self[:key] = "my_folder/#{self.class.generate_unique_secure_token}"
self.save
self[:key]
end
end
Active Storage by default doesn't contain a path/folder feature but you can override the function by
model.file.attach(key: "downloads/filename", io: File.open(file), content_type: file.content_type, filename: "#{file.original_filename}")
Doing this will store the key with the path where you want to store the file in the s3 subdirectory and upload it at the exact place where you want.

undefined method `version' for main:Object carrierwave

I have carrierwave's uploader working without applying any changes to uploaded images. Though when I comment out the version block in the generated uploader file I get an "undefined method `version' for main:Object" message.
version is a built in method, and based on what googled, and even a railscast video on it I should be able to just uncomment it and roll.
# Create different versions of your uploaded files:
version :thumb do
process :resize_to_limit => [313, 344]
end
What could I have going on in my codebase that would be causing an issue with carrierwave's version method?
Error implicitly says that your version method not in scope of carrierwave uploader.
Your uploader should look like this.
class Uploader < CarrierWave::Uploader::Base
version 'anything' do
....
end
end
But as per error your uploader code is some thing like this:
class Uploader < CarrierWave::Uploader::Base
...
end
version 'anything' do
....
end
As your version method is not present inside uploader. Ruby considering it for main object when try to execute.
Few more thing also you need to make sure present in your code. You must have rmagick gem in your gemfile. You have imagemagick installed on your development machine.
include CarrierWave::RMagick uncommented in your uploader.

Make CarrierWave to not copy original file for versions

As I understand it for each version defined in an uploader CarrierWave will copy original file from cache to tmp path and hand over this tmp file to whatever processing is defined for this version. Then it can store all files. Sometimes (e.g. when generating thumbnail for videofile) this copying can be prohibitively expensive. Can I make CarrierWave to not copy and to let me generate versions from the original file while it's in cache?
Edit I have move_to_cache and move_to_store to return false true (oops I forget my own head soon). And I wrote a test processing module on the lines of CarrierWave::RMagick:
module CarrierWave
module Thumbnailer
def generate_thumbnails
debugger
x = 1
end
end
end
and I have the following lines inside the uploader
version :thumb do
process :generate_thumbnails
end
But when execution is stopped on the debugger (that's where I can start processing) CarrierWave has already copied and renamed the uploaded file. I can see both of them inside the cache directory.
Check out the readme under Large Files ... from the README:
class MyUploader < CarrierWave::Uploader::Base
def move_to_cache
true
end
def move_to_store
true
end
end
When the move_to_cache and/or move_to_store methods return true, files
will be moved (instead of copied) to the cache and store respectively.

How do I generate website thumbnails without 3rd party tools?

Are there any gems out there for Rails 3.2.1 that generate website thumbnails? I see a lot of 3rd party solutions but I don't like the fact that they aren't hosted on my server. It's really important the app I'm building is as stable as possible and I think this is not a good solution in the long run.
My ruby knowledge is fairly good, I think enough to use a gem and implement it, but definitely not good enough to write something like this from scratch if no gems exist.
Thanks!
You could try dragonfly or carrierwave
Well, here's the first thing that came up on Rubygems: thumbnailer. It uses Amazon and costs a small fee per image it generates, so you probably don't want this...
But there's also thumbnailer-ruby which looks like it works completely on the local machine. Haven't tested it out, though. It appears that this doesn't actually do what you want. Nevermind.
Now another gem called snapurl looks pretty fancy. Once again, I haven't tried it out yet. I'll do that now.
EDIT: Won't run for me; keeps failing with an error.
https://url2png.com/ has worked great so far
aBrowshot has a gem available.
There's no need to use a third party service for this.
You can do something like this in your model:
class MySexyModel < ActiveRecord::Base
... stuff
# Generate the thumbnail on validate so we can return errors on failure
validate :generate_thumbnail_from_url
# Cleanup temp files when we are done
after_save :cleanup_temp_thumbnail
# Generate a thumbnail from the remote URL
def generate_thumbnail_from_url
# Skip thumbnail generation if:
# a) there are already other validation errors
# b) an image was manually specified
# c) an image is already stored and the URL hasn't changed
skip_generate = self.errors.any? || (self.image_changed? ||
(self.image_stored? && !self.url_changed?))
# p "*** generating thumbnail: #{!skip_generate}"
return if skip_generate
# Generate and assign an image or set a validation error
begin
tempfile = temp_thumbnail_path
cmd = "wkhtmltoimage --quality 95 \"#{self.url}\" \"#{tempfile}\""
# p "*** grabbing thumbnail: #{cmd}"
system(cmd) # sometimes returns false even if image was saved
self.image = File.new(tempfile) # will throw if not saved
rescue => e
# p "*** thumbnail error: #{e}"
self.errors.add(:base, "Cannot generate thumbnail. Is your URL valid?")
ensure
end
end
# Return the absolute path to the temporary thumbnail file
def temp_thumbnail_path
File.expand_path("#{self.url.parameterize.slice(0, 20)}.jpg", Dragonfly.app.datastore.root_path)
end
# Cleanup the temporary thumbnail image
def cleanup_temp_thumbnail
File.delete(temp_thumbnail_path) rescue 0
end
end
The original post is on this blog: http://sourcey.com

How to make carrierwave delete the file when destroying a record?

I'm using the carrierwave gem to upload files.
I have built a system for users to flag images as inappropriate and for admins to remove the images. From what I can tell, calling destroy on the image will only remove the path name from the table.
Is there a way to have carrierwave actually remove the file itself? Or should rails automatically remove the file when I destroy the image path?
Like #mu_is_too_short said, you can use File#delete.
Here's a code snippet you could use as a helper with a little tweaking in your rails app.
def remove_file(file)
File.delete(file)
end
or if you just have the filename stored in file
def remove_file(file)
File.delete("./path/to/#{file}")
end
Not sure what CarrierWave offers for this, but you could use FileUtils in the Ruby standard library with an ActiveRecord callback.
For instance,
require 'FileUtils'
before_destroy :remove_hard_image
def remove_hard_image
FileUtils.rm(path_to_image)
end
Sidenote: This code is from memory.
If one wants to delete a file but does not want to specify the full filename you can use the below.
Can also be used to delete many files or all files in a directory with a specific extension...
file = Rails.root.join("tmp", "foo*")
or
file = Rails.root.join("tmp", ".pdf")
files = Dir.glob(file) #will build an array of the full filepath & filename(s)
files.each do |f|
File.delete(f)
end

Resources