active_storage_validations 1.2.0 → 1.3.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +30 -17
  3. data/config/locales/da.yml +1 -1
  4. data/config/locales/de.yml +1 -0
  5. data/config/locales/en.yml +1 -0
  6. data/config/locales/es.yml +1 -0
  7. data/config/locales/fr.yml +1 -0
  8. data/config/locales/it.yml +1 -0
  9. data/config/locales/ja.yml +1 -0
  10. data/config/locales/nl.yml +1 -0
  11. data/config/locales/pl.yml +1 -0
  12. data/config/locales/pt-BR.yml +1 -0
  13. data/config/locales/ru.yml +1 -0
  14. data/config/locales/sv.yml +1 -0
  15. data/config/locales/tr.yml +1 -0
  16. data/config/locales/uk.yml +1 -0
  17. data/config/locales/vi.yml +1 -0
  18. data/config/locales/zh-CN.yml +1 -0
  19. data/lib/active_storage_validations/aspect_ratio_validator.rb +10 -34
  20. data/lib/active_storage_validations/attached_validator.rb +5 -3
  21. data/lib/active_storage_validations/base_size_validator.rb +3 -1
  22. data/lib/active_storage_validations/concerns/active_storageable.rb +28 -0
  23. data/lib/active_storage_validations/concerns/errorable.rb +2 -3
  24. data/lib/active_storage_validations/concerns/loggable.rb +9 -0
  25. data/lib/active_storage_validations/concerns/metadatable.rb +31 -0
  26. data/lib/active_storage_validations/content_type_spoof_detector.rb +130 -0
  27. data/lib/active_storage_validations/content_type_validator.rb +56 -22
  28. data/lib/active_storage_validations/dimension_validator.rb +31 -52
  29. data/lib/active_storage_validations/limit_validator.rb +5 -3
  30. data/lib/active_storage_validations/marcel_extensor.rb +5 -0
  31. data/lib/active_storage_validations/matchers/concerns/attachable.rb +27 -9
  32. data/lib/active_storage_validations/matchers/concerns/messageable.rb +1 -1
  33. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +7 -0
  34. data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +127 -0
  35. data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +1 -10
  36. data/lib/active_storage_validations/matchers.rb +4 -15
  37. data/lib/active_storage_validations/metadata.rb +7 -10
  38. data/lib/active_storage_validations/processable_image_validator.rb +17 -32
  39. data/lib/active_storage_validations/size_validator.rb +2 -6
  40. data/lib/active_storage_validations/total_size_validator.rb +3 -7
  41. data/lib/active_storage_validations/version.rb +1 -1
  42. data/lib/active_storage_validations.rb +2 -0
  43. metadata +18 -12
@@ -1,16 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/active_storageable.rb'
3
4
  require_relative 'concerns/errorable.rb'
4
5
  require_relative 'concerns/symbolizable.rb'
6
+ require_relative 'content_type_spoof_detector.rb'
5
7
 
6
8
  module ActiveStorageValidations
7
9
  class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
10
+ include ActiveStorageable
8
11
  include OptionProcUnfolding
9
12
  include Errorable
10
13
  include Symbolizable
11
14
 
12
15
  AVAILABLE_CHECKS = %i[with in].freeze
13
- ERROR_TYPES = %i[content_type_invalid].freeze
16
+ ERROR_TYPES = %i[
17
+ content_type_invalid
18
+ spoofed_content_type
19
+ ].freeze
14
20
 
15
21
  def check_validity!
16
22
  ensure_exactly_one_validator_option
@@ -18,31 +24,24 @@ module ActiveStorageValidations
18
24
  end
19
25
 
20
26
  def validate_each(record, attribute, _value)
21
- return true unless record.send(attribute).attached?
27
+ return if no_attachments?(record, attribute)
22
28
 
23
29
  types = authorized_types(record)
24
- return true if types.empty?
30
+ return if types.empty?
25
31
 
