Skip to content

Instantly share code, notes, and snippets.

@nicolas-brousse
Created September 12, 2018 11:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicolas-brousse/bbc43bdd5a3f2902e7bf7a73e371edf4 to your computer and use it in GitHub Desktop.
Save nicolas-brousse/bbc43bdd5a3f2902e7bf7a73e371edf4 to your computer and use it in GitHub Desktop.
Rails ActiveStorage FileSizeValidator
# frozen_string_literal: true
module ActiveStorage
module Validations
class FileSizeValidator < ActiveModel::EachValidator
include ActiveSupport::NumberHelper
MESSAGES = { minimum: :file_size_too_short, maximum: :file_size_too_long }.freeze
CHECKS = { minimum: :>=, maximum: :<= }.freeze
RESERVED_OPTIONS = %i[minimum maximum within file_size_too_short file_size_too_long].freeze
def initialize(options)
if (range = (options.delete(:in) || options.delete(:within)))
raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
options[:minimum], options[:maximum] = range.minmax
end
options[:minimum] = 1 if options[:allow_blank] == false && options[:minimum].nil?
super
end
def check_validity! # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
keys = CHECKS.keys & options.keys
if keys.empty?
raise ArgumentError, "Range unspecified. Specify the :in, :within, :maximum, :minimum, or :is option."
end
keys.each do |key|
value = options[key]
unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY || value.is_a?(Symbol) \
|| value.is_a?(Proc)
raise ArgumentError, ":#{key} must be a nonnegative Integer, Infinity, Symbol, or Proc"
end
end
end
def validate_each(record, attribute, association_or_value)
if association_or_value.is_a? ActiveStorage::Attached::One
association_or_value = [association_or_value.attachment]
elsif association_or_value.is_a? ActiveStorage::Attached::Many
association_or_value = association_or_value.attachments
else
raise ArgumentError, "value must be a ActiveStorage::Attachment or ActiveStorage::Attached"
end
# if record.class._reflect_on_association(attribute)
# association_or_value = Array.wrap(association_or_value).reject(&:marked_for_destruction?)
# end
association_or_value.each do |value|
validate_one(record, attribute, value)
end
end
private
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
def validate_one(record, attribute, value)
file_size = value.blob.try(:byte_size)
errors_options = options.except(*RESERVED_OPTIONS)
CHECKS.each do |key, validity_check|
next unless (check_value = options[key])
if record.send(attribute).attached? || skip_nil_check?(key)
case check_value
when Proc
check_value = check_value.call(record)
when Symbol
check_value = record.send(check_value)
end
next if file_size.send(validity_check, check_value)
end
errors_options[:size] = number_to_human_size(check_value)
default_message = options[MESSAGES[key]]
errors_options[:message] ||= default_message if default_message
value.purge_later
record.errors.add(attribute, MESSAGES[key], errors_options)
end
end
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
def skip_nil_check?(key)
key == :maximum && options[:allow_nil].nil? && options[:allow_blank].nil?
end
end
module ClassMethods
def validates_file_size_of(*attr_names)
validates_with FileSizeValidator, _merge_attributes(attr_names)
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment