active_storage_validations 1.1.3 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +133 -69
  3. data/config/locales/da.yml +33 -0
  4. data/config/locales/de.yml +5 -0
  5. data/config/locales/en.yml +5 -0
  6. data/config/locales/es.yml +5 -0
  7. data/config/locales/fr.yml +5 -0
  8. data/config/locales/it.yml +5 -0
  9. data/config/locales/ja.yml +5 -0
  10. data/config/locales/nl.yml +5 -0
  11. data/config/locales/pl.yml +5 -0
  12. data/config/locales/pt-BR.yml +5 -0
  13. data/config/locales/ru.yml +5 -0
  14. data/config/locales/sv.yml +10 -1
  15. data/config/locales/tr.yml +5 -0
  16. data/config/locales/uk.yml +5 -0
  17. data/config/locales/vi.yml +5 -0
  18. data/config/locales/zh-CN.yml +5 -0
  19. data/lib/active_storage_validations/aspect_ratio_validator.rb +47 -22
  20. data/lib/active_storage_validations/attached_validator.rb +12 -3
  21. data/lib/active_storage_validations/base_size_validator.rb +66 -0
  22. data/lib/active_storage_validations/concerns/errorable.rb +38 -0
  23. data/lib/active_storage_validations/concerns/symbolizable.rb +8 -6
  24. data/lib/active_storage_validations/content_type_validator.rb +41 -6
  25. data/lib/active_storage_validations/dimension_validator.rb +15 -15
  26. data/lib/active_storage_validations/limit_validator.rb +44 -7
  27. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
  28. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +25 -36
  29. data/lib/active_storage_validations/matchers/base_size_validator_matcher.rb +134 -0
  30. data/lib/active_storage_validations/matchers/concerns/active_storageable.rb +17 -0
  31. data/lib/active_storage_validations/matchers/concerns/allow_blankable.rb +26 -0
  32. data/lib/active_storage_validations/matchers/concerns/attachable.rb +48 -0
  33. data/lib/active_storage_validations/matchers/concerns/contextable.rb +47 -0
  34. data/lib/active_storage_validations/matchers/concerns/messageable.rb +26 -0
  35. data/lib/active_storage_validations/matchers/concerns/rspecable.rb +25 -0
  36. data/lib/active_storage_validations/matchers/concerns/validatable.rb +11 -10
  37. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +44 -27
  38. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +67 -59
  39. data/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb +78 -0
  40. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +8 -126
  41. data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +40 -0
  42. data/lib/active_storage_validations/matchers.rb +3 -0
  43. data/lib/active_storage_validations/metadata.rb +60 -28
  44. data/lib/active_storage_validations/processable_image_validator.rb +14 -5
  45. data/lib/active_storage_validations/size_validator.rb +7 -51
  46. data/lib/active_storage_validations/total_size_validator.rb +49 -0
  47. data/lib/active_storage_validations/version.rb +1 -1
  48. data/lib/active_storage_validations.rb +3 -2
  49. metadata +38 -39
  50. data/lib/active_storage_validations/error_handler.rb +0 -21
@@ -1,24 +1,50 @@
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/attachable'
6
+ require_relative 'concerns/contextable.rb'
7
+ require_relative 'concerns/messageable.rb'
8
+ require_relative 'concerns/rspecable.rb'
3
9
  require_relative 'concerns/validatable.rb'
4
10
 
5
11
  module ActiveStorageValidations
6
12
  module Matchers
7
- def validate_dimensions_of(name)
8
- DimensionValidatorMatcher.new(name)
13
+ def validate_dimensions_of(attribute_name)
14
+ DimensionValidatorMatcher.new(attribute_name)
9
15
  end
10
16
 
11
17
  class DimensionValidatorMatcher
18
+ include ActiveStorageable
19
+ include AllowBlankable
20
+ include Attachable
21
+ include Contextable
22
+ include Messageable
23
+ include Rspecable
12
24
  include Validatable
13
25
 
14
26
  def initialize(attribute_name)
27
+ initialize_allow_blankable
28
+ initialize_contextable
29
+ initialize_messageable
30
+ initialize_rspecable
15
31
  @attribute_name = attribute_name
16
32
  @width_min = @width_max = @height_min = @height_max = nil
17
- @custom_message = nil
18
33
  end
