active_storage_validations 1.0.4 → 1.1.4

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +126 -48
  3. data/config/locales/de.yml +6 -1
  4. data/config/locales/en.yml +5 -1
  5. data/config/locales/es.yml +6 -1
  6. data/config/locales/fr.yml +6 -1
  7. data/config/locales/it.yml +6 -1
  8. data/config/locales/ja.yml +6 -1
  9. data/config/locales/nl.yml +6 -1
  10. data/config/locales/pl.yml +6 -1
  11. data/config/locales/pt-BR.yml +6 -1
  12. data/config/locales/ru.yml +7 -2
  13. data/config/locales/sv.yml +23 -0
  14. data/config/locales/tr.yml +6 -1
  15. data/config/locales/uk.yml +6 -1
  16. data/config/locales/vi.yml +6 -1
  17. data/config/locales/zh-CN.yml +6 -1
  18. data/lib/active_storage_validations/aspect_ratio_validator.rb +57 -27
  19. data/lib/active_storage_validations/attached_validator.rb +20 -5
  20. data/lib/active_storage_validations/concerns/errorable.rb +38 -0
  21. data/lib/active_storage_validations/concerns/symbolizable.rb +12 -0
  22. data/lib/active_storage_validations/content_type_validator.rb +47 -7
  23. data/lib/active_storage_validations/dimension_validator.rb +61 -30
  24. data/lib/active_storage_validations/limit_validator.rb +49 -5
  25. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +128 -0
  26. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +54 -23
  27. data/lib/active_storage_validations/matchers/concerns/active_storageable.rb +17 -0
  28. data/lib/active_storage_validations/matchers/concerns/allow_blankable.rb +26 -0
  29. data/lib/active_storage_validations/matchers/concerns/contextable.rb +35 -0
  30. data/lib/active_storage_validations/matchers/concerns/messageable.rb +26 -0
  31. data/lib/active_storage_validations/matchers/concerns/rspecable.rb +25 -0
  32. data/lib/active_storage_validations/matchers/concerns/validatable.rb +48 -0
  33. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +75 -32
  34. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +99 -52
  35. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +96 -31
  36. data/lib/active_storage_validations/matchers.rb +1 -0
  37. data/lib/active_storage_validations/metadata.rb +42 -28
  38. data/lib/active_storage_validations/processable_image_validator.rb +16 -10
  39. data/lib/active_storage_validations/size_validator.rb +32 -9
  40. data/lib/active_storage_validations/version.rb +1 -1
  41. data/lib/active_storage_validations.rb +3 -0
  42. metadata +29 -4
@@ -1,5 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/active_storageable.rb'
4
+ require_relative 'concerns/allow_blankable.rb'
5
+ require_relative 'concerns/contextable.rb'
6
+ require_relative 'concerns/messageable.rb'
7
+ require_relative 'concerns/rspecable.rb'
8
+ require_relative 'concerns/validatable.rb'
9
+
3
10
  module ActiveStorageValidations
4
11
  module Matchers
5
12
  def validate_dimensions_of(name)
@@ -7,14 +14,35 @@ module ActiveStorageValidations
7
14
  end
8
15
 
9
16
  class DimensionValidatorMatcher
17
+ include ActiveStorageable
18
+ include AllowBlankable
19
+ include Contextable
20
+ include Messageable
21
+ include Rspecable
22
+ include Validatable
23
+
10
24
  def initialize(attribute_name)
25
+ initialize_allow_blankable
26
+ initialize_contextable
27
+ initialize_messageable
28
+ initialize_rspecable
11
29
  @attribute_name = attribute_name
12
30
  @width_min = @width_max = @height_min = @height_max = nil
13
- @custom_message = nil
14
31
  end
15
32
 
16
33
  def description
17
- "validate image dimensions of #{@attribute_name}"
34
+ "validate the image dimensions of :#{@attribute_name}"
35
+ end
36
+
37
+ def failure_message
38
+ message = ["is expected to validate dimensions of :#{@attribute_name}"]
39
+ build_failure_message(message)
40
+ message.join("\n")
41
+ end
42
+
43
+ def width(width)
44
+ @width_min = @width_max = width
45
+ self
18
46
  end
19
47
 
20
48
  def width_min(width)
@@ -27,13 +55,13 @@ module ActiveStorageValidations
27
55
  self
28
56
  end
29
57
 
30
- def with_message(message)
31
- @custom_message = message
58
+ def width_between(range)
59
+ @width_min, @width_max = range.first, range.last
32
60
  self
