active_storage_validations 1.0.4 → 1.1.4

Sign up to get free protection for your applications and to get access to all the features.
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