active_storage_validations 0.9.5 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +63 -8
- data/config/locales/en.yml +1 -0
- data/config/locales/pt-BR.yml +14 -14
- data/config/locales/zh-CN.yml +22 -0
- data/lib/active_storage_validations/aspect_ratio_validator.rb +19 -22
- data/lib/active_storage_validations/content_type_validator.rb +15 -9
- data/lib/active_storage_validations/dimension_validator.rb +45 -38
- data/lib/active_storage_validations/limit_validator.rb +12 -10
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +14 -26
- data/lib/active_storage_validations/matchers.rb +1 -1
- data/lib/active_storage_validations/metadata.rb +75 -23
- data/lib/active_storage_validations/option_proc_unfolding.rb +16 -0
- data/lib/active_storage_validations/processable_image_validator.rb +43 -0
- data/lib/active_storage_validations/size_validator.rb +27 -14
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +2 -0
- metadata +94 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 267c083e1e37ff53853d928bf3df0a69b8c9cf0662c3cf744f99cde3d5809125
|
4
|
+
data.tar.gz: 36554892e5c2df7ad812119b6acfae985e4adbe1dd8f7c29699f8f9ba69f0506
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6cbe5d7521afc8680a9df584968c4e3da7c5e1602efc30f60dc6890a54bb9d609daf7e7eaa0924d6d7971f13e9c763e4063c228d14645b8c5261f1702c4a21c9
|
7
|
+
data.tar.gz: 989e058c095e07790f647af2582355dddd715101b6dce10d4b521865362c5857f8df869270ae88e5eb58d20fa698ed470f4b181517ac016439d45f0e30049e21
|
data/README.md
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
[<img src="https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/more_gems.png?raw=true"
|
2
|
+
/>](https://www.railsjazz.com/?utm_source=github&utm_medium=top&utm_campaign=active_storage_validations)
|
3
|
+
|
1
4
|
# Active Storage Validations
|
2
5
|
|
3
6
|
[![MiniTest](https://github.com/igorkasyanchuk/active_storage_validations/workflows/MiniTest/badge.svg)](https://github.com/igorkasyanchuk/active_storage_validations/actions)
|
@@ -16,7 +19,9 @@ This gems doing it for you. Just use `attached: true` or `content_type: 'image/p
|
|
16
19
|
* validates dimension of images/videos
|
17
20
|
* validates number of uploaded files (min/max required)
|
18
21
|
* validates aspect ratio (if square, portrait, landscape, is_16_9, ...)
|
22
|
+
* validates if file can be processed by MiniMagick or Vips
|
19
23
|
* custom error messages
|
24
|
+
* allow procs for dynamic determination of values
|
20
25
|
|
21
26
|
## Usage
|
22
27
|
|
@@ -26,16 +31,18 @@ For example you have a model like this and you want to add validation.
|
|
26
31
|
class User < ApplicationRecord
|
27
32
|
has_one_attached :avatar
|
28
33
|
has_many_attached :photos
|
34
|
+
has_one_attached :image
|
29
35
|
|
30
36
|
validates :name, presence: true
|
31
37
|
|
32
38
|
validates :avatar, attached: true, content_type: 'image/png',
|
33
39
|
dimension: { width: 200, height: 200 }
|
34
|
-
validates :photos, attached: true, content_type: ['image/png', 'image/
|
40
|
+
validates :photos, attached: true, content_type: ['image/png', 'image/jpeg'],
|
35
41
|
dimension: { width: { min: 800, max: 2400 },
|
36
42
|
height: { min: 600, max: 1800 }, message: 'is not given between dimension' }
|
37
43
|
validates :image, attached: true,
|
38
|
-
|
44
|
+
processable_image: true,
|
45
|
+
content_type: ['image/png', 'image/jpeg'],
|
39
46
|
aspect_ratio: :landscape
|
40
47
|
end
|
41
48
|
```
|
@@ -44,13 +51,15 @@ or
|
|
44
51
|
|
45
52
|
```ruby
|
46
53
|
class Project < ApplicationRecord
|
54
|
+
has_one_attached :logo
|
47
55
|
has_one_attached :preview
|
48
56
|
has_one_attached :attachment
|
49
57
|
has_many_attached :documents
|
50
58
|
|
51
59
|
validates :title, presence: true
|
52
60
|
|
53
|
-
validates :
|
61
|
+
validates :logo, attached: true, size: { less_than: 100.megabytes , message: 'is too large' }
|
62
|
+
validates :preview, attached: true, size: { between: 1.kilobyte..100.megabytes , message: 'is not given between size' }
|
54
63
|
validates :attachment, attached: true, content_type: { in: 'application/pdf', message: 'is not a PDF' }
|
55
64
|
validates :documents, limit: { min: 1, max: 3 }
|
56
65
|
end
|
@@ -58,14 +67,15 @@ end
|
|
58
67
|
|
59
68
|
### More examples
|
60
69
|
|
61
|
-
- Content type validation using symbols. In order to infer the correct mime type from the symbol, the types must be registered with `MimeMagic::EXTENSIONS
|
70
|
+
- Content type validation using symbols. In order to infer the correct mime type from the symbol, the types must be registered with `Marcel::EXTENSIONS` (`MimeMagic::EXTENSIONS` for Rails <= 6.1.3).
|
62
71
|
|
63
72
|
```ruby
|
64
73
|
class User < ApplicationRecord
|
65
74
|
has_one_attached :avatar
|
66
75
|
has_many_attached :photos
|
67
76
|
|
68
|
-
validates :avatar, attached: true, content_type: :png #
|
77
|
+
validates :avatar, attached: true, content_type: :png # Marcel::Magic.by_extension(:png).to_s => 'image/png'
|
78
|
+
# Rails <= 6.1.3; MimeMagic.by_extension(:png).to_s => 'image/png'
|
69
79
|
# or
|
70
80
|
validates :photos, attached: true, content_type: [:png, :jpg, :jpeg]
|
71
81
|
# or
|
@@ -117,6 +127,18 @@ class User < ApplicationRecord
|
|
117
127
|
end
|
118
128
|
```
|
119
129
|
|
130
|
+
- Proc Usage:
|
131
|
+
|
132
|
+
Procs can be used instead of values in all the above examples. They will be called on every validation.
|
133
|
+
```ruby
|
134
|
+
class User < ApplicationRecord
|
135
|
+
has_many_attached :proc_files
|
136
|
+
|
137
|
+
validates :proc_files, limit: { max: -> (record) { record.admin? ? 100 : 10 } }
|
138
|
+
end
|
139
|
+
|
140
|
+
```
|
141
|
+
|
120
142
|
## Internationalization (I18n)
|
121
143
|
|
122
144
|
Active Storage Validations uses I18n for error messages. For this, add these keys in your translation file:
|
@@ -144,6 +166,7 @@ en:
|
|
144
166
|
aspect_ratio_not_landscape: "must be a landscape image"
|
145
167
|
aspect_ratio_is_not: "must have an aspect ratio of %{aspect_ratio}"
|
146
168
|
aspect_ratio_unknown: "has an unknown aspect ratio"
|
169
|
+
image_not_processable: "is not a valid image"
|
147
170
|
```
|
148
171
|
|
149
172
|
In some cases, Active Storage Validations provides variables to help you customize messages:
|
@@ -176,6 +199,8 @@ gem 'active_storage_validations'
|
|
176
199
|
|
177
200
|
# Optional, to use :dimension validator or :aspect_ratio validator
|
178
201
|
gem 'mini_magick', '>= 4.9.5'
|
202
|
+
# Or
|
203
|
+
gem 'ruby-vips', '>= 2.1.0'
|
179
204
|
```
|
180
205
|
|
181
206
|
And then execute:
|
@@ -289,9 +314,23 @@ end
|
|
289
314
|
|
290
315
|
To run tests in root folder of gem:
|
291
316
|
|
292
|
-
* `BUNDLE_GEMFILE=gemfiles/rails_5_2.gemfile bundle exec rake test` to run for Rails 5.2
|
293
317
|
* `BUNDLE_GEMFILE=gemfiles/rails_6_0.gemfile bundle exec rake test` to run for Rails 6.0
|
294
318
|
* `BUNDLE_GEMFILE=gemfiles/rails_6_1.gemfile bundle exec rake test` to run for Rails 6.1
|
319
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test` to run for Rails 7.0
|
320
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test` to run for Rails main branch
|
321
|
+
|
322
|
+
Snippet to run in console:
|
323
|
+
|
324
|
+
```
|
325
|
+
BUNDLE_GEMFILE=gemfiles/rails_6_0.gemfile bundle
|
326
|
+
BUNDLE_GEMFILE=gemfiles/rails_6_1.gemfile bundle
|
327
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle
|
328
|
+
BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle
|
329
|
+
BUNDLE_GEMFILE=gemfiles/rails_6_0.gemfile bundle exec rake test
|
330
|
+
BUNDLE_GEMFILE=gemfiles/rails_6_1.gemfile bundle exec rake test
|
331
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test
|
332
|
+
BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test
|
333
|
+
```
|
295
334
|
|
296
335
|
## Known issues
|
297
336
|
|
@@ -344,11 +383,27 @@ You are welcome to contribute.
|
|
344
383
|
- https://github.com/jayshepherd
|
345
384
|
- https://github.com/ohbarye
|
346
385
|
- https://github.com/randsina
|
347
|
-
- https://github.com
|
386
|
+
- https://github.com/vietqhoang
|
387
|
+
- https://github.com/kemenaran
|
388
|
+
- https://github.com/jrmhaig
|
389
|
+
- https://github.com/tagliala
|
390
|
+
- https://github.com/evedovelli
|
391
|
+
- https://github.com/JuanVqz
|
392
|
+
- https://github.com/luiseugenio
|
393
|
+
- https://github.com/equivalent
|
394
|
+
- https://github.com/NARKOZ
|
395
|
+
- https://github.com/stephensolis
|
396
|
+
- https://github.com/kwent
|
397
|
+
= https://github.com/Animesh-Ghosh
|
398
|
+
= https://github.com/gr8bit
|
399
|
+
= https://github.com/codegeek319
|
400
|
+
= https://github.com/clwy-cn
|
401
|
+
= https://github.com/kukicola
|
402
|
+
= https://github.com/sobrinho
|
348
403
|
|
349
404
|
## License
|
350
405
|
|
351
406
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
352
407
|
|
353
408
|
[<img src="https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/more_gems.png?raw=true"
|
354
|
-
/>](https://www.railsjazz.com
|
409
|
+
/>](https://www.railsjazz.com/?utm_source=github&utm_medium=bottom&utm_campaign=active_storage_validations)
|
data/config/locales/en.yml
CHANGED
data/config/locales/pt-BR.yml
CHANGED
@@ -2,21 +2,21 @@ pt-BR:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "tem um tipo de arquivo inválido"
|
5
|
-
file_size_out_of_range: "tamanho %{file_size} está fora da faixa de tamanho válida"
|
6
|
-
limit_out_of_range: "número total está fora do limite"
|
5
|
+
file_size_out_of_range: "tem tamanho %{file_size} e está fora da faixa de tamanho válida"
|
6
|
+
limit_out_of_range: "o número total está fora do limite"
|
7
7
|
image_metadata_missing: "não é uma imagem válida"
|
8
|
-
dimension_min_inclusion: "deve ser maior ou igual a %{width} x %{height}
|
9
|
-
dimension_max_inclusion: "deve ser menor ou igual a %{width} x %{height}
|
10
|
-
dimension_width_inclusion: "
|
11
|
-
dimension_height_inclusion: "
|
12
|
-
dimension_width_greater_than_or_equal_to: "
|
13
|
-
dimension_height_greater_than_or_equal_to: "
|
14
|
-
dimension_width_less_than_or_equal_to: "
|
15
|
-
dimension_height_less_than_or_equal_to: "
|
16
|
-
dimension_width_equal_to: "
|
17
|
-
dimension_height_equal_to: "
|
8
|
+
dimension_min_inclusion: "deve ser maior ou igual a %{width} x %{height} pixels"
|
9
|
+
dimension_max_inclusion: "deve ser menor ou igual a %{width} x %{height} pixels"
|
10
|
+
dimension_width_inclusion: "deve ter largura entre %{min} e %{max} pixels"
|
11
|
+
dimension_height_inclusion: "deve ter altura entre %{min} e %{max} pixels"
|
12
|
+
dimension_width_greater_than_or_equal_to: "deve ter largura maior ou igual a %{length} pixels"
|
13
|
+
dimension_height_greater_than_or_equal_to: "deve ter altura maior ou igual a %{length} pixels"
|
14
|
+
dimension_width_less_than_or_equal_to: "deve ter largura menor ou igual a %{length} pixels"
|
15
|
+
dimension_height_less_than_or_equal_to: "deve ter altura menor ou igual a %{length} pixels"
|
16
|
+
dimension_width_equal_to: "deve ter largura igual a %{length} pixels"
|
17
|
+
dimension_height_equal_to: "deve ter altura igual a %{length} pixels"
|
18
18
|
aspect_ratio_not_square: "não é uma imagem quadrada"
|
19
|
-
aspect_ratio_not_portrait: "não
|
20
|
-
aspect_ratio_not_landscape: "não
|
19
|
+
aspect_ratio_not_portrait: "não está no formato retrato"
|
20
|
+
aspect_ratio_not_landscape: "não está no formato paisagem"
|
21
21
|
aspect_ratio_is_not: "não contém uma proporção de %{aspect_ratio}"
|
22
22
|
aspect_ratio_unknown: "não tem uma proporção definida"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
zh-CN:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
content_type_invalid: "文件类型错误"
|
5
|
+
file_size_out_of_range: "文件大小 %{file_size} 超出限定范围"
|
6
|
+
limit_out_of_range: "文件数超出限定范围"
|
7
|
+
image_metadata_missing: "不是有效的图像"
|
8
|
+
dimension_min_inclusion: "必须大于或等于 %{width} x %{height} 像素"
|
9
|
+
dimension_max_inclusion: "必须小于或等于 %{width} x %{height} 像素"
|
10
|
+
dimension_width_inclusion: "宽度不在 %{min} 和 %{max} 像素之间"
|
11
|
+
dimension_height_inclusion: "高度不在 %{min} 和 %{max} 像素之间"
|
12
|
+
dimension_width_greater_than_or_equal_to: "宽度必须大于或等于 %{length} 像素"
|
13
|
+
dimension_height_greater_than_or_equal_to: "高度必须大于或等于 %{length} 像素"
|
14
|
+
dimension_width_less_than_or_equal_to: "宽度必须小于或等于 %{length} 像素"
|
15
|
+
dimension_height_less_than_or_equal_to: "高度必须小于或等于 %{length} 像素"
|
16
|
+
dimension_width_equal_to: "宽度必须等于 %{length} 像素"
|
17
|
+
dimension_height_equal_to: "高度必须等于 %{length} 像素"
|
18
|
+
aspect_ratio_not_square: "必须是方形图片"
|
19
|
+
aspect_ratio_not_portrait: "必须是竖屏图片"
|
20
|
+
aspect_ratio_not_landscape: "必须是横屏图片"
|
21
|
+
aspect_ratio_is_not: "纵横比必须是 %{aspect_ratio}"
|
22
|
+
aspect_ratio_unknown: "未知的纵横比"
|
@@ -4,21 +4,17 @@ require_relative 'metadata.rb'
|
|
4
4
|
|
5
5
|
module ActiveStorageValidations
|
6
6
|
class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
|
7
|
+
include OptionProcUnfolding
|
8
|
+
|
7
9
|
AVAILABLE_CHECKS = %i[with].freeze
|
8
10
|
PRECISION = 3
|
9
11
|
|
10
|
-
def initialize(options)
|
11
|
-
require 'mini_magick' unless defined?(MiniMagick)
|
12
|
-
super(options)
|
13
|
-
end
|
14
|
-
|
15
|
-
|
16
12
|
def check_validity!
|
17
13
|
return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
18
|
-
raise ArgumentError, 'You must pass
|
14
|
+
raise ArgumentError, 'You must pass :with to the validator'
|
19
15
|
end
|
20
16
|
|
21
|
-
if Rails
|
17
|
+
if Rails.gem_version >= Gem::Version.new('6.0.0')
|
22
18
|
def validate_each(record, attribute, _value)
|
23
19
|
return true unless record.send(attribute).attached?
|
24
20
|
|
@@ -37,14 +33,14 @@ module ActiveStorageValidations
|
|
37
33
|
# Rails 5
|
38
34
|
def validate_each(record, attribute, _value)
|
39
35
|
return true unless record.send(attribute).attached?
|
40
|
-
|
36
|
+
|
41
37
|
files = Array.wrap(record.send(attribute))
|
42
|
-
|
38
|
+
|
43
39
|
files.each do |file|
|
44
40
|
# Analyze file first if not analyzed to get all required metadata.
|
45
41
|
file.analyze; file.reload unless file.analyzed?
|
46
42
|
metadata = file.metadata
|
47
|
-
|
43
|
+
|
48
44
|
next if is_valid?(record, attribute, metadata)
|
49
45
|
break
|
50
46
|
end
|
@@ -56,26 +52,27 @@ module ActiveStorageValidations
|
|
56
52
|
|
57
53
|
|
58
54
|
def is_valid?(record, attribute, metadata)
|
55
|
+
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
59
56
|
if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
|
60
|
-
add_error(record, attribute,
|
57
|
+
add_error(record, attribute, :image_metadata_missing, flat_options[:with])
|
61
58
|
return false
|
62
59
|
end
|
63
60
|
|
64
|
-
case
|
61
|
+
case flat_options[:with]
|
65
62
|
when :square
|
66
63
|
return true if metadata[:width] == metadata[:height]
|
67
|
-
add_error(record, attribute, :aspect_ratio_not_square)
|
64
|
+
add_error(record, attribute, :aspect_ratio_not_square, flat_options[:with])
|
68
65
|
|
69
66
|
when :portrait
|
70
67
|
return true if metadata[:height] > metadata[:width]
|
71
|
-
add_error(record, attribute, :aspect_ratio_not_portrait)
|
68
|
+
add_error(record, attribute, :aspect_ratio_not_portrait, flat_options[:with])
|
72
69
|
|
73
70
|
when :landscape
|
74
71
|
return true if metadata[:width] > metadata[:height]
|
75
|
-
add_error(record, attribute, :aspect_ratio_not_landscape)
|
72
|
+
add_error(record, attribute, :aspect_ratio_not_landscape, flat_options[:with])
|
76
73
|
|
77
74
|
else
|
78
|
-
if
|
75
|
+
if flat_options[:with] =~ /is_(\d*)_(\d*)/
|
79
76
|
x = $1.to_i
|
80
77
|
y = $2.to_i
|
81
78
|
|
@@ -83,17 +80,17 @@ module ActiveStorageValidations
|
|
83
80
|
|
84
81
|
add_error(record, attribute, :aspect_ratio_is_not, "#{x}x#{y}")
|
85
82
|
else
|
86
|
-
add_error(record, attribute, :aspect_ratio_unknown)
|
83
|
+
add_error(record, attribute, :aspect_ratio_unknown, flat_options[:with])
|
87
84
|
end
|
88
85
|
end
|
89
86
|
false
|
90
87
|
end
|
91
88
|
|
92
89
|
|
93
|
-
def add_error(record, attribute,
|
94
|
-
|
95
|
-
return if record.errors.added?(attribute,
|
96
|
-
record.errors.add(attribute,
|
90
|
+
def add_error(record, attribute, default_message, interpolate)
|
91
|
+
message = options[:message].presence || default_message
|
92
|
+
return if record.errors.added?(attribute, message)
|
93
|
+
record.errors.add(attribute, message, aspect_ratio: interpolate)
|
97
94
|
end
|
98
95
|
|
99
96
|
end
|
@@ -2,16 +2,23 @@
|
|
2
2
|
|
3
3
|
module ActiveStorageValidations
|
4
4
|
class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
|
5
|
+
include OptionProcUnfolding
|
6
|
+
|
7
|
+
AVAILABLE_CHECKS = %i[with in].freeze
|
8
|
+
|
5
9
|
def validate_each(record, attribute, _value)
|
6
|
-
return true
|
10
|
+
return true unless record.send(attribute).attached?
|
7
11
|
|
12
|
+
types = authorized_types(record)
|
13
|
+
return true if types.empty?
|
14
|
+
|
8
15
|
files = Array.wrap(record.send(attribute))
|
9
16
|
|
10
|
-
errors_options = { authorized_types: types_to_human_format }
|
17
|
+
errors_options = { authorized_types: types_to_human_format(types) }
|
11
18
|
errors_options[:message] = options[:message] if options[:message].present?
|
12
19
|
|
13
20
|
files.each do |file|
|
14
|
-
next if is_valid?(file)
|
21
|
+
next if is_valid?(file, types)
|
15
22
|
|
16
23
|
errors_options[:content_type] = content_type(file)
|
17
24
|
record.errors.add(attribute, :content_type_invalid, **errors_options)
|
@@ -19,10 +26,9 @@ module ActiveStorageValidations
|
|
19
26
|
end
|
20
27
|
end
|
21
28
|
|
22
|
-
def
|
23
|
-
|
24
|
-
|
25
|
-
@types = (Array.wrap(options[:with]) + Array.wrap(options[:in])).compact.map do |type|
|
29
|
+
def authorized_types(record)
|
30
|
+
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
31
|
+
(Array.wrap(flat_options[:with]) + Array.wrap(flat_options[:in])).compact.map do |type|
|
26
32
|
if type.is_a?(Regexp)
|
27
33
|
type
|
28
34
|
else
|
@@ -31,7 +37,7 @@ module ActiveStorageValidations
|
|
31
37
|
end
|
32
38
|
end
|
33
39
|
|
34
|
-
def types_to_human_format
|
40
|
+
def types_to_human_format(types)
|
35
41
|
types
|
36
42
|
.map { |type| type.to_s.split('/').last.upcase }
|
37
43
|
.join(', ')
|
@@ -41,7 +47,7 @@ module ActiveStorageValidations
|
|
41
47
|
file.blob.present? && file.blob.content_type
|
42
48
|
end
|
43
49
|
|
44
|
-
def is_valid?(file)
|
50
|
+
def is_valid?(file, types)
|
45
51
|
file_type = content_type(file)
|
46
52
|
types.any? do |type|
|
47
53
|
type == file_type || (type.is_a?(Regexp) && type.match?(file_type.to_s))
|
@@ -4,27 +4,30 @@ require_relative 'metadata.rb'
|
|
4
4
|
|
5
5
|
module ActiveStorageValidations
|
6
6
|
class DimensionValidator < ActiveModel::EachValidator # :nodoc
|
7
|
+
include OptionProcUnfolding
|
8
|
+
|
7
9
|
AVAILABLE_CHECKS = %i[width height min max].freeze
|
8
10
|
|
9
|
-
def
|
10
|
-
|
11
|
+
def process_options(record)
|
12
|
+
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
11
13
|
|
12
14
|
[:width, :height].each do |length|
|
13
|
-
if
|
14
|
-
if range =
|
15
|
+
if flat_options[length] and flat_options[length].is_a?(Hash)
|
16
|
+
if (range = flat_options[length][:in])
|
15
17
|
raise ArgumentError, ":in must be a Range" unless range.is_a?(Range)
|
16
|
-
|
18
|
+
flat_options[length][:min], flat_options[length][:max] = range.min, range.max
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
[:min, :max].each do |dim|
|
21
|
-
if range =
|
23
|
+
if (range = flat_options[dim])
|
22
24
|
raise ArgumentError, ":#{dim} must be a Range (width..height)" unless range.is_a?(Range)
|
23
|
-
|
24
|
-
|
25
|
+
flat_options[:width] = { dim => range.first }
|
26
|
+
flat_options[:height] = { dim => range.last }
|
25
27
|
end
|
26
28
|
end
|
27
|
-
|
29
|
+
|
30
|
+
flat_options
|
28
31
|
end
|
29
32
|
|
30
33
|
|
@@ -34,7 +37,7 @@ module ActiveStorageValidations
|
|
34
37
|
end
|
35
38
|
|
36
39
|
|
37
|
-
if Rails
|
40
|
+
if Rails.gem_version >= Gem::Version.new('6.0.0')
|
38
41
|
def validate_each(record, attribute, _value)
|
39
42
|
return true unless record.send(attribute).attached?
|
40
43
|
|
@@ -66,63 +69,67 @@ module ActiveStorageValidations
|
|
66
69
|
|
67
70
|
|
68
71
|
def is_valid?(record, attribute, file_metadata)
|
72
|
+
flat_options = process_options(record)
|
69
73
|
# Validation fails unless file metadata contains valid width and height.
|
70
74
|
if file_metadata[:width].to_i <= 0 || file_metadata[:height].to_i <= 0
|
71
|
-
add_error(record, attribute,
|
75
|
+
add_error(record, attribute, :image_metadata_missing)
|
72
76
|
return false
|
73
77
|
end
|
74
78
|
|
75
79
|
# Validation based on checks :min and :max (:min, :max has higher priority to :width, :height).
|
76
|
-
if
|
77
|
-
if
|
78
|
-
(
|
79
|
-
(
|
80
|
+
if flat_options[:min] || flat_options[:max]
|
81
|
+
if flat_options[:min] && (
|
82
|
+
(flat_options[:width][:min] && file_metadata[:width] < flat_options[:width][:min]) ||
|
83
|
+
(flat_options[:height][:min] && file_metadata[:height] < flat_options[:height][:min])
|
80
84
|
)
|
81
|
-
add_error(record, attribute,
|
85
|
+
add_error(record, attribute, :dimension_min_inclusion, width: flat_options[:width][:min], height: flat_options[:height][:min])
|
82
86
|
return false
|
83
87
|
end
|
84
|
-
if
|
85
|
-
(
|
86
|
-
(
|
88
|
+
if flat_options[:max] && (
|
89
|
+
(flat_options[:width][:max] && file_metadata[:width] > flat_options[:width][:max]) ||
|
90
|
+
(flat_options[:height][:max] && file_metadata[:height] > flat_options[:height][:max])
|
87
91
|
)
|
88
|
-
add_error(record, attribute,
|
92
|
+
add_error(record, attribute, :dimension_max_inclusion, width: flat_options[:width][:max], height: flat_options[:height][:max])
|
89
93
|
return false
|
90
94
|
end
|
91
95
|
|
92
96
|
# Validation based on checks :width and :height.
|
93
97
|
else
|
98
|
+
width_or_height_invalid = false
|
94
99
|
[:width, :height].each do |length|
|
95
|
-
next unless
|
96
|
-
if
|
97
|
-
if
|
98
|
-
add_error(record, attribute,
|
99
|
-
|
100
|
+
next unless flat_options[length]
|
101
|
+
if flat_options[length].is_a?(Hash)
|
102
|
+
if flat_options[length][:in] && (file_metadata[length] < flat_options[length][:min] || file_metadata[length] > flat_options[length][:max])
|
103
|
+
add_error(record, attribute, :"dimension_#{length}_inclusion", min: flat_options[length][:min], max: flat_options[length][:max])
|
104
|
+
width_or_height_invalid = true
|
100
105
|
else
|
101
|
-
if
|
102
|
-
add_error(record, attribute,
|
103
|
-
|
106
|
+
if flat_options[length][:min] && file_metadata[length] < flat_options[length][:min]
|
107
|
+
add_error(record, attribute, :"dimension_#{length}_greater_than_or_equal_to", length: flat_options[length][:min])
|
108
|
+
width_or_height_invalid = true
|
104
109
|
end
|
105
|
-
if
|
106
|
-
add_error(record, attribute,
|
107
|
-
|
110
|
+
if flat_options[length][:max] && file_metadata[length] > flat_options[length][:max]
|
111
|
+
add_error(record, attribute, :"dimension_#{length}_less_than_or_equal_to", length: flat_options[length][:max])
|
112
|
+
width_or_height_invalid = true
|
108
113
|
end
|
109
114
|
end
|
110
115
|
else
|
111
|
-
if file_metadata[length] !=
|
112
|
-
add_error(record, attribute,
|
113
|
-
|
116
|
+
if file_metadata[length] != flat_options[length]
|
117
|
+
add_error(record, attribute, :"dimension_#{length}_equal_to", length: flat_options[length])
|
118
|
+
width_or_height_invalid = true
|
114
119
|
end
|
115
120
|
end
|
116
121
|
end
|
122
|
+
|
123
|
+
return false if width_or_height_invalid
|
117
124
|
end
|
118
125
|
|
119
126
|
true # valid file
|
120
127
|
end
|
121
128
|
|
122
|
-
def add_error(record, attribute,
|
123
|
-
|
124
|
-
return if record.errors.added?(attribute,
|
125
|
-
record.errors.add(attribute,
|
129
|
+
def add_error(record, attribute, default_message, **attrs)
|
130
|
+
message = options[:message].presence || default_message
|
131
|
+
return if record.errors.added?(attribute, message)
|
132
|
+
record.errors.add(attribute, message, **attrs)
|
126
133
|
end
|
127
134
|
|
128
135
|
end
|
@@ -2,11 +2,12 @@
|
|
2
2
|
|
3
3
|
module ActiveStorageValidations
|
4
4
|
class LimitValidator < ActiveModel::EachValidator # :nodoc:
|
5
|
+
include OptionProcUnfolding
|
6
|
+
|
5
7
|
AVAILABLE_CHECKS = %i[max min].freeze
|
6
8
|
|
7
9
|
def check_validity!
|
8
10
|
return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
9
|
-
|
10
11
|
raise ArgumentError, 'You must pass either :max or :min to the validator'
|
11
12
|
end
|
12
13
|
|
@@ -14,19 +15,20 @@ module ActiveStorageValidations
|
|
14
15
|
return true unless record.send(attribute).attached?
|
15
16
|
|
16
17
|
files = Array.wrap(record.send(attribute)).compact.uniq
|
17
|
-
|
18
|
+
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
19
|
+
errors_options = { min: flat_options[:min], max: flat_options[:max] }
|
18
20
|
|
19
|
-
return true if files_count_valid?(files.count)
|
21
|
+
return true if files_count_valid?(files.count, flat_options)
|
20
22
|
record.errors.add(attribute, options[:message].presence || :limit_out_of_range, **errors_options)
|
21
23
|
end
|
22
24
|
|
23
|
-
def files_count_valid?(count)
|
24
|
-
if
|
25
|
-
count >=
|
26
|
-
elsif
|
27
|
-
count <=
|
28
|
-
elsif
|
29
|
-
count >=
|
25
|
+
def files_count_valid?(count, flat_options)
|
26
|
+
if flat_options[:max].present? && flat_options[:min].present?
|
27
|
+
count >= flat_options[:min] && count <= flat_options[:max]
|
28
|
+
elsif flat_options[:max].present?
|
29
|
+
count <= flat_options[:max]
|
30
|
+
elsif flat_options[:min].present?
|
31
|
+
count >= flat_options[:min]
|
30
32
|
end
|
31
33
|
end
|
32
34
|
end
|
@@ -33,15 +33,19 @@ module ActiveStorageValidations
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def failure_message
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
message = ["Expected #{@attribute_name}"]
|
37
|
+
|
38
|
+
if @allowed_types
|
39
|
+
message << "Accept content types: #{allowed_types.join(", ")}"
|
40
|
+
message << "#{@missing_allowed_types.join(", ")} were rejected"
|
41
|
+
end
|
42
|
+
|
43
|
+
if @rejected_types
|
44
|
+
message << "Reject content types: #{rejected_types.join(", ")}"
|
45
|
+
message << "#{@missing_rejected_types.join(", ")} were accepted"
|
46
|
+
end
|
47
|
+
|
48
|
+
message.join("\n")
|
45
49
|
end
|
46
50
|
|
47
51
|
protected
|
@@ -57,7 +61,7 @@ module ActiveStorageValidations
|
|
57
61
|
end
|
58
62
|
|
59
63
|
def rejected_types
|
60
|
-
@rejected_types ||
|
64
|
+
@rejected_types || []
|
61
65
|
end
|
62
66
|
|
63
67
|
def allowed_types_allowed?
|
@@ -70,22 +74,6 @@ module ActiveStorageValidations
|
|
70
74
|
@missing_rejected_types.none?
|
71
75
|
end
|
72
76
|
|
73
|
-
def accepted_types_and_failures
|
74
|
-
if @missing_allowed_types.present?
|
75
|
-
"#{@missing_allowed_types.join(", ")} were rejected."
|
76
|
-
else
|
77
|
-
"All were accepted successfully."
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
def rejected_types_and_failures
|
82
|
-
if @missing_rejected_types.present?
|
83
|
-
"#{@missing_rejected_types.join(", ")} were accepted."
|
84
|
-
else
|
85
|
-
"All were rejected successfully."
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
77
|
def type_allowed?(type)
|
90
78
|
@subject.public_send(@attribute_name).attach(attachment_for(type))
|
91
79
|
@subject.validate
|
@@ -22,7 +22,7 @@ module ActiveStorageValidations
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def self.mock_metadata(attachment, width, height)
|
25
|
-
if Rails
|
25
|
+
if Rails.gem_version >= Gem::Version.new('6.0.0')
|
26
26
|
# Mock the Metadata class for rails 6
|
27
27
|
mock = OpenStruct.new(metadata: { width: width, height: height })
|
28
28
|
stub_method(ActiveStorageValidations::Metadata, :new, mock) do
|
@@ -1,11 +1,34 @@
|
|
1
1
|
module ActiveStorageValidations
|
2
2
|
class Metadata
|
3
|
+
class InvalidImageError < StandardError; end
|
4
|
+
|
3
5
|
attr_reader :file
|
4
6
|
|
5
7
|
def initialize(file)
|
8
|
+
require_image_processor
|
6
9
|
@file = file
|
7
10
|
end
|
8
11
|
|
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
|
30
|
+
end
|
31
|
+
|
9
32
|
def metadata
|
10
33
|
read_image do |image|
|
11
34
|
if rotated_image?(image)
|
@@ -14,6 +37,16 @@ module ActiveStorageValidations
|
|
14
37
|
{ width: image.width, height: image.height }
|
15
38
|
end
|
16
39
|
end
|
40
|
+
rescue InvalidImageError
|
41
|
+
logger.info "Skipping image analysis because ImageMagick or Vips doesn't support the file"
|
42
|
+
{}
|
43
|
+
end
|
44
|
+
|
45
|
+
def valid?
|
46
|
+
read_image
|
47
|
+
true
|
48
|
+
rescue InvalidImageError
|
49
|
+
false
|
17
50
|
end
|
18
51
|
|
19
52
|
private
|
@@ -21,17 +54,16 @@ module ActiveStorageValidations
|
|
21
54
|
def read_image
|
22
55
|
is_string = file.is_a?(String)
|
23
56
|
if is_string || file.is_a?(ActiveStorage::Blob)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
57
|
+
blob =
|
58
|
+
if is_string
|
59
|
+
if Rails.gem_version < Gem::Version.new('6.1.0')
|
60
|
+
ActiveStorage::Blob.find_signed(file)
|
61
|
+
else
|
62
|
+
ActiveStorage::Blob.find_signed!(file)
|
63
|
+
end
|
64
|
+
else
|
65
|
+
file
|
31
66
|
end
|
32
|
-
else
|
33
|
-
blob = file
|
34
|
-
end
|
35
67
|
|
36
68
|
tempfile = Tempfile.new(["ActiveStorage-#{blob.id}-", blob.filename.extension_with_delimiter])
|
37
69
|
tempfile.binmode
|
@@ -43,29 +75,49 @@ module ActiveStorageValidations
|
|
43
75
|
tempfile.flush
|
44
76
|
tempfile.rewind
|
45
77
|
|
46
|
-
image =
|
78
|
+
image = if image_processor == :vips && defined?(Vips) && Vips::get_suffixes.include?(File.extname(tempfile.path).downcase)
|
79
|
+
Vips::Image.new_from_file(tempfile.path)
|
80
|
+
elsif defined?(MiniMagick)
|
81
|
+
MiniMagick::Image.new(tempfile.path)
|
82
|
+
end
|
47
83
|
else
|
48
|
-
image =
|
84
|
+
image = if image_processor == :vips && defined?(Vips) && Vips::get_suffixes.include?(File.extname(read_file_path).downcase)
|
85
|
+
Vips::Image.new_from_file(read_file_path)
|
86
|
+
elsif defined?(MiniMagick)
|
87
|
+
MiniMagick::Image.new(read_file_path)
|
88
|
+
end
|
49
89
|
end
|
50
90
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
rescue LoadError
|
58
|
-
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
|
91
|
+
|
92
|
+
raise InvalidImageError unless valid_image?(image)
|
93
|
+
yield image if block_given?
|
94
|
+
rescue LoadError, NameError
|
95
|
+
logger.info "Skipping image analysis because the mini_magick or ruby-vips gem isn't installed"
|
59
96
|
{}
|
60
|
-
rescue
|
61
|
-
logger.error "Skipping image analysis due to an
|
97
|
+
rescue exception_class => error
|
98
|
+
logger.error "Skipping image analysis due to an #{exception_class.name.split('::').map(&:downcase).join(' ').capitalize} error: #{error.message}"
|
62
99
|
{}
|
63
100
|
ensure
|
64
101
|
image = nil
|
65
102
|
end
|
66
103
|
|
104
|
+
def valid_image?(image)
|
105
|
+
return false unless image
|
106
|
+
|
107
|
+
image_processor == :vips && image.is_a?(Vips::Image) ? image.avg : image.valid?
|
108
|
+
rescue exception_class
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
67
112
|
def rotated_image?(image)
|
68
|
-
|
113
|
+
if image_processor == :vips && image.is_a?(Vips::Image)
|
114
|
+
image.get('exif-ifd0-Orientation').include?('Right-top') ||
|
115
|
+
image.get('exif-ifd0-Orientation').include?('Left-bottom')
|
116
|
+
else
|
117
|
+
%w[ RightTop LeftBottom ].include?(image["%[orientation]"])
|
118
|
+
end
|
119
|
+
rescue exception_class # field "exif-ifd0-Orientation" not found
|
120
|
+
false
|
69
121
|
end
|
70
122
|
|
71
123
|
def read_file_path
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module ActiveStorageValidations
|
2
|
+
module OptionProcUnfolding
|
3
|
+
|
4
|
+
def unfold_procs(record, object, only_keys)
|
5
|
+
case object
|
6
|
+
when Hash
|
7
|
+
object.merge(object) { |key, value| only_keys&.exclude?(key) ? {} : unfold_procs(record, value, nil) }
|
8
|
+
when Array
|
9
|
+
object.map { |o| unfold_procs(record, o, only_keys) }
|
10
|
+
else
|
11
|
+
object.is_a?(Proc) ? object.call(record) : object
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'metadata.rb'
|
4
|
+
|
5
|
+
module ActiveStorageValidations
|
6
|
+
class ProcessableImageValidator < ActiveModel::EachValidator # :nodoc
|
7
|
+
include OptionProcUnfolding
|
8
|
+
|
9
|
+
if Rails.gem_version >= Gem::Version.new('6.0.0')
|
10
|
+
def validate_each(record, attribute, _value)
|
11
|
+
return true unless record.send(attribute).attached?
|
12
|
+
|
13
|
+
changes = record.attachment_changes[attribute.to_s]
|
14
|
+
return true if changes.blank?
|
15
|
+
|
16
|
+
files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
|
17
|
+
|
18
|
+
files.each do |file|
|
19
|
+
add_error(record, attribute, :image_not_processable) unless Metadata.new(file).valid?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
else
|
23
|
+
# Rails 5
|
24
|
+
def validate_each(record, attribute, _value)
|
25
|
+
return true unless record.send(attribute).attached?
|
26
|
+
|
27
|
+
files = Array.wrap(record.send(attribute))
|
28
|
+
|
29
|
+
files.each do |file|
|
30
|
+
add_error(record, attribute, :image_not_processable) unless Metadata.new(file).valid?
|
31
|
+
end
|
32
|
+
end
|
33
|
+
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
|
+
end
|
43
|
+
end
|
@@ -2,14 +2,15 @@
|
|
2
2
|
|
3
3
|
module ActiveStorageValidations
|
4
4
|
class SizeValidator < ActiveModel::EachValidator # :nodoc:
|
5
|
+
include OptionProcUnfolding
|
6
|
+
|
5
7
|
delegate :number_to_human_size, to: ActiveSupport::NumberHelper
|
6
8
|
|
7
9
|
AVAILABLE_CHECKS = %i[less_than less_than_or_equal_to greater_than greater_than_or_equal_to between].freeze
|
8
10
|
|
9
11
|
def check_validity!
|
10
12
|
return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
11
|
-
|
12
|
-
raise ArgumentError, 'You must pass either :less_than, :greater_than, or :between to the validator'
|
13
|
+
raise ArgumentError, 'You must pass either :less_than(_or_equal_to), :greater_than(_or_equal_to), or :between to the validator'
|
13
14
|
end
|
14
15
|
|
15
16
|
def validate_each(record, attribute, _value)
|
@@ -20,28 +21,40 @@ module ActiveStorageValidations
|
|
20
21
|
|
21
22
|
errors_options = {}
|
22
23
|
errors_options[:message] = options[:message] if options[:message].present?
|
24
|
+
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
23
25
|
|
24
26
|
files.each do |file|
|
25
|
-
next if content_size_valid?(file.blob.byte_size)
|
27
|
+
next if content_size_valid?(file.blob.byte_size, flat_options)
|
26
28
|
|
27
29
|
errors_options[:file_size] = number_to_human_size(file.blob.byte_size)
|
30
|
+
errors_options[:min_size] = number_to_human_size(min_size(flat_options))
|
31
|
+
errors_options[:max_size] = number_to_human_size(max_size(flat_options))
|
32
|
+
|
28
33
|
record.errors.add(attribute, :file_size_out_of_range, **errors_options)
|
29
34
|
break
|
30
35
|
end
|
31
36
|
end
|
32
37
|
|
33
|
-
def content_size_valid?(file_size)
|
34
|
-
if
|
35
|
-
|
36
|
-
elsif
|
37
|
-
file_size <
|
38
|
-
elsif
|
39
|
-
file_size <=
|
40
|
-
elsif
|
41
|
-
file_size >
|
42
|
-
elsif
|
43
|
-
file_size >=
|
38
|
+
def content_size_valid?(file_size, flat_options)
|
39
|
+
if flat_options[:between].present?
|
40
|
+
flat_options[:between].include?(file_size)
|
41
|
+
elsif flat_options[:less_than].present?
|
42
|
+
file_size < flat_options[:less_than]
|
43
|
+
elsif flat_options[:less_than_or_equal_to].present?
|
44
|
+
file_size <= flat_options[:less_than_or_equal_to]
|
45
|
+
elsif flat_options[:greater_than].present?
|
46
|
+
file_size > flat_options[:greater_than]
|
47
|
+
elsif flat_options[:greater_than_or_equal_to].present?
|
48
|
+
file_size >= flat_options[:greater_than_or_equal_to]
|
44
49
|
end
|
45
50
|
end
|
51
|
+
|
52
|
+
def min_size(flat_options)
|
53
|
+
flat_options[:between]&.min || flat_options[:greater_than] || flat_options[:greater_than_or_equal_to]
|
54
|
+
end
|
55
|
+
|
56
|
+
def max_size(flat_options)
|
57
|
+
flat_options[:between]&.max || flat_options[:less_than] || flat_options[:less_than_or_equal_to]
|
58
|
+
end
|
46
59
|
end
|
47
60
|
end
|
@@ -2,12 +2,14 @@
|
|
2
2
|
|
3
3
|
require 'active_storage_validations/railtie'
|
4
4
|
require 'active_storage_validations/engine'
|
5
|
+
require 'active_storage_validations/option_proc_unfolding'
|
5
6
|
require 'active_storage_validations/attached_validator'
|
6
7
|
require 'active_storage_validations/content_type_validator'
|
7
8
|
require 'active_storage_validations/size_validator'
|
8
9
|
require 'active_storage_validations/limit_validator'
|
9
10
|
require 'active_storage_validations/dimension_validator'
|
10
11
|
require 'active_storage_validations/aspect_ratio_validator'
|
12
|
+
require 'active_storage_validations/processable_image_validator'
|
11
13
|
|
12
14
|
ActiveSupport.on_load(:active_record) do
|
13
15
|
send :include, ActiveStorageValidations
|
metadata
CHANGED
@@ -1,17 +1,59 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_storage_validations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Igor Kasyanchuk
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-10-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name:
|
14
|
+
name: activejob
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: activemodel
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 5.2.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 5.2.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: activestorage
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 5.2.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 5.2.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: activesupport
|
15
57
|
requirement: !ruby/object:Gem::Requirement
|
16
58
|
requirements:
|
17
59
|
- - ">="
|
@@ -52,6 +94,20 @@ dependencies:
|
|
52
94
|
- - ">="
|
53
95
|
- !ruby/object:Gem::Version
|
54
96
|
version: 4.9.5
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: ruby-vips
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 2.1.0
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 2.1.0
|
55
111
|
- !ruby/object:Gem::Dependency
|
56
112
|
name: pry
|
57
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -94,6 +150,34 @@ dependencies:
|
|
94
150
|
- - ">="
|
95
151
|
- !ruby/object:Gem::Version
|
96
152
|
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: marcel
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: globalid
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
97
181
|
description: Validations for Active Storage (presence)
|
98
182
|
email:
|
99
183
|
- igorkasyanchuk@gmail.com
|
@@ -117,6 +201,7 @@ files:
|
|
117
201
|
- config/locales/tr.yml
|
118
202
|
- config/locales/uk.yml
|
119
203
|
- config/locales/vi.yml
|
204
|
+
- config/locales/zh-CN.yml
|
120
205
|
- lib/active_storage_validations.rb
|
121
206
|
- lib/active_storage_validations/aspect_ratio_validator.rb
|
122
207
|
- lib/active_storage_validations/attached_validator.rb
|
@@ -130,6 +215,8 @@ files:
|
|
130
215
|
- lib/active_storage_validations/matchers/dimension_validator_matcher.rb
|
131
216
|
- lib/active_storage_validations/matchers/size_validator_matcher.rb
|
132
217
|
- lib/active_storage_validations/metadata.rb
|
218
|
+
- lib/active_storage_validations/option_proc_unfolding.rb
|
219
|
+
- lib/active_storage_validations/processable_image_validator.rb
|
133
220
|
- lib/active_storage_validations/railtie.rb
|
134
221
|
- lib/active_storage_validations/size_validator.rb
|
135
222
|
- lib/active_storage_validations/version.rb
|
@@ -138,7 +225,7 @@ homepage: https://github.com/igorkasyanchuk/active_storage_validations
|
|
138
225
|
licenses:
|
139
226
|
- MIT
|
140
227
|
metadata: {}
|
141
|
-
post_install_message:
|
228
|
+
post_install_message:
|
142
229
|
rdoc_options: []
|
143
230
|
require_paths:
|
144
231
|
- lib
|
@@ -153,8 +240,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
240
|
- !ruby/object:Gem::Version
|
154
241
|
version: '0'
|
155
242
|
requirements: []
|
156
|
-
rubygems_version: 3.
|
157
|
-
signing_key:
|
243
|
+
rubygems_version: 3.2.3
|
244
|
+
signing_key:
|
158
245
|
specification_version: 4
|
159
246
|
summary: Validations for Active Storage
|
160
247
|
test_files: []
|