active_storage_validations 1.0.4 → 3.0.2
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 +785 -245
- data/config/locales/da.yml +63 -0
- data/config/locales/de.yml +60 -19
- data/config/locales/en-GB.yml +63 -0
- data/config/locales/en.yml +60 -20
- data/config/locales/es.yml +60 -19
- data/config/locales/fr.yml +60 -19
- data/config/locales/it.yml +60 -19
- data/config/locales/ja.yml +60 -19
- data/config/locales/nl.yml +60 -19
- data/config/locales/pl.yml +60 -19
- data/config/locales/pt-BR.yml +60 -19
- data/config/locales/ru.yml +60 -19
- data/config/locales/sv.yml +63 -0
- data/config/locales/tr.yml +60 -19
- data/config/locales/uk.yml +60 -19
- data/config/locales/vi.yml +60 -19
- data/config/locales/zh-CN.yml +60 -19
- data/lib/active_storage_validations/analyzer/audio_analyzer.rb +58 -0
- data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +60 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +46 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +56 -0
- data/lib/active_storage_validations/analyzer/image_analyzer.rb +49 -0
- data/lib/active_storage_validations/analyzer/null_analyzer.rb +18 -0
- data/lib/active_storage_validations/analyzer/pdf_analyzer.rb +89 -0
- data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +61 -0
- data/lib/active_storage_validations/analyzer/video_analyzer.rb +130 -0
- data/lib/active_storage_validations/analyzer.rb +88 -0
- data/lib/active_storage_validations/aspect_ratio_validator.rb +157 -97
- data/lib/active_storage_validations/attached_validator.rb +22 -5
- data/lib/active_storage_validations/base_comparison_validator.rb +83 -0
- data/lib/active_storage_validations/content_type_validator.rb +219 -31
- data/lib/active_storage_validations/dimension_validator.rb +187 -97
- data/lib/active_storage_validations/duration_validator.rb +70 -0
- data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +56 -0
- data/lib/active_storage_validations/extensors/asv_marcelable.rb +12 -0
- data/lib/active_storage_validations/limit_validator.rb +76 -9
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +48 -25
- data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +150 -0
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +98 -39
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +93 -55
- data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +39 -0
- data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +127 -0
- data/lib/active_storage_validations/matchers/pages_validator_matcher.rb +39 -0
- data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +78 -0
- data/lib/active_storage_validations/matchers/shared/asv_active_storageable.rb +19 -0
- data/lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb +28 -0
- data/lib/active_storage_validations/matchers/shared/asv_attachable.rb +72 -0
- data/lib/active_storage_validations/matchers/shared/asv_contextable.rb +57 -0
- data/lib/active_storage_validations/matchers/shared/asv_messageable.rb +28 -0
- data/lib/active_storage_validations/matchers/shared/asv_rspecable.rb +27 -0
- data/lib/active_storage_validations/matchers/shared/asv_validatable.rb +56 -0
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +17 -71
- data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +47 -0
- data/lib/active_storage_validations/matchers.rb +17 -21
- data/lib/active_storage_validations/pages_validator.rb +61 -0
- data/lib/active_storage_validations/processable_file_validator.rb +37 -0
- data/lib/active_storage_validations/railtie.rb +14 -0
- data/lib/active_storage_validations/shared/asv_active_storageable.rb +30 -0
- data/lib/active_storage_validations/shared/asv_analyzable.rb +89 -0
- data/lib/active_storage_validations/shared/asv_attachable.rb +236 -0
- data/lib/active_storage_validations/shared/asv_errorable.rb +64 -0
- data/lib/active_storage_validations/shared/asv_loggable.rb +11 -0
- data/lib/active_storage_validations/shared/asv_optionable.rb +29 -0
- data/lib/active_storage_validations/shared/asv_symbolizable.rb +14 -0
- data/lib/active_storage_validations/size_validator.rb +24 -41
- data/lib/active_storage_validations/total_size_validator.rb +52 -0
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +27 -13
- metadata +113 -31
- data/lib/active_storage_validations/metadata.rb +0 -151
- data/lib/active_storage_validations/option_proc_unfolding.rb +0 -16
- data/lib/active_storage_validations/processable_image_validator.rb +0 -43
|
@@ -1,136 +1,226 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative "shared/asv_active_storageable"
|
|
4
|
+
require_relative "shared/asv_analyzable"
|
|
5
|
+
require_relative "shared/asv_attachable"
|
|
6
|
+
require_relative "shared/asv_errorable"
|
|
7
|
+
require_relative "shared/asv_optionable"
|
|
8
|
+
require_relative "shared/asv_symbolizable"
|
|
4
9
|
|
|
5
10
|
module ActiveStorageValidations
|
|
6
11
|
class DimensionValidator < ActiveModel::EachValidator # :nodoc
|
|
7
|
-
include
|
|
12
|
+
include ASVActiveStorageable
|
|
13
|
+
include ASVAnalyzable
|
|
14
|
+
include ASVAttachable
|
|
15
|
+
include ASVErrorable
|
|
16
|
+
include ASVOptionable
|
|
17
|
+
include ASVSymbolizable
|
|
8
18
|
|
|
9
19
|
AVAILABLE_CHECKS = %i[width height min max].freeze
|
|
20
|
+
ERROR_TYPES = %i[
|
|
21
|
+
dimension_min_not_included_in
|
|
22
|
+
dimension_max_not_included_in
|
|
23
|
+
dimension_width_not_included_in
|
|
24
|
+
dimension_height_not_included_in
|
|
25
|
+
dimension_width_not_greater_than_or_equal_to
|
|
26
|
+
dimension_height_not_greater_than_or_equal_to
|
|
27
|
+
dimension_width_not_less_than_or_equal_to
|
|
28
|
+
dimension_height_not_less_than_or_equal_to
|
|
29
|
+
dimension_width_not_equal_to
|
|
30
|
+
dimension_height_not_equal_to
|
|
31
|
+
media_metadata_missing
|
|
32
|
+
].freeze
|
|
33
|
+
METADATA_KEYS = %i[width height].freeze
|
|
10
34
|
|
|
11
|
-
def
|
|
12
|
-
|
|
35
|
+
def check_validity!
|
|
36
|
+
ensure_at_least_one_validator_option
|
|
37
|
+
ensure_dimension_in_option_validity
|
|
38
|
+
ensure_min_max_option_validity
|
|
39
|
+
end
|
|
13
40
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
41
|
+
def validate_each(record, attribute, _value)
|
|
42
|
+
return if no_attachments?(record, attribute)
|
|
43
|
+
|
|
44
|
+
validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def ensure_at_least_one_validator_option
|
|
50
|
+
unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
|
51
|
+
raise ArgumentError, "You must pass either :width, :height, :min or :max to the validator"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ensure_dimension_in_option_validity
|
|
56
|
+
%i[width height].each do |dimension|
|
|
57
|
+
if options[dimension]&.is_a?(Hash) && options[dimension][:in].present?
|
|
58
|
+
raise ArgumentError, "{ #{dimension}: { in: value } } value must be a Range (min..max)" if !options[dimension][:in].is_a?(Range) && !options[dimension][:in].is_a?(Proc)
|
|
20
59
|
end
|
|
21
60
|
end
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def ensure_min_max_option_validity
|
|
64
|
+
%i[min max].each do |bound|
|
|
65
|
+
if options[bound].present?
|
|
66
|
+
raise ArgumentError, "{ #{bound}: value } value must be a Range (#{bound}_width..#{bound}_height)" if !options[bound]&.is_a?(Range) && !options[bound]&.is_a?(Proc)
|
|
27
67
|
end
|
|
28
68
|
end
|
|
69
|
+
end
|
|
29
70
|
|
|
30
|
-
|
|
71
|
+
def is_valid?(record, attribute, file, metadata)
|
|
72
|
+
flat_options = process_options(record)
|
|
73
|
+
errors_options = initialize_error_options(options, file)
|
|
74
|
+
|
|
75
|
+
return add_media_metadata_missing_error(record, attribute, file, errors_options) unless valid_metadata?(metadata)
|
|
76
|
+
|
|
77
|
+
if min_max_validation?(flat_options)
|
|
78
|
+
validate_min_max(record, attribute, metadata, flat_options, errors_options)
|
|
79
|
+
else
|
|
80
|
+
validate_width_height(record, attribute, metadata, flat_options, errors_options)
|
|
81
|
+
end
|
|
31
82
|
end
|
|
32
83
|
|
|
84
|
+
def valid_metadata?(metadata)
|
|
85
|
+
metadata[:width].to_i > 0 && metadata[:height].to_i > 0
|
|
86
|
+
end
|
|
33
87
|
|
|
34
|
-
def
|
|
35
|
-
|
|
36
|
-
raise ArgumentError, 'You must pass either :width, :height, :min or :max to the validator'
|
|
88
|
+
def min_max_validation?(flat_options)
|
|
89
|
+
flat_options[:min] || flat_options[:max]
|
|
37
90
|
end
|
|
38
91
|
|
|
92
|
+
def validate_min_max(record, attribute, metadata, flat_options, errors_options)
|
|
93
|
+
return false unless validate_min(record, attribute, metadata, flat_options, errors_options)
|
|
94
|
+
return false unless validate_max(record, attribute, metadata, flat_options, errors_options)
|
|
39
95
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return true unless record.send(attribute).attached?
|
|
96
|
+
true
|
|
97
|
+
end
|
|
43
98
|
|
|
44
|
-
|
|
45
|
-
|
|
99
|
+
def validate_width_height(record, attribute, metadata, flat_options, errors_options)
|
|
100
|
+
%i[width height].each do |dimension|
|
|
101
|
+
next unless flat_options[dimension]
|
|
46
102
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
break
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
else
|
|
55
|
-
# Rails 5
|
|
56
|
-
def validate_each(record, attribute, _value)
|
|
57
|
-
return true unless record.send(attribute).attached?
|
|
58
|
-
|
|
59
|
-
files = Array.wrap(record.send(attribute))
|
|
60
|
-
files.each do |file|
|
|
61
|
-
# Analyze file first if not analyzed to get all required metadata.
|
|
62
|
-
file.analyze; file.reload unless file.analyzed?
|
|
63
|
-
metadata = file.metadata rescue {}
|
|
64
|
-
next if is_valid?(record, attribute, metadata)
|
|
65
|
-
break
|
|
103
|
+
if flat_options[dimension].is_a?(Hash)
|
|
104
|
+
validate_range(record, attribute, dimension, metadata, flat_options, errors_options)
|
|
105
|
+
else
|
|
106
|
+
validate_exact(record, attribute, dimension, metadata, flat_options, errors_options)
|
|
66
107
|
end
|
|
67
108
|
end
|
|
68
109
|
end
|
|
69
110
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
# Validation based on checks :min and :max (:min, :max has higher priority to :width, :height).
|
|
80
|
-
if flat_options[:min] || flat_options[:max]
|
|
81
|
-
if flat_options[:min] && (
|
|
82
|
-
(flat_options[:width][:min] && file_metadata[:width] < flat_options[:width][:min]) ||
|
|
83
|
-
(flat_options[:height][:min] && file_metadata[:height] < flat_options[:height][:min])
|
|
84
|
-
)
|
|
85
|
-
add_error(record, attribute, :dimension_min_inclusion, width: flat_options[:width][:min], height: flat_options[:height][:min])
|
|
86
|
-
return false
|
|
111
|
+
# rubocop:disable Metrics/BlockLength
|
|
112
|
+
%i[min max].each do |bound|
|
|
113
|
+
define_method("validate_#{bound}") do |record, attribute, metadata, flat_options, errors_options|
|
|
114
|
+
if send(:"invalid_#{bound}?", flat_options, metadata)
|
|
115
|
+
send(:"add_#{bound}_error", record, attribute, flat_options, errors_options)
|
|
116
|
+
false
|
|
117
|
+
else
|
|
118
|
+
true
|
|
87
119
|
end
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
define_method("validate_dimension_#{bound}") do |record, attribute, dimension, metadata, flat_options, errors_options|
|
|
123
|
+
if send(:"invalid_dimension_#{bound}?", flat_options, dimension, metadata)
|
|
124
|
+
send(:"add_dimension_#{bound}_error", record, attribute, dimension, flat_options, errors_options)
|
|
125
|
+
false
|
|
126
|
+
else
|
|
127
|
+
true
|
|
94
128
|
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
define_method("invalid_#{bound}?") do |flat_options, metadata|
|
|
132
|
+
flat_options[bound] && (
|
|
133
|
+
send(:"invalid_dimension_#{bound}?", flat_options, :width, metadata) ||
|
|
134
|
+
send(:"invalid_dimension_#{bound}?", flat_options, :height, metadata)
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
define_method("invalid_dimension_#{bound}?") do |flat_options, dimension, metadata|
|
|
139
|
+
flat_options[dimension][bound] && metadata[dimension].public_send(bound == :min ? :< : :>, flat_options[dimension][bound])
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
define_method("add_#{bound}_error") do |record, attribute, flat_options, errors_options|
|
|
143
|
+
errors_options[:width] = flat_options[:width][bound]
|
|
144
|
+
errors_options[:height] = flat_options[:height][bound]
|
|
145
|
+
add_error(record, attribute, :"dimension_#{bound}_not_included_in", **errors_options)
|
|
146
|
+
end
|
|
95
147
|
|
|
96
|
-
#
|
|
148
|
+
define_method("add_dimension_#{bound}_error") do |record, attribute, dimension, flat_options, errors_options|
|
|
149
|
+
error_type = bound == :min ? :not_greater_than_or_equal_to : :not_less_than_or_equal_to
|
|
150
|
+
errors_options[:length] = flat_options[dimension][bound]
|
|
151
|
+
add_error(record, attribute, :"dimension_#{dimension}_#{error_type}", **errors_options)
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
# rubocop:enable Metrics/BlockLength
|
|
155
|
+
|
|
156
|
+
def validate_range(record, attribute, dimension, metadata, flat_options, errors_options)
|
|
157
|
+
if in_option_used?(flat_options, dimension)
|
|
158
|
+
return false unless validate_in(record, attribute, dimension, metadata, flat_options, errors_options)
|
|
97
159
|
else
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
next unless flat_options[length]
|
|
101
|
-
if flat_options[length].is_a?(Hash)
|
|
102
|
-
if flat_options[length][:in] && (file_metadata[length] < flat_options[length][:min] || file_metadata[length] > flat_options[length][:max])
|
|
103
|
-
add_error(record, attribute, :"dimension_#{length}_inclusion", min: flat_options[length][:min], max: flat_options[length][:max])
|
|
104
|
-
width_or_height_invalid = true
|
|
105
|
-
else
|
|
106
|
-
if flat_options[length][:min] && file_metadata[length] < flat_options[length][:min]
|
|
107
|
-
add_error(record, attribute, :"dimension_#{length}_greater_than_or_equal_to", length: flat_options[length][:min])
|
|
108
|
-
width_or_height_invalid = true
|
|
109
|
-
end
|
|
110
|
-
if flat_options[length][:max] && file_metadata[length] > flat_options[length][:max]
|
|
111
|
-
add_error(record, attribute, :"dimension_#{length}_less_than_or_equal_to", length: flat_options[length][:max])
|
|
112
|
-
width_or_height_invalid = true
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
else
|
|
116
|
-
if file_metadata[length] != flat_options[length]
|
|
117
|
-
add_error(record, attribute, :"dimension_#{length}_equal_to", length: flat_options[length])
|
|
118
|
-
width_or_height_invalid = true
|
|
119
|
-
end
|
|
120
|
-
end
|
|
121
|
-
end
|
|
160
|
+
return false unless validate_dimension_min_max(record, attribute, dimension, metadata, flat_options, errors_options)
|
|
161
|
+
end
|
|
122
162
|
|
|
123
|
-
|
|
163
|
+
true
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def validate_in(record, attribute, dimension, metadata, flat_options, errors_options)
|
|
167
|
+
if outside_range?(metadata[dimension], flat_options[dimension])
|
|
168
|
+
add_range_error(record, attribute, dimension, flat_options, errors_options)
|
|
169
|
+
false
|
|
170
|
+
else
|
|
171
|
+
true
|
|
124
172
|
end
|
|
173
|
+
end
|
|
125
174
|
|
|
126
|
-
|
|
175
|
+
def in_option_used?(flat_options, dimension)
|
|
176
|
+
flat_options[dimension][:in]
|
|
127
177
|
end
|
|
128
178
|
|
|
129
|
-
def
|
|
130
|
-
|
|
131
|
-
return if record.errors.added?(attribute, message)
|
|
132
|
-
record.errors.add(attribute, message, **attrs)
|
|
179
|
+
def outside_range?(value, options)
|
|
180
|
+
value < options[:min] || value > options[:max]
|
|
133
181
|
end
|
|
134
182
|
|
|
183
|
+
def add_range_error(record, attribute, dimension, flat_options, errors_options)
|
|
184
|
+
errors_options[:min] = flat_options[dimension][:min]
|
|
185
|
+
errors_options[:max] = flat_options[dimension][:max]
|
|
186
|
+
add_error(record, attribute, :"dimension_#{dimension}_not_included_in", **errors_options)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def validate_dimension_min_max(record, attribute, dimension, metadata, flat_options, errors_options)
|
|
190
|
+
%i[min max].each do |bound|
|
|
191
|
+
send(:"validate_dimension_#{bound}", record, attribute, dimension, metadata, flat_options, errors_options)
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def validate_exact(record, attribute, dimension, metadata, flat_options, errors_options)
|
|
196
|
+
if metadata[dimension] != flat_options[dimension]
|
|
197
|
+
errors_options[:length] = flat_options[dimension]
|
|
198
|
+
add_error(record, attribute, :"dimension_#{dimension}_not_equal_to", **errors_options)
|
|
199
|
+
false
|
|
200
|
+
else
|
|
201
|
+
true
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def process_options(record)
|
|
206
|
+
flat_options = set_flat_options(record)
|
|
207
|
+
|
|
208
|
+
%i[width height].each do |dimension|
|
|
209
|
+
if flat_options[dimension] and flat_options[dimension].is_a?(Hash)
|
|
210
|
+
if (range = flat_options[dimension][:in])
|
|
211
|
+
flat_options[dimension][:min], flat_options[dimension][:max] = range.min, range.max
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
%i[min max].each do |bound|
|
|
217
|
+
if (range = flat_options[bound])
|
|
218
|
+
flat_options[:width] = { bound => range.first }
|
|
219
|
+
flat_options[:height] = { bound => range.last }
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
flat_options
|
|
224
|
+
end
|
|
135
225
|
end
|
|
136
226
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_comparison_validator"
|
|
4
|
+
|
|
5
|
+
module ActiveStorageValidations
|
|
6
|
+
class DurationValidator < BaseComparisonValidator
|
|
7
|
+
include ASVAnalyzable
|
|
8
|
+
include ASVAttachable
|
|
9
|
+
|
|
10
|
+
ERROR_TYPES = %i[
|
|
11
|
+
duration_not_less_than
|
|
12
|
+
duration_not_less_than_or_equal_to
|
|
13
|
+
duration_not_greater_than
|
|
14
|
+
duration_not_greater_than_or_equal_to
|
|
15
|
+
duration_not_between
|
|
16
|
+
duration_not_equal_to
|
|
17
|
+
].freeze
|
|
18
|
+
METADATA_KEYS = %i[duration].freeze
|
|
19
|
+
|
|
20
|
+
def validate_each(record, attribute, _value)
|
|
21
|
+
return if no_attachments?(record, attribute)
|
|
22
|
+
|
|
23
|
+
flat_options = set_flat_options(record)
|
|
24
|
+
|
|
25
|
+
attachables_and_blobs(record, attribute).each do |attachable, blob|
|
|
26
|
+
duration = begin
|
|
27
|
+
metadata_for(blob, attachable, METADATA_KEYS)&.fetch(:duration, nil)
|
|
28
|
+
rescue ActiveStorage::FileNotFoundError
|
|
29
|
+
add_attachment_missing_error(record, attribute, attachable)
|
|
30
|
+
next
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
if duration.to_i <= 0
|
|
34
|
+
add_media_metadata_missing_error(record, attribute, attachable)
|
|
35
|
+
next
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
is_valid?(duration, flat_options) || populate_error_options_and_add_error(record, attribute, attachable, flat_options, duration)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def populate_error_options_and_add_error(record, attribute, attachable, flat_options, duration)
|
|
45
|
+
errors_options = initialize_error_options(options, attachable)
|
|
46
|
+
populate_error_options(errors_options, flat_options, duration)
|
|
47
|
+
|
|
48
|
+
error_type = set_error_type(flat_options)
|
|
49
|
+
|
|
50
|
+
add_error(record, attribute, error_type, **errors_options)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def format_bound_value(value)
|
|
54
|
+
return nil unless value
|
|
55
|
+
|
|
56
|
+
custom_value = value == value.to_i ? value.to_i : value
|
|
57
|
+
ActiveSupport::Duration.build(custom_value).inspect
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def populate_error_options(errors_options, flat_options, duration)
|
|
61
|
+
super(errors_options, flat_options)
|
|
62
|
+
errors_options[:duration] = format_bound_value(duration)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def set_error_type(flat_options)
|
|
66
|
+
keys = AVAILABLE_CHECKS & flat_options.keys
|
|
67
|
+
"duration_not_#{keys.first}".to_sym
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveStorageValidations
|
|
4
|
+
module ASVBlobMetadatable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
# rubocop:disable Metrics/BlockLength
|
|
8
|
+
included do
|
|
9
|
+
# This method returns the metadata that has been set by our gem.
|
|
10
|
+
# The metadata is stored in the blob's custom metadata. All keys are prefixed with 'asv_'
|
|
11
|
+
# to avoid conflicts with other metadata.
|
|
12
|
+
# It is not to set a active_storage_validation key equal to a a hash of our gem's metadata,
|
|
13
|
+
# because this would result in errors down the road with services such as S3.
|
|
14
|
+
#
|
|
15
|
+
# Because of how the metadata is stored, we need to convert the values from String
|
|
16
|
+
# to Integer or Boolean.
|
|
17
|
+
def active_storage_validations_metadata
|
|
18
|
+
metadata.dig("custom")
|
|
19
|
+
&.select { |key, _| key.to_s.start_with?("asv_") }
|
|
20
|
+
&.transform_keys { |key| key.to_s.delete_prefix("asv_") }
|
|
21
|
+
&.transform_values do |value|
|
|
22
|
+
case value
|
|
23
|
+
when /\A\d+\z/ then value.to_i
|
|
24
|
+
when /\A\d+\.\d+\z/ then value.to_f
|
|
25
|
+
when "true" then true
|
|
26
|
+
when "false" then false
|
|
27
|
+
else value
|
|
28
|
+
end
|
|
29
|
+
end || {}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# This method sets the metadata that has been detected by our gem.
|
|
33
|
+
# The metadata is stored in the blob's custom metadata. All keys are prefixed with 'asv_'.
|
|
34
|
+
# We need to store values as String, because services such as S3 will not accept other types.
|
|
35
|
+
def merge_into_active_storage_validations_metadata(hash)
|
|
36
|
+
aws_compatible_metadata = normalize_active_storage_validations_metadata_for_aws(hash)
|
|
37
|
+
|
|
38
|
+
metadata["custom"] ||= {}
|
|
39
|
+
metadata["custom"].merge!(aws_compatible_metadata)
|
|
40
|
+
|
|
41
|
+
active_storage_validations_metadata
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def normalize_active_storage_validations_metadata_for_aws(hash)
|
|
45
|
+
hash.transform_keys { |key, _| key.to_s.start_with?("asv_") ? key : "asv_#{key}" }
|
|
46
|
+
.transform_values(&:to_s)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def remove_active_storage_validations_metadata!
|
|
50
|
+
metadata["custom"] ||= {}
|
|
51
|
+
metadata["custom"].delete_if { |key, _| key.to_s.start_with?("asv_") }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
# rubocop:enable Metrics/BlockLength
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "marcel"
|
|
4
|
+
|
|
5
|
+
Marcel::MimeType.extend "application/x-rar-compressed", parents: %(application/x-rar)
|
|
6
|
+
Marcel::MimeType.extend "audio/x-hx-aac-adts", parents: %(audio/x-aac)
|
|
7
|
+
Marcel::MimeType.extend "audio/x-m4a", parents: %(audio/mp4)
|
|
8
|
+
Marcel::MimeType.extend "text/xml", parents: %(application/xml) # alias
|
|
9
|
+
Marcel::MimeType.extend "video/theora", parents: %(video/ogg)
|
|
10
|
+
|
|
11
|
+
# Add empty content type
|
|
12
|
+
Marcel::MimeType.extend "inode/x-empty", extensions: %w[empty]
|
|
@@ -1,25 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "shared/asv_active_storageable"
|
|
4
|
+
require_relative "shared/asv_errorable"
|
|
5
|
+
require_relative "shared/asv_optionable"
|
|
6
|
+
require_relative "shared/asv_symbolizable"
|
|
7
|
+
|
|
3
8
|
module ActiveStorageValidations
|
|
4
9
|
class LimitValidator < ActiveModel::EachValidator # :nodoc:
|
|
5
|
-
include
|
|
10
|
+
include ASVActiveStorageable
|
|
11
|
+
include ASVErrorable
|
|
12
|
+
include ASVOptionable
|
|
13
|
+
include ASVSymbolizable
|
|
6
14
|
|
|
7
15
|
AVAILABLE_CHECKS = %i[max min].freeze
|
|
16
|
+
ERROR_TYPES = %i[
|
|
17
|
+
limit_out_of_range
|
|
18
|
+
limit_min_not_reached
|
|
19
|
+
limit_max_exceeded
|
|
20
|
+
].freeze
|
|
8
21
|
|
|
9
22
|
def check_validity!
|
|
10
|
-
|
|
11
|
-
|
|
23
|
+
ensure_at_least_one_validator_option
|
|
24
|
+
ensure_arguments_validity
|
|
12
25
|
end
|
|
13
26
|
|
|
14
|
-
def validate_each(record, attribute,
|
|
15
|
-
files =
|
|
16
|
-
flat_options =
|
|
17
|
-
|
|
27
|
+
def validate_each(record, attribute, _value)
|
|
28
|
+
files = attached_files(record, attribute).reject(&:blank?)
|
|
29
|
+
flat_options = set_flat_options(record)
|
|
30
|
+
count = files.count
|
|
31
|
+
|
|
32
|
+
return if files_count_valid?(count, flat_options)
|
|
18
33
|
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
errors_options = initialize_and_populate_error_options(options, flat_options, count)
|
|
35
|
+
error_type = set_error_type(flat_options, count)
|
|
36
|
+
add_error(record, attribute, error_type, **errors_options)
|
|
21
37
|
end
|
|
22
38
|
|
|
39
|
+
private
|
|
40
|
+
|
|
23
41
|
def files_count_valid?(count, flat_options)
|
|
24
42
|
if flat_options[:max].present? && flat_options[:min].present?
|
|
25
43
|
count >= flat_options[:min] && count <= flat_options[:max]
|
|
@@ -29,5 +47,54 @@ module ActiveStorageValidations
|
|
|
29
47
|
count >= flat_options[:min]
|
|
30
48
|
end
|
|
31
49
|
end
|
|
50
|
+
|
|
51
|
+
def ensure_at_least_one_validator_option
|
|
52
|
+
unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
|
53
|
+
raise ArgumentError, "You must pass either :max or :min to the validator"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ensure_arguments_validity
|
|
58
|
+
return true if min_max_are_proc? || min_or_max_is_proc_and_other_not_present?
|
|
59
|
+
|
|
60
|
+
raise ArgumentError, "You must pass integers to :min and :max" if min_or_max_defined_and_not_integer?
|
|
61
|
+
raise ArgumentError, "You must pass a higher value to :max than to :min" if min_higher_than_max?
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def min_max_are_proc?
|
|
65
|
+
options[:min]&.is_a?(Proc) && options[:max]&.is_a?(Proc)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def min_or_max_is_proc_and_other_not_present?
|
|
69
|
+
(options[:min]&.is_a?(Proc) && options[:max].nil?) ||
|
|
70
|
+
(options[:min].nil? && options[:max]&.is_a?(Proc))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def min_or_max_defined_and_not_integer?
|
|
74
|
+
(options.key?(:min) && !options[:min].is_a?(Integer)) ||
|
|
75
|
+
(options.key?(:max) && !options[:max].is_a?(Integer))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def min_higher_than_max?
|
|
79
|
+
options[:min] > options[:max] if options[:min].is_a?(Integer) && options[:max].is_a?(Integer)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def initialize_and_populate_error_options(options, flat_options, count)
|
|
83
|
+
errors_options = initialize_error_options(options)
|
|
84
|
+
errors_options[:min] = flat_options[:min]
|
|
85
|
+
errors_options[:max] = flat_options[:max]
|
|
86
|
+
errors_options[:count] = count
|
|
87
|
+
errors_options
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def set_error_type(flat_options, count)
|
|
91
|
+
if flat_options[:min] && flat_options[:max]
|
|
92
|
+
:limit_out_of_range
|
|
93
|
+
elsif flat_options[:min] && count < flat_options[:min]
|
|
94
|
+
:limit_min_not_reached
|
|
95
|
+
else
|
|
96
|
+
:limit_max_exceeded
|
|
97
|
+
end
|
|
98
|
+
end
|
|
32
99
|
end
|
|
33
100
|
end
|