RailsAdmin Carrierwave Multiple Upload undefined method `filename' for nil:NilClass - carrierwave

Following Carrierwave and RailsAdmin instructions I can preform multiple uploads in RailsAdmin with Carrierwave, using aws S3. When trying to delete one of the images I get:
undefined method 'filename' for nil:NilClass
The assets field is a json field, assets:json
This is in my model:
attr_accessor :delete_assets
after_validation do
uploaders = assets.delete_if do |uploader|
if Array(delete_assets).include?(uploader.file.filename)
uploader.remove!
true
end
end
write_attribute(:assets, uploaders.map { |uploader| uploader.file.filename })
end
I tried uploader.file.identifier but read that when using fog aws S3 the uploader.file.filename should be used.
My resources so far:
RailsAdmin
https://github.com/carrierwaveuploader/carrierwave/blob/master/README.md#multiple-file-uploads
Carrierwave:
https://github.com/sferik/rails_admin/wiki/CarrierWave
Stackoverflow:
Rails Admin - undefined method `' for using Carrierwave multiple upload
How do I delete a previously uploaded image from this json array? Any help is much appreciated!

The problem here is certain uploaders don't have a file attached. So your error is because you're calling filename on nil - aka uploader.file returns nil.
You'll likely want to handle this occurrence, something like the following:
attr_accessor :delete_assets
after_validation do
uploaders = assets.delete_if do |uploader|
next unless uploader.file # <-- skip deleting assets without a file
if Array(delete_assets).include?(uploader.file.filename)
uploader.remove!
true
end
end
write_attribute(:assets, uploaders.map { |uploader| uploader.file.filename })
end
Or if you'd rather remove all assets missing a file:
attr_accessor :delete_assets
after_validation do
uploaders = assets.delete_if do |uploader|
next true unless uploader.file # <-- note 'true' added here
if Array(delete_assets).include?(uploader.file.filename)
uploader.remove!
true
end
end
write_attribute(:assets, uploaders.map { |uploader| uploader.file.filename })
end
Otherwise, the route you'll need to go down will be validating the presence of a file on an uploader, which is likely a separate question.
Hope this helps - let me know how you get on or if you have any questions here :)

Related

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"

Default Value for ActiveStorage Rails 5.2.0

I am using Rails ActiveStorage. I want that whenever the value of attachment (in my case image is null then replace it with "abc.png" which is present in assets folder..)
This is what my model.rb file looks like but this code does not seem to work. I am looking for how to set default / nil value for avatar.
has_one_attached :avatar #bot icon
after_create_commit check_avatar(self)
def check_avatar(self)
if(!self.avatar.present?)
{
self.avatar = "abc.png"
}
end
Active storage doesn't provide default option like paperclip does, however, you can do write your method to attach default file in case image is nil. you can use attach method to do so.
def image_nil
if !self.image?
user.image.attach(io: File.open('/path/to/file'), filename: 'file.pdf', content_type: 'image/jpeg')
end
end
you can omit content_type but it is good to provide it, the content type you provide will serve as a fallback in case analyzer can't do it.
Hope this helps!

Testing Carrierwave Uploads with Minitest

Just trying to add some basic tests to my Carrierwave Uploader. I am starting with the default generated tests and trying to go from there. I am getting a weird error and not sure where to go from here.
I have a polymorphic Upload model which has:
mount_uploader :file, DocumentUploader
My DocumentUploader has something like this:
def store_dir
"#{model.uploadable_type.downcase.pluralize.underscore}/#{model.parent_asset.id}/uploads/#{model.id}/"
end
This makes my store directories look like:
/locations/24/uploads/56
when I run the default tests for example:
test "should destroy upload" do
assert_difference('Upload.count', -1) do
delete :destroy, id: #upload
end
assert_redirected_to uploads_path
end
I get:
ERROR["test_should_destroy_upload", UploadsControllerTest,1.9893769259997498]
test_should_destroy_upload#UploadsControllerTest (1.99s)
NoMethodError: NoMethodError: undefined method `id' for nil:NilClass
app/uploaders/document_uploader.rb:34:in `store_dir'
In my Uploads fixtures I have the polymorphic associates set etc. Themodel is not being set to the Uploadable association in the DocumentUploader i.e. it's nil.
All my other tests work so far and my uploader works fine in production and development etc. I am sure I am missing something trivial in the set-up or it's a Carrierwave specific issue.
Chiming in really late here.
Using fog with carrierwave I have found to be a drag. carrierwave-aws gem is non-negotiable for me.
The Read Me provides a quick, overall stub to add to the uploads.rb configuration file:
config.aws_credentials = {
[...], # Required
stub_responses: Rails.env.test? # Optional, avoid hitting S3 actual during tests
}
For the presentation layer, I find this acceptable because, you often want to see results, and particularly the versions of such results. Once those run properly, they are hard to break if the method does not change - and those are rather static methods.