26
- files = Array.wrap(record.send(attribute))
27
-
28
- files.each do |file|
29
- next if is_valid?(file, types)
30
-
31
- errors_options = initialize_error_options(options, file)
32
- errors_options[:authorized_types] = types_to_human_format(types)
33
- errors_options[:content_type] = content_type(file)
34
- add_error(record, attribute, ERROR_TYPES.first, **errors_options)
35
- break
32
+ attached_files(record, attribute).each do |file|
33
+ is_valid?(record, attribute, file, types)
36
34
  end
37
35
  end
38
36
 
37
+ private
38
+
39
39
  def authorized_types(record)
40
40
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
41
41
  (Array.wrap(flat_options[:with]) + Array.wrap(flat_options[:in])).compact.map do |type|
42
- if type.is_a?(Regexp)
43
- type
44
- else
45
- Marcel::MimeType.for(declared_type: type.to_s, extension: type.to_s)
42
+ case type
43
+ when String, Symbol then Marcel::MimeType.for(declared_type: type.to_s, extension: type.to_s)
44
+ when Regexp then type
46
45
  end
47
46
  end
48
47
  end
@@ -54,13 +53,44 @@ module ActiveStorageValidations
54
53
  end
55
54
 
56
55
  def content_type(file)
57
- file.blob.present? && file.blob.content_type
56
+ # We remove potential mime type parameters
57
+ file.blob.present? && file.blob.content_type.downcase.split(/[;,\s]/, 2).first
58
+ end
59
+
60
+ def is_valid?(record, attribute, file, types)
61
+ file_type_in_authorized_types?(record, attribute, file, types) &&
62
+ not_spoofing_content_type?(record, attribute, file)
58
63
  end
59
64
 
60
- def is_valid?(file, types)
65
+ def file_type_in_authorized_types?(record, attribute, file, types)
61
66
  file_type = content_type(file)
62
- types.any? do |type|
63
- type == file_type || (type.is_a?(Regexp) && type.match?(file_type.to_s))
67
+ file_type_is_authorized = types.any? do |type|
68
+ case type
69
+ when String then type == file_type
70
+ when Regexp then type.match?(file_type.to_s)
71
+ end
72
+ end
73
+
74
+ if file_type_is_authorized
75
+ true
76
+ else
77
+ errors_options = initialize_error_options(options, file)
78
+ errors_options[:authorized_types] = types_to_human_format(types)
79
+ errors_options[:content_type] = content_type(file)
80
+ add_error(record, attribute, ERROR_TYPES.first, **errors_options)
81
+ false
82
+ end
83
+ end
84
+
85
+ def not_spoofing_content_type?(record, attribute, file)
86
+ return true unless enable_spoofing_protection?
87
+
88
+ if ContentTypeSpoofDetector.new(record, attribute, file).spoofed?
89
+ errors_options = initialize_error_options(options, file)
90
+ add_error(record, attribute, ERROR_TYPES.second, **errors_options)
91
+ false
92
+ else
93
+ true
64
94
  end
65
95
  end
66
96
 
@@ -81,7 +111,7 @@ module ActiveStorageValidations
81
111
  def invalid_content_type_message(content_type)
82
112
  <<~ERROR_MESSAGE
83
113
  You must pass valid content types to the validator
84
- '#{content_type}' is not find in Marcel::EXTENSIONS mimes
114
+ '#{content_type}' is not found in Marcel::EXTENSIONS mimes
85
115
  ERROR_MESSAGE
86
116
  end
87
117
 
@@ -93,5 +123,9 @@ module ActiveStorageValidations
93
123
  false # We always validate regexes
94
124
  end
95
125
  end
126
+
127
+ def enable_spoofing_protection?
128
+ options[:spoofing_protection] == true
129
+ end
96
130
  end
97
131
  end
@@ -1,13 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/active_storageable.rb'
3
4
  require_relative 'concerns/errorable.rb'
5
+ require_relative 'concerns/metadatable.rb'
4
6
  require_relative 'concerns/symbolizable.rb'
5
- require_relative 'metadata.rb'
6
7
 
7
8
  module ActiveStorageValidations
8
9
  class DimensionValidator < ActiveModel::EachValidator # :nodoc
9
- include OptionProcUnfolding
10
+ include ActiveStorageable
10
11
  include Errorable
12
+ include OptionProcUnfolding
13
+ include Metadatable
11
14
  include Symbolizable
12
15
 
13
16
  AVAILABLE_CHECKS = %i[width height min max].freeze
@@ -25,65 +28,19 @@ module ActiveStorageValidations
25
28
  dimension_height_equal_to
26
29
  ].freeze
27
30
 
28
- def process_options(record)
29
- flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
30
-
31
- [:width, :height].each do |length|
32
- if flat_options[length] and flat_options[length].is_a?(Hash)
33
- if (range = flat_options[length][:in])
34
- raise ArgumentError, ":in must be a Range" unless range.is_a?(Range)
35
- flat_options[length][:min], flat_options[length][:max] = range.min, range.max
36
- end
37
- end
38
- end
39
- [:min, :max].each do |dim|
40
- if (range = flat_options[dim])
41
- raise ArgumentError, ":#{dim} must be a Range (width..height)" unless range.is_a?(Range)
42
- flat_options[:width] = { dim => range.first }
43
- flat_options[:height] = { dim => range.last }
44
- end
45
- end
46
-
47
- flat_options
48
- end
49
-
50
31
  def check_validity!
51
32
  unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
52
33
  raise ArgumentError, 'You must pass either :width, :height, :min or :max to the validator'
53
34
  end
54
35
  end
55
36
 
37
+ def validate_each(record, attribute, _value)
38
+ return if no_attachments?(record, attribute)
56
39
 
57
- if Rails.gem_version >= Gem::Version.new('6.0.0')
58
- def validate_each(record, attribute, _value)
59
- return true unless record.send(attribute).attached?
60
-
61
- changes = record.attachment_changes[attribute.to_s]
62
- return true if changes.blank?
63
-
64
- files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
65
- files.each do |file|
66
- metadata = Metadata.new(file).metadata
67
- next if is_valid?(record, attribute, file, metadata)
68
- break
69
- end
70
- end
71
- else
72
- # Rails 5
73
- def validate_each(record, attribute, _value)
74
- return true unless record.send(attribute).attached?
75
-
76
- files = Array.wrap(record.send(attribute))
77
- files.each do |file|
78
- # Analyze file first if not analyzed to get all required metadata.
79
- file.analyze; file.reload unless file.analyzed?
80
- metadata = file.metadata rescue {}
81
- next if is_valid?(record, attribute, file, metadata)
82
- break
83
- end
84
- end
40
+ validate_changed_files_from_metadata(record, attribute)
85
41
  end
86
42
 
43
+ private
87
44
 
88
45
  def is_valid?(record, attribute, file, metadata)
89
46
  flat_options = process_options(record)
@@ -163,5 +120,27 @@ module ActiveStorageValidations
163
120
 
164
121
  true # valid file
165
122
  end
123
+
124
+ def process_options(record)
125
+ flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
126
+
127
+ [:width, :height].each do |length|
128
+ if flat_options[length] and flat_options[length].is_a?(Hash)
129
+ if (range = flat_options[length][:in])
130
+ raise ArgumentError, ":in must be a Range" unless range.is_a?(Range)
131
+ flat_options[length][:min], flat_options[length][:max] = range.min, range.max
132
+ end
133
+ end
134
+ end
135
+ [:min, :max].each do |dim|
136
+ if (range = flat_options[dim])
137
+ raise ArgumentError, ":#{dim} must be a Range (width..height)" unless range.is_a?(Range)
138
+ flat_options[:width] = { dim => range.first }
139
+ flat_options[:height] = { dim => range.last }
140
+ end
141
+ end
142
+
143
+ flat_options
144
+ end
166
145
  end
