Ruby on Rails - Active Storage - how to accept only pdf and doc?
Asked Answered
M

8

15

Is it possible to add a validation to accept only .pdf and .doc files using Active Storage?

Mead answered 19/1, 2018 at 20:30 Comment(1)
use carrierwave gem it will helpPolymerize
S
21

Currently you have to write your own validator which looks at the attachment's MIME type:

class Item
  has_one_attached :document

  validate :correct_document_mime_type

  private

  def correct_document_mime_type
    if document.attached? && !document.content_type.in?(%w(application/msword application/pdf))
      document.purge # delete the uploaded file
      errors.add(:document, 'Must be a PDF or a DOC file')
    end
  end
end

Also, there are some useful shortcut methods image?, audio?, video? and text? that check against multiple mime types.

Selfgovernment answered 23/1, 2018 at 12:11 Comment(6)
application/msword isn't the content_type for a word doc. It's application/vnd.openxmlformats-officedocument.wordprocessingml.document as stated below.Warrant
@Warrant The OP asked about .doc (the old MS Word format) in which case the MIME type is really application/msword. The type you mentioned is valid for the newer XML-based .docx. I guess most people would want to use both in their validators.Selfgovernment
Right, my mistake!Warrant
Activestorage should have proper validations in rails 6. In the meantime, I found I also need to include document.purge along with errors.add, otherwise the blob is left orphaned.Stumpy
This solution will not stop the file from being uploaded, but will merely stop it from being saved.Penninite
Should use purge_later() instead of purge(). From docs: Deletes the file on the service and then destroys the blob record. This is the recommended way to dispose of unwanted blobs. Note, though, that deleting the file off the service will initiate a HTTP connection to the service, which may be slow or prevented, so you should not use this method inside a transaction or in callbacks. Use purge_later instead.Samford
F
11

there is a gem which provides validation for active storage

gem 'activestorage-validator'

https://github.com/aki77/activestorage-validator

  validates :avatar, presence: true, blob: { content_type: :image }
  validates :photos, presence: true, blob: { content_type: ['image/png', 'image/jpg', 'image/jpeg'], size_range: 1..5.megabytes }
Forsooth answered 10/10, 2018 at 10:47 Comment(3)
typo precence should be presenceNutrient
Isn't this code validating only if the attachment is an image? What should be the code for a pdf? I am struggling with it.Chrome
@Chrome it's just MIME type. In your case: application/pdfDrake
F
6

Since ActiveStorage doesn't have validations yet, I found the following helps in my forms.

<div class="field">
  <%= f.label :deliverable %>
  <%= f.file_field :deliverable, direct_upload: true, 
    accept: 'application/pdf, 
    application/zip,application/vnd.openxmlformats-officedocument.wordprocessingml.document' %>
 </div>
Foundation answered 27/4, 2018 at 2:31 Comment(2)
There should always be a validation at the backend. Any use can disable this very easily :)Mezzo
How would you go about backend validation when direct uploads are being used? The file doesn't ever reach the server directly. It would have to be, "after the fact" validation.Helbonnah
E
2

I was looking at https://gist.github.com/lorenadl/a1eb26efdf545b4b2b9448086de3961d

and it looks like on your view you have to do something like this

<div class="field">
  <%= f.label :deliverable %>
  <%= f.file_field :deliverable, direct_upload: true, 
    accept: 'application/pdf, 
    application/zip,application/vnd.openxmlformats-officedocument.wordprocessingml.document' %>
 </div>

Now on your model, you can do something like this

class User < ApplicationRecord
  has_one_attached :document

  validate :check_file_type

  private

  def check_file_type
    if document.attached? && !document.content_type.in?(%w(application/msword application/pdf))
      document.purge # delete the uploaded file
      errors.add(:document, 'Must be a PDF or a DOC file')
    end
  end
end

I hope that this helps

Elastomer answered 12/4, 2019 at 6:35 Comment(0)
S
1
class Book < ApplicationRecord
  has_one_attached :image
  has_many_attached :documents

  validate :image_validation
  validate :documents_validation

  def documents_validation
    error_message = ''
    documents_valid = true
    if documents.attached?
      documents.each do |document|
        if !document.blob.content_type.in?(%w(application/xls application/odt application/ods pdf application/tar application/tar.gz application/docx application/doc application/rtf application/txt application/rar application/zip application/pdf image/jpeg image/jpg image/png))
          documents_valid = false
          error_message = 'The document wrong format'
        elsif document.blob.byte_size > (100 * 1024 * 1024) && document.blob.content_type.in?(%w(application/xls application/odt application/ods pdf application/tar application/tar.gz application/docx application/doc application/rtf application/txt application/rar application/zip application/pdf image/jpeg image/jpg image/png))
          documents_valid = false
          error_message = 'The document oversize limited (100MB)'
        end
      end
    end
    unless documents_valid
      errors.add(:documents, error_message)
      self.documents.purge
      DestroyInvalidationRecordsJob.perform_later('documents', 'Book', self.id)
    end
  end

  def image_validation
    if image.attached?
      if !image.blob.content_type.in?(%w(image/jpeg image/jpg image/png))
        image.purge_later
        errors.add(:image, 'The image wrong format')
      elsif image.blob.content_type.in?(%w(image/jpeg image/jpg image/png)) && image.blob.byte_size > (5 * 1024 * 1024) # Limit size 5MB
        image.purge_later
        errors.add(:image, 'The image oversize limited (5MB)')
      end
    elsif image.attached? == false
      image.purge_later
      errors.add(:image, 'The image required.')
    end
  end
end

And in job Destroy

class DestroyInvalidationRecordsJob < ApplicationJob
  queue_as :default

  def perform(record_name, record_type, record_id)
    attachments = ActiveStorage::Attachment.where(name: record_name, record_type: record_type, record_id: record_id)
    attachments.each do |attachment|
      blob = ActiveStorage::Blob.find(attachment.blob_id)
      attachment.destroy
      blob.destroy if blob.present?
    end
  end
end
Strumpet answered 25/12, 2018 at 4:46 Comment(0)
R
1

I was doing direct uploads with ActiveStorage. Since validators don't exist yet I simply overwrote the DirectUploadsController Create method:

# This is a kind of monkey patch which overrides the default create so I can do some validation.
# Active Storage validation wont be released until Rails 6.
class DirectUploadsController < ActiveStorage::DirectUploadsController
  def create
    puts "Do validation here"
    super
  end
end

also need to overwrite the route:

  post '/rails/active_storage/direct_uploads', to: 'direct_uploads#create'
Ras answered 29/12, 2018 at 22:9 Comment(0)
P
1

+1 for using gem 'activestorage-validator' with ActiveStorage

In your model you can validate doc, docx and pdf formats this way:

has_one_attached :cv
validates :cv, blob: { content_type: ['application/msword', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', 'application/pdf'], size_range: 0..5.megabytes }
Punctate answered 31/3, 2019 at 20:59 Comment(0)
C
1

Adding a gem for just a few lines of code is not always needed. Here's a working example based on a small alteration on the example above.

validate :logo_content_type, if: -> { logo.attached? }


def logo_content_type
  allowed = %w[image/jpeg image/jpeg image/webp image/png]
  if allowed.exclude?(logo.content_type)
    errors.add(:logo, message: 'Logo must be a JPG, JPEG, WEBP or PNG')
  end
end
Coax answered 14/6, 2021 at 13:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.