public_id doesn't match between cloudinary and carrierwave - ruby-on-rails

I'm using CarrierWave and Cloudinary to upload multiple pictures to my blogposts. I upload them from my browser.
This happens with the use of a file field in the post form.
<%=file_field_tag "images[]", type: :file, multiple: true %>
When the form is being submitted, a picture instance is created for each one of the images and the image is being uploaded to Cloudinary.
def create
#post = Post.new(post_params)
if #post.save
if params[:images]
params[:images].each do |image|
#post.pictures.create(image: image)
Cloudinary::Uploader.upload(image)
end
end
end
end
The ImageUploader I use is almost default (exept for including the cloudinary plugin)
class ImageUploader < CarrierWave::Uploader::Base
include Cloudinary::CarrierWave
storage :file
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}"
end
end
Now, the image is being saved to my server, and the image is being uploaded to cloudinary. But somehow the public_id's never match. Does anyone understand why not? Is there a new public_id created when I call Cloudinary::Uploader.upload(image)?

After checking the project that Tal Lev-Ami (thanks a lot for this!) refered me to, I figured out that the problem with my ImageUploader was the line
storage :file
After commenting out this line, everything worked perfectly (and I don't have to manually upload my images). If anyone understands why this line was causing the problem, be my guest to post it here for future reference.

There is no need to manually upload the image to Cloudinary. When using the CarrierWave uploader, the image will get automatically uploaded to Cloudinary and public_id updated in the model.

Related

Rails Paperclip, Deleting Attachment with default_url option doesn't change attachment url

My user model has avatar attachment
class User
has_attached_file :avatar, styles: { medium: '300x300#', thumb: '150x150#' }, default_url: :default_url_by_gender
def default_url_by_gender
if female?
'female.svg'
else
'male.svg'
end
end
end
Before uploading an image the avatar.url return default url, when I upload an image and save then delete it, the avatar.url still direct to the deleted image url not the default_url
I delete the avatar with following code:
user.avatar = nil
user.save
and also tried these methods after checking
question 1 and question2 about same issue
user.avatar.destroy
user.save
#also tried this
user.update(avatar_file_name: nil, avatar_content_type: nil, avatar_file_size: nil)
I am using rails 5.1.6, paperclip (~> 5.2.0)
You need to use purge, not destroy. From the official docs, https://edgeguides.rubyonrails.org/active_storage_overview.html#removing-files
To remove an attachment from a model, call purge on the attachment. Removal can be done in the background if your application is setup to use Active Job. Purging deletes the blob and the file from the storage service.
# Synchronously destroy the avatar and actual resource files.
user.avatar.purge
# Destroy the associated models and actual resource files async, via Active Job.
user.avatar.purge_later
Deleting the asset in the way you've done does not remove the attachment between the instance and the asset:
user.avatar.destroy
user.avatar.attached? => true
user.avatar.purge
user.avatar.attached? => false

ActiveStorage 5.2.1 - uploaded asset is nil since upload has not finished. How to wait for finished upload?

I use ActiveStorage for user generated stylesheets which will be uploaded to s3 in order to include them in a custom user styled web page.
So I have a model CustomeTheme
has_one_attached :style, dependent: :purge_later
and an after_save callback which does the upload after the custom style has been saved
self.style.attach(io: File.open(File.join(asset_path, name)), filename: name, content_type: 'text/css')
Included in a layout
= stylesheet_link_tag url_for(#custom_theme.style)
The problem now is, that the user saves the style and and sees a preview of the custom web page but without the custom style (404 at this point of time) since the uploaded to s3 has not finished yet, at least thats what I suppose.
to_model delegated to attachment, but attachment is nil
/usr/local/bundle/gems/activesupport-5.2.1/lib/active_support/core_ext/module/delegation.rb:278:in `rescue in method_missing'
/usr/local/bundle/gems/activesupport-5.2.1/lib/active_support/core_ext/module/delegation.rb:274:in `method_missing'
/usr/local/bundle/gems/actionpack-5.2.1/lib/action_dispatch/routing/polymorphic_routes.rb:265:in `handle_model'
/usr/local/bundle/gems/actionpack-5.2.1/lib/action_dispatch/routing/polymorphic_routes.rb:280:in `handle_model_call'
/usr/local/bundle/gems/actionview-5.2.1/lib/action_view/routing_url_for.rb:117:in `url_for'
So the question remains unclear to me how could i know that the asset (no matter whether it is a style or an image) is ready to be displayed?
2 possible approaches:
Define a route for upload status checks and then run an interval in Javascript to check for upload status for a given upload id. When it finishes, the endpoint returns the asset URL, which then you can use. (e.g. If the asset is an image, then you just put that on an <img> tag src attribute).
Another approach would be something like what Delayed Paperclip does:
In the default setup, when you upload an image for the first time and try to display it before the job has been completed, Paperclip will be none the wiser and output the url of the image which is yet to be processed, which will result in a broken image link being displayed on the page.
To have the missing image url be outputted by paperclip while the image is being processed, all you need to do is add a #{attachment_name}_processing column to the specific model you want to enable this feature for.
class AddAvatarProcessingToUser < ActiveRecord::Migration
def self.up
add_column :users, :avatar_processing, :boolean
end
def self.down
remove_column :users, :avatar_processing
end
end
#user = User.new(avatar: File.new(...))
#user.save
#user.avatar.url #=> "/images/original/missing.png"
# Process job
#user.reload
#user.avatar.url #=> "/system/images/3/original/IMG_2772.JPG?1267562148"

Active Storage with Amazon S3 not saving with filename specified but using file key instead

I am having an issue with Active Storage. When I upload to Amazon S3, instead of saving the file inside the bucket with the original name like myfile.zip it is saving it as the key which is associated with that file. So in Cyberduck I am seeing something like this: 5YE1aJQuFYyWNr6BSHxhQ48t. Without any file extension.
I am not sure if there is some setting in Rails 5 or whether it is within Amazon S3 but I have spent hours Googling around to figure out why this is happening.
Any pointers would be really appreciated!
Best regards,
Andrew
This is by design, from ActiveStorage. The file is stored by it's key and without extension on S3, but when the URL is generated by ActiveStorage, the disposition and filename are set.
def url(key, expires_in:, filename:, disposition:, content_type:)
instrument :url, key: key do |payload|
generated_url = object_for(key).presigned_url :get, expires_in: expires_in.to_i,
response_content_disposition: content_disposition_with(type: disposition, filename: filename),
response_content_type: content_type
payload[:url] = generated_url
generated_url
end
end
This is probably done to avoid filename escaping issues that you'd run into otherwise.
You can read more about the Content-Disposition headers here.
You can still reference the name with filename accessor.
class User < ApplicationRecord
has_one_attached :photo
...
end
filename = User.first.photo.filename
In order to have a custom filename on S3, you should update both blob.key and the name on S3.
Active storage uploads images on S3 using blob.key as remote image path and name.
For my usage, I only changed the name for 'images variants' with a Monkey Patch that allows to generate a key terminating by the filename :
config/initializers/active_storate_variant.rb :
ActiveStorage::Variant.class_eval do
def key
"variants/#{blob.key}/#{Digest::SHA256.hexdigest(variation.key)}/#{filename}"
end
end
So when I need the public url for an image variant, I just call image.url('400x400')
This is how my Image model is customized :
class Image < ApplicationRecord
belongs_to :imageable, polymorphic: true
has_one_attached :picture
SIZES = { '400x400' => '400x400' }
def url(size)
return "https://placehold.it/#{size}" unless picture.attached?
'https://my_s3_subdomain.amazonaws.com/' +
picture.variant(resize: SIZES[size]).processed.key
end
...
end
If someone has a better way to do that, I would be happy to see it :)

CarrierWave: detect if an image has already been uploaded

A model is seeded with a remote url for an image, meaning the db entry it is not created in Rails. Then the first time it is fetched from the DB in Rails I want to detect that the image has not been uploaded, assign the remote_seed_url for the image url and save! to trigger the CarrierWave upload. I want to do this only once, obviously, but the code below sometimes uploads the same image more than once.
class Item
include Mongoid::Document
include Sunspot::Mongoid2
field :image, type: String
field :remote_seed_url, type: String #URL for image
mount_uploader :image, ImageUploader
end
Then in a controller
def show
# item = Item.find(params[:id])
# if CarrierWave has already uploded then do nothing
if !#item.image?
#item.image = #item.remote_seed_url # next save will trigger the upload?
#item.save!
end
respond_to do ...
end
The value of #item.image is usually "new" or "old" and #item.image? sometimes returns false when I can see that it has already uploaded the image. This causes the above code to upload multiple times.
Is the controller code correct to get the image uploaded only once?
Is there some way I might have messed things up and caused #item.image? to return false when it should be true? Maybe image aging?
After uploader is mounted on a field, when you call that field, you get an uploader object. If you want to check if there is a file, you should call "file" on that object:
[1] pry(main)> item.image.class
=> ImageUploader
[2] pry(main)> item.image.file.class
=> CarrierWave::SanitizedFile
[3] pry(main)> item.image.file.nil?
=> false

Carrierwave on the fly resize

I'm using carrierwave and I have this problem:
Suppose once the project has been delivered you need to add a section where the images in the system need to be displayed with a different size. I don' t want to regenerate the new dimension for each one of the images already in the system. I want to be able to generate (and cache it) whenever a view demands. Something like: " /> . If the new size 500x150 already exists, then returns the cached url, else generate it and return the cached url
I like pretty much Carrierwave but unfortunately doesn't have any on the fly resize feature out of the box. Everyone says it should be pretty simple add this feature but I found almost nothing. The only thing which goes pretty close is this uploader https://gist.github.com/DAddYE/1541912
I had to modify it to make it work so here is my version
class ImageUploader < FileUploader
include CarrierWave::RMagick
#version :thumb do
# process :resize_to_fill => [100,100]
#end
#
#version :thumb_square do
# process :resize_to_fill => [100,100]
#end
#
#version :full do
# process :resize_to_fit => [550, 550]
#end
def re_size(string_size)
if self.file.nil?
return self
end
begun_at = Time.now
string_size.gsub!(/#/, '!')
uploader = Class.new(self.class)
uploader.versions.clear
uploader.version_names = [string_size]
img = uploader.new(model, mounted_as)
img.retrieve_from_store!(self.file.identifier)
cached = File.join(CarrierWave.root, img.url)
unless File.exist?(cached)
img.cache!(self)
img.send(:original_filename=, self.file.original_filename)
size = string_size.split(/x|!/).map(&:to_i)
resizer = case string_size
when /[!]/ then :resize_to_fit
# add more like when />/ then ...
else :resize_to_fill
end
img.send(resizer, *size)
FileUtils.mv(img.file.file, cached)
#img.store!
end
img
end
def extension_white_list
%w[jpg jpeg gif png]
end
def filename
Digest::MD5.hexdigest(original_filename) << File.extname(original_filename) if original_filename
end
def cache_dir
"#{Rails.root}/tmp/uploads"
end
def default_url
'/general/no-image.png'
end
end
The problem with this version is that apparently when calling re_size("100x100").url, the url gets generated and returned before the actual resized image is created resulting in a page with broken links which displays good on any subsequent refresh.
Anyone achieved better results willing to share? :)
Please don't tell me to switch to Dragonfly. I'm using Carrierwave and i really like it. Also it seamlessly integrates with RailsAdmin which is part of my projects too.
Why don't you just generate a different version of the image, such as a thumbnail? In your image_uploader.rb
# Create different versions of your uploaded files:
include CarrierWave::RMagick
version :thumb do
process :resize_to_limit => [100, 100]
end
Then in your view just call
<%= image_tag nameofimage.image_url(:thumb).to_s %>
You could create multiple versions of our original image without resizing the original image. The processing is done by RMagick which you'll need to install.
RMagick requires you to have ImageMagick, so you'll need to install that as well. These can be a little tricky to get installed and working but well worth it. Plus the stackoverflow community has provided a lot of help with this issue.
Error installing Rmagick on Mountain Lion
rmagick gem install "Can't find Magick-config"

Resources