167
146
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/active_storageable.rb'
3
4
  require_relative 'concerns/errorable.rb'
4
5
  require_relative 'concerns/symbolizable.rb'
5
6
 
6
7
  module ActiveStorageValidations
7
8
  class LimitValidator < ActiveModel::EachValidator # :nodoc:
9
+ include ActiveStorageable
8
10
  include OptionProcUnfolding
9
11
  include Errorable
10
12
  include Symbolizable
@@ -19,11 +21,11 @@ module ActiveStorageValidations
19
21
  ensure_arguments_validity
20
22
  end
21
23
 
22
- def validate_each(record, attribute, _)
23
- files = Array.wrap(record.send(attribute)).reject { |file| file.blank? }.compact.uniq
24
+ def validate_each(record, attribute, _value)
25
+ files = attached_files(record, attribute).reject(&:blank?)
24
26
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
25
27
 
26
- return true if files_count_valid?(files.count, flat_options)
28
+ return if files_count_valid?(files.count, flat_options)
27
29
 
28
30
  errors_options = initialize_error_options(options)
29
31
  errors_options[:min] = flat_options[:min]
@@ -0,0 +1,5 @@
1
+ Marcel::MimeType.extend "application/x-rar-compressed", parents: %(application/x-rar)
2
+ Marcel::MimeType.extend "audio/x-hx-aac-adts", parents: %(audio/x-aac)
3
+ Marcel::MimeType.extend "audio/x-m4a", parents: %(audio/mp4)
4
+ Marcel::MimeType.extend "text/xml", parents: %(application/xml) # alias
5
+ Marcel::MimeType.extend "video/theora", parents: %(video/ogg)
@@ -8,6 +8,27 @@ module ActiveStorageValidations
8
8
  @subject.public_send(@attribute_name)
9
9
  end
10
10
 
11
+ def attach_files(count)
12
+ return unless count.positive?
13
+
14
+ file_array = Array.new(count, dummy_file)
15
+
16
+ @subject.public_send(@attribute_name).attach(file_array)
17
+ end
18
+
19
+ def detach_file
20
+ @subject.attachment_changes.delete(@attribute_name.to_s)
21
+ end
22
+ alias :detach_files :detach_file
23
+
24
+ def file_attached?
25
+ @subject.public_send(@attribute_name).attached?
26
+ end
27
+
28
+ def dummy_blob
29
+ ActiveStorage::Blob.create_and_upload!(**dummy_file)
30
+ end
31
+
11
32
  def dummy_file
12
33
  {
13
34
  io: io,
@@ -18,8 +39,8 @@ module ActiveStorageValidations
18
39
 
19
40
  def processable_image
20
41
  {
21
- io: File.open(Rails.root.join('public', 'image_1920x1080.png')),
22
- filename: 'image_1920x1080_file.png',
42
+ io: StringIO.new(image_data),
43
+ filename: 'processable_image.png',
23
44
  content_type: 'image/png'
24
45
  }
25
46
  end
@@ -27,7 +48,7 @@ module ActiveStorageValidations
27
48
  def not_processable_image
28
49
  {
29
50
  io: Tempfile.new('.'),
30
- filename: 'processable.txt',
51
+ filename: 'not_processable_image.txt',
31
52
  content_type: 'text/plain'
32
53
  }
33
54
  end
@@ -36,12 +57,9 @@ module ActiveStorageValidations
36
57
  @io ||= Tempfile.new('Hello world!')
37
58
  end
38
59
 
39
- def detach_file
40
- @subject.attachment_changes.delete(@attribute_name.to_s)
41
- end
42
-
43
- def file_attached?
44
- @subject.public_send(@attribute_name).attached?
60
+ def image_data
61
+ # Binary data for a 1x1 transparent PNG image
62
+ "\x89PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00\nIDATx\x9Cc\x00\x01\x00\x00\x05\x00\x01\r\n\x2D\xB4\x00\x00\x00\x00IEND\xAE\x42\x60\x82"
45
63
  end
46
64
  end
47
65
  end
@@ -18,7 +18,7 @@ module ActiveStorageValidations
18
18
 
19
19
  def has_an_error_message_which_is_custom_message?
20
20
  validator_errors_for_attribute.one? do |error|
21
- error[:error] == @custom_message
21
+ error[:custom_message] == @custom_message
22
22
  end
23
23
  end
24
24
  end
@@ -130,6 +130,13 @@ module ActiveStorageValidations
130
130
  content_type: type
131
131
  }
