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
Related
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
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"
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.
I'm using the Paperclip gem on a Rails 3.2 app where users can upload a specialized file type that contains an image and other information (let's call it a ".elf" file).
I've written all the code needed to extract an image from an elf file via a class called ElfObject, and this works well when tested in the app's console. What I want to do is extract the image and other data from the .elf file before Paperclip saves it to AWS S3, store the other data in the model, and then save just the image object as the Paperclip attachment on S3. Here's the relevant code in the model:
class Photo < ActiveRecord::Base
validates_attachment :attachment,
:presence => true
has_attached_file :attachment,
:storage => :s3,
[[S3 credentials removed]]
before_attachment_post_process :import_photo
attr_accessible :name, :attachment, :properties
def import_photo
if attachment_file_name =~ %r{^*\.elf$}
origfile = attachment.queued_for_write
elf = ElfObject.read(origfile)
properties = elf.get_properties
attachment = elf.image.write "image_for_#{attachment_file_name}.png"
save!
end
end
When I try to upload this way on the app, it raises the error ArgumentError (Invalid argument 'file'. Expected String, got Hash.) from the line elf = ElfObject.read(origfile). If I try something like elf = ElfObject.read(origfile.path), I get NoMethodError (undefined method `path' for #).
Clearly, I'm not fully understanding how to access the file from Paperclip prior to it being posted--any ideas on where I'm going wrong and how to fix it?
It seems like the problem is exactly what the error is saying... that origfile is a Hash, rather than a String.
If that is the case then attachment.queued_for_write returns a Hash, which means you need to find the key that holds the file path string.
origfile = attachment.queued_for_write
p origfile.inspect #print it out so you can figure out what key holds the path
Edited answer try this:
def import_photo
if attachment_file_name =~ %r{^*\.elf$}
origfile = attachment.queued_for_write[:original]
elf = ElfObject.read(origfile.path)
properties = elf.get_properties
attachment = elf.image.write "image_for_#{attachment_file_name}.png"
save!
end
end
I'm trying to develop direct fileuploads to S3 for my app. I'm following the github tutorial for this and everything is more or less ok but get an error message when trying to make the post processing.
I did the following:
I have an activerecord model called clip.rb:
class Clip < ActiveRecord::Base
belongs_to :attachable, :polymorphic => true
mount_uploader :avatar, AvatarUploader
attr_accessible :id, :avatar, :name, :clipat_file_name, :attachable_id, :attachable_type, :clipat, :project_id, :user_id, :path, :parent_id,
def save_and_process_avatar(options = {})
if options[:now] or 1==1
self.remote_avatar_url = avatar.direct_fog_url(:with_path => true)
save
else
Resque.enqueue(AvatarProcessor, attributes)
end
end
Then I have an uploader: avatar_uploader.rb
class AvatarUploader < CarrierWave::Uploader::Base
include CarrierWave::RMagick
include CarrierWaveDirect::Uploader
def store_dir
"uploads/#{model.class.to_s.underscore}/#{mounted_as}" #I removed /#{model.id} from the template because it creates an empty directory on the server. But If I put it back, the same problem remains
end
version :thumb do
process :resize_to_limit => [50, 50]
end
end
and an avatar controller:
class AvatarsController < ApplicationController
def new
#uploader = Clip.new.avatar
#uploader.success_action_redirect = 'http://localhost:3000/clips'
end
end
and finally my clip_controller:
class ClipsController < ApplicationController
def index
if params[:key]
key=params[:key].split("/")
clip = Clip.new
clip.attachable_id = key[3]
clip.attachable_type = "Pmdocument"
clip.key = params[:key]
# clip.save
clip.save_and_process_avatar
end
#clips = Clip.where("avatar is not null")
respond_to do |format|
format.html # index.html.erb
format.json { render json: #clips.collect { |p| p.to_jq_upload }.to_json }
end
end
When I upload a file, if I just save my "clip", everything is ok. If I use the save_and_process method however, an error arises at line:
self.remote_avatar_url = avatar.direct_fog_url(:with_path => true)
This is the error message:
OpenURI::HTTPError (403 Forbidden):
app/models/clip.rb:38:in `save_and_process_avatar'
app/controllers/clips_controller.rb:22:in `index'
Rendered /Users/nico/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-3.1.3/lib/action_dispatch/middleware/templates/rescues/_trace.erb (1.4ms)
Rendered /Users/nico/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-3.1.3/lib/action_dispatch/middleware/templates/rescues/_request_and_response.erb (1.2ms)
Rendered /Users/nico/.rvm/gems/ruby-1.9.2-p290/gems/actionpack-3.1.3/lib/action_dispatch/middleware/templates/rescues/diagnostics.erb within rescues/layout (5.2ms)
I've been hanging on this for two days so, any help would be greatly appreciated!!! Thanks!!! Nicolas.
My bet is that the URL supplied to self.remote_avatar_url is incorrect. I had this same problem and the code that CWDirect gem provides didn't work for me and gave me an incorrect URL, thus CarrierWave couldn't download and process the image. The whole 403 Forbidden error message was a crap message from Amazon--this leads one to believe that there is something wrong with Permissions; in my case there was absolutely nothing wrong with permissions. It was just that there was no image there. Here is the code that got this working for me, notice I've changed how the URL is formed:
def save_and_process_image(options = {})
if options[:now]
# debugger
self.remote_image_url = image.direct_fog_url+self.key # OLD CODE THAT AINT WORKIN! --> image.direct_fog_url(:with_path => true)
save
else
# Resque.enqueue(AvatarProcessor, attributes)
# TODO: Implement background processing
end
end
Note that the name of my mounted field is image and not avatar.
How I got to this point and fixed it--try this out, use the rails debugger (just uncomment the debugger line above) to freeze the program just before the self.remote_image_url line, then while in debug mode type irb to start up the console. Then you can print out and see really what value 'image.direct_fog_url(:with_path => true)' is giving you. You can copy and paste this into a browser. If it's wrong (probably is) then you will get the silly Permissions error (even though it's not a permissions problem), but when it's correct either you will see the uploaded image or the image will download.
It's a good idea to have your Amazon S3 console open and viewing your dev bucket so you can find the image that just uploaded. Find the image in the console and go to its properties and you can see the web address/url that you are supposed to be using.
Hope this helps. Because of the misleading error this was hard to track down for me, I spent a bunch of time trying to correct permissions on my S3 bucket but this wasn't the problem, just that code from the CWDirect github page doesn't work for me (gem version??).