【问题标题】:ActiveStorage File Attachment ValidationActiveStorage 文件附件验证
【发布时间】:2018-06-17 22:22:15
【问题描述】:

有没有办法使用 ActiveStorage 验证附件?例如,如果我想验证内容类型或文件大小?

类似 Paperclip 的方法会很棒!

  validates_attachment_content_type :logo, content_type: /\Aimage\/.*\Z/
  validates_attachment_size :logo, less_than: 1.megabytes

【问题讨论】:

    标签: ruby-on-rails rails-activestorage ruby-on-rails-5.2


    【解决方案1】:

    遇到了这个宝石:https://github.com/igorkasyanchuk/active_storage_validations

    class User < ApplicationRecord
      has_one_attached :avatar
      has_many_attached :photos
    
      validates :name, presence: true
    
      validates :avatar, attached: true, content_type: 'image/png',
                                         dimension: { width: 200, height: 200 }
      validates :photos, attached: true, content_type: ['image/png', 'image/jpg', 'image/jpeg'],
                                         dimension: { width: { min: 800, max: 2400 },
                                                      height: { min: 600, max: 1800 }, message: 'is not given between dimension' }
    end
    

    【讨论】:

      【解决方案2】:

      这是我在 Rails 5.2 中验证内容类型的解决方案,您可能知道它有一个陷阱,即附件一旦分配给模型就会被保存。它也可能适用于 Rails 6。我所做的是猴子补丁 ActiveStorage::Attachment 包含验证:

      config/initializers/active_storage_attachment_validations.rb:

      Rails.configuration.to_prepare do
        ActiveStorage::Attachment.class_eval do
          ALLOWED_CONTENT_TYPES = %w[image/png image/jpg image/jpeg].freeze
      
          validates :content_type, content_type: { in: ALLOWED_CONTENT_TYPES, message: 'of attached files is not valid' }
        end
      end
      

      app/validators/content_type_validator.rb:

      class ContentTypeValidator < ActiveModel::EachValidator
        def validate_each(record, attribute, _value)
          return true if types.empty?
          return true if content_type_valid?(record)
      
          errors_options = { authorized_types: types.join(', ') }
          errors_options[:message] = options[:message] if options[:message].present?
          errors_options[:content_type] = record.blob&.content_type
          record.errors.add(attribute, :content_type_invalid, errors_options)
        end
      
        private
      
        def content_type_valid?(record)
          record.blob&.content_type.in?(types)
        end
      
        def types
          Array.wrap(options[:with]) + Array.wrap(options[:in])
        end
      end
      

      由于在 Rails 5 中实现了attach 方法:

          def attach(*attachables)
            attachables.flatten.collect do |attachable|
              if record.new_record?
                attachments.build(record: record, blob: create_blob_from(attachable))
              else
                attachments.create!(record: record, blob: create_blob_from(attachable))
              end
            end
          end
      

      create! 方法会在验证失败时引发 ActiveRecord::RecordInvalid 异常,但它只需要被抢救,仅此而已。

      【讨论】:

      • 感谢您的周到!我遇到了一个相关的问题,并且能够根据我的需要调整这个答案。
      • 使用此解决方案,未通过验证的附件仍会保留在文件系统中。可以使用ActiveStorage::Blob.unattached.each(&amp;:purge) 清理它们
      【解决方案3】:

      我找到了一种使用 before_save 回调来验证和删除附件的方法。 这是一种有用的方法,因为如果您在事务期间验证文件(并且您想清除它),在添加错误之后它将回滚删除附件。

      before_save :check_logo_file, on: %i[create update]
      
      def check_favicon_content_type
          PartnerValidators::CustomPartnerFaviconValidator.new.validate(self)
      end
      
      module PartnerValidators
          class CustomPartnerFaviconValidator < ActiveModel::Validator
              ALLOWED_MIME_TYPES = %w(image/vnd.microsoft.icon image/x-icon image/png).freeze
              private_constant :ALLOWED_MIME_TYPES
      
              def validate(partner)
                  if partner.favicon.attached? && invalid_content_type?(partner)
                      partner.errors.add(:favicon, I18n.t("active_admin.errors.favicon"))
                      partner.favicon.purge
                  end
              end
      
              private
      
              def invalid_content_type?(partner)
                  !partner.favicon.blob.content_type.in?(ALLOWED_MIME_TYPES)
              end
          end
      end
      

      【讨论】:

        【解决方案4】:

        ActiveStorage 目前不支持验证。根据https://github.com/rails/rails/issues/31656.


        更新:

        Rails 6 将支持 ActiveStorage 验证。

        https://github.com/rails/rails/commit/e8682c5bf051517b0b265e446aa1a7eccfd47bf7

        Uploaded files assigned to a record are persisted to storage when the record
        is saved instead of immediately.
        In Rails 5.2, the following causes an uploaded file in `params[:avatar]` to
        be stored:
        ```ruby
        @user.avatar = params[:avatar]
        ```
        In Rails 6, the uploaded file is stored when `@user` is successfully saved.
        

        【讨论】:

        • 确认了。如果验证失败(模型未保存),rails 6 不会将文件保存到存储中。我还没有检查直接上传到存储的行为。
        【解决方案5】:

        将ActiveStorage的DirectUploadsController的内容复制到app/controllers/active_storage/direct_uploads_controller.rb文件中并修改create方法。您可以向此控制器添加身份验证,添加对文件大小或 mime 类型的常规验证,因为此控制器的 create 方法会为要上传的文件创建 url。因此,您可以通过控制此控制器中的大小和 mime 类型来阻止任何文件上传。

        一个简单的验证可以是:

        # ...
        def create
          raise SomeError if blob_args[:byte_size] > 10240 # 10 megabytes
          blob = ActiveStorage::Blob.create_before_direct_upload!(blob_args)
          render json: direct_upload_json(blob)
        end
        # ...
        

        【讨论】:

        • 我不同意将验证移至控制器的概念。控制器几乎没有经过测试,我不认为验证应该是集成测试的一部分,而应该是单元测试的一部分。在我看来,最好尽可能在模型中保留任何验证工作,并可能出于可用性目的添加某种客户端验证。但是一定要让你的控制器保持苗条——你最终会感谢自己的 :)
        【解决方案6】:

        你可以使用很棒的https://github.com/musaffa/file_validators gem

        class Profile < ActiveRecord::Base
          has_one_attached :avatar
          validates :avatar, file_size: { less_than_or_equal_to: 100.kilobytes },
            file_content_type: { allow: ['image/jpeg', 'image/png'] }
        end
        

        我将它与表单对象一起使用,所以我不能 100% 确定它是否直接与 AR 一起使用,但它应该...

        【讨论】:

        • 您能否详细说明解决方案
        • 此方法有效,但仅当在 file_size 选项中通过 lambda 验证时,因为 AR 没有 size 方法,但如果您通过 Lamba,则此方法有效
        【解决方案7】:

        嗯,它并不漂亮,但在他们进行一些验证之前,这可能是必要的:

          validate :logo_validation
        
          def logo_validation
            if logo.attached?
              if logo.blob.byte_size > 1000000
                logo.purge
                errors[:base] << 'Too big'
              elsif !logo.blob.content_type.starts_with?('image/')
                logo.purge
                errors[:base] << 'Wrong format'
              end
            end
          end
        

        【讨论】:

        • 旧值即使无效也会被覆盖。
        • 这对我有用,但是通过这种方式,结果附加了一个名为 size 的参数并委托给 AR 附件,由于 AR 导致的崩溃没有 size 方法并返回以下消息:undefined method 'size' for #&lt;ActiveStorage::Attached::One:0x00007fe6b7350160&gt;,你以前有过这个错误吗?你解决了吗?谢谢
        • 如果您在本地保存文件,请确认当验证触发时,active_storage_blobs 记录不会被创建,并且文件不会被保存在应用程序的storage 目录中.我的经验是模型上的自定义 ActiveStorage 验证只会停止创建 active_storage_attachments 记录,但文件仍将保存到磁盘,active_storage_blobs 记录仍保存在数据库中。您可能需要一份工作来清除孤立的附件和 blob。
        • 这是我一直在寻找的答案。不是愚蠢的随机宝石。我想查看验证的原始详细信息以及代码的放置位置。
        • rails 6 不需要清除。如果您的 logo_validation 失败,Rails 6 不会将文件保存到存储中。如果模型成功保存,它只会将文件上传/存储到存储中。尚未通过直接上传来确认这一点。
        猜你喜欢
        • 2021-05-02
        • 2019-08-04
        • 1970-01-01
        • 1970-01-01
        • 2019-02-18
        • 1970-01-01
        • 2019-05-13
        • 2018-10-31
        • 2020-03-01
        相关资源
        最近更新 更多