33
61
  end
34
62
 
35
- def width(width)
36
- @width_min = @width_max = width
63
+ def height(height)
64
+ @height_min = @height_max = height
37
65
  self
38
66
  end
39
67
 
@@ -47,42 +75,40 @@ module ActiveStorageValidations
47
75
  self
48
76
  end
49
77
 
50
- def width_between(range)
51
- @width_min, @width_max = range.first, range.last
52
- self
53
- end
54
-
55
78
  def height_between(range)
56
79
  @height_min, @height_max = range.first, range.last
57
80
  self
58
81
  end
59
82
 
60
- def height(height)
61
- @height_min = @height_max = height
62
- self
63
- end
64
-
65
83
  def matches?(subject)
66
84
  @subject = subject.is_a?(Class) ? subject.new : subject
67
- responds_to_methods &&
68
- width_not_smaller_than_min? && width_larger_than_min? && width_smaller_than_max? && width_not_larger_than_max? && width_equals? &&
69
- height_not_smaller_than_min? && height_larger_than_min? && height_smaller_than_max? && height_not_larger_than_max? && height_equals?
70
- end
71
85
 
72
- def failure_message
73
- <<~MESSAGE
74
- is expected to validate dimensions of #{@attribute_name}
75
- width between #{@width_min} and #{@width_max}
76
- height between #{@height_min} and #{@height_max}
77
- MESSAGE
86
+ is_a_valid_active_storage_attribute? &&
87
+ is_context_valid? &&
88
+ is_allowing_blank? &&
89
+ is_custom_message_valid? &&
90
+ width_not_smaller_than_min? &&
91
+ width_larger_than_min? &&
92
+ width_smaller_than_max? &&
93
+ width_not_larger_than_max? &&
94
+ width_equals? &&
95
+ height_not_smaller_than_min? &&
96
+ height_larger_than_min? &&
97
+ height_smaller_than_max? &&
98
+ height_not_larger_than_max? &&
99
+ height_equals?
78
100
  end
79
101
 
80
102
  protected
81
103
 
82
- def responds_to_methods
83
- @subject.respond_to?(@attribute_name) &&
84
- @subject.public_send(@attribute_name).respond_to?(:attach) &&
85
- @subject.public_send(@attribute_name).respond_to?(:detach)
104
+ def build_failure_message(message)
105
+ return unless @failure_message_artefacts.present?
106
+
107
+ message << " but there seem to have issues with the matcher methods you used, since:"
108
+ @failure_message_artefacts.each do |error_case|
109
+ message << " validation failed when provided with a #{error_case[:width]}x#{error_case[:height]}px test image"
110
+ end
111
+ message << " whereas it should have passed"
86
112
  end
87
113
 
88
114
  def valid_width
@@ -94,62 +120,83 @@ module ActiveStorageValidations
94
120
  end
95
121
 
96
122
  def width_not_smaller_than_min?
97
- @width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height, 'width')
123
+ @width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height)
98
124
  end
99
125
 
100
126
  def width_larger_than_min?
101
- @width_min.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_min + 1, valid_height, 'width')
127
+ @width_min.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_min + 1, valid_height)
102
128
  end
103
129
 
104
130
  def width_smaller_than_max?
105
- @width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height, 'width')
131
+ @width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height)
106
132
  end
107
133
 
108
134
  def width_not_larger_than_max?
109
- @width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height, 'width')
135
+ @width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height)
110
136
  end
111
137
 
112
138
  def width_equals?
113
- @width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height, 'width')
139
+ @width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height)
114
140
  end
115
141
 
116
142
  def height_not_smaller_than_min?
117
- @height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1, 'height')
143
+ @height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1)
118
144
  end
119
145
 
120
146
  def height_larger_than_min?
121
- @height_min.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_min + 1, 'height')
147
+ @height_min.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_min + 1)
122
148
  end
123
149
 
124
150
  def height_smaller_than_max?
125
- @height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1, 'height')
151
+ @height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1)
126
152
  end
127
153
 
128
154
  def height_not_larger_than_max?
129
- @height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1, 'height')
155
+ @height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1)
130
156
  end
131
157
 
132
158
  def height_equals?
133
- @height_min.nil? || @height_min != @height_max || passes_validation_with_dimensions(valid_width, @height_min, 'height')
159
+ @height_min.nil? || @height_min != @height_max || passes_validation_with_dimensions(valid_width, @height_min)
134
160
  end
135
161
 
136
- def passes_validation_with_dimensions(width, height, check)
137
- @subject.public_send(@attribute_name).attach attachment_for(width, height)
162
+ def passes_validation_with_dimensions(width, height)
163
+ mock_dimensions_for(attach_file, width, height) do
164
+ validate
165
+ is_valid? || add_failure_message_artefact(width, height)
166
+ end
167
+ end
168
+
169
+ def add_failure_message_artefact(width, height)
170
+ @failure_message_artefacts << { width: width, height: height }
171
+ false
172
+ end
173
+
174
+ def is_custom_message_valid?
175
+ return true unless @custom_message
138
176
 
139
- attachment = @subject.public_send(@attribute_name)
177
+ mock_dimensions_for(attach_file, -1, -1) do
178
+ validate
179
+ has_an_error_message_which_is_custom_message?
180
+ end
181
+ end
182
+
183
+ def mock_dimensions_for(attachment, width, height)
140
184
  Matchers.mock_metadata(attachment, width, height) do
141
- @subject.validate
142
- exclude_error_message = @custom_message || "dimension_#{check}"
143
- @subject.errors.details[@attribute_name].none? do |error|
144
- error[:error].to_s.include?(exclude_error_message) ||
145
- error[:error].to_s.include?("dimension_min") ||
146
- error[:error].to_s.include?("dimension_max")
147
- end
185
+ yield
148
186
  end
149
187
  end
150
188
 
151
- def attachment_for(width, height)
152
- { io: Tempfile.new('Hello world!'), filename: 'test.png', content_type: 'image/png' }
189
+ def attach_file
190
+ @subject.public_send(@attribute_name).attach(dummy_file)
191
+ @subject.public_send(@attribute_name)
192
+ end
193
+
194
+ def dummy_file
195
+ {
196
+ io: Tempfile.new('Hello world!'),
197
+ filename: 'test.png',
198
+ content_type: 'image/png'
199
+ }
153
200
  end
154
201
  end
155
202
  end
@@ -2,6 +2,14 @@
2
2
 
3
3
  # Big thank you to the paperclip validation matchers:
4
4
  # https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_size_matcher.rb
5
+
6
+ require_relative 'concerns/active_storageable.rb'
7
+ require_relative 'concerns/allow_blankable.rb'
8
+ require_relative 'concerns/contextable.rb'
9
+ require_relative 'concerns/messageable.rb'
10
+ require_relative 'concerns/rspecable.rb'
11
+ require_relative 'concerns/validatable.rb'
12
+
5
13
  module ActiveStorageValidations
6
14
  module Matchers
7
15
  def validate_size_of(name)
@@ -9,85 +17,142 @@ module ActiveStorageValidations
9
17
  end
10
18
 
11
19
  class SizeValidatorMatcher
20
+ include ActiveStorageable
21
+ include AllowBlankable
22
+ include Contextable
23
+ include Messageable
24
+ include Rspecable
25
+ include Validatable
26
+
12
27
  def initialize(attribute_name)
28
+ initialize_allow_blankable
29
+ initialize_contextable
30
+ initialize_messageable
31
+ initialize_rspecable
13
32
  @attribute_name = attribute_name
14
- @low = @high = nil
33
+ @min = @max = nil
15
34
  end
16
35
 
17
36
  def description
18
- "validate file size of #{@attribute_name}"
37
+ "validate file size of :#{@attribute_name}"
38
+ end
39
+
40
+ def failure_message
41
+ message = ["is expected to validate file size of :#{@attribute_name}"]
42
+ build_failure_message(message)
43
+ message.join("\n")
19
44
  end
20
45
 
21
46
  def less_than(size)
22
- @high = size - 1.byte
47
+ @max = size - 1.byte
23
48
  self
24
49
  end
25
50
 
26
51
  def less_than_or_equal_to(size)
27
- @high = size
52
+ @max = size
28
53
  self
29
54
  end
30
55
 
31
56
  def greater_than(size)
32
- @low = size + 1.byte
57
+ @min = size + 1.byte
33
58
  self
34
59
  end
35
60
 
36
61
  def greater_than_or_equal_to(size)
37
- @low = size
62
+ @min = size
38
63
  self
39
64
  end
40
65
 
41
66
  def between(range)
42
- @low, @high = range.first, range.last
67
+ @min, @max = range.first, range.last
43
68
  self
44
69
  end
45
70
 
