active_storage_validations 0.9.5 → 1.0.3
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.
- 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
|
[](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: []
|