19
34
 
20
35
  def description
21
- "validate image dimensions of #{@attribute_name}"
36
+ "validate the image dimensions of :#{@attribute_name}"
37
+ end
38
+
39
+ def failure_message
40
+ message = ["is expected to validate dimensions of :#{@attribute_name}"]
41
+ build_failure_message(message)
42
+ message.join("\n")
43
+ end
44
+
45
+ def width(width)
46
+ @width_min = @width_max = width
47
+ self
22
48
  end
23
49
 
24
50
  def width_min(width)
@@ -31,13 +57,13 @@ module ActiveStorageValidations
31
57
  self
32
58
  end
33
59
 
34
- def with_message(message)
35
- @custom_message = message
60
+ def width_between(range)
61
+ @width_min, @width_max = range.first, range.last
36
62
  self
37
63
  end
38
64
 
39
- def width(width)
40
- @width_min = @width_max = width
65
+ def height(height)
66
+ @height_min = @height_max = height
41
67
  self
42
68
  end
43
69
 
@@ -51,25 +77,18 @@ module ActiveStorageValidations
51
77
  self
52
78
  end
53
79
 
54
- def width_between(range)
55
- @width_min, @width_max = range.first, range.last
56
- self
57
- end
58
-
59
80
  def height_between(range)
60
81
  @height_min, @height_max = range.first, range.last
61
82
  self
62
83
  end
63
84
 
64
- def height(height)
65
- @height_min = @height_max = height
66
- self
67
- end
68
-
69
85
  def matches?(subject)
70
86
  @subject = subject.is_a?(Class) ? subject.new : subject
71
87
 
72
- responds_to_methods &&
88
+ is_a_valid_active_storage_attribute? &&
89
+ is_context_valid? &&
90
+ is_allowing_blank? &&
91
+ is_custom_message_valid? &&
73
92
  width_not_smaller_than_min? &&
74
93
  width_larger_than_min? &&
75
94
  width_smaller_than_max? &&
@@ -79,24 +98,19 @@ module ActiveStorageValidations
79
98
  height_larger_than_min? &&
80
99
  height_smaller_than_max? &&
81
100
  height_not_larger_than_max? &&
82
- height_equals? &&
83
- validate_custom_message?
84
- end
85
-
86
- def failure_message
87
- <<~MESSAGE
88
- is expected to validate dimensions of #{@attribute_name}
89
- width between #{@width_min} and #{@width_max}
90
- height between #{@height_min} and #{@height_max}
91
- MESSAGE
101
+ height_equals?
92
102
  end
93
103
 
94
104
  protected
95
105
 
96
- def responds_to_methods
97
- @subject.respond_to?(@attribute_name) &&
98
- @subject.public_send(@attribute_name).respond_to?(:attach) &&
99
- @subject.public_send(@attribute_name).respond_to?(:detach)
106
+ def build_failure_message(message)
107
+ return unless @failure_message_artefacts.present?
108
+
109
+ message << " but there seem to have issues with the matcher methods you used, since:"
110
+ @failure_message_artefacts.each do |error_case|
111
+ message << " validation failed when provided with a #{error_case[:width]}x#{error_case[:height]}px test image"
112
+ end
113
+ message << " whereas it should have passed"
100
114
  end
101
115
 
102
116
  def valid_width
@@ -108,57 +122,64 @@ module ActiveStorageValidations
108
122
  end
109
123
 
110
124
  def width_not_smaller_than_min?
111
- @width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height, 'width')
125
+ @width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height)
112
126
  end
113
127
 
114
128
  def width_larger_than_min?
115
- @width_min.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_min + 1, valid_height, 'width')
129
+ @width_min.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_min + 1, valid_height)
116
130
  end
117
131
 
118
132
  def width_smaller_than_max?
119
- @width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height, 'width')
133
+ @width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height)
120
134
  end
121
135
 
122
136
  def width_not_larger_than_max?
123
- @width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height, 'width')
137
+ @width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height)
124
138
  end
125
139
 
126
140
  def width_equals?
127
- @width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height, 'width')
141
+ @width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height)
128
142
  end
129
143
 
130
144
  def height_not_smaller_than_min?
131
- @height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1, 'height')
145
+ @height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1)
132
146
  end
133
147
 
134
148
  def height_larger_than_min?
