active_storage_validations 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +145 -73
- data/config/locales/da.yml +33 -0
- data/config/locales/de.yml +6 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/es.yml +6 -0
- data/config/locales/fr.yml +6 -0
- data/config/locales/it.yml +6 -0
- data/config/locales/ja.yml +6 -0
- data/config/locales/nl.yml +6 -0
- data/config/locales/pl.yml +6 -0
- data/config/locales/pt-BR.yml +5 -0
- data/config/locales/ru.yml +7 -1
- data/config/locales/sv.yml +10 -1
- data/config/locales/tr.yml +6 -0
- data/config/locales/uk.yml +6 -0
- data/config/locales/vi.yml +6 -0
- data/config/locales/zh-CN.yml +6 -0
- data/lib/active_storage_validations/aspect_ratio_validator.rb +49 -22
- data/lib/active_storage_validations/attached_validator.rb +18 -4
- data/lib/active_storage_validations/base_size_validator.rb +66 -0
- data/lib/active_storage_validations/concerns/errorable.rb +38 -0
- data/lib/active_storage_validations/concerns/symbolizable.rb +12 -0
- data/lib/active_storage_validations/content_type_validator.rb +47 -8
- data/lib/active_storage_validations/dimension_validator.rb +30 -15
- data/lib/active_storage_validations/limit_validator.rb +48 -8
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +46 -33
- data/lib/active_storage_validations/matchers/base_size_validator_matcher.rb +134 -0
- data/lib/active_storage_validations/matchers/concerns/active_storageable.rb +17 -0
- data/lib/active_storage_validations/matchers/concerns/allow_blankable.rb +26 -0
- data/lib/active_storage_validations/matchers/concerns/attachable.rb +48 -0
- data/lib/active_storage_validations/matchers/concerns/contextable.rb +47 -0
- data/lib/active_storage_validations/matchers/concerns/messageable.rb +26 -0
- data/lib/active_storage_validations/matchers/concerns/rspecable.rb +25 -0
- data/lib/active_storage_validations/matchers/concerns/validatable.rb +54 -0
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +76 -52
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +93 -55
- data/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb +78 -0
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +9 -88
- data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +40 -0
- data/lib/active_storage_validations/matchers.rb +3 -0
- data/lib/active_storage_validations/metadata.rb +60 -28
- data/lib/active_storage_validations/processable_image_validator.rb +16 -5
- data/lib/active_storage_validations/size_validator.rb +18 -43
- data/lib/active_storage_validations/total_size_validator.rb +49 -0
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +3 -2
- metadata +42 -26
- data/lib/active_storage_validations/error_handler.rb +0 -18
data/config/locales/tr.yml
CHANGED
@@ -7,6 +7,11 @@ tr:
|
|
7
7
|
file_size_not_greater_than: "dosya boyutu %{min_size} boyutundan büyük olmalıdır (geçerli boyut %{file_size}'dir)"
|
8
8
|
file_size_not_greater_than_or_equal_to: "dosya boyutu %{min_size} değerinden büyük veya eşit olmalıdır (geçerli boyut %{file_size}'dir)"
|
9
9
|
file_size_not_between: "dosya boyutu %{min_size} ile %{max_size} arasında olmalıdır (geçerli boyut %{file_size}'dir)"
|
10
|
+
total_file_size_not_less_than: "toplam dosya boyutu %{max_size} boyutundan küçük olmalıdır (geçerli boyut %{total_file_size}'dir)"
|
11
|
+
total_file_size_not_less_than_or_equal_to: "toplam dosya boyutu %{max_size} değerinden küçük veya ona eşit olmalıdır (geçerli boyut %{total_file_size}'dir)"
|
12
|
+
total_file_size_not_greater_than: "toplam dosya boyutu %{min_size} boyutundan büyük olmalıdır (geçerli boyut %{total_file_size}'dir)"
|
13
|
+
total_file_size_not_greater_than_or_equal_to: "toplam dosya boyutu %{min_size} değerinden büyük veya eşit olmalıdır (geçerli boyut %{total_file_size}'dir)"
|
14
|
+
total_file_size_not_between: "toplam dosya boyutu %{min_size} ile %{max_size} arasında olmalıdır (geçerli boyut %{total_file_size}'dir)"
|
10
15
|
limit_out_of_range: "toplam miktar aralık dışında"
|
11
16
|
image_metadata_missing: "geçerli bir imaj değil"
|
12
17
|
dimension_min_inclusion: "%{width} x %{height} piksele eşit ya da büyük olmalı"
|
@@ -24,3 +29,4 @@ tr:
|
|
24
29
|
aspect_ratio_not_landscape: "yatay bir imaj olmalı"
|
25
30
|
aspect_ratio_is_not: "%{aspect_ratio} en boy oranına sahip olmalı"
|
26
31
|
aspect_ratio_unknown: "bilinmeyen en boy oranı"
|
32
|
+
image_not_processable: "geçerli bir imaj değil"
|
data/config/locales/uk.yml
CHANGED
@@ -7,6 +7,11 @@ uk:
|
|
7
7
|
file_size_not_greater_than: "розмір файлу має бути більшим ніж %{min_size} (поточний розмір %{file_size})"
|
8
8
|
file_size_not_greater_than_or_equal_to: "розмір файлу має бути більшим або дорівнювати %{min_size} (поточний розмір %{file_size})"
|
9
9
|
file_size_not_between: "розмір файлу має бути від %{min_size} до %{max_size} (поточний розмір %{file_size})"
|
10
|
+
total_file_size_not_less_than: "загальний розмір файлу має бути менше %{max_size} (поточний розмір %{total_file_size})"
|
11
|
+
total_file_size_not_less_than_or_equal_to: "загальний розмір файлу має бути меншим або дорівнювати %{max_size} (поточний розмір %{total_file_size})"
|
12
|
+
total_file_size_not_greater_than: "загальний розмір файлу має бути більшим ніж %{min_size} (поточний розмір %{total_file_size})"
|
13
|
+
total_file_size_not_greater_than_or_equal_to: "загальний розмір файлу має бути більшим або дорівнювати %{min_size} (поточний розмір %{total_file_size})"
|
14
|
+
total_file_size_not_between: "загальний розмір файлу має бути від %{min_size} до %{max_size} (поточний розмір %{total_file_size})"
|
10
15
|
limit_out_of_range: "кількість файлів більше необхідного"
|
11
16
|
image_metadata_missing: "не є допустимим зображенням"
|
12
17
|
dimension_min_inclusion: "мусить бути більше або дорівнювати %{width} x %{height} пікселям"
|
@@ -24,3 +29,4 @@ uk:
|
|
24
29
|
aspect_ratio_not_landscape: "мусить бути пейзажне зображення"
|
25
30
|
aspect_ratio_is_not: "мусить мати співвідношення сторін %{aspect_ratio}"
|
26
31
|
aspect_ratio_unknown: "має невідоме співвідношення сторін"
|
32
|
+
image_not_processable: "не є допустимим зображенням"
|
data/config/locales/vi.yml
CHANGED
@@ -7,6 +7,11 @@ vi:
|
|
7
7
|
file_size_not_greater_than: "kích thước tệp phải lớn hơn %{min_size} (kích thước hiện tại là %{file_size})"
|
8
8
|
file_size_not_greater_than_or_equal_to: "kích thước tệp phải lớn hơn hoặc bằng %{min_size} (kích thước hiện tại là %{file_size})"
|
9
9
|
file_size_not_between: "kích thước tệp phải nằm trong khoảng từ %{min_size} đến %{max_size} (kích thước hiện tại là %{file_size})"
|
10
|
+
total_file_size_not_less_than: "tổng kích thước tệp phải nhỏ hơn %{max_size} (kích thước hiện tại là %{total_file_size})"
|
11
|
+
total_file_size_not_less_than_or_equal_to: "tổng kích thước tệp phải nhỏ hơn hoặc bằng %{max_size} (kích thước hiện tại là %{total_file_size})"
|
12
|
+
total_file_size_not_greater_than: "tổng kích thước tệp phải lớn hơn %{min_size} (kích thước hiện tại là %{total_file_size})"
|
13
|
+
total_file_size_not_greater_than_or_equal_to: "tổng kích thước tệp phải lớn hơn hoặc bằng %{min_size} (kích thước hiện tại là %{total_file_size})"
|
14
|
+
total_file_size_not_between: "tổng kích thước tệp phải nằm trong khoảng từ %{min_size} đến %{max_size} (kích thước hiện tại là %{total_file_size})"
|
10
15
|
limit_out_of_range: "tổng số tệp vượt giới hạn"
|
11
16
|
image_metadata_missing: "không phải là ảnh"
|
12
17
|
dimension_min_inclusion: "phải lớn hơn hoặc bằng %{width} x %{height} pixel"
|
@@ -24,3 +29,4 @@ vi:
|
|
24
29
|
aspect_ratio_not_landscape: "phải là ảnh ngang"
|
25
30
|
aspect_ratio_is_not: "phải có tỉ lệ ảnh %{aspect_ratio}"
|
26
31
|
aspect_ratio_unknown: "tỉ lệ ảnh không xác định"
|
32
|
+
image_not_processable: "không phải là ảnh"
|
data/config/locales/zh-CN.yml
CHANGED
@@ -7,6 +7,11 @@ zh-CN:
|
|
7
7
|
file_size_not_greater_than: "文件大小必须大于 %{min_size}(当前大小为 %{file_size})"
|
8
8
|
file_size_not_greater_than_or_equal_to: "文件大小必须大于或等于 %{min_size}(当前大小为 %{file_size})"
|
9
9
|
file_size_not_between: "文件大小必须介于 %{min_size} 和 %{max_size} 之间(当前大小为 %{file_size})"
|
10
|
+
total_file_size_not_less_than: "总文件大小必须小于 %{max_size}(当前大小为 %{total_file_size})"
|
11
|
+
total_file_size_not_less_than_or_equal_to: "文件总大小必须小于或等于 %{max_size}(当前大小为 %{total_file_size})"
|
12
|
+
total_file_size_not_greater_than: "总文件大小必须大于 %{min_size}(当前大小为 %{total_file_size})"
|
13
|
+
total_file_size_not_greater_than_or_equal_to: "文件总大小必须大于或等于 %{min_size}(当前大小为 %{total_file_size})"
|
14
|
+
total_file_size_not_between: "总文件大小必须介于 %{min_size} 和 %{max_size} 之间(当前大小为 %{total_file_size})"
|
10
15
|
limit_out_of_range: "文件数超出限定范围"
|
11
16
|
image_metadata_missing: "不是有效的图像"
|
12
17
|
dimension_min_inclusion: "必须大于或等于 %{width} x %{height} 像素"
|
@@ -24,3 +29,4 @@ zh-CN:
|
|
24
29
|
aspect_ratio_not_landscape: "必须是横屏图片"
|
25
30
|
aspect_ratio_is_not: "纵横比必须是 %{aspect_ratio}"
|
26
31
|
aspect_ratio_unknown: "未知的纵横比"
|
32
|
+
image_not_processable: "不是有效的图像"
|
@@ -1,19 +1,31 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/errorable.rb'
|
4
|
+
require_relative 'concerns/symbolizable.rb'
|
3
5
|
require_relative 'metadata.rb'
|
4
6
|
|
5
7
|
module ActiveStorageValidations
|
6
8
|
class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
|
7
9
|
include OptionProcUnfolding
|
8
|
-
include
|
10
|
+
include Errorable
|
11
|
+
include Symbolizable
|
9
12
|
|
10
13
|
AVAILABLE_CHECKS = %i[with].freeze
|
11
|
-
|
14
|
+
NAMED_ASPECT_RATIOS = %i[square portrait landscape].freeze
|
15
|
+
ASPECT_RATIO_REGEX = /is_([1-9]\d*)_([1-9]\d*)/.freeze
|
16
|
+
ERROR_TYPES = %i[
|
17
|
+
image_metadata_missing
|
18
|
+
aspect_ratio_not_square
|
19
|
+
aspect_ratio_not_portrait
|
20
|
+
aspect_ratio_not_landscape
|
21
|
+
aspect_ratio_is_not
|
22
|
+
aspect_ratio_unknown
|
23
|
+
].freeze
|
24
|
+
PRECISION = 3.freeze
|
12
25
|
|
13
26
|
def check_validity!
|
14
|
-
|
15
|
-
|
16
|
-
end
|
27
|
+
ensure_at_least_one_validator_option
|
28
|
+
ensure_aspect_ratio_validity
|
17
29
|
end
|
18
30
|
|
19
31
|
if Rails.gem_version >= Gem::Version.new('6.0.0')
|
@@ -27,7 +39,7 @@ module ActiveStorageValidations
|
|
27
39
|
|
28
40
|
files.each do |file|
|
29
41
|
metadata = Metadata.new(file).metadata
|
30
|
-
next if is_valid?(record, attribute, metadata)
|
42
|
+
next if is_valid?(record, attribute, file, metadata)
|
31
43
|
break
|
32
44
|
end
|
33
45
|
end
|
@@ -43,19 +55,17 @@ module ActiveStorageValidations
|
|
43
55
|
file.analyze; file.reload unless file.analyzed?
|
44
56
|
metadata = file.metadata
|
45
57
|
|
46
|
-
next if is_valid?(record, attribute, metadata)
|
58
|
+
next if is_valid?(record, attribute, file, metadata)
|
47
59
|
break
|
48
60
|
end
|
49
61
|
end
|
50
62
|
end
|
51
63
|
|
52
|
-
|
53
64
|
private
|
54
65
|
|
55
|
-
|
56
|
-
def is_valid?(record, attribute, metadata)
|
66
|
+
def is_valid?(record, attribute, file, metadata)
|
57
67
|
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
58
|
-
errors_options = initialize_error_options(options)
|
68
|
+
errors_options = initialize_error_options(options, file)
|
59
69
|
|
60
70
|
if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
|
61
71
|
errors_options[:aspect_ratio] = flat_options[:with]
|
@@ -80,21 +90,38 @@ module ActiveStorageValidations
|
|
80
90
|
errors_options[:aspect_ratio] = flat_options[:with]
|
81
91
|
add_error(record, attribute, :aspect_ratio_not_landscape, **errors_options)
|
82
92
|
|
93
|
+
when ASPECT_RATIO_REGEX
|
94
|
+
flat_options[:with] =~ ASPECT_RATIO_REGEX
|
95
|
+
x = $1.to_i
|
96
|
+
y = $2.to_i
|
97
|
+
|
98
|
+
return true if x > 0 && y > 0 && (x.to_f / y).round(PRECISION) == (metadata[:width].to_f / metadata[:height]).round(PRECISION)
|
99
|
+
|
100
|
+
errors_options[:aspect_ratio] = "#{x}:#{y}"
|
101
|
+
add_error(record, attribute, :aspect_ratio_is_not, **errors_options)
|
83
102
|
else
|
84
|
-
|
85
|
-
|
86
|
-
|
103
|
+
errors_options[:aspect_ratio] = flat_options[:with]
|
104
|
+
add_error(record, attribute, :aspect_ratio_unknown, **errors_options)
|
105
|
+
return false
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def ensure_at_least_one_validator_option
|
110
|
+
unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
111
|
+
raise ArgumentError, 'You must pass :with to the validator'
|
112
|
+
end
|
113
|
+
end
|
87
114
|
|
88
|
-
|
115
|
+
def ensure_aspect_ratio_validity
|
116
|
+
return true if options[:with]&.is_a?(Proc)
|
89
117
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
118
|
+
unless NAMED_ASPECT_RATIOS.include?(options[:with]) || options[:with] =~ ASPECT_RATIO_REGEX
|
119
|
+
raise ArgumentError, <<~ERROR_MESSAGE
|
120
|
+
You must pass a valid aspect ratio to the validator
|
121
|
+
It should either be a named aspect ratio (#{NAMED_ASPECT_RATIOS.join(', ')})
|
122
|
+
Or an aspect ratio like 'is_16_9' (matching /#{ASPECT_RATIO_REGEX.source}/)
|
123
|
+
ERROR_MESSAGE
|
96
124
|
end
|
97
|
-
false
|
98
125
|
end
|
99
126
|
end
|
100
127
|
end
|
@@ -1,15 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/errorable.rb'
|
4
|
+
require_relative 'concerns/symbolizable.rb'
|
5
|
+
|
3
6
|
module ActiveStorageValidations
|
4
7
|
class AttachedValidator < ActiveModel::EachValidator # :nodoc:
|
5
|
-
include
|
8
|
+
include Errorable
|
9
|
+
include Symbolizable
|
10
|
+
|
11
|
+
ERROR_TYPES = %i[blank].freeze
|
12
|
+
|
13
|
+
def check_validity!
|
14
|
+
%i[allow_nil allow_blank].each do |not_authorized_option|
|
15
|
+
if options.include?(not_authorized_option)
|
16
|
+
raise ArgumentError, "You cannot pass the :#{not_authorized_option} option to the #{self.class.name.split('::').last.underscore}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
6
20
|
|
7
21
|
def validate_each(record, attribute, _value)
|
8
|
-
return if record.send(attribute).attached?
|
22
|
+
return if record.send(attribute).attached? &&
|
23
|
+
!Array.wrap(record.send(attribute)).all?(&:marked_for_destruction?)
|
9
24
|
|
10
25
|
errors_options = initialize_error_options(options)
|
11
|
-
|
12
|
-
add_error(record, attribute, :blank, **errors_options)
|
26
|
+
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
|
13
27
|
end
|
14
28
|
end
|
15
29
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'concerns/errorable.rb'
|
4
|
+
require_relative 'concerns/symbolizable.rb'
|
5
|
+
|
6
|
+
module ActiveStorageValidations
|
7
|
+
class BaseSizeValidator < ActiveModel::EachValidator # :nodoc:
|
8
|
+
include OptionProcUnfolding
|
9
|
+
include Errorable
|
10
|
+
include Symbolizable
|
11
|
+
|
12
|
+
delegate :number_to_human_size, to: ActiveSupport::NumberHelper
|
13
|
+
|
14
|
+
AVAILABLE_CHECKS = %i[
|
15
|
+
less_than
|
16
|
+
less_than_or_equal_to
|
17
|
+
greater_than
|
18
|
+
greater_than_or_equal_to
|
19
|
+
between
|
20
|
+
].freeze
|
21
|
+
|
22
|
+
def initialize(*args)
|
23
|
+
if self.class == BaseSizeValidator
|
24
|
+
raise NotImplementedError, 'BaseSizeValidator is an abstract class and cannot be instantiated directly.'
|
25
|
+
end
|
26
|
+
super
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_validity!
|
30
|
+
unless AVAILABLE_CHECKS.one? { |argument| options.key?(argument) }
|
31
|
+
raise ArgumentError, 'You must pass either :less_than(_or_equal_to), :greater_than(_or_equal_to), or :between to the validator'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def is_valid?(size, flat_options)
|
38
|
+
return false if size < 0
|
39
|
+
|
40
|
+
if flat_options[:between].present?
|
41
|
+
flat_options[:between].include?(size)
|
42
|
+
elsif flat_options[:less_than].present?
|
43
|
+
size < flat_options[:less_than]
|
44
|
+
elsif flat_options[:less_than_or_equal_to].present?
|
45
|
+
size <= flat_options[:less_than_or_equal_to]
|
46
|
+
elsif flat_options[:greater_than].present?
|
47
|
+
size > flat_options[:greater_than]
|
48
|
+
elsif flat_options[:greater_than_or_equal_to].present?
|
49
|
+
size >= flat_options[:greater_than_or_equal_to]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def populate_error_options(errors_options, flat_options)
|
54
|
+
errors_options[:min_size] = number_to_human_size(min_size(flat_options))
|
55
|
+
errors_options[:max_size] = number_to_human_size(max_size(flat_options))
|
56
|
+
end
|
57
|
+
|
58
|
+
def min_size(flat_options)
|
59
|
+
flat_options[:between]&.min || flat_options[:greater_than] || flat_options[:greater_than_or_equal_to]
|
60
|
+
end
|
61
|
+
|
62
|
+
def max_size(flat_options)
|
63
|
+
flat_options[:between]&.max || flat_options[:less_than] || flat_options[:less_than_or_equal_to]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module ActiveStorageValidations
|
2
|
+
module Errorable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def initialize_error_options(options, file = nil)
|
6
|
+
not_explicitly_written_options = %i(with in)
|
7
|
+
curated_options = options.except(*not_explicitly_written_options)
|
8
|
+
|
9
|
+
active_storage_validations_options = {
|
10
|
+
validator_type: self.class.to_sym,
|
11
|
+
custom_message: (options[:message] if options[:message].present?),
|
12
|
+
filename: (get_filename(file) unless self.class.to_sym == :total_size)
|
13
|
+
}.compact
|
14
|
+
|
15
|
+
curated_options.merge(active_storage_validations_options)
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_error(record, attribute, error_type, **errors_options)
|
19
|
+
type = errors_options[:custom_message].presence || error_type
|
20
|
+
return if record.errors.added?(attribute, type)
|
21
|
+
|
22
|
+
# You can read https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-add
|
23
|
+
# to better understand how Rails model errors work
|
24
|
+
record.errors.add(attribute, type, **errors_options)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def get_filename(file)
|
30
|
+
return nil unless file
|
31
|
+
|
32
|
+
case file
|
33
|
+
when ActiveStorage::Attached, ActiveStorage::Attachment then file.blob&.filename&.to_s
|
34
|
+
when Hash then file[:filename]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,28 +1,37 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/errorable.rb'
|
4
|
+
require_relative 'concerns/symbolizable.rb'
|
5
|
+
|
3
6
|
module ActiveStorageValidations
|
4
7
|
class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
|
5
8
|
include OptionProcUnfolding
|
6
|
-
include
|
9
|
+
include Errorable
|
10
|
+
include Symbolizable
|
7
11
|
|
8
12
|
AVAILABLE_CHECKS = %i[with in].freeze
|
9
|
-
|
13
|
+
ERROR_TYPES = %i[content_type_invalid].freeze
|
14
|
+
|
15
|
+
def check_validity!
|
16
|
+
ensure_exactly_one_validator_option
|
17
|
+
ensure_content_types_validity
|
18
|
+
end
|
19
|
+
|
10
20
|
def validate_each(record, attribute, _value)
|
11
21
|
return true unless record.send(attribute).attached?
|
12
22
|
|
13
23
|
types = authorized_types(record)
|
14
24
|
return true if types.empty?
|
15
|
-
|
16
|
-
files = Array.wrap(record.send(attribute))
|
17
25
|
|
18
|
-
|
19
|
-
errors_options[:authorized_types] = types_to_human_format(types)
|
26
|
+
files = Array.wrap(record.send(attribute))
|
20
27
|
|
21
28
|
files.each do |file|
|
22
29
|
next if is_valid?(file, types)
|
23
30
|
|
31
|
+
errors_options = initialize_error_options(options, file)
|
32
|
+
errors_options[:authorized_types] = types_to_human_format(types)
|
24
33
|
errors_options[:content_type] = content_type(file)
|
25
|
-
add_error(record, attribute,
|
34
|
+
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
|
26
35
|
break
|
27
36
|
end
|
28
37
|
end
|
@@ -40,7 +49,7 @@ module ActiveStorageValidations
|
|
40
49
|
|
41
50
|
def types_to_human_format(types)
|
42
51
|
types
|
43
|
-
.map { |type| type.to_s.split('/').last.upcase }
|
52
|
+
.map { |type| type.is_a?(Regexp) ? type.source : type.to_s.split('/').last.upcase }
|
44
53
|
.join(', ')
|
45
54
|
end
|
46
55
|
|
@@ -54,5 +63,35 @@ module ActiveStorageValidations
|
|
54
63
|
type == file_type || (type.is_a?(Regexp) && type.match?(file_type.to_s))
|
55
64
|
end
|
56
65
|
end
|
66
|
+
|
67
|
+
def ensure_exactly_one_validator_option
|
68
|
+
unless AVAILABLE_CHECKS.one? { |argument| options.key?(argument) }
|
69
|
+
raise ArgumentError, 'You must pass either :with or :in to the validator'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def ensure_content_types_validity
|
74
|
+
return true if options[:with]&.is_a?(Proc) || options[:in]&.is_a?(Proc)
|
75
|
+
|
76
|
+
([options[:with]] || options[:in]).each do |content_type|
|
77
|
+
raise ArgumentError, invalid_content_type_message(content_type) if invalid_content_type?(content_type)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def invalid_content_type_message(content_type)
|
82
|
+
<<~ERROR_MESSAGE
|
83
|
+
You must pass valid content types to the validator
|
84
|
+
'#{content_type}' is not find in Marcel::EXTENSIONS mimes
|
85
|
+
ERROR_MESSAGE
|
86
|
+
end
|
87
|
+
|
88
|
+
def invalid_content_type?(content_type)
|
89
|
+
case content_type
|
90
|
+
when String, Symbol
|
91
|
+
Marcel::MimeType.for(declared_type: content_type.to_s, extension: content_type.to_s) == 'application/octet-stream'
|
92
|
+
when Regexp
|
93
|
+
false # We always validate regexes
|
94
|
+
end
|
95
|
+
end
|
57
96
|
end
|
58
97
|
end
|
@@ -1,13 +1,29 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/errorable.rb'
|
4
|
+
require_relative 'concerns/symbolizable.rb'
|
3
5
|
require_relative 'metadata.rb'
|
4
6
|
|
5
7
|
module ActiveStorageValidations
|
6
8
|
class DimensionValidator < ActiveModel::EachValidator # :nodoc
|
7
9
|
include OptionProcUnfolding
|
8
|
-
include
|
10
|
+
include Errorable
|
11
|
+
include Symbolizable
|
9
12
|
|
10
13
|
AVAILABLE_CHECKS = %i[width height min max].freeze
|
14
|
+
ERROR_TYPES = %i[
|
15
|
+
image_metadata_missing
|
16
|
+
dimension_min_inclusion
|
17
|
+
dimension_max_inclusion
|
18
|
+
dimension_width_inclusion
|
19
|
+
dimension_height_inclusion
|
20
|
+
dimension_width_greater_than_or_equal_to
|
21
|
+
dimension_height_greater_than_or_equal_to
|
22
|
+
dimension_width_less_than_or_equal_to
|
23
|
+
dimension_height_less_than_or_equal_to
|
24
|
+
dimension_width_equal_to
|
25
|
+
dimension_height_equal_to
|
26
|
+
].freeze
|
11
27
|
|
12
28
|
def process_options(record)
|
13
29
|
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
@@ -31,7 +47,6 @@ module ActiveStorageValidations
|
|
31
47
|
flat_options
|
32
48
|
end
|
33
49
|
|
34
|
-
|
35
50
|
def check_validity!
|
36
51
|
unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
37
52
|
raise ArgumentError, 'You must pass either :width, :height, :min or :max to the validator'
|
@@ -49,7 +64,7 @@ module ActiveStorageValidations
|
|
49
64
|
files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
|
50
65
|
files.each do |file|
|
51
66
|
metadata = Metadata.new(file).metadata
|
52
|
-
next if is_valid?(record, attribute, metadata)
|
67
|
+
next if is_valid?(record, attribute, file, metadata)
|
53
68
|
break
|
54
69
|
end
|
55
70
|
end
|
@@ -63,19 +78,19 @@ module ActiveStorageValidations
|
|
63
78
|
# Analyze file first if not analyzed to get all required metadata.
|
64
79
|
file.analyze; file.reload unless file.analyzed?
|
65
80
|
metadata = file.metadata rescue {}
|
66
|
-
next if is_valid?(record, attribute, metadata)
|
81
|
+
next if is_valid?(record, attribute, file, metadata)
|
67
82
|
break
|
68
83
|
end
|
69
84
|
end
|
70
85
|
end
|
71
86
|
|
72
87
|
|
73
|
-
def is_valid?(record, attribute,
|
88
|
+
def is_valid?(record, attribute, file, metadata)
|
74
89
|
flat_options = process_options(record)
|
75
|
-
errors_options = initialize_error_options(options)
|
90
|
+
errors_options = initialize_error_options(options, file)
|
76
91
|
|
77
92
|
# Validation fails unless file metadata contains valid width and height.
|
78
|
-
if
|
93
|
+
if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
|
79
94
|
add_error(record, attribute, :image_metadata_missing, **errors_options)
|
80
95
|
return false
|
81
96
|
end
|
@@ -83,8 +98,8 @@ module ActiveStorageValidations
|
|
83
98
|
# Validation based on checks :min and :max (:min, :max has higher priority to :width, :height).
|
84
99
|
if flat_options[:min] || flat_options[:max]
|
85
100
|
if flat_options[:min] && (
|
86
|
-
(flat_options[:width][:min] &&
|
87
|
-
(flat_options[:height][:min] &&
|
101
|
+
(flat_options[:width][:min] && metadata[:width] < flat_options[:width][:min]) ||
|
102
|
+
(flat_options[:height][:min] && metadata[:height] < flat_options[:height][:min])
|
88
103
|
)
|
89
104
|
errors_options[:width] = flat_options[:width][:min]
|
90
105
|
errors_options[:height] = flat_options[:height][:min]
|
@@ -93,8 +108,8 @@ module ActiveStorageValidations
|
|
93
108
|
return false
|
94
109
|
end
|
95
110
|
if flat_options[:max] && (
|
96
|
-
(flat_options[:width][:max] &&
|
97
|
-
(flat_options[:height][:max] &&
|
111
|
+
(flat_options[:width][:max] && metadata[:width] > flat_options[:width][:max]) ||
|
112
|
+
(flat_options[:height][:max] && metadata[:height] > flat_options[:height][:max])
|
98
113
|
)
|
99
114
|
errors_options[:width] = flat_options[:width][:max]
|
100
115
|
errors_options[:height] = flat_options[:height][:max]
|
@@ -110,7 +125,7 @@ module ActiveStorageValidations
|
|
110
125
|
[:width, :height].each do |length|
|
111
126
|
next unless flat_options[length]
|
112
127
|
if flat_options[length].is_a?(Hash)
|
113
|
-
if flat_options[length][:in] && (
|
128
|
+
if flat_options[length][:in] && (metadata[length] < flat_options[length][:min] || metadata[length] > flat_options[length][:max])
|
114
129
|
error_type = :"dimension_#{length}_inclusion"
|
115
130
|
errors_options[:min] = flat_options[length][:min]
|
116
131
|
errors_options[:max] = flat_options[length][:max]
|
@@ -118,13 +133,13 @@ module ActiveStorageValidations
|
|
118
133
|
add_error(record, attribute, error_type, **errors_options)
|
119
134
|
width_or_height_invalid = true
|
120
135
|
else
|
121
|
-
if flat_options[length][:min] &&
|
136
|
+
if flat_options[length][:min] && metadata[length] < flat_options[length][:min]
|
122
137
|
error_type = :"dimension_#{length}_greater_than_or_equal_to"
|
123
138
|
errors_options[:length] = flat_options[length][:min]
|
124
139
|
|
125
140
|
add_error(record, attribute, error_type, **errors_options)
|
126
141
|
width_or_height_invalid = true
|
127
|
-
elsif flat_options[length][:max] &&
|
142
|
+
elsif flat_options[length][:max] && metadata[length] > flat_options[length][:max]
|
128
143
|
error_type = :"dimension_#{length}_less_than_or_equal_to"
|
129
144
|
errors_options[:length] = flat_options[length][:max]
|
130
145
|
|
@@ -133,7 +148,7 @@ module ActiveStorageValidations
|
|
133
148
|
end
|
134
149
|
end
|
135
150
|
else
|
136
|
-
if
|
151
|
+
if metadata[length] != flat_options[length]
|
137
152
|
error_type = :"dimension_#{length}_equal_to"
|
138
153
|
errors_options[:length] = flat_options[length]
|
139
154
|
|
@@ -1,29 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/errorable.rb'
|
4
|
+
require_relative 'concerns/symbolizable.rb'
|
5
|
+
|
3
6
|
module ActiveStorageValidations
|
4
7
|
class LimitValidator < ActiveModel::EachValidator # :nodoc:
|
5
8
|
include OptionProcUnfolding
|
6
|
-
include
|
9
|
+
include Errorable
|
10
|
+
include Symbolizable
|
7
11
|
|
8
12
|
AVAILABLE_CHECKS = %i[max min].freeze
|
13
|
+
ERROR_TYPES = %i[
|
14
|
+
limit_out_of_range
|
15
|
+
].freeze
|
9
16
|
|
10
17
|
def check_validity!
|
11
|
-
|
12
|
-
|
13
|
-
end
|
18
|
+
ensure_at_least_one_validator_option
|
19
|
+
ensure_arguments_validity
|
14
20
|
end
|
15
21
|
|
16
22
|
def validate_each(record, attribute, _)
|
17
|
-
files = Array.wrap(record.send(attribute)).compact.uniq
|
23
|
+
files = Array.wrap(record.send(attribute)).reject { |file| file.blank? }.compact.uniq
|
18
24
|
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
25
|
+
|
26
|
+
return true if files_count_valid?(files.count, flat_options)
|
27
|
+
|
19
28
|
errors_options = initialize_error_options(options)
|
20
29
|
errors_options[:min] = flat_options[:min]
|
21
30
|
errors_options[:max] = flat_options[:max]
|
22
|
-
|
23
|
-
return true if files_count_valid?(files.count, flat_options)
|
24
|
-
add_error(record, attribute, :limit_out_of_range, **errors_options)
|
31
|
+
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
|
25
32
|
end
|
26
33
|
|
34
|
+
private
|
35
|
+
|
27
36
|
def files_count_valid?(count, flat_options)
|
28
37
|
if flat_options[:max].present? && flat_options[:min].present?
|
29
38
|
count >= flat_options[:min] && count <= flat_options[:max]
|
@@ -33,5 +42,36 @@ module ActiveStorageValidations
|
|
33
42
|
count >= flat_options[:min]
|
34
43
|
end
|
35
44
|
end
|
45
|
+
|
46
|
+
def ensure_at_least_one_validator_option
|
47
|
+
unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
48
|
+
raise ArgumentError, 'You must pass either :max or :min to the validator'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def ensure_arguments_validity
|
53
|
+
return true if min_max_are_proc? || min_or_max_is_proc_and_other_not_present?
|
54
|
+
|
55
|
+
raise ArgumentError, 'You must pass integers to :min and :max' if min_or_max_defined_and_not_integer?
|
56
|
+
raise ArgumentError, 'You must pass a higher value to :max than to :min' if min_higher_than_max?
|
57
|
+
end
|
58
|
+
|
59
|
+
def min_max_are_proc?
|
60
|
+
options[:min]&.is_a?(Proc) && options[:max]&.is_a?(Proc)
|
61
|
+
end
|
62
|
+
|
63
|
+
def min_or_max_is_proc_and_other_not_present?
|
64
|
+
(options[:min]&.is_a?(Proc) && options[:max].nil?) ||
|
65
|
+
(options[:min].nil? && options[:max]&.is_a?(Proc))
|
66
|
+
end
|
67
|
+
|
68
|
+
def min_or_max_defined_and_not_integer?
|
69
|
+
(options.key?(:min) && !options[:min].is_a?(Integer)) ||
|
70
|
+
(options.key?(:max) && !options[:max].is_a?(Integer))
|
71
|
+
end
|
72
|
+
|
73
|
+
def min_higher_than_max?
|
74
|
+
options[:min] > options[:max] if options[:min].is_a?(Integer) && options[:max].is_a?(Integer)
|
75
|
+
end
|
36
76
|
end
|
37
77
|
end
|