CarrierWave multiple file types validation with single uploader - ruby-on-rails-3

How to validate the extension of uploaded file when using single uploader for multiple file type?
I am using the single model namely Asset containing the attribute file. Uploader is mounted on file attribute. Asset model having one more attribute called feature_id. feature_id refers to features like video, audio, etc.
So, how should I validate the file type with multiple extension whitelist depending upon feature_id value?
Using ruby 1.9 and rails 3.2.11
Any help will be greatly appreciated.

Although the answer has been accepted but I've got a better way for this. Try this code.
class MyUploader < CarrierWave::Uploader::Base
def extension_white_list
if model.feature_id == 1
%w(jpg jpeg gif png)
elsif model.feature_id == 2
%w(pdf doc docx xls xlsx)
elsif model.feature_id == 3
%w(mp3 wav wma ogg)
end
end
end
feature_id == 1 means you want to allow just picture uploads, feature_id == 2 means that only documents will be allowed to be uploaded and feature_id == 3 will allow you to upload only audio files.
Hopefully it answers the question. You can add more checks for other types of files.

Define your white list in your uploader as shown in the docs for carrierwave.
class MyUploader < CarrierWave::Uploader::Base
def extension_white_list
%w(jpg jpeg gif png)
end
end

I ran into the exact same use case:
In Asset.rb
Validate the format of the filename
validates :asset_file,
format:{
with: %r{\.(pdf|doc|png)$}i, message: "Wrong file format"
}
Use a regular expression to test the file name:
Here you can play with the regex:
http://rubular.com/r/Z3roRDDXAf
Hope this helps!

Related

Rails: Retrieving JPG instead of HEIC from Cloudinary

I'm currently working on a Rails 6 application using ActiveStorage where I'm trying to render images as jpg. I'm using Cloudinary to render the images. I'm trying to support .HEIC images in my web app. A user can upload HEIC images to Cloudinary but I would like for my application to render the image as jpg
When I render the image I see that the browser is rendering the HEIC image which is not supported by browsers.
ActiveStorage uploads the image to the cloud:
Redirected to http://res.cloudinary.com/XXXXXXXXX/image/upload/xxxxxxxxxxxq3r4.HEIC
Completed 302 Found in 24ms (ActiveRecord: 16.1ms | Allocations: 2588)
[ActiveJob] [ActiveStorage::AnalyzeJob] [ac0d5880-xxxxxxxxxxxxxxxxxxxxxxxxxx] Cloudinary Storage (338.6ms) Downloaded file from key: kjpith3bxxxxxxxxxxxxxxxx
[ActiveJob] [ActiveStorage::AnalyzeJob] [ac0d5880-a243-4fef-xxxxxxxxxxxxxxxxxxxx] Skipping image analysis because ImageMagick doesn't support the file
However, I try to render the image from the views as jpg using the following.
<%= cl_image_tag(url_for(post.image), :format => :jpg , class: "card-home__img") %>
But the image is still calling the HEIC image format from this url:
https://res.cloudinary.com/artsyspace/image/upload/v1584732132/wbnknx9ighl6p4ok072u7kd8r5og.heic
Instead of calling the jpg
https://res.cloudinary.com/artsyspace/image/upload/v1584732132/wbnknx9ighl6p4ok072u7kd8r5og.jpg
How can I configure Cloudinary and ActiveStorage to render images or convert images to jpg?
Weirdly, the documentation in one place says the argument is fetch_format, while another shows an example using format.
Worst case, if you're having trouble with the cl_image_tag helper, you can write your own to construct the URL with a .png extension.
https://res.cloudinary.com/artsyspace/image/upload/v1584732132/wbnknx9ighl6p4ok072u7kd8r5og.jpg
It looks like you are sending the full url to the cl_image_tag method.
The cl_image_tag needs just the public id to generate the url.
So the call should be:
<%= cl_image_tag("wbnknx9ighl6p4ok072u7kd8r5og", :format => :png , class: "card-home__img") %>
Of course, make sure to change the hardcoded public id above to the variable holding the public id.
You can get the public id of the resource in the response of the upload.
And one note on the difference between Cloudinary's format and fetch_format:
format would change the extension of the resource i.e
Cloudinary::Utils.cloudinary_url('sample', :format => "png")
will produce https://res.cloudinary.com/demo/image/upload/sample.png
while using fetch_foramt will change the format using the relevant flag, i.e
Cloudinary::Utils.cloudinary_url('sample', :fetch_format => "png")
which will produce https://res.cloudinary.com/demo/image/upload/f_png/sample
In this specific case, both would produce the same png image, but using fetch_format would allow using one of Cloudinary's best features which is optimizing the image automatically using :fetch_format => "auto": https://res.cloudinary.com/demo/image/upload/f_auto/sample
In Rails 6, .key will return the Cloudfare's image ID.
<%= cl_image_tag(post.image.key, :format => :jpg , class: "card-home__img") %>

How do I get dimensions from a CloudinaryPreloadedFile

I need to save the dimensions of my images in my database to help me render images in a pinterest style gallery format.
I use to have this method:
def update_asset_attributes
if image.present? && image_changed?
ap image.file
self.image_content_type = image.file.content_type
self.image_file_size = image.file.size
self.image_width, self.image_height = `identify -format "%wx%h" #{image.file.path}`.split(/x/)
end
end
But now it says: NoMethodError - undefined method content_type for #<Cloudinary::CarrierWave::PreloadedCloudinaryFile:0x007f9834d81840>:
CloudinaryPreloadedFile doesn't have this information at the moment. You can either -
Pass the information by yourself from the javascript code to the server (you can use the cloudinarydone callback data.result object).
Use the attachinary gem.
If the number of images uploaded per hour are small, you can use the Admin API to get the resource's information given it's public_id.