135
- @height_min.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_min + 1, 'height')
149
+ @height_min.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_min + 1)
136
150
  end
137
151
 
138
152
  def height_smaller_than_max?
139
- @height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1, 'height')
153
+ @height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1)
140
154
  end
141
155
 
142
156
  def height_not_larger_than_max?
143
- @height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1, 'height')
157
+ @height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1)
144
158
  end
145
159
 
146
160
  def height_equals?
147
- @height_min.nil? || @height_min != @height_max || passes_validation_with_dimensions(valid_width, @height_min, 'height')
161
+ @height_min.nil? || @height_min != @height_max || passes_validation_with_dimensions(valid_width, @height_min)
148
162
  end
149
163
 
150
- def passes_validation_with_dimensions(width, height, check)
164
+ def passes_validation_with_dimensions(width, height)
151
165
  mock_dimensions_for(attach_file, width, height) do
152
166
  validate
153
- is_valid?
167
+ detach_file
168
+ is_valid? || add_failure_message_artefact(width, height)
154
169
  end
155
170
  end
156
171
 
157
- def validate_custom_message?
172
+ def add_failure_message_artefact(width, height)
173
+ @failure_message_artefacts << { width: width, height: height }
174
+ false
175
+ end
176
+
177
+ def is_custom_message_valid?
158
178
  return true unless @custom_message
159
179
 
160
180
  mock_dimensions_for(attach_file, -1, -1) do
161
181
  validate
182
+ detach_file
162
183
  has_an_error_message_which_is_custom_message?
163
184
  end
164
185
  end
@@ -168,19 +189,6 @@ module ActiveStorageValidations
168
189
  yield
169
190
  end
170
191
  end
171
-
172
- def attach_file
173
- @subject.public_send(@attribute_name).attach(dummy_file)
174
- @subject.public_send(@attribute_name)
175
- end
176
-
177
- def dummy_file
178
- {
179
- io: Tempfile.new('Hello world!'),
180
- filename: 'test.png',
181
- content_type: 'image/png'
182
- }
183
- end
184
192
  end
185
193
  end
186
194
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'concerns/active_storageable.rb'
4
+ require_relative 'concerns/allow_blankable.rb'
5
+ require_relative 'concerns/attachable.rb'
6
+ require_relative 'concerns/contextable.rb'
7
+ require_relative 'concerns/messageable.rb'
8
+ require_relative 'concerns/rspecable.rb'
9
+ require_relative 'concerns/validatable.rb'
10
+
11
+ module ActiveStorageValidations
12
+ module Matchers
13
+ def validate_processable_image_of(name)
14
+ ProcessableImageValidatorMatcher.new(name)
15
+ end
16
+
17
+ class ProcessableImageValidatorMatcher
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
+ end
33
+
34
+ def description
35
+ "validate that :#{@attribute_name} is a processable image"
36
+ end
37
+
38
+ def failure_message
39
+ "is expected to validate the processable image of :#{@attribute_name}"
40
+ end
41
+
42
+ def matches?(subject)
43
+ @subject = subject.is_a?(Class) ? subject.new : subject
44
+
45
+ is_a_valid_active_storage_attribute? &&
46
+ is_context_valid? &&
47
+ is_custom_message_valid? &&
48
+ is_valid_when_image_processable? &&
49
+ is_invalid_when_image_not_processable?
50
+ end
51
+
52
+ private
53
+
54
+ def is_valid_when_image_processable?
55
+ attach_file(processable_image)
56
+ validate
57
+ detach_file
58
+ is_valid?
59
+ end
60
+
61
+ def is_invalid_when_image_not_processable?
62
+ attach_file(not_processable_image)
63
+ validate
64
+ detach_file
65
+ !is_valid?
66
+ end
67
+
68
+ def is_custom_message_valid?
69
+ return true unless @custom_message
70
+
71
+ attach_file(not_processable_image)
72
+ validate
73
+ detach_file
74
+ has_an_error_message_which_is_custom_message?
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,140 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Big thank you to the paperclip validation matchers:
4
- # https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_size_matcher.rb
5
-
6
- require_relative 'concerns/validatable.rb'
3
+ require_relative 'base_size_validator_matcher'
7
4
 
8
5
  module ActiveStorageValidations
9
6
  module Matchers
