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.
- checksums.yaml +4 -4
- data/README.md +126 -48
- data/config/locales/de.yml +6 -1
- data/config/locales/en.yml +5 -1
- data/config/locales/es.yml +6 -1
- data/config/locales/fr.yml +6 -1
- data/config/locales/it.yml +6 -1
- data/config/locales/ja.yml +6 -1
- data/config/locales/nl.yml +6 -1
- data/config/locales/pl.yml +6 -1
- data/config/locales/pt-BR.yml +6 -1
- data/config/locales/ru.yml +7 -2
- data/config/locales/sv.yml +23 -0
- data/config/locales/tr.yml +6 -1
- data/config/locales/uk.yml +6 -1
- data/config/locales/vi.yml +6 -1
- data/config/locales/zh-CN.yml +6 -1
- data/lib/active_storage_validations/aspect_ratio_validator.rb +57 -27
- data/lib/active_storage_validations/attached_validator.rb +20 -5
- data/lib/active_storage_validations/concerns/errorable.rb +38 -0
- data/lib/active_storage_validations/concerns/symbolizable.rb +12 -0
- data/lib/active_storage_validations/content_type_validator.rb +47 -7
- data/lib/active_storage_validations/dimension_validator.rb +61 -30
- data/lib/active_storage_validations/limit_validator.rb +49 -5
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +128 -0
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +54 -23
- data/lib/active_storage_validations/matchers/concerns/active_storageable.rb +17 -0
- data/lib/active_storage_validations/matchers/concerns/allow_blankable.rb +26 -0
- data/lib/active_storage_validations/matchers/concerns/contextable.rb +35 -0
- data/lib/active_storage_validations/matchers/concerns/messageable.rb +26 -0
- data/lib/active_storage_validations/matchers/concerns/rspecable.rb +25 -0
- data/lib/active_storage_validations/matchers/concerns/validatable.rb +48 -0
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +75 -32
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +99 -52
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +96 -31
- data/lib/active_storage_validations/matchers.rb +1 -0
- data/lib/active_storage_validations/metadata.rb +42 -28
- data/lib/active_storage_validations/processable_image_validator.rb +16 -10
- data/lib/active_storage_validations/size_validator.rb +32 -9
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +3 -0
- 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
|
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
|
31
|
-
@
|
58
|
+
def width_between(range)
|
59
|
+
@width_min, @width_max = range.first, range.last
|
32
60
|
self
|
33
61
|
end
|
34
62
|
|
35
|
-
def
|
36
|
-
@
|
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
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
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
|
83
|
-
@
|
84
|
-
|
85
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
137
|
-
|
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
|
-
|
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
|
-
|
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
|
152
|
-
|
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
|
-
@
|
33
|
+
@min = @max = nil
|
15
34
|
end
|
16
35
|
|
17
36
|
def description
|
18
|
-
"validate file size of
|
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
|
-
@
|
47
|
+
@max = size - 1.byte
|
23
48
|
self
|
24
49
|
end
|
25
50
|
|
26
51
|
def less_than_or_equal_to(size)
|
27
|
-
@
|
52
|
+
@max = size
|
28
53
|
self
|
29
54
|
end
|
30
55
|
|
31
56
|
def greater_than(size)
|
32
|
-
@
|
57
|
+
@min = size + 1.byte
|
33
58
|
self
|
34
59
|
end
|
35
60
|
|
36
61
|
def greater_than_or_equal_to(size)
|
37
|
-
@
|
62
|
+
@min = size
|
38
63
|
self
|
39
64
|
end
|
40
65
|
|
41
66
|
def between(range)
|
42
|
-
@
|
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
|
-
|
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
|
-
|
52
|
-
|
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
|
56
|
-
|
96
|
+
def not_lower_than_min?
|
97
|
+
@min.nil? || !passes_validation_with_size(@min - 1)
|
57
98
|
end
|
58
99
|
|
59
|
-
|
100
|
+
def higher_than_min?
|
101
|
+
@min.nil? || passes_validation_with_size(@min + 1)
|
102
|
+
end
|
60
103
|
|
61
|
-
def
|
62
|
-
@
|
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
|
68
|
-
@
|
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
|
72
|
-
|
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
|
76
|
-
@
|
120
|
+
def add_failure_message_artefact(size)
|
121
|
+
@failure_message_artefacts << { size: size }
|
122
|
+
false
|
77
123
|
end
|
78
124
|
|
79
|
-
def
|
80
|
-
|
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
|
84
|
-
io
|
85
|
-
|
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
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
99
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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[
|
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
|
-
|
13
|
-
|
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
|
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
|
52
|
+
add_error(record, attribute, error_type, **errors_options)
|
34
53
|
break
|
35
54
|
end
|
36
55
|
end
|
37
56
|
|
38
|
-
|
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?
|