Paperclip dynamic url?

I have a Rails ActiveModel Product with a Paperclip image attachment column that needs to get it's image.url from 2 sources. One is an old S3 bucket/CloudFront, other is our new S3 bucket/CloudFront. They have completely different credentials.
If the instance Product.image_file_name is containing "old:" I want the URL to be something like cloudfront_url/products/file_name, if it doesn't - it should use the new S3 bucket/CloudFront. Upload is going to happen only on the new S3 bucket, but it'll fallback on the old one if image_file_name is containing old: as I mentioned.
Currently I'm authorized only with the new S3 bucket, not with the old one.
I have read that I should do something like:
class Product
has_attached_file: :image, url: dynamic_url_method
def dynamic_url_method
.... do some logic based on image_file_name
return constructed_url
end
end
However when I do that, I get undefined local variable dynamic_url_method.
If I wrap it in a lambda as said in https://stackoverflow.com/a/10493048 I get Error "no implicit conversion of Proc into String".
Have you guys successfully gotten Paperclip to work with a dynamic URL? It would be a life-saver if you know how to do so.
Completely scrap the whole idea of dynamic URL parameter given to the Paperclip attachment. It breaks S3 image uploading cause Paperclip can't figure out which URL to use.
The solution is to introduce a new column in your schema called image_url.
The column will be updated on initialize/update in the ActiveModel and used in the web pages.
In code
class Product
has_attached_file: :image
after_create :update_image_url
after_update :update_image_url
def update_image_url
new_image_url = # some logic based on image_file_name that would either return the CloudFront URL or save the URL from image.url which is generated by Paperclip
# Paperclip does not update image_file_name_changed? so we can't say
# after_create or after_update if: image_file_name_changed? instead
# we have to manually check that image_url and new_image_url are different
update(image_url: new_image_url) if image_url != new_image_url
end
end

How can I reference images in the asset pipeline from a model?

I have a model with a method to return a url to a person's avatar that looks like this:
def avatar_url
if self.avatar?
self.avatar.url # This uses paperclip
else
"/images/avatars/none.png"
end
end
I'm in the midst of upgrading to 3.1, so now the hard-coded none image needs be referenced through the asset pipeline. In a controller or view, I would just wrap it in image_path(), but I don't have that option in the model. How can I generate the correct url to the image?
I struggled with getting this right for a while so I thought I'd post the answer here. Whilst the above works for a standard default image (i.e. same one for each paperclip style), if you need multiple default styles you need a different approach.
If you want to have the default url play nice with the asset pipeline and asset sync and want different default images per style then you need to generate the asset path without fingerprints otherwise you'll get lots of AssetNotPrecompiled errors.
Like so:
:default_url => ActionController::Base.helpers.asset_path("/missing/:style.png", :digest => false)
or in your paperclip options:
:default_url => lambda { |a| "#{a.instance.create_default_url}" }
and then an instance method in the model that has the paperclip attachment:
def create_default_url
ActionController::Base.helpers.asset_path("/missing/:style.png", :digest => false)
end
In this case you can still use the interpolation (:style) but will have to turn off the asset fingerprinting/digest.
This all seems to work fine as long as you are syncing assets without the digest as well as those with the digest.
Personally, I don't think you should really be putting this default in a model, since it's a view detail. In your (haml) view:
= image_tag(#image.avatar_url || 'none.png')
Or, create your own helper and use it like so:
= avatar_or_default(#image)
When things like this are hard in rails, it's often a sign that it's not exactly right.
We solved this problem using draper: https://github.com/jcasimir/draper. Draper let us add a wrapper around our models (for use in views) that have access to helpers.
Paperclip has an option to specify default url
has_attached_file :avatar, :default_url => '/images/.../missing_:style.png'
You can use this to serve default image' in case user has not uploaded avatar.
Using rails active storage I solved this problem by doing this:
# Post.rb
def Post < ApplicationRecord
has_one_attached :image
def thumbnail
self.image.attached? ? self.image.variant(resize: "150x150").processed.service_url : 'placeholder.png';
end
def medium
self.image.attached? ? self.image.variant(resize: "300x300").processed.service_url : 'placeholder.png';
end
def large
self.image.attached? ? self.image.variant(resize: "600x600").processed.service_url : 'placeholder.png';
end
end
Then in your views simply call:
<%= image_tag #post.thumbnail %>,

Resources