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
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveStorageValidations
|
|
4
|
+
# ActiveStorageValidations::ASVAttachable
|
|
5
|
+
#
|
|
6
|
+
# Validator methods for analyzing attachable.
|
|
7
|
+
#
|
|
8
|
+
# An attachable is a file representation such as ActiveStorage::Blob,
|
|
9
|
+
# ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile, Hash, String,
|
|
10
|
+
# File or Pathname
|
|
11
|
+
module ASVAttachable
|
|
12
|
+
extend ActiveSupport::Concern
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
# Loop through the newly submitted attachables to validate them. Using
|
|
17
|
+
# attachables is the only way to get the attached file io that is necessary
|
|
18
|
+
# to perform file analyses.
|
|
19
|
+
def validate_changed_files_from_metadata(record, attribute, metadata_keys)
|
|
20
|
+
attachables_and_blobs(record, attribute).each do |attachable, blob|
|
|
21
|
+
metadata = begin
|
|
22
|
+
metadata_for(blob, attachable, metadata_keys)
|
|
23
|
+
rescue ActiveStorage::FileNotFoundError
|
|
24
|
+
add_attachment_missing_error(record, attribute, attachable)
|
|
25
|
+
next
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
is_valid?(record, attribute, attachable, metadata)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Retrieve an array-like of attachables and blobs. Unlike its name suggests,
|
|
33
|
+
# getting attachables from attachment_changes is not getting the changed
|
|
34
|
+
# attachables but all attachables from the `has_many_attached` relation.
|
|
35
|
+
# For the `has_one_attached` relation, it only yields the new attachable,
|
|
36
|
+
# but if we are validating previously attached file, we need to use the blob
|
|
37
|
+
# See #attach at: https://github.com/rails/rails/blob/main/activestorage/lib/active_storage/attached/many.rb
|
|
38
|
+
#
|
|
39
|
+
# Some file could be passed several times, we just need to perform the
|
|
40
|
+
# analysis once on the file, therefore the use of #uniq.
|
|
41
|
+
def attachables_and_blobs(record, attribute)
|
|
42
|
+
changes = changes_for(record, attribute)
|
|
43
|
+
|
|
44
|
+
return to_enum(:attachables_and_blobs, record, attribute) if changes.blank? || !block_given?
|
|
45
|
+
|
|
46
|
+
if changes.is_a?(ActiveStorage::Attached::Changes::CreateMany)
|
|
47
|
+
changes.attachables.uniq.zip(changes.blobs.uniq).each do |attachable, blob|
|
|
48
|
+
yield attachable, blob
|
|
49
|
+
end
|
|
50
|
+
else
|
|
51
|
+
yield changes.is_a?(ActiveStorage::Attached::Changes::CreateOne) ? changes.attachable : changes.blob, changes.blob
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def changes_for(record, attribute)
|
|
56
|
+
if record.public_send(attribute).is_a?(ActiveStorage::Attached::One)
|
|
57
|
+
record.attachment_changes[attribute.to_s].presence || record.public_send(attribute)
|
|
58
|
+
else
|
|
59
|
+
record.attachment_changes[attribute.to_s]
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Retrieve the full declared content_type from attachable.
|
|
64
|
+
# rubocop:disable Metrics/MethodLength
|
|
65
|
+
def full_attachable_content_type(attachable)
|
|
66
|
+
case attachable
|
|
67
|
+
when ActiveStorage::Blob
|
|
68
|
+
attachable.content_type
|
|
69
|
+
when ActionDispatch::Http::UploadedFile
|
|
70
|
+
attachable.content_type
|
|
71
|
+
when Rack::Test::UploadedFile
|
|
72
|
+
attachable.content_type
|
|
73
|
+
when String
|
|
74
|
+
blob = ActiveStorage::Blob.find_signed!(attachable)
|
|
75
|
+
blob.content_type
|
|
76
|
+
when Hash
|
|
77
|
+
attachable[:content_type]
|
|
78
|
+
when File
|
|
79
|
+
supports_file_attachment? ? marcel_content_type_from_filename(attachable) : raise_rails_like_error(attachable)
|
|
80
|
+
when Pathname
|
|
81
|
+
supports_pathname_attachment? ? marcel_content_type_from_filename(attachable) : raise_rails_like_error(attachable)
|
|
82
|
+
else
|
|
83
|
+
raise_rails_like_error(attachable)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
# rubocop:enable Metrics/MethodLength
|
|
87
|
+
|
|
88
|
+
# Retrieve the declared content_type from attachable without potential mime
|
|
89
|
+
# type parameters (e.g. 'application/x-rar-compressed;version=5')
|
|
90
|
+
def attachable_content_type(attachable)
|
|
91
|
+
(full_attachable_content_type(attachable) && content_type_without_parameters(full_attachable_content_type(attachable)) || marcel_content_type_from_filename(attachable))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Remove the potential mime type parameters from the content_type (e.g.
|
|
95
|
+
# 'application/x-rar-compressed;version=5')
|
|
96
|
+
def content_type_without_parameters(content_type)
|
|
97
|
+
content_type && content_type.downcase.split(/[;,\s]/, 2).first
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Retrieve the content_type from attachable using the same logic as Rails
|
|
101
|
+
# ActiveStorage::Blob::Identifiable#identify_content_type
|
|
102
|
+
def attachable_content_type_rails_like(attachable)
|
|
103
|
+
Marcel::MimeType.for(
|
|
104
|
+
attachable_io(attachable, max_byte_size: 4.kilobytes),
|
|
105
|
+
name: attachable_filename(attachable).to_s,
|
|
106
|
+
declared_type: full_attachable_content_type(attachable)
|
|
107
|
+
)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Retrieve the media type of the attachable, which is the first part of the
|
|
111
|
+
# content type (or mime type).
|
|
112
|
+
# Possible values are: application/audio/example/font/image/model/text/video
|
|
113
|
+
def attachable_media_type(attachable)
|
|
114
|
+
(full_attachable_content_type(attachable) || marcel_content_type_from_filename(attachable)).split("/").first
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Retrieve the io from attachable.
|
|
118
|
+
# rubocop:disable Metrics/MethodLength
|
|
119
|
+
# rubocop:disable Metrics/AbcSize
|
|
120
|
+
def attachable_io(attachable, max_byte_size: nil)
|
|
121
|
+
io = case attachable
|
|
122
|
+
when ActiveStorage::Blob
|
|
123
|
+
(max_byte_size && supports_blob_download_chunk?) ? attachable.download_chunk(0...max_byte_size) : attachable.download
|
|
124
|
+
when ActionDispatch::Http::UploadedFile
|
|
125
|
+
max_byte_size ? attachable.read(max_byte_size) : attachable.read
|
|
126
|
+
when Rack::Test::UploadedFile
|
|
127
|
+
max_byte_size ? attachable.read(max_byte_size) : attachable.read
|
|
128
|
+
when String
|
|
129
|
+
blob = ActiveStorage::Blob.find_signed!(attachable)
|
|
130
|
+
(max_byte_size && supports_blob_download_chunk?) ? blob.download_chunk(0...max_byte_size) : blob.download
|
|
131
|
+
when Hash
|
|
132
|
+
max_byte_size ? attachable[:io].read(max_byte_size) : attachable[:io].read
|
|
133
|
+
when File
|
|
134
|
+
raise_rails_like_error(attachable) unless supports_file_attachment?
|
|
135
|
+
max_byte_size ? attachable.read(max_byte_size) : attachable.read
|
|
136
|
+
when Pathname
|
|
137
|
+
raise_rails_like_error(attachable) unless supports_pathname_attachment?
|
|
138
|
+
max_byte_size ? attachable.read(max_byte_size) : attachable.read
|
|
139
|
+
else
|
|
140
|
+
raise_rails_like_error(attachable)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
rewind_attachable_io(attachable)
|
|
144
|
+
io
|
|
145
|
+
end
|
|
146
|
+
# rubocop:enable Metrics/MethodLength
|
|
147
|
+
# rubocop:enable Metrics/AbcSize
|
|
148
|
+
|
|
149
|
+
# Rewind the io attachable.
|
|
150
|
+
def rewind_attachable_io(attachable)
|
|
151
|
+
case attachable
|
|
152
|
+
when ActiveStorage::Blob, String
|
|
153
|
+
# nothing to do
|
|
154
|
+
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
|
|
155
|
+
attachable.rewind
|
|
156
|
+
when Hash
|
|
157
|
+
attachable[:io].rewind
|
|
158
|
+
when File
|
|
159
|
+
raise_rails_like_error(attachable) unless supports_file_attachment?
|
|
160
|
+
attachable.rewind
|
|
161
|
+
when Pathname
|
|
162
|
+
raise_rails_like_error(attachable) unless supports_pathname_attachment?
|
|
163
|
+
File.open(attachable) { |f| f.rewind }
|
|
164
|
+
else
|
|
165
|
+
raise_rails_like_error(attachable)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Retrieve the declared filename from attachable.
|
|
170
|
+
# rubocop:disable Metrics/MethodLength
|
|
171
|
+
def attachable_filename(attachable)
|
|
172
|
+
case attachable
|
|
173
|
+
when ActiveStorage::Blob
|
|
174
|
+
attachable.filename
|
|
175
|
+
when ActionDispatch::Http::UploadedFile
|
|
176
|
+
attachable.original_filename
|
|
177
|
+
when Rack::Test::UploadedFile
|
|
178
|
+
attachable.original_filename
|
|
179
|
+
when String
|
|
180
|
+
blob = ActiveStorage::Blob.find_signed!(attachable)
|
|
181
|
+
blob.filename
|
|
182
|
+
when Hash
|
|
183
|
+
attachable[:filename]
|
|
184
|
+
when File
|
|
185
|
+
supports_file_attachment? ? File.basename(attachable) : raise_rails_like_error(attachable)
|
|
186
|
+
when Pathname
|
|
187
|
+
supports_pathname_attachment? ? File.basename(attachable) : raise_rails_like_error(attachable)
|
|
188
|
+
else
|
|
189
|
+
raise_rails_like_error(attachable)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
# rubocop:enable Metrics/MethodLength
|
|
193
|
+
|
|
194
|
+
# Raise the same Rails error for not-implemented file representations.
|
|
195
|
+
def raise_rails_like_error(attachable)
|
|
196
|
+
raise(
|
|
197
|
+
ArgumentError,
|
|
198
|
+
"Could not find or build blob: expected attachable, " \
|
|
199
|
+
"got #{attachable.inspect}"
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Check if the current Rails version supports File or Pathname attachment
|
|
204
|
+
#
|
|
205
|
+
# https://github.com/rails/rails/blob/7-1-stable/activestorage/CHANGELOG.md#rails-710rc1-september-27-2023
|
|
206
|
+
def supports_file_attachment?
|
|
207
|
+
Rails.gem_version >= Gem::Version.new("7.1.0.rc1")
|
|
208
|
+
end
|
|
209
|
+
alias :supports_pathname_attachment? :supports_file_attachment?
|
|
210
|
+
|
|
211
|
+
# Check if the current Rails version supports ActiveStorage::Blob#download_chunk
|
|
212
|
+
#
|
|
213
|
+
# https://github.com/rails/rails/blob/7-0-stable/activestorage/CHANGELOG.md#rails-700alpha1-september-15-2021
|
|
214
|
+
def supports_blob_download_chunk?
|
|
215
|
+
Rails.gem_version >= Gem::Version.new("7.0.0.alpha1")
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Retrieve the content_type from the file name only
|
|
219
|
+
def marcel_content_type_from_filename(attachable)
|
|
220
|
+
Marcel::MimeType.for(name: attachable_filename(attachable).to_s)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Add a media metadata missing error when metadata is missing.
|
|
224
|
+
def add_media_metadata_missing_error(record, attribute, attachable, already_set_errors_options = nil)
|
|
225
|
+
errors_options = already_set_errors_options || initialize_error_options(options, attachable)
|
|
226
|
+
add_error(record, attribute, :media_metadata_missing, **errors_options)
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
# Add an attachment missing error when an ActiveStorage::FileNotFoundError
|
|
230
|
+
# is raised.
|
|
231
|
+
def add_attachment_missing_error(record, attribute, attachable)
|
|
232
|
+
errors_options = initialize_error_options(options, attachable)
|
|
233
|
+
add_error(record, attribute, :attachment_missing, **errors_options)
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveStorageValidations
|
|
4
|
+
module ASVErrorable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
def initialize_error_options(options, file = nil)
|
|
8
|
+
not_explicitly_written_options = %i[with in]
|
|
9
|
+
curated_options = options.except(*not_explicitly_written_options)
|
|
10
|
+
|
|
11
|
+
active_storage_validations_options = {
|
|
12
|
+
validator_type: self.class.to_sym,
|
|
13
|
+
custom_message: (options[:message] if options[:message].present?),
|
|
14
|
+
filename: (get_filename(file) unless self.class.to_sym == :total_size)
|
|
15
|
+
}.compact
|
|
16
|
+
|
|
17
|
+
curated_options.merge(active_storage_validations_options)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_error(record, attribute, error_type, **errors_options)
|
|
21
|
+
return if record.errors.added?(attribute, error_type)
|
|
22
|
+
|
|
23
|
+
error = record.errors.add(attribute, error_type, **errors_options)
|
|
24
|
+
|
|
25
|
+
# Rails 8.0.2 introduced a new way to mark errors as nested
|
|
26
|
+
# https://github.com/igorkasyanchuk/active_storage_validations/issues/377
|
|
27
|
+
if Rails.gem_version >= Gem::Version.new("8.0.2")
|
|
28
|
+
# Mark errors as nested when they occur in a parent/child context
|
|
29
|
+
set_nested_error(record, error) if updating_through_parent?(record)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# You can read https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-add
|
|
33
|
+
# to better understand how Rails model errors work
|
|
34
|
+
error
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def get_filename(file)
|
|
40
|
+
return nil unless file
|
|
41
|
+
|
|
42
|
+
case file
|
|
43
|
+
when ActiveStorage::Attached, ActiveStorage::Attachment then file.blob&.filename&.to_s
|
|
44
|
+
when ActiveStorage::Blob then file.filename
|
|
45
|
+
when Hash then file[:filename]
|
|
46
|
+
end.to_s
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def updating_through_parent?(record)
|
|
50
|
+
record.instance_variable_defined?(:@marked_for_destruction) ||
|
|
51
|
+
record.instance_variable_defined?(:@_destroy) ||
|
|
52
|
+
(record.respond_to?(:parent) && record.parent.present?)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def set_nested_error(record, error)
|
|
56
|
+
reflection = record.class.reflect_on_association(:parent)
|
|
57
|
+
|
|
58
|
+
if reflection
|
|
59
|
+
association = record.association(reflection.name)
|
|
60
|
+
record.errors.objects.append(ActiveRecord::Associations::NestedError.new(association, error))
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveStorageValidations
|
|
4
|
+
# ActiveStorageValidations::ASVOptionable
|
|
5
|
+
#
|
|
6
|
+
# Helper method to flatten the validator options.
|
|
7
|
+
module ASVOptionable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def set_flat_options(record)
|
|
13
|
+
flatten_options(record, self.options)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def flatten_options(record, options, available_checks = self.class::AVAILABLE_CHECKS)
|
|
17
|
+
case options
|
|
18
|
+
when Hash
|
|
19
|
+
options.merge(options) do |key, value|
|
|
20
|
+
available_checks&.exclude?(key) ? {} : flatten_options(record, value, nil)
|
|
21
|
+
end
|
|
22
|
+
when Array
|
|
23
|
+
options.map { |option| flatten_options(record, option, available_checks) }
|
|
24
|
+
else
|
|
25
|
+
options.is_a?(Proc) ? options.call(record) : options
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveStorageValidations
|
|
4
|
+
module ASVSymbolizable
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
def to_sym
|
|
9
|
+
validator_class = self.name.split("::").last
|
|
10
|
+
validator_class.sub(/Validator/, "").underscore.to_sym
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -1,60 +1,43 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "base_comparison_validator"
|
|
4
|
+
|
|
3
5
|
module ActiveStorageValidations
|
|
4
|
-
class SizeValidator <
|
|
5
|
-
|
|
6
|
+
class SizeValidator < BaseComparisonValidator
|
|
7
|
+
ERROR_TYPES = %i[
|
|
8
|
+
file_size_not_less_than
|
|
9
|
+
file_size_not_less_than_or_equal_to
|
|
10
|
+
file_size_not_greater_than
|
|
11
|
+
file_size_not_greater_than_or_equal_to
|
|
12
|
+
file_size_not_between
|
|
13
|
+
file_size_not_equal_to
|
|
14
|
+
].freeze
|
|
6
15
|
|
|
7
16
|
delegate :number_to_human_size, to: ActiveSupport::NumberHelper
|
|
8
17
|
|
|
9
|
-
AVAILABLE_CHECKS = %i[less_than less_than_or_equal_to greater_than greater_than_or_equal_to between].freeze
|
|
10
|
-
|
|
11
|
-
def check_validity!
|
|
12
|
-
return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
|
13
|
-
raise ArgumentError, 'You must pass either :less_than(_or_equal_to), :greater_than(_or_equal_to), or :between to the validator'
|
|
14
|
-
end
|
|
15
|
-
|
|
16
18
|
def validate_each(record, attribute, _value)
|
|
17
|
-
|
|
18
|
-
return true unless record.send(attribute).attached?
|
|
19
|
+
return if no_attachments?(record, attribute)
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
flat_options = set_flat_options(record)
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
|
23
|
+
attached_files(record, attribute).each do |file|
|
|
24
|
+
next if is_valid?(file.blob.byte_size, flat_options)
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
errors_options = initialize_error_options(options, file)
|
|
27
|
+
populate_error_options(errors_options, flat_options)
|
|
28
|
+
errors_options[:file_size] = format_bound_value(file.blob.byte_size)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
errors_options[:max_size] = number_to_human_size(max_size(flat_options))
|
|
30
|
+
keys = AVAILABLE_CHECKS & flat_options.keys
|
|
31
|
+
error_type = "file_size_not_#{keys.first}".to_sym
|
|
32
32
|
|
|
33
|
-
record
|
|
34
|
-
break
|
|
33
|
+
add_error(record, attribute, error_type, **errors_options)
|
|
35
34
|
end
|
|
36
35
|
end
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
if flat_options[:between].present?
|
|
40
|
-
flat_options[:between].include?(file_size)
|
|
41
|
-
elsif flat_options[:less_than].present?
|
|
42
|
-
file_size < flat_options[:less_than]
|
|
43
|
-
elsif flat_options[:less_than_or_equal_to].present?
|
|
44
|
-
file_size <= flat_options[:less_than_or_equal_to]
|
|
45
|
-
elsif flat_options[:greater_than].present?
|
|
46
|
-
file_size > flat_options[:greater_than]
|
|
47
|
-
elsif flat_options[:greater_than_or_equal_to].present?
|
|
48
|
-
file_size >= flat_options[:greater_than_or_equal_to]
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def min_size(flat_options)
|
|
53
|
-
flat_options[:between]&.min || flat_options[:greater_than] || flat_options[:greater_than_or_equal_to]
|
|
54
|
-
end
|
|
37
|
+
private
|
|
55
38
|
|
|
56
|
-
def
|
|
57
|
-
|
|
39
|
+
def format_bound_value(value)
|
|
40
|
+
number_to_human_size(value)
|
|
58
41
|
end
|
|
59
42
|
end
|
|
60
43
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_comparison_validator"
|
|
4
|
+
|
|
5
|
+
module ActiveStorageValidations
|
|
6
|
+
class TotalSizeValidator < BaseComparisonValidator
|
|
7
|
+
ERROR_TYPES = %i[
|
|
8
|
+
total_file_size_not_less_than
|
|
9
|
+
total_file_size_not_less_than_or_equal_to
|
|
10
|
+
total_file_size_not_greater_than
|
|
11
|
+
total_file_size_not_greater_than_or_equal_to
|
|
12
|
+
total_file_size_not_between
|
|
13
|
+
total_file_size_not_equal_to
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
16
|
+
delegate :number_to_human_size, to: ActiveSupport::NumberHelper
|
|
17
|
+
|
|
18
|
+
def validate_each(record, attribute, _value)
|
|
19
|
+
custom_check_validity!(record, attribute)
|
|
20
|
+
|
|
21
|
+
return if no_attachments?(record, attribute)
|
|
22
|
+
|
|
23
|
+
total_file_size = attached_files(record, attribute).sum { |file| file.blob.byte_size }
|
|
24
|
+
flat_options = set_flat_options(record)
|
|
25
|
+
|
|
26
|
+
return if is_valid?(total_file_size, flat_options)
|
|
27
|
+
|
|
28
|
+
errors_options = initialize_error_options(options, nil)
|
|
29
|
+
populate_error_options(errors_options, flat_options)
|
|
30
|
+
errors_options[:total_file_size] = format_bound_value(total_file_size)
|
|
31
|
+
|
|
32
|
+
keys = AVAILABLE_CHECKS & flat_options.keys
|
|
33
|
+
error_type = "total_file_size_not_#{keys.first}".to_sym
|
|
34
|
+
|
|
35
|
+
add_error(record, attribute, error_type, **errors_options)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def custom_check_validity!(record, attribute)
|
|
41
|
+
# We can't perform this check in the #check_validity! hook because we do not
|
|
42
|
+
# have enough data (only options & attributes are accessible)
|
|
43
|
+
unless record.send(attribute).is_a?(ActiveStorage::Attached::Many)
|
|
44
|
+
raise ArgumentError, "This validator is only available for has_many_attached relations"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def format_bound_value(value)
|
|
49
|
+
number_to_human_size(value)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require 'active_storage_validations/option_proc_unfolding'
|
|
6
|
-
require 'active_storage_validations/attached_validator'
|
|
7
|
-
require 'active_storage_validations/content_type_validator'
|
|
8
|
-
require 'active_storage_validations/size_validator'
|
|
9
|
-
require 'active_storage_validations/limit_validator'
|
|
10
|
-
require 'active_storage_validations/dimension_validator'
|
|
11
|
-
require 'active_storage_validations/aspect_ratio_validator'
|
|
12
|
-
require 'active_storage_validations/processable_image_validator'
|
|
3
|
+
require "active_model"
|
|
4
|
+
require "active_support/concern"
|
|
13
5
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
6
|
+
require "active_storage_validations/analyzer"
|
|
7
|
+
require "active_storage_validations/analyzer/image_analyzer"
|
|
8
|
+
require "active_storage_validations/analyzer/image_analyzer/image_magick"
|
|
9
|
+
require "active_storage_validations/analyzer/image_analyzer/vips"
|
|
10
|
+
require "active_storage_validations/analyzer/null_analyzer"
|
|
11
|
+
require "active_storage_validations/analyzer/video_analyzer"
|
|
12
|
+
require "active_storage_validations/analyzer/audio_analyzer"
|
|
13
|
+
require "active_storage_validations/analyzer/pdf_analyzer"
|
|
14
|
+
|
|
15
|
+
require "active_storage_validations/extensors/asv_blob_metadatable"
|
|
16
|
+
require "active_storage_validations/extensors/asv_marcelable"
|
|
17
|
+
|
|
18
|
+
require "active_storage_validations/attached_validator"
|
|
19
|
+
require "active_storage_validations/content_type_validator"
|
|
20
|
+
require "active_storage_validations/limit_validator"
|
|
21
|
+
require "active_storage_validations/dimension_validator"
|
|
22
|
+
require "active_storage_validations/duration_validator"
|
|
23
|
+
require "active_storage_validations/aspect_ratio_validator"
|
|
24
|
+
require "active_storage_validations/processable_file_validator"
|
|
25
|
+
require "active_storage_validations/size_validator"
|
|
26
|
+
require "active_storage_validations/total_size_validator"
|
|
27
|
+
require "active_storage_validations/pages_validator"
|
|
28
|
+
|
|
29
|
+
require "active_storage_validations/engine"
|
|
30
|
+
require "active_storage_validations/railtie"
|