Class: ActiveStorageValidations::ContentTypeValidator

Inherits:
ActiveModel::EachValidator
  • Object
show all
Includes:
Errorable, OptionProcUnfolding, Symbolizable
Defined in:
lib/active_storage_validations/content_type_validator.rb

Overview

:nodoc:

Constant Summary collapse

AVAILABLE_CHECKS =
%i[with in].freeze
ERROR_TYPES =
%i[content_type_invalid].freeze

Instance Method Summary collapse

Methods included from Errorable

#add_error, #initialize_error_options

Methods included from OptionProcUnfolding

#unfold_procs

Instance Method Details

#authorized_types(record) ⇒ Object



39
40
41
42
43
44
45
46
47
48
# File 'lib/active_storage_validations/content_type_validator.rb', line 39

def authorized_types(record)
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
  (Array.wrap(flat_options[:with]) + Array.wrap(flat_options[:in])).compact.map do |type|
    if type.is_a?(Regexp)
      type
    else
      Marcel::MimeType.for(declared_type: type.to_s, extension: type.to_s)
    end
  end
end

#check_validity!Object



15
16
17
18
# File 'lib/active_storage_validations/content_type_validator.rb', line 15

def check_validity!
  ensure_exactly_one_validator_option
  ensure_content_types_validity
end

#content_type(file) ⇒ Object



56
57
58
# File 'lib/active_storage_validations/content_type_validator.rb', line 56

def content_type(file)
  file.blob.present? && file.blob.content_type
end

#ensure_content_types_validityObject



73
74
75
76
77
78
79
# File 'lib/active_storage_validations/content_type_validator.rb', line 73

def ensure_content_types_validity
  return true if options[:with]&.is_a?(Proc) || options[:in]&.is_a?(Proc)

  ([options[:with]] || options[:in]).each do |content_type|
    raise ArgumentError, invalid_content_type_message(content_type) if invalid_content_type?(content_type)
  end
end

#ensure_exactly_one_validator_optionObject



67
68
69
70
71
# File 'lib/active_storage_validations/content_type_validator.rb', line 67

def ensure_exactly_one_validator_option
  unless AVAILABLE_CHECKS.one? { |argument| options.key?(argument) }
    raise ArgumentError, 'You must pass either :with or :in to the validator'
  end
end

#invalid_content_type?(content_type) ⇒ Boolean

Returns:

  • (Boolean)


88
89
90
91
92
93
94
95
# File 'lib/active_storage_validations/content_type_validator.rb', line 88

def invalid_content_type?(content_type)
  case content_type
  when String, Symbol
    Marcel::MimeType.for(declared_type: content_type.to_s, extension: content_type.to_s) == 'application/octet-stream'
  when Regexp
    false # We always validate regexes
  end
end

#invalid_content_type_message(content_type) ⇒ Object



81
82
83
84
85
86
# File 'lib/active_storage_validations/content_type_validator.rb', line 81

def invalid_content_type_message(content_type)
  <<~ERROR_MESSAGE
    You must pass valid content types to the validator
    '#{content_type}' is not find in Marcel::EXTENSIONS mimes
  ERROR_MESSAGE
end

#is_valid?(file, types) ⇒ Boolean

Returns:

  • (Boolean)


60
61
62
63
64
65
# File 'lib/active_storage_validations/content_type_validator.rb', line 60

def is_valid?(file, types)
  file_type = content_type(file)
  types.any? do |type|
    type == file_type || (type.is_a?(Regexp) && type.match?(file_type.to_s))
  end
end

#types_to_human_format(types) ⇒ Object



50
51
52
53
54
# File 'lib/active_storage_validations/content_type_validator.rb', line 50

def types_to_human_format(types)
  types
    .map { |type| type.is_a?(Regexp) ? type.source : type.to_s.split('/').last.upcase }
    .join(', ')
end

#validate_each(record, attribute, _value) ⇒ Object



20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/active_storage_validations/content_type_validator.rb', line 20

def validate_each(record, attribute, _value)
  return true unless record.send(attribute).attached?

  types = authorized_types(record)
  return true if types.empty?

  files = Array.wrap(record.send(attribute))

  files.each do |file|
    next if is_valid?(file, types)

    errors_options = initialize_error_options(options, file)
    errors_options[:authorized_types] = types_to_human_format(types)
    errors_options[:content_type] = content_type(file)
    add_error(record, attribute, ERROR_TYPES.first, **errors_options)
    break
  end
end