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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +785 -245
  3. data/config/locales/da.yml +63 -0
  4. data/config/locales/de.yml +60 -19
  5. data/config/locales/en-GB.yml +63 -0
  6. data/config/locales/en.yml +60 -20
  7. data/config/locales/es.yml +60 -19
  8. data/config/locales/fr.yml +60 -19
  9. data/config/locales/it.yml +60 -19
  10. data/config/locales/ja.yml +60 -19
  11. data/config/locales/nl.yml +60 -19
  12. data/config/locales/pl.yml +60 -19
  13. data/config/locales/pt-BR.yml +60 -19
  14. data/config/locales/ru.yml +60 -19
  15. data/config/locales/sv.yml +63 -0
  16. data/config/locales/tr.yml +60 -19
  17. data/config/locales/uk.yml +60 -19
  18. data/config/locales/vi.yml +60 -19
  19. data/config/locales/zh-CN.yml +60 -19
  20. data/lib/active_storage_validations/analyzer/audio_analyzer.rb +58 -0
  21. data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +60 -0
  22. data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +46 -0
  23. data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +56 -0
  24. data/lib/active_storage_validations/analyzer/image_analyzer.rb +49 -0
  25. data/lib/active_storage_validations/analyzer/null_analyzer.rb +18 -0
  26. data/lib/active_storage_validations/analyzer/pdf_analyzer.rb +89 -0
  27. data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +61 -0
  28. data/lib/active_storage_validations/analyzer/video_analyzer.rb +130 -0
  29. data/lib/active_storage_validations/analyzer.rb +88 -0
  30. data/lib/active_storage_validations/aspect_ratio_validator.rb +157 -97
  31. data/lib/active_storage_validations/attached_validator.rb +22 -5
  32. data/lib/active_storage_validations/base_comparison_validator.rb +83 -0
  33. data/lib/active_storage_validations/content_type_validator.rb +219 -31
  34. data/lib/active_storage_validations/dimension_validator.rb +187 -97
  35. data/lib/active_storage_validations/duration_validator.rb +70 -0
  36. data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +56 -0
  37. data/lib/active_storage_validations/extensors/asv_marcelable.rb +12 -0
  38. data/lib/active_storage_validations/limit_validator.rb +76 -9
  39. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
  40. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +48 -25
  41. data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +150 -0
  42. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +98 -39
  43. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +93 -55
  44. data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +39 -0
  45. data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +127 -0
  46. data/lib/active_storage_validations/matchers/pages_validator_matcher.rb +39 -0
  47. data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +78 -0
  48. data/lib/active_storage_validations/matchers/shared/asv_active_storageable.rb +19 -0
  49. data/lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb +28 -0
  50. data/lib/active_storage_validations/matchers/shared/asv_attachable.rb +72 -0
  51. data/lib/active_storage_validations/matchers/shared/asv_contextable.rb +57 -0
  52. data/lib/active_storage_validations/matchers/shared/asv_messageable.rb +28 -0
  53. data/lib/active_storage_validations/matchers/shared/asv_rspecable.rb +27 -0
  54. data/lib/active_storage_validations/matchers/shared/asv_validatable.rb +56 -0
  55. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +17 -71
  56. data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +47 -0
  57. data/lib/active_storage_validations/matchers.rb +17 -21
  58. data/lib/active_storage_validations/pages_validator.rb +61 -0
  59. data/lib/active_storage_validations/processable_file_validator.rb +37 -0
  60. data/lib/active_storage_validations/railtie.rb +14 -0
  61. data/lib/active_storage_validations/shared/asv_active_storageable.rb +30 -0
  62. data/lib/active_storage_validations/shared/asv_analyzable.rb +89 -0
  63. data/lib/active_storage_validations/shared/asv_attachable.rb +236 -0
  64. data/lib/active_storage_validations/shared/asv_errorable.rb +64 -0
  65. data/lib/active_storage_validations/shared/asv_loggable.rb +11 -0
  66. data/lib/active_storage_validations/shared/asv_optionable.rb +29 -0
  67. data/lib/active_storage_validations/shared/asv_symbolizable.rb +14 -0
  68. data/lib/active_storage_validations/size_validator.rb +24 -41
  69. data/lib/active_storage_validations/total_size_validator.rb +52 -0
  70. data/lib/active_storage_validations/version.rb +1 -1
  71. data/lib/active_storage_validations.rb +27 -13
  72. metadata +113 -31
  73. data/lib/active_storage_validations/metadata.rb +0 -151
  74. data/lib/active_storage_validations/option_proc_unfolding.rb +0 -16
  75. 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageValidations
4
+ module ASVLoggable
5
+ extend ActiveSupport::Concern
6
+
7
+ def logger
8
+ Rails.logger
9
+ end
10
+ end
11
+ 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 < ActiveModel::EachValidator # :nodoc:
5
- include OptionProcUnfolding
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
- # only attached
18
- return true unless record.send(attribute).attached?
19
+ return if no_attachments?(record, attribute)
19
20
 
20
- files = Array.wrap(record.send(attribute))
21
+ flat_options = set_flat_options(record)
21
22
 
22
- errors_options = {}
23
- errors_options[:message] = options[:message] if options[:message].present?
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
- files.each do |file|
27
- next if content_size_valid?(file.blob.byte_size, flat_options)
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
- errors_options[:file_size] = number_to_human_size(file.blob.byte_size)
30
- errors_options[:min_size] = number_to_human_size(min_size(flat_options))
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.errors.add(attribute, :file_size_out_of_range, **errors_options)
34
- break
33
+ add_error(record, attribute, error_type, **errors_options)
35
34
  end
36
35
  end
37
36
 
38
- def content_size_valid?(file_size, flat_options)
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 max_size(flat_options)
57
- flat_options[:between]&.max || flat_options[:less_than] || flat_options[:less_than_or_equal_to]
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,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageValidations
4
- VERSION = '1.0.4'
4
+ VERSION = "3.0.2"
5
5
  end
@@ -1,16 +1,30 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'active_storage_validations/railtie'
4
- require 'active_storage_validations/engine'
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
- ActiveSupport.on_load(:active_record) do
15
- send :include, ActiveStorageValidations
16
- end
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"