132
132
  end
133
+
134
+ # Due to the way we build test attachments in #attachment_for
135
+ # (ie spoofed file basically), we need to ignore the error related to
136
+ # content type spoofing in our matcher to pass the tests
137
+ def validator_errors_for_attribute
138
+ super.reject { |hash| hash[:error] == :spoofed_content_type }
139
+ end
133
140
  end
134
141
  end
135
142
  end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'concerns/active_storageable'
4
+ require_relative 'concerns/allow_blankable'
5
+ require_relative 'concerns/attachable'
6
+ require_relative 'concerns/contextable'
7
+ require_relative 'concerns/messageable'
8
+ require_relative 'concerns/rspecable'
9
+ require_relative 'concerns/validatable'
10
+
11
+ module ActiveStorageValidations
12
+ module Matchers
13
+ def validate_limits_of(name)
14
+ LimitValidatorMatcher.new(name)
15
+ end
16
+
17
+ class LimitValidatorMatcher
18
+ include ActiveStorageable
19
+ include AllowBlankable
20
+ include Attachable
21
+ include Contextable
22
+ include Messageable
23
+ include Rspecable
24
+ include Validatable
25
+
26
+ def initialize(attribute_name)
27
+ initialize_allow_blankable
28
+ initialize_contextable
29
+ initialize_messageable
30
+ initialize_rspecable
31
+ @attribute_name = attribute_name
32
+ @min = @max = nil
33
+ end
34
+
35
+ def description
36
+ "validate the limit files of :#{@attribute_name}"
37
+ end
38
+
39
+ def failure_message
40
+ message = ["is expected to validate limit file of :#{@attribute_name}"]
41
+ build_failure_message(message)
42
+ message.join("\n")
43
+ end
44
+
45
+ def min(count)
46
+ @min = count
47
+ self
48
+ end
49
+
50
+ def max(count)
51
+ @max = count
52
+ self
53
+ end
54
+
55
+ def matches?(subject)
56
+ @subject = subject.is_a?(Class) ? subject.new : subject
57
+
58
+ is_a_valid_active_storage_attribute? &&
59
+ is_context_valid? &&
60
+ is_custom_message_valid? &&
61
+ file_count_not_smaller_than_min? &&
62
+ file_count_equal_min? &&
63
+ file_count_larger_than_min? &&
64
+ file_count_smaller_than_max? &&
65
+ file_count_equal_max? &&
66
+ file_count_not_larger_than_max?
67
+ end
68
+
69
+ private
70
+
71
+ def build_failure_message(message)
72
+ return unless @failure_message_artefacts.present?
73
+
74
+ message << " but there seem to have issues with the matcher methods you used, since:"
75
+ @failure_message_artefacts.each do |error_case|
76
+ message << " validation failed when provided with #{error_case[:count]} file(s)"
77
+ end
78
+ message << " whereas it should have passed"
79
+ end
80
+
81
+ def file_count_not_smaller_than_min?
82
+ @min.nil? || @min.zero? || !passes_validation_with_limits(@min - 1)
83
+ end
84
+
85
+ def file_count_equal_min?
86
+ @min.nil? || @min.zero? || passes_validation_with_limits(@min)
87
+ end
88
+
89
+ def file_count_larger_than_min?
90
+ @min.nil? || @min.zero? || @min == @max || passes_validation_with_limits(@min + 1)
91
+ end
92
+
93
+ def file_count_smaller_than_max?
94
+ @max.nil? || @min == @max || passes_validation_with_limits(@max - 1)
95
+ end
96
+
97
+ def file_count_equal_max?
98
+ @max.nil? || passes_validation_with_limits(@max)
99
+ end
100
+
101
+ def file_count_not_larger_than_max?
102
+ @max.nil? || !passes_validation_with_limits(@max + 1)
103
+ end
104
+
105
+ def passes_validation_with_limits(count)
106
+ attach_files(count)
107
+ validate
108
+ detach_files
109
+ is_valid? || add_failure_message_artefact(count)
110
+ end
111
+
112
+ def is_custom_message_valid?
113
+ return true if !@custom_message || (@min&.zero? && @max.nil?)
114
+
115
+ @min.nil? ? attach_files(@max + 1) : attach_files(@min - 1)
116
+ validate
117
+ detach_files
118
+ has_an_error_message_which_is_custom_message?
119
+ end
120
+
121
+ def add_failure_message_artefact(count)
122
+ @failure_message_artefacts << { count: count }
123
+ false
124
+ end
125
+ end
126
+ end
127
+ end
@@ -22,19 +22,10 @@ module ActiveStorageValidations
22
22
  protected