46
71
  def matches?(subject)
47
72
  @subject = subject.is_a?(Class) ? subject.new : subject
48
- responds_to_methods && lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
73
+
74
+ is_a_valid_active_storage_attribute? &&
75
+ is_context_valid? &&
76
+ is_allowing_blank? &&
77
+ is_custom_message_valid? &&
78
+ not_lower_than_min? &&
79
+ higher_than_min? &&
80
+ lower_than_max? &&
81
+ not_higher_than_max?
49
82
  end
50
83
 
51
- def failure_message
52
- "is expected to validate file size of #{@attribute_name} to be between #{@low} and #{@high} bytes"
84
+ protected
85
+
86
+ def build_failure_message(message)
87
+ return unless @failure_message_artefacts.present?
88
+
89
+ message << " but there seem to have issues with the matcher methods you used, since:"
90
+ @failure_message_artefacts.each do |error_case|
91
+ message << " validation failed when provided with a #{error_case[:size]} bytes test file"
92
+ end
93
+ message << " whereas it should have passed"
53
94
  end
54
95
 
55
- def failure_message_when_negated
56
- "is expected to not validate file size of #{@attribute_name} to be between #{@low} and #{@high} bytes"
96
+ def not_lower_than_min?
97
+ @min.nil? || !passes_validation_with_size(@min - 1)
57
98
  end
58
99
 
59
- protected
100
+ def higher_than_min?
101
+ @min.nil? || passes_validation_with_size(@min + 1)
102
+ end
60
103
 
61
- def responds_to_methods
62
- @subject.respond_to?(@attribute_name) &&
63
- @subject.public_send(@attribute_name).respond_to?(:attach) &&
64
- @subject.public_send(@attribute_name).respond_to?(:detach)
104
+ def lower_than_max?
105
+ @max.nil? || @max == Float::INFINITY || passes_validation_with_size(@max - 1)
65
106
  end
66
107
 
67
- def lower_than_low?
68
- @low.nil? || !passes_validation_with_size(@low - 1)
108
+ def not_higher_than_max?
109
+ @max.nil? || @max == Float::INFINITY || !passes_validation_with_size(@max + 1)
69
110
  end
70
111
 
71
- def higher_than_low?
72
- @low.nil? || passes_validation_with_size(@low + 1)
112
+ def passes_validation_with_size(size)
113
+ mock_size_for(io, size) do
114
+ attach_file
115
+ validate
116
+ is_valid? || add_failure_message_artefact(size)
117
+ end
73
118
  end
74
119
 
75
- def lower_than_high?
76
- @high.nil? || @high == Float::INFINITY || passes_validation_with_size(@high - 1)
120
+ def add_failure_message_artefact(size)
121
+ @failure_message_artefacts << { size: size }
122
+ false
77
123
  end
78
124
 
79
- def higher_than_high?
80
- @high.nil? || @high == Float::INFINITY || !passes_validation_with_size(@high + 1)
125
+ def is_custom_message_valid?
126
+ return true unless @custom_message
127
+
128
+ mock_size_for(io, -1.kilobytes) do
129
+ attach_file
130
+ validate
131
+ has_an_error_message_which_is_custom_message?
132
+ end
81
133
  end
82
134
 
83
- def passes_validation_with_size(new_size)
84
- io = Tempfile.new('Hello world!')
85
- Matchers.stub_method(io, :size, new_size) do
86
- @subject.public_send(@attribute_name).attach(io: io, filename: 'test.png', content_type: 'image/pg')
87
- @subject.validate
88
- @subject.errors.details[@attribute_name].all? { |error| error[:error] != :file_size_out_of_range }
135
+ def mock_size_for(io, size)
136
+ Matchers.stub_method(io, :size, size) do
137
+ yield
89
138
  end
90
139
  end
140
+
141
+ def attach_file
142
+ @subject.public_send(@attribute_name).attach(dummy_file)
143
+ end
144
+
145
+ def dummy_file
146
+ {
147
+ io: io,
148
+ filename: 'test.png',
149
+ content_type: 'image/png'
150
+ }
151
+ end
152
+
153
+ def io
154
+ @io ||= Tempfile.new('Hello world!')
155
+ end
91
156
  end
92
157
  end
93
158
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_storage_validations/matchers/aspect_ratio_validator_matcher'
3
4
  require 'active_storage_validations/matchers/attached_validator_matcher'
4
5
  require 'active_storage_validations/matchers/content_type_validator_matcher'