10
- def validate_size_of(name)
11
- SizeValidatorMatcher.new(name)
7
+ def validate_size_of(attribute_name)
8
+ SizeValidatorMatcher.new(attribute_name)
12
9
  end
13
10
 
14
- class SizeValidatorMatcher
15
- include Validatable
16
-
17
- def initialize(attribute_name)
18
- @attribute_name = attribute_name
19
- @min = @max = nil
20
- @custom_message = nil
21
- end
22
-
11
+ class SizeValidatorMatcher < BaseSizeValidatorMatcher
23
12
  def description
24
- "validate file size of #{@attribute_name}"
25
- end
26
-
27
- def less_than(size)
28
- @max = size - 1.byte
29
- self
30
- end
31
-
32
- def less_than_or_equal_to(size)
33
- @max = size
34
- self
35
- end
36
-
37
- def greater_than(size)
38
- @min = size + 1.byte
39
- self
40
- end
41
-
42
- def greater_than_or_equal_to(size)
43
- @min = size
44
- self
45
- end
46
-
47
- def between(range)
48
- @min, @max = range.first, range.last
49
- self
50
- end
51
-
52
- def with_message(message)
53
- @custom_message = message
54
- self
55
- end
56
-
57
- def matches?(subject)
58
- @subject = subject.is_a?(Class) ? subject.new : subject
59
-
60
- responds_to_methods &&
61
- not_lower_than_min? &&
62
- higher_than_min? &&
63
- lower_than_max? &&
64
- not_higher_than_max? &&
65
- validate_custom_message?
13
+ "validate file size of :#{@attribute_name}"
66
14
  end
67
15
 
68
16
  def failure_message
69
- "is expected to validate file size of #{@attribute_name} to be between #{@min} and #{@max} bytes"
70
- end
71
-
72
- def failure_message_when_negated
73
- "is expected to not validate file size of #{@attribute_name} to be between #{@min} and #{@max} bytes"
74
- end
75
-
76
- protected
77
-
78
- def responds_to_methods
79
- @subject.respond_to?(@attribute_name) &&
80
- @subject.public_send(@attribute_name).respond_to?(:attach) &&
81
- @subject.public_send(@attribute_name).respond_to?(:detach)
82
- end
83
-
84
- def not_lower_than_min?
85
- @min.nil? || !passes_validation_with_size(@min - 1)
86
- end
87
-
88
- def higher_than_min?
89
- @min.nil? || passes_validation_with_size(@min + 1)
90
- end
91
-
92
- def lower_than_max?
93
- @max.nil? || @max == Float::INFINITY || passes_validation_with_size(@max - 1)
94
- end
95
-
96
- def not_higher_than_max?
97
- @max.nil? || @max == Float::INFINITY || !passes_validation_with_size(@max + 1)
98
- end
99
-
100
- def passes_validation_with_size(size)
101
- mock_size_for(io, size) do
102
- attach_file
103
- validate
104
- is_valid?
105
- end
106
- end
107
-
108
- def validate_custom_message?
109
- return true unless @custom_message
110
-
111
- mock_size_for(io, -1.kilobytes) do
112
- attach_file
113
- validate
114
- has_an_error_message_which_is_custom_message?
115
- end
116
- end
117
-
118
- def mock_size_for(io, size)
119
- Matchers.stub_method(io, :size, size) do
120
- yield
121
- end
122
- end
123
-
124
- def attach_file
125
- @subject.public_send(@attribute_name).attach(dummy_file)
126
- end
127
-
128
- def dummy_file
129
- {
130
- io: io,
131
- filename: 'test.png',
132
- content_type: 'image/png'
133
- }
134
- end
135
-
136
- def io
137
- @io ||= Tempfile.new('Hello world!')
17
+ message = ["is expected to validate file size of :#{@attribute_name}"]
18
+ build_failure_message(message)
19
+ message.join("\n")
138
20
  end
139
21
  end