23
23
 
24
24
  def attach_file
25
- # We attach blobs instead of io for has_many_attached relation
25
+ # has_many_attached relation
26
26
  @subject.public_send(@attribute_name).attach([dummy_blob])
27
27
  @subject.public_send(@attribute_name)
28
28
  end
29
-
30
- def dummy_blob
31
- ActiveStorage::Blob.create_and_upload!(
32
- io: io,
33
- filename: 'test.png',
34
- content_type: 'image/png',
35
- service_name: 'test'
36
- )
37
- end
38
29
  end
39
30
  end
40
31
  end
@@ -3,6 +3,7 @@
3
3
  require 'active_storage_validations/matchers/aspect_ratio_validator_matcher'
4
4
  require 'active_storage_validations/matchers/attached_validator_matcher'
5
5
  require 'active_storage_validations/matchers/processable_image_validator_matcher'
6
+ require 'active_storage_validations/matchers/limit_validator_matcher'
6
7
  require 'active_storage_validations/matchers/content_type_validator_matcher'
7
8
  require 'active_storage_validations/matchers/dimension_validator_matcher'
8
9
  require 'active_storage_validations/matchers/size_validator_matcher'
@@ -25,21 +26,9 @@ module ActiveStorageValidations
25
26
  end
26
27
 
27
28
  def self.mock_metadata(attachment, width, height)
28
- if Rails.gem_version >= Gem::Version.new('6.0.0')
29
- # Mock the Metadata class for rails 6
30
- mock = OpenStruct.new(metadata: { width: width, height: height })
31
- stub_method(ActiveStorageValidations::Metadata, :new, mock) do
32
- yield
33
- end
34
- else
35
- # Stub the metadata analysis for rails 5
36
- stub_method(attachment, :analyze, true) do
37
- stub_method(attachment, :analyzed?, true) do
38
- stub_method(attachment, :metadata, { width: width, height: height }) do
39
- yield
40
- end
41
- end
42
- end
29
+ mock = Struct.new(:metadata).new({ width: width, height: height })
30
+ stub_method(ActiveStorageValidations::Metadata, :new, mock) do
31
+ yield
43
32
  end
44
33
  end
45
34
  end
@@ -1,5 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'concerns/loggable'
4
+
1
5
  module ActiveStorageValidations
2
6
  class Metadata
7
+ include Loggable
8
+
3
9
  class InvalidImageError < StandardError; end
4
10
 
5
11
  attr_reader :file
@@ -62,11 +68,7 @@ module ActiveStorageValidations
62
68
  if is_string || file.is_a?(ActiveStorage::Blob)
63
69
  blob =
64
70
  if is_string