5
6
  require 'active_storage_validations/matchers/dimension_validator_matcher'
@@ -4,29 +4,18 @@ module ActiveStorageValidations
4
4
 
5
5
  attr_reader :file
6
6
 
7
+ DEFAULT_IMAGE_PROCESSOR = :mini_magick.freeze
8
+
7
9
  def initialize(file)
8
10
  require_image_processor
9
11
  @file = file
10
12
  end
11
13
 
12
- def image_processor
13
- Rails.application.config.active_storage.variant_processor
14
- end
15
-
16
- def exception_class
17
- if image_processor == :vips && defined?(Vips)
18
- Vips::Error
19
- elsif defined?(MiniMagick)
20
- MiniMagick::Error
21
- end
22
- end
23
-
24
- def require_image_processor
25
- if image_processor == :vips
26
- require 'vips' unless defined?(Vips)
27
- else
28
- require 'mini_magick' unless defined?(MiniMagick)
29
- end
14
+ def valid?
15
+ read_image
16
+ true
17
+ rescue InvalidImageError
18
+ false
30
19
  end
31
20
 
32
21
  def metadata
@@ -42,14 +31,31 @@ module ActiveStorageValidations
42
31
  {}
43
32
  end
44
33
 
45
- def valid?
46
- read_image
47
- true
48
- rescue InvalidImageError
49
- false
34
+ private
35
+
36
+ def image_processor
37
+ # Rails returns nil for default image processor, because it is set in an after initiliaze callback
38
+ # https://github.com/rails/rails/blob/89d8569abe2564c8187debf32dd3b4e33d6ad983/activestorage/lib/active_storage/engine.rb
39
+ Rails.application.config.active_storage.variant_processor || DEFAULT_IMAGE_PROCESSOR
50
40
  end
51
41
 
52
- private
42
+ def require_image_processor
43
+ case image_processor
44
+ when :vips then require 'vips' unless defined?(Vips)
45
+ when :mini_magick then require 'mini_magick' unless defined?(MiniMagick)
46
+ end
47
+ end
48
+
49
+ def exception_class
50
+ case image_processor
51
+ when :vips then Vips::Error
52
+ when :mini_magick then MiniMagick::Error
53
+ end
54
+ end
55
+
56
+ def vips_image_processor?
57
+ image_processor == :vips
58
+ end
53
59
 
54
60
  def read_image
55
61
  is_string = file.is_a?(String)
@@ -95,8 +101,16 @@ module ActiveStorageValidations
95
101
  end
96
102
 
97
103
  def new_image_from_path(path)
98
- if image_processor == :vips && defined?(Vips) && (Vips::get_suffixes.include?(File.extname(path).downcase) || !Vips::respond_to?(:vips_foreign_get_suffixes))
99
- Vips::Image.new_from_file(path)
104
+ if vips_image_processor? && (Vips::get_suffixes.include?(File.extname(path).downcase) || !Vips::respond_to?(:vips_foreign_get_suffixes))
105
+ begin
106
+ Vips::Image.new_from_file(path)
107
+ rescue exception_class
108
+ # We handle cases where an error is raised when reading the file
109
+ # because Vips can throw errors rather than returning false
110
+ # We stumble upon this issue while reading 0 byte size file
111
+ # https://github.com/janko/image_processing/issues/97
112
+ false
113
+ end
100
114
  elsif defined?(MiniMagick)
101
115
  MiniMagick::Image.new(path)
102
116
  end
@@ -105,13 +119,13 @@ module ActiveStorageValidations
105
119
  def valid_image?(image)
106
120
  return false unless image
107
121
 
108
- image_processor == :vips && image.is_a?(Vips::Image) ? image.avg : image.valid?
122
+ vips_image_processor? && image.is_a?(Vips::Image) ? image.avg : image.valid?
109
123
  rescue exception_class
110
124
  false
111
125
  end
112
126
 
113
127
  def rotated_image?(image)
114
- if image_processor == :vips && image.is_a?(Vips::Image)
128
+ if vips_image_processor? && image.is_a?(Vips::Image)
115
129
  image.get('exif-ifd0-Orientation').include?('Right-top') ||
116
130
  image.get('exif-ifd0-Orientation').include?('Left-bottom')
117
131
  else
@@ -1,10 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/errorable.rb'
4
+ require_relative 'concerns/symbolizable.rb'
3
5
  require_relative 'metadata.rb'
4
6
 
5
7
  module ActiveStorageValidations
