active_storage_validations 1.1.3 → 1.2.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 (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