140
22
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_size_validator_matcher'
4
+
5
+ module ActiveStorageValidations
6
+ module Matchers
7
+ def validate_total_size_of(attribute_name)
8
+ TotalSizeValidatorMatcher.new(attribute_name)
9
+ end
10
+
11
+ class TotalSizeValidatorMatcher < BaseSizeValidatorMatcher
12
+ def description
13
+ "validate total file size of :#{@attribute_name}"
14
+ end
15
+
16
+ def failure_message
17
+ message = ["is expected to validate total file size of :#{@attribute_name}"]
18
+ build_failure_message(message)
19
+ message.join("\n")
20
+ end
21
+
22
+ protected
23
+
24
+ def attach_file
25
+ # We attach blobs instead of io for has_many_attached relation
26
+ @subject.public_send(@attribute_name).attach([dummy_blob])
27
+ @subject.public_send(@attribute_name)
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
+ end
39
+ end
40
+ end
@@ -1,9 +1,12 @@
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'
5
+ require 'active_storage_validations/matchers/processable_image_validator_matcher'
4
6
  require 'active_storage_validations/matchers/content_type_validator_matcher'
5
7
  require 'active_storage_validations/matchers/dimension_validator_matcher'
6
8
  require 'active_storage_validations/matchers/size_validator_matcher'
9
+ require 'active_storage_validations/matchers/total_size_validator_matcher'
7
10
 
8
11
  module ActiveStorageValidations
9
12
  module Matchers
@@ -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 initialize 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,23 +101,49 @@ 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? && (supported_vips_suffix?(path) || vips_version_below_8_8? || open_uri_tempfile?(path))
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
103
117
  end
104
118
 
119
+ def supported_vips_suffix?(path)
120
+ Vips::get_suffixes.include?(File.extname(path).downcase)
121
+ end
122
+
123
+ def vips_version_below_8_8?
124
+ # FYI, Vips 8.8 was released in 2019
125
+ # https://github.com/libvips/libvips/releases/tag/v8.8.0
126
+ !Vips::respond_to?(:vips_foreign_get_suffixes)
127
+ end
128
+
129
+ def open_uri_tempfile?(path)
130
+ # When trying to open urls for 'large' images, OpenURI will return a
131
+ # tempfile. That tempfile does not have an extension indicating the type
132
+ # of file. However, Vips will be able to process it anyway.
133
+ # The 'large' file value is derived from OpenUri::Buffer class (> 10ko)
134
+ path.split('/').last.starts_with?("open-uri")
135
+ end
136
+
105
137
  def valid_image?(image)
106
138
  return false unless image
107
139
 
108
- image_processor == :vips && image.is_a?(Vips::Image) ? image.avg : image.valid?
140
+ vips_image_processor? && image.is_a?(Vips::Image) ? image.avg : image.valid?
109
141
  rescue exception_class
110
142
  false
111
143
  end
112
144
 
113
145
  def rotated_image?(image)
114
- if image_processor == :vips && image.is_a?(Vips::Image)
146
+ if vips_image_processor? && image.is_a?(Vips::Image)
115
147
  image.get('exif-ifd0-Orientation').include?('Right-top') ||
116
148
  image.get('exif-ifd0-Orientation').include?('Left-bottom')
117
149
  else
@@ -1,27 +1,33 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/errorable.rb'
3
4
  require_relative 'concerns/symbolizable.rb'
4
5
  require_relative 'metadata.rb'
5
6
 
6
7
  module ActiveStorageValidations
7
8
  class ProcessableImageValidator < ActiveModel::EachValidator # :nodoc
8
9
  include OptionProcUnfolding
9
- include ErrorHandler
10
+ include Errorable
10
11
  include Symbolizable
11
12
 
13
+ ERROR_TYPES = %i[
14
+ image_not_processable
15
+ ].freeze
16
+
12
17
  if Rails.gem_version >= Gem::Version.new('6.0.0')
13
18
  def validate_each(record, attribute, _value)
14
19
  return true unless record.send(attribute).attached?
15
20
 
16
- errors_options = initialize_error_options(options)
17
-
18
21
  changes = record.attachment_changes[attribute.to_s]
19
22
  return true if changes.blank?
20
23
 
21
24
  files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
22
25
 
23
26
  files.each do |file|
24
- add_error(record, attribute, :image_not_processable, **errors_options) 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
25
31
  end
26
32
  end
27
33
  else
@@ -32,7 +38,10 @@ module ActiveStorageValidations
32
38
  files = Array.wrap(record.send(attribute))
33
39
 
34
40
  files.each do |file|
35
- add_error(record, attribute, :image_not_processable, **errors_options) 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
36
45
  end
37
46
  end
38
47
  end