6
8
  class ProcessableImageValidator < ActiveModel::EachValidator # :nodoc
7
9
  include OptionProcUnfolding
10
+ include Errorable
11
+ include Symbolizable
12
+
13
+ ERROR_TYPES = %i[
14
+ image_not_processable
15
+ ].freeze
8
16
 
9
17
  if Rails.gem_version >= Gem::Version.new('6.0.0')
10
18
  def validate_each(record, attribute, _value)
@@ -16,7 +24,10 @@ module ActiveStorageValidations
16
24
  files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
17
25
 
18
26
  files.each do |file|
19
- add_error(record, attribute, :image_not_processable) unless Metadata.new(file).valid?
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
20
31
  end
21
32
  end
22
33
  else
@@ -27,17 +38,12 @@ module ActiveStorageValidations
27
38
  files = Array.wrap(record.send(attribute))
28
39
 
29
40
  files.each do |file|
30
- add_error(record, attribute, :image_not_processable) unless Metadata.new(file).valid?
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
31
45
  end
32
46
  end
33
47
  end
34
-
35
- private
36
-
37
- def add_error(record, attribute, default_message)
38
- message = options[:message].presence || default_message
39
- return if record.errors.added?(attribute, message)
40
- record.errors.add(attribute, message)
41
- end
42
48
  end
43
49
  end
@@ -1,16 +1,35 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/errorable.rb'
4
+ require_relative 'concerns/symbolizable.rb'
5
+
3
6
  module ActiveStorageValidations
4
7
  class SizeValidator < ActiveModel::EachValidator # :nodoc:
5
8
  include OptionProcUnfolding
9
+ include Errorable
10
+ include Symbolizable
6
11
 
7
12
  delegate :number_to_human_size, to: ActiveSupport::NumberHelper
8
13
 
9
- AVAILABLE_CHECKS = %i[less_than less_than_or_equal_to greater_than greater_than_or_equal_to between].freeze
14
+ AVAILABLE_CHECKS = %i[
15
+ less_than
16
+ less_than_or_equal_to
17
+ greater_than
18
+ greater_than_or_equal_to
19
+ between
20
+ ].freeze
21
+ ERROR_TYPES = %i[
22
+ file_size_not_less_than
23
+ file_size_not_less_than_or_equal_to
24
+ file_size_not_greater_than
25
+ file_size_not_greater_than_or_equal_to
26
+ file_size_not_between
27
+ ].freeze
10
28
 
11
29
  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'
30
+ unless AVAILABLE_CHECKS.one? { |argument| options.key?(argument) }
31
+ raise ArgumentError, 'You must pass either :less_than(_or_equal_to), :greater_than(_or_equal_to), or :between to the validator'
32
+ end
14
33
  end
15
34
 
16
35
  def validate_each(record, attribute, _value)
@@ -18,24 +37,28 @@ module ActiveStorageValidations
18
37
  return true unless record.send(attribute).attached?
19
38
 
20
39
  files = Array.wrap(record.send(attribute))
21
-
22
- errors_options = {}
23
- errors_options[:message] = options[:message] if options[:message].present?
24
40
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
25
41
 
26
42
  files.each do |file|
27
- next if content_size_valid?(file.blob.byte_size, flat_options)
43
+ next if is_valid?(file.blob.byte_size, flat_options)
28
44
 
45
+ errors_options = initialize_error_options(options, file)
29
46
  errors_options[:file_size] = number_to_human_size(file.blob.byte_size)
30
47
  errors_options[:min_size] = number_to_human_size(min_size(flat_options))
31
48
  errors_options[:max_size] = number_to_human_size(max_size(flat_options))
49
+ keys = AVAILABLE_CHECKS & flat_options.keys
50
+ error_type = "file_size_not_#{keys.first}".to_sym
32
51
 
33
- record.errors.add(attribute, :file_size_out_of_range, **errors_options)
52
+ add_error(record, attribute, error_type, **errors_options)
34
53
  break
35
54
  end
36
55
  end
37
56
 
38
- def content_size_valid?(file_size, flat_options)
57
+ private
58
+
59
+ def is_valid?(file_size, flat_options)
60
+ return false if file_size < 0
61
+
39
62
  if flat_options[:between].present?
40
63
  flat_options[:between].include?(file_size)
41
64
  elsif flat_options[:less_than].present?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageValidations
4
- VERSION = '1.0.4'
4
+ VERSION = '1.1.4'
5
5
  end