65
- if Rails.gem_version < Gem::Version.new('6.1.0')
66
- ActiveStorage::Blob.find_signed(file)
67
- else
68
- ActiveStorage::Blob.find_signed!(file)
69
- end
71
+ ActiveStorage::Blob.find_signed!(file)
70
72
  else
71
73
  file
72
74
  end
@@ -174,10 +176,5 @@ module ActiveStorageValidations
174
176
  raise "Something wrong with params."
175
177
  end
176
178
  end
177
-
178
- def logger
179
- Rails.logger
180
- end
181
-
182
179
  end
183
180
  end
@@ -1,49 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/active_storageable.rb'
3
4
  require_relative 'concerns/errorable.rb'
5
+ require_relative 'concerns/metadatable.rb'
4
6
  require_relative 'concerns/symbolizable.rb'
5
- require_relative 'metadata.rb'
6
7
 
7
8
  module ActiveStorageValidations
8
9
  class ProcessableImageValidator < ActiveModel::EachValidator # :nodoc
9
- include OptionProcUnfolding
10
+ include ActiveStorageable
10
11
  include Errorable
12
+ include Metadatable
11
13
  include Symbolizable
12
14
 
13
15
  ERROR_TYPES = %i[
14
16
  image_not_processable
15
17
  ].freeze
16
18
 
17
- if Rails.gem_version >= Gem::Version.new('6.0.0')
18
- def validate_each(record, attribute, _value)
19
- return true unless record.send(attribute).attached?
20
-
21
- changes = record.attachment_changes[attribute.to_s]
22
- return true if changes.blank?
23
-
24
- files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
25
-
26
- files.each do |file|
27
- if !Metadata.new(file).valid?
28
- errors_options = initialize_error_options(options, file)
29
- add_error(record, attribute, ERROR_TYPES.first , **errors_options) unless Metadata.new(file).valid?
30
- end
31
- end
32
- end
33
- else
34
- # Rails 5
35
- def validate_each(record, attribute, _value)
36
- return true unless record.send(attribute).attached?
37
-
38
- files = Array.wrap(record.send(attribute))
39
-
40
- files.each do |file|
41
- if !Metadata.new(file).valid?
42
- errors_options = initialize_error_options(options, file)
43
- add_error(record, attribute, ERROR_TYPES.first , **errors_options) unless Metadata.new(file).valid?
44
- end
45
- end
46
- end
19
+ def validate_each(record, attribute, _value)
20
+ return if no_attachments?(record, attribute)
21
+
22
+ validate_changed_files_from_metadata(record, attribute)
23
+ end
24
+
25
+ private
26
+
27
+ def is_valid?(record, attribute, attachable, metadata)
28
+ return if !metadata.empty?
29
+
30
+ errors_options = initialize_error_options(options, attachable)
31
+ add_error(record, attribute, ERROR_TYPES.first , **errors_options)
47
32
  end
48
33
  end
49
34
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'concerns/errorable.rb'
4
- require_relative 'concerns/symbolizable.rb'
5
3
  require_relative 'base_size_validator.rb'
6
4
 
7
5
  module ActiveStorageValidations
@@ -15,12 +13,11 @@ module ActiveStorageValidations
15
13
  ].freeze
16
14
 
17
15
  def validate_each(record, attribute, _value)
18
- return true unless record.send(attribute).attached?
16
+ return if no_attachments?(record, attribute)
19
17
 
20
- files = Array.wrap(record.send(attribute))
21
18
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
22
19
 
23
- files.each do |file|
20
+ attached_files(record, attribute).each do |file|
24
21
  next if is_valid?(file.blob.byte_size, flat_options)
25
22
 
26
23
  errors_options = initialize_error_options(options, file)
@@ -31,7 +28,6 @@ module ActiveStorageValidations
31
28
  error_type = "file_size_not_#{keys.first}".to_sym
32
29
 
33
30
  add_error(record, attribute, error_type, **errors_options)
34
- break
35
31
  end
36
32
  end
37
33
  end