How could store multiple files with carrierwave on Rails

How to upload 2 different files in a form with carrierwave.
At first I create two different uploader to hold the 2 files.
> app/uploaders/cdf_uploader.rb
> app/uploaders/msword_uploader.rb
but when I upload the 2 files , it will auto put the files in 2 different folder.
public/uploads/CONTOLLER/cdf_file
public/uploads/CONTOLLER/msword_file
but how could I store the 2 files in one folder.
but keep my table structure unchange.
http://d.pr/i/7nDu
you can check the form view snapshot here http://d.pr/i/EQWE
Thanks ~
Change the store_dir and rest all will look good then so in your uploaders just defined a common store_dir
something like this
def store_dir
"public/uploads/storage/#{model.id}"
end
NOTE
If the file are of same name and extension you might find odd behavior one overwriting and other and causing problem in carrierwave just and assumption though :)

Paperclip not saving attachment

I am unable to get Paperclip to save my attachment. Rather than saving a single image (such as an avatar as seems to be the common usage), I need to be able to upload/save multiple files; therefore I have a User model and an Asset model. The file information gets properly stored in the asset table, but the attachment itself is not saved in the filesystem as expected.
My log shows the message:
"[paperclip] Saving attachments."
but the attachment is not saved.
Here's a gist with the details: https://gist.github.com/1717415
It's gotta be something simple that I'm missing...
OK... found the problem and it's now working.
The first issue was my naming of the columns in the asset model. I had used simple names: i.e., :description, :file_name, :file_size, :content_type. What I needed to use was: :upload_description, :upload_file_name, :upload_file_size, :upload_content_type where the 'upload' (or whatever you want to use) is a prefix that Paperclip will recognize.And of course that changed my Asset model to reference :upload not :asset, as in:
has_attached_file :upload
Secondly (and this post Adding :multipart => true throws Undefined Method "name" error was key to understanding this) was that you cannot specify the full column name (:upload_file_name) in your view, just specify the prefix and Paperclip magically understands what you want.
Hope this helps someone else!
Did you install ImageMagick?
Did you added image_magick command_path via initializer?
if not, checkout this answer:
Weird paperclip error message

Rails - Use same attachment for all emails using layout

I'm probably missing something obvious, but I've got a logo I'd like to include in all of the emails I send from my app. I have a master layout I'm using for all of those Mailers. I assume there's a way to do keep it DRY and not have to add the line of code to attach the file in every mailer method. Can someone point me in the right direction or correct my line of thought.
Thanks!
Callbacks using before_filter and after_filter will be supported in a future Rails release:
http://github.com/rails/rails/commit/4f28c4fc9a51bbab76d5dcde033c47aa6711339b
Since they will be implemented using AbstractController::Callbacks, you can do the following to mimic the functionality that will be present in ActionMailer::Base once Rails 4 is released:
class YourMailer < ActionMailer::Base
if self.included_modules.include?(AbstractController::Callbacks)
raise "You've already included AbstractController::Callbacks, remove this line."
else
include AbstractController::Callbacks
end
before_filter :add_inline_attachments!
private
def add_inline_attachments!
attachments.inline["footer.jpg"] = File.read('/path/to/filename.jpg')
end
end
This includes the module that will be used in a future rails version, so the callback hooks available to you will be the same to ensure future compatibility. The code will raise when you try to upgrade to a Rails version that already includes AbstractController::Callbacks, so you will be reminded to remove the conditional logic.
I hacked a little something, it's not ideal, but it works.
If you use
default "SOMEHEADER", Proc.new { set_layout }
And then define set_layout
def set_layout
attachments.inline["logo.png"] = File.read("logopath.png")
attachments.inline["footer.jpg"] = File.read("footerpath.png")
"SOME HEADER VALUE"
end
Then because set_layout gets called to set the header, it also adds the inline attachments. It basically creates a callback for adding attachments.
An actual callback system in ActionMailer would be preferable, but this works too.
Thought I would share since I was looking for this answer on this question earlier today.
in the layout file that your mailer uses u can add the following
<%= image_tag('logo.png') %>
I am assuming that the mail being sent out is html or multipart.
Also you will need to make changes in the environment files. ActionMailer does not get a default base_url. For e.g in environments/development.rb I added the following
config.action_mailer.default_url_options = { :host => "localhost:3000" }
If you want to do it in a dRY manner (and as an attachment) maybe you could do something like
class MyMailer < ActionMailer::Base
default :attachment => File.read(File.join(Rails.root,'public','images','logo.png'))
end
I know you've asked about attaching inline images, but here's a different approach that achieves the same thing with less complexity..
Using inline base64 encoded images in the html layout - no attachments required!
Basically just change the src="..." of your logo image to the format:
<img alt="example logo"
width="32px"
height="32px"
src="data:image/png;base64,iVBORw0KGgoAAA....."/>
I use the online base64 encoder / decoder tool at http://www.base64-image.net for generating the complete <img /> tag
This approach has a few benefits:
- no attachment code, which makes the backend server code cleaner and easier to read
- no increase in email size - inline image attachments are converted to base64 anyway so this approach doesn't make the email payload any larger
- it's widely supported - if the receiving email client is showing html, it's pretty likely it also supports this method