active_storage_validations 0.8.5 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +108 -14
- data/config/locales/de.yml +8 -8
- data/config/locales/fr.yml +22 -0
- data/config/locales/ja.yml +22 -0
- data/config/locales/ru.yml +22 -0
- data/config/locales/tr.yml +22 -0
- data/config/locales/uk.yml +22 -0
- data/lib/active_storage_validations/matchers.rb +43 -0
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +51 -0
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +95 -0
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +138 -0
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +87 -0
- data/lib/active_storage_validations/metadata.rb +4 -3
- data/lib/active_storage_validations/version.rb +1 -1
- metadata +13 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 63ae1be092a1780d41de3af6c4189ee9e92804ce53b9fb30f91c929eb4c2efc6
|
4
|
+
data.tar.gz: 73e74ca24800fc11bbdc3b0b95e2f044311b96728cb7da30209c00af77e45b32
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c701c6d1db92c8d0db7ca00616a6247462abe94486d73dcb71c0d41e99f82081af8cfe84fb7f2d56a719dc70fe47d696208ee372a26dfed770dc0cf9106eafd3
|
7
|
+
data.tar.gz: b437692cf0c6d11e4ad341c413c448e825bd29537696d885cdb6fbe30fd8a1b9b56d724cbaee41c9b3656b7261544b00d89cbaa809d69773d1786b21fad77d1a
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Active Storage Validations
|
2
2
|
|
3
|
-
If you are using `active_storage` gem and you want to add simple validations for it, like presence or content_type you need to write a custom
|
3
|
+
If you are using `active_storage` gem and you want to add simple validations for it, like presence or content_type you need to write a custom validation method.
|
4
4
|
|
5
5
|
This gems doing it for you. Just use `attached: true` or `content_type: 'image/png'` validation.
|
6
6
|
|
@@ -113,10 +113,11 @@ class User < ApplicationRecord
|
|
113
113
|
# you can also pass dynamic aspect ratio, like :is_4_3, :is_16_9, etc
|
114
114
|
validates :photos, aspect_ratio: :is_4_3
|
115
115
|
end
|
116
|
+
```
|
116
117
|
|
117
118
|
## Internationalization (I18n)
|
118
119
|
|
119
|
-
Active Storage Validations
|
120
|
+
Active Storage Validations uses I18n for error messages. For this, add these keys in your translation file:
|
120
121
|
|
121
122
|
```yml
|
122
123
|
en:
|
@@ -136,19 +137,18 @@ en:
|
|
136
137
|
dimension_height_less_than_or_equal_to: "height must be less than or equal to %{length} pixel."
|
137
138
|
dimension_width_equal_to: "width must be equal to %{length} pixel."
|
138
139
|
dimension_height_equal_to: "height must be equal to %{length} pixel."
|
139
|
-
aspect_ratio_not_square: "
|
140
|
-
aspect_ratio_not_portrait: "
|
141
|
-
aspect_ratio_not_landscape: "
|
142
|
-
aspect_ratio_is_not: "
|
140
|
+
aspect_ratio_not_square: "must be a square image"
|
141
|
+
aspect_ratio_not_portrait: "must be a portrait image"
|
142
|
+
aspect_ratio_not_landscape: "must be a landscape image"
|
143
|
+
aspect_ratio_is_not: "must have an aspect ratio of %{aspect_ratio}"
|
143
144
|
aspect_ratio_unknown: "has an unknown aspect ratio"
|
144
|
-
|
145
145
|
```
|
146
146
|
|
147
|
-
In some cases Active Storage Validations provides variables to help you customize messages:
|
147
|
+
In some cases, Active Storage Validations provides variables to help you customize messages:
|
148
148
|
|
149
|
-
The "content_type_invalid" key has two variables that you can use, a variable named "content_type" containing the content type of the send file and a variable named "
|
149
|
+
The "content_type_invalid" key has two variables that you can use, a variable named "content_type" containing the content type of the send file and a variable named "authorized_types" containing the list of authorized content types.
|
150
150
|
|
151
|
-
|
151
|
+
The variables are not used by default to leave the choice to the user.
|
152
152
|
|
153
153
|
For example :
|
154
154
|
|
@@ -188,20 +188,105 @@ Very simple example of validation with file attached, content type check and cus
|
|
188
188
|
|
189
189
|
[](https://raw.githubusercontent.com/igorkasyanchuk/active_storage_validations/master/docs/preview.png)
|
190
190
|
|
191
|
+
## Test matchers
|
192
|
+
Provides RSpec-compatible and Minitest-compatible matchers for testing the validators.
|
193
|
+
|
194
|
+
### RSpec
|
195
|
+
|
196
|
+
In `spec_helper.rb`, you'll need to require the matchers:
|
197
|
+
|
198
|
+
```ruby
|
199
|
+
require 'active_storage_validations/matchers'
|
200
|
+
```
|
201
|
+
|
202
|
+
And _include_ the module:
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
RSpec.configure do |config|
|
206
|
+
config.include ActiveStorageValidations::Matchers
|
207
|
+
end
|
208
|
+
```
|
209
|
+
|
210
|
+
Example (Note that the options are chainable):
|
211
|
+
|
212
|
+
```ruby
|
213
|
+
describe User do
|
214
|
+
it { is_expected.to validate_attached_of(:avatar) }
|
215
|
+
|
216
|
+
it { is_expected.to validate_content_type_of(:avatar).allowing('image/png', 'image/gif') }
|
217
|
+
it { is_expected.to validate_content_type_of(:avatar).rejecting('text/plain', 'text/xml') }
|
218
|
+
|
219
|
+
it { is_expected.to validate_dimensions_of(:avatar).width(250) }
|
220
|
+
it { is_expected.to validate_dimensions_of(:avatar).height(200) }
|
221
|
+
it { is_expected.to validate_dimensions_of(:avatar).width_min(200) }
|
222
|
+
it { is_expected.to validate_dimensions_of(:avatar).width_max(500) }
|
223
|
+
it { is_expected.to validate_dimensions_of(:avatar).height_min(100) }
|
224
|
+
it { is_expected.to validate_dimensions_of(:avatar).height_max(300) }
|
225
|
+
it { is_expected.to validate_dimensions_of(:avatar).width_between(200..500) }
|
226
|
+
it { is_expected.to validate_dimensions_of(:avatar).height_between(100..300) }
|
227
|
+
|
228
|
+
it { is_expected.to validate_size_of(:avatar).less_than(50.kilobytes) }
|
229
|
+
it { is_expected.to validate_size_of(:avatar).less_than_or_equal_to(50.kilobytes) }
|
230
|
+
it { is_expected.to validate_size_of(:avatar).greater_than(1.kilobyte) }
|
231
|
+
it { is_expected.to validate_size_of(:avatar).greater_than_or_equal_to(1.kilobyte) }
|
232
|
+
it { is_expected.to validate_size_of(:avatar).between(100..500.kilobytes) }
|
233
|
+
end
|
234
|
+
```
|
235
|
+
|
236
|
+
### Minitest
|
237
|
+
To use the following syntax, make sure you have the [shoulda-context](https://github.com/thoughtbot/shoulda-context) gem up and running. To make use of the matchers you need to require the matchers:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
require 'active_storage_validations/matchers'
|
241
|
+
```
|
242
|
+
|
243
|
+
And _extend_ the module:
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
class ActiveSupport::TestCase
|
247
|
+
extend ActiveStorageValidations::Matchers
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
Example (Note that the options are chainable):
|
252
|
+
|
253
|
+
```ruby
|
254
|
+
class UserTest < ActiveSupport::TestCase
|
255
|
+
should validate_attached_of(:avatar)
|
256
|
+
|
257
|
+
should validate_content_type_of(:avatar).allowing('image/png', 'image/gif')
|
258
|
+
should validate_content_type_of(:avatar).rejecting('text/plain', 'text/xml')
|
259
|
+
|
260
|
+
should validate_dimensions_of(:avatar).width(250)
|
261
|
+
should validate_dimensions_of(:avatar).height(200)
|
262
|
+
should validate_dimensions_of(:avatar).width_min(200)
|
263
|
+
should validate_dimensions_of(:avatar).width_max(500)
|
264
|
+
should validate_dimensions_of(:avatar).height_min(100)
|
265
|
+
should validate_dimensions_of(:avatar).height_max(300)
|
266
|
+
should validate_dimensions_of(:avatar).width_between(200..500)
|
267
|
+
should validate_dimensions_of(:avatar).height_between(100..300)
|
268
|
+
|
269
|
+
should validate_size_of(:avatar).less_than(50.kilobytes)
|
270
|
+
should validate_size_of(:avatar).less_than_or_equal_to(50.kilobytes)
|
271
|
+
should validate_size_of(:avatar).greater_than(1.kilobyte)
|
272
|
+
should validate_size_of(:avatar).greater_than_or_equal_to(1.kilobyte)
|
273
|
+
should validate_size_of(:avatar).between(100..500.kilobytes)
|
274
|
+
end
|
275
|
+
```
|
276
|
+
|
191
277
|
## Todo
|
192
278
|
|
193
279
|
* verify with remote storages (s3, etc)
|
194
280
|
* verify how it works with direct upload
|
195
281
|
* better error message when content_size is invalid
|
196
282
|
* add more translations
|
197
|
-
* add aspect ratio validation
|
198
283
|
|
199
284
|
## Tests & Contributing
|
200
285
|
|
201
286
|
To run tests in root folder of gem:
|
202
287
|
|
203
288
|
* `BUNDLE_GEMFILE=gemfiles/rails_5_2.gemfile bundle exec rake test` to run for Rails 5.2
|
204
|
-
* `BUNDLE_GEMFILE=gemfiles/
|
289
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_6_0.gemfile bundle exec rake test` to run for Rails 6.0
|
205
290
|
|
206
291
|
To play with app `cd test/dummy` and `rails s -b 0.0.0.0` (before `rails db:migrate`).
|
207
292
|
|
@@ -209,12 +294,13 @@ To play with app `cd test/dummy` and `rails s -b 0.0.0.0` (before `rails db:migr
|
|
209
294
|
|
210
295
|
- There is an issue in Rails which it possible to get if you have added a validation and generating for example an image preview of attachments. It can be fixed with this:
|
211
296
|
|
212
|
-
```
|
297
|
+
```erb
|
213
298
|
<% if @user.avatar.attached? && @user.avatar.attachment.blob.present? && @user.avatar.attachment.blob.persisted? %>
|
214
299
|
<%= image_tag @user.avatar %>
|
215
300
|
<% end %>
|
216
301
|
```
|
217
|
-
|
302
|
+
|
303
|
+
This is a Rails issue, and is fixed in Rails 6.
|
218
304
|
|
219
305
|
## Contributing
|
220
306
|
You are welcome to contribute.
|
@@ -238,6 +324,14 @@ You are welcome to contribute.
|
|
238
324
|
- https://github.com/victorbueno
|
239
325
|
- https://github.com/UICJohn
|
240
326
|
- https://github.com/giovannibonetti
|
327
|
+
- https://github.com/dlepage
|
328
|
+
- https://github.com/StefSchenkelaars
|
329
|
+
- https://github.com/willnet
|
330
|
+
- https://github.com/mohanklein
|
331
|
+
- https://github.com/High5Apps
|
332
|
+
- https://github.com/mschnitzer
|
333
|
+
- https://github.com/sinankeskin
|
334
|
+
- https://github.com/alejandrodevs
|
241
335
|
|
242
336
|
## License
|
243
337
|
|
data/config/locales/de.yml
CHANGED
@@ -2,16 +2,16 @@ de:
|
|
2
2
|
errors:
|
3
3
|
messages:
|
4
4
|
content_type_invalid: "hat einen ungültigen Dateityp"
|
5
|
-
file_size_out_of_range: "Dateigröße
|
5
|
+
file_size_out_of_range: "Dateigröße %{file_size} liegt nicht im erlaubten Bereich"
|
6
6
|
limit_out_of_range: "Anzahl ist außerhalb des gültigen Bereichs"
|
7
|
-
image_metadata_missing: "ist
|
7
|
+
image_metadata_missing: "ist kein gültiges Bild"
|
8
8
|
dimension_min_inclusion: "muss größer oder gleich %{width} x %{height} Pixel sein"
|
9
9
|
dimension_max_inclusion: "muss kleiner oder gleich %{width} x %{height} Pixel sein"
|
10
|
-
dimension_width_inclusion: "
|
11
|
-
dimension_height_inclusion: "
|
12
|
-
dimension_width_greater_than_or_equal_to: "
|
13
|
-
dimension_height_greater_than_or_equal_to: "
|
10
|
+
dimension_width_inclusion: "Bildbreite muss zwischen %{min} und %{max} Pixel liegen"
|
11
|
+
dimension_height_inclusion: "Bildhöhe muss zwischen %{min} und %{max} Pixel liegen"
|
12
|
+
dimension_width_greater_than_or_equal_to: "Bildbreite muss größer oder gleich %{length} Pixel sein"
|
13
|
+
dimension_height_greater_than_or_equal_to: "Bildhöhe muss größer oder gleich %{length} Pixel sein"
|
14
14
|
dimension_width_less_than_or_equal_to: "Breite muss kleiner oder gleich %{length} Pixel sein"
|
15
15
|
dimension_height_less_than_or_equal_to: "Höhe muss kleiner oder gleich %{length} Pixel sein"
|
16
|
-
dimension_width_equal_to: "
|
17
|
-
dimension_height_equal_to: "
|
16
|
+
dimension_width_equal_to: "Bildbreite muss genau %{length} Pixel sein"
|
17
|
+
dimension_height_equal_to: "Bildhöhe muss genau %{length} Pixel sein"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
fr:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
content_type_invalid: "a un type de contenu non valide"
|
5
|
+
file_size_out_of_range: "la taille %{file_size} n'est pas comprise dans la plage permise"
|
6
|
+
limit_out_of_range: "le nombre total est hors limites"
|
7
|
+
image_metadata_missing: "n'est pas une image valide"
|
8
|
+
dimension_min_inclusion: "doit être supérieur ou égal à %{width} x %{height} pixels"
|
9
|
+
dimension_max_inclusion: "doit être inférieur ou égal à %{width} x %{height} pixels"
|
10
|
+
dimension_width_inclusion: "la largeur n'est pas comprise entre %{min} et %{max} pixels"
|
11
|
+
dimension_height_inclusion: "la hauteur n'est pas comprise entre %{min} et %{max} pixels"
|
12
|
+
dimension_width_greater_than_or_equal_to: "la largeur doit être supérieure ou égale à %{length} pixels"
|
13
|
+
dimension_height_greater_than_or_equal_to: "la hauteur doit être supérieure ou égale à %{length} pixels"
|
14
|
+
dimension_width_less_than_or_equal_to: "la largeur doit être inférieure ou égale à %{length} pixels"
|
15
|
+
dimension_height_less_than_or_equal_to: "la hauteur doit être inférieure ou égale à %{length} pixels"
|
16
|
+
dimension_width_equal_to: "la largeur doit être égale à %{length} pixels"
|
17
|
+
dimension_height_equal_to: "la hauteur doit être égale à %{length} pixels"
|
18
|
+
aspect_ratio_not_square: "doit être une image en format carrée"
|
19
|
+
aspect_ratio_not_portrait: "doit être une image en format portrait"
|
20
|
+
aspect_ratio_not_landscape: "doit être une image en format paysage"
|
21
|
+
aspect_ratio_is_not: "doit avoir un rapport hauteur / largeur de %{aspect_ratio}"
|
22
|
+
aspect_ratio_unknown: "a un rapport d'aspect inconnu"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
ja:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
content_type_invalid: "のContent Typeが不正です"
|
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: "のアスペクト比を取得できませんでした"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
ru:
|
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: "имеет неизвестное соотношение сторон"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
tr:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
content_type_invalid: "geçersiz dosya tipine sahip"
|
5
|
+
file_size_out_of_range: "dosya boyutu %{file_size} gerekli aralık dışında"
|
6
|
+
limit_out_of_range: "toplam miktar aralık dışında"
|
7
|
+
image_metadata_missing: "geçerli bir imaj değil"
|
8
|
+
dimension_min_inclusion: "%{width} x %{height} piksele eşit ya da büyük olmalı"
|
9
|
+
dimension_max_inclusion: "%{width} x %{height} piksele eşit ya da küçük olmalı"
|
10
|
+
dimension_width_inclusion: "en %{min} ve %{max} piksel aralığı dışında"
|
11
|
+
dimension_height_inclusion: "boy %{min} ve %{max} piksel aralığı dışında"
|
12
|
+
dimension_width_greater_than_or_equal_to: "en %{length} piksele eşit ya da büyük olmalı"
|
13
|
+
dimension_height_greater_than_or_equal_to: "boy %{length} piksele eşit ya da büyük olmalı"
|
14
|
+
dimension_width_less_than_or_equal_to: "en %{length} piksele eşit ya da küçük olmalı"
|
15
|
+
dimension_height_less_than_or_equal_to: "boy %{length} piksele eşit ya da küçük olmalı"
|
16
|
+
dimension_width_equal_to: "en %{length} piksele eşit olmalı"
|
17
|
+
dimension_height_equal_to: "boy %{length} piksele eşit olmalı"
|
18
|
+
aspect_ratio_not_square: "kare bir imaj olmalı"
|
19
|
+
aspect_ratio_not_portrait: "dikey bir imaj olmalı"
|
20
|
+
aspect_ratio_not_landscape: "yatay bir imaj olmalı"
|
21
|
+
aspect_ratio_is_not: "%{aspect_ratio} en boy oranına sahip olmalı"
|
22
|
+
aspect_ratio_unknown: "bilinmeyen en boy oranı"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
uk:
|
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: "має невідоме співвідношення сторін"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_storage_validations/matchers/attached_validator_matcher'
|
4
|
+
require 'active_storage_validations/matchers/content_type_validator_matcher'
|
5
|
+
require 'active_storage_validations/matchers/dimension_validator_matcher'
|
6
|
+
require 'active_storage_validations/matchers/size_validator_matcher'
|
7
|
+
|
8
|
+
module ActiveStorageValidations
|
9
|
+
module Matchers
|
10
|
+
# Helper to stub a method with either RSpec or Minitest (whatever is available)
|
11
|
+
def self.stub_method(object, method, result)
|
12
|
+
if defined?(Minitest::Mock)
|
13
|
+
object.stub(method, result) do
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
elsif defined?(RSpec::Mocks)
|
17
|
+
RSpec::Mocks.allow_message(object, method) { result }
|
18
|
+
yield
|
19
|
+
else
|
20
|
+
raise 'Need either Minitest::Mock or RSpec::Mocks to run this validator matcher'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.mock_metadata(attachment, width, height)
|
25
|
+
if Rails::VERSION::MAJOR >= 6
|
26
|
+
# Mock the Metadata class for rails 6
|
27
|
+
mock = OpenStruct.new(metadata: { width: width, height: height })
|
28
|
+
stub_method(ActiveStorageValidations::Metadata, :new, mock) do
|
29
|
+
yield
|
30
|
+
end
|
31
|
+
else
|
32
|
+
# Stub the metadata analysis for rails 5
|
33
|
+
stub_method(attachment, :analyze, true) do
|
34
|
+
stub_method(attachment, :analyzed?, true) do
|
35
|
+
stub_method(attachment, :metadata, { width: width, height: height }) do
|
36
|
+
yield
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
def validate_attached_of(name)
|
6
|
+
AttachedValidatorMatcher.new(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
class AttachedValidatorMatcher
|
10
|
+
def initialize(attribute_name)
|
11
|
+
@attribute_name = attribute_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def description
|
15
|
+
"validate #{@attribute_name} must be attached"
|
16
|
+
end
|
17
|
+
|
18
|
+
def matches?(subject)
|
19
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
20
|
+
invalid_when_not_attached && valid_when_attached
|
21
|
+
end
|
22
|
+
|
23
|
+
def failure_message
|
24
|
+
"is expected to validate attached of #{@attribute_name}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def failure_message_when_negated
|
28
|
+
"is expected to not validate attached of #{@attribute_name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def valid_when_attached
|
34
|
+
instance = @subject
|
35
|
+
instance.public_send(@attribute_name).attach(attachable)
|
36
|
+
instance.validate
|
37
|
+
instance.errors.details[@attribute_name].exclude?(error: :blank)
|
38
|
+
end
|
39
|
+
|
40
|
+
def invalid_when_not_attached
|
41
|
+
instance = @subject
|
42
|
+
instance.validate
|
43
|
+
instance.errors.details[@attribute_name].include?(error: :blank)
|
44
|
+
end
|
45
|
+
|
46
|
+
def attachable
|
47
|
+
{ io: Tempfile.new('.'), filename: 'dummy.txt', content_type: 'text/plain' }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Big thank you to the paperclip validation matchers:
|
4
|
+
# https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb
|
5
|
+
module ActiveStorageValidations
|
6
|
+
module Matchers
|
7
|
+
def validate_content_type_of(name)
|
8
|
+
ContentTypeValidatorMatcher.new(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
class ContentTypeValidatorMatcher
|
12
|
+
def initialize(attribute_name)
|
13
|
+
@attribute_name = attribute_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
"validate the content types allowed on attachment #{@attribute_name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def allowing(*types)
|
21
|
+
@allowed_types = types.flatten
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def rejecting(*types)
|
26
|
+
@rejected_types = types.flatten
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def matches?(subject)
|
31
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
32
|
+
allowed_types_allowed? && rejected_types_rejected?
|
33
|
+
end
|
34
|
+
|
35
|
+
def failure_message
|
36
|
+
<<~MESSAGE
|
37
|
+
Expected #{@attribute_name}
|
38
|
+
|
39
|
+
Accept content types: #{allowed_types.join(", ")}
|
40
|
+
#{accepted_types_and_failures}
|
41
|
+
|
42
|
+
Reject content types: #{rejected_types.join(", ")}
|
43
|
+
#{rejected_types_and_failures}
|
44
|
+
MESSAGE
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def allowed_types
|
50
|
+
@allowed_types || []
|
51
|
+
end
|
52
|
+
|
53
|
+
def rejected_types
|
54
|
+
@rejected_types || (Mime::LOOKUP.keys - allowed_types)
|
55
|
+
end
|
56
|
+
|
57
|
+
def allowed_types_allowed?
|
58
|
+
@missing_allowed_types ||= allowed_types.reject { |type| type_allowed?(type) }
|
59
|
+
@missing_allowed_types.none?
|
60
|
+
end
|
61
|
+
|
62
|
+
def rejected_types_rejected?
|
63
|
+
@missing_rejected_types ||= rejected_types.select { |type| type_allowed?(type) }
|
64
|
+
@missing_rejected_types.none?
|
65
|
+
end
|
66
|
+
|
67
|
+
def accepted_types_and_failures
|
68
|
+
if @missing_allowed_types.present?
|
69
|
+
"#{@missing_allowed_types.join(", ")} were rejected."
|
70
|
+
else
|
71
|
+
"All were accepted successfully."
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def rejected_types_and_failures
|
76
|
+
if @missing_rejected_types.present?
|
77
|
+
"#{@missing_rejected_types.join(", ")} were accepted."
|
78
|
+
else
|
79
|
+
"All were rejected successfully."
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def type_allowed?(type)
|
84
|
+
@subject.public_send(@attribute_name).attach(attachment_for(type))
|
85
|
+
@subject.validate
|
86
|
+
@subject.errors.details[@attribute_name].all? { |error| error[:error] != :content_type_invalid }
|
87
|
+
end
|
88
|
+
|
89
|
+
def attachment_for(type)
|
90
|
+
suffix = type.to_s.split('/').last
|
91
|
+
{ io: Tempfile.new('.'), filename: "test.#{suffix}", content_type: type }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
def validate_dimensions_of(name)
|
6
|
+
DimensionValidatorMatcher.new(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
class DimensionValidatorMatcher
|
10
|
+
def initialize(attribute_name)
|
11
|
+
@attribute_name = attribute_name
|
12
|
+
@width_min = @width_max = @height_min = @height_max = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def description
|
16
|
+
"validate image dimensions of #{@attribute_name}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def width_min(width)
|
20
|
+
@width_min = width
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def width_max(width)
|
25
|
+
@width_max = width
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def width(width)
|
30
|
+
@width_min = @width_max = width
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
def height_min(height)
|
35
|
+
@height_min = height
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def height_max(height)
|
40
|
+
@height_max = height
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def width_between(range)
|
45
|
+
@width_min, @width_max = range.first, range.last
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def height_between(range)
|
50
|
+
@height_min, @height_max = range.first, range.last
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def height(height)
|
55
|
+
@height_min = @height_max = height
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
def matches?(subject)
|
60
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
61
|
+
width_smaller_than_min? && width_larger_than_min? && width_smaller_than_max? && width_larger_than_max? && width_equals? &&
|
62
|
+
height_smaller_than_min? && height_larger_than_min? && height_smaller_than_max? && height_larger_than_max? && height_equals?
|
63
|
+
end
|
64
|
+
|
65
|
+
def failure_message
|
66
|
+
<<~MESSAGE
|
67
|
+
is expected to validate dimensions of #{@attribute_name}
|
68
|
+
width between #{@width_min} and #{@width_max}
|
69
|
+
height between #{@height_min} and #{@height_max}
|
70
|
+
MESSAGE
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def valid_width
|
76
|
+
((@width_min || 0) + (@width_max || 2000)) / 2
|
77
|
+
end
|
78
|
+
|
79
|
+
def valid_height
|
80
|
+
((@height_min || 0) + (@height_max || 2000)) / 2
|
81
|
+
end
|
82
|
+
|
83
|
+
def width_smaller_than_min?
|
84
|
+
@width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height, 'width')
|
85
|
+
end
|
86
|
+
|
87
|
+
def width_larger_than_min?
|
88
|
+
@width_min.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_min + 1, valid_height, 'width')
|
89
|
+
end
|
90
|
+
|
91
|
+
def width_smaller_than_max?
|
92
|
+
@width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height, 'width')
|
93
|
+
end
|
94
|
+
|
95
|
+
def width_larger_than_max?
|
96
|
+
@width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height, 'width')
|
97
|
+
end
|
98
|
+
|
99
|
+
def width_equals?
|
100
|
+
@width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height, 'width')
|
101
|
+
end
|
102
|
+
|
103
|
+
def height_smaller_than_min?
|
104
|
+
@height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1, 'height')
|
105
|
+
end
|
106
|
+
|
107
|
+
def height_larger_than_min?
|
108
|
+
@height_min.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_min + 1, 'height')
|
109
|
+
end
|
110
|
+
|
111
|
+
def height_smaller_than_max?
|
112
|
+
@height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1, 'height')
|
113
|
+
end
|
114
|
+
|
115
|
+
def height_larger_than_max?
|
116
|
+
@height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1, 'height')
|
117
|
+
end
|
118
|
+
|
119
|
+
def height_equals?
|
120
|
+
@height_min.nil? || @height_min != @height_max || passes_validation_with_dimensions(valid_width, @height_min, 'height')
|
121
|
+
end
|
122
|
+
|
123
|
+
def passes_validation_with_dimensions(width, height, check)
|
124
|
+
@subject.public_send(@attribute_name).attach attachment_for(width, height)
|
125
|
+
|
126
|
+
attachment = @subject.public_send(@attribute_name)
|
127
|
+
Matchers.mock_metadata(attachment, width, height) do
|
128
|
+
@subject.validate
|
129
|
+
@subject.errors.details[@attribute_name].all? { |error| error[:error].to_s.exclude?("dimension_#{check}") }
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def attachment_for(width, height)
|
134
|
+
{ io: Tempfile.new('Hello world!'), filename: 'test.png', content_type: 'image/png' }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Big thank you to the paperclip validation matchers:
|
4
|
+
# https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_size_matcher.rb
|
5
|
+
module ActiveStorageValidations
|
6
|
+
module Matchers
|
7
|
+
def validate_size_of(name)
|
8
|
+
SizeValidatorMatcher.new(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
class SizeValidatorMatcher
|
12
|
+
def initialize(attribute_name)
|
13
|
+
@attribute_name = attribute_name
|
14
|
+
@low = @high = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
def description
|
18
|
+
"validate file size of #{@attribute_name}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def less_than(size)
|
22
|
+
@high = size - 1.byte
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def less_than_or_equal_to(size)
|
27
|
+
@high = size
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def greater_than(size)
|
32
|
+
@low = size + 1.byte
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def greater_than_or_equal_to(size)
|
37
|
+
@low = size
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def between(range)
|
42
|
+
@low, @high = range.first, range.last
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def matches?(subject)
|
47
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
48
|
+
lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
|
49
|
+
end
|
50
|
+
|
51
|
+
def failure_message
|
52
|
+
"is expected to validate file size of #{@attribute_name} to be between #{@low} and #{@high} bytes"
|
53
|
+
end
|
54
|
+
|
55
|
+
def failure_message_when_negated
|
56
|
+
"is expected to not validate file size of #{@attribute_name} to be between #{@low} and #{@high} bytes"
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def lower_than_low?
|
62
|
+
@low.nil? || !passes_validation_with_size(@low - 1)
|
63
|
+
end
|
64
|
+
|
65
|
+
def higher_than_low?
|
66
|
+
@low.nil? || passes_validation_with_size(@low + 1)
|
67
|
+
end
|
68
|
+
|
69
|
+
def lower_than_high?
|
70
|
+
@high.nil? || @high == Float::INFINITY || passes_validation_with_size(@high - 1)
|
71
|
+
end
|
72
|
+
|
73
|
+
def higher_than_high?
|
74
|
+
@high.nil? || @high == Float::INFINITY || !passes_validation_with_size(@high + 1)
|
75
|
+
end
|
76
|
+
|
77
|
+
def passes_validation_with_size(new_size)
|
78
|
+
io = Tempfile.new('Hello world!')
|
79
|
+
Matchers.stub_method(io, :size, new_size) do
|
80
|
+
@subject.public_send(@attribute_name).attach(io: io, filename: 'test.png', content_type: 'image/pg')
|
81
|
+
@subject.validate
|
82
|
+
@subject.errors.details[@attribute_name].all? { |error| error[:error] != :file_size_out_of_range }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -19,11 +19,12 @@ module ActiveStorageValidations
|
|
19
19
|
private
|
20
20
|
|
21
21
|
def read_image
|
22
|
-
|
23
|
-
|
22
|
+
is_string = file.is_a?(String)
|
23
|
+
if is_string || file.is_a?(ActiveStorage::Blob)
|
24
|
+
blob = is_string ? ActiveStorage::Blob.find_signed(file) : file
|
24
25
|
|
25
26
|
tempfile = Tempfile.new(["ActiveStorage-#{blob.id}-", blob.filename.extension_with_delimiter])
|
26
|
-
tempfile.binmode
|
27
|
+
tempfile.binmode
|
27
28
|
|
28
29
|
blob.download do |chunk|
|
29
30
|
tempfile.write(chunk)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_storage_validations
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.9.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Igor Kasyanchuk
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-08-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -80,20 +80,6 @@ dependencies:
|
|
80
80
|
- - ">="
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
|
-
- !ruby/object:Gem::Dependency
|
84
|
-
name: coffee-rails
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
86
|
-
requirements:
|
87
|
-
- - ">="
|
88
|
-
- !ruby/object:Gem::Version
|
89
|
-
version: '0'
|
90
|
-
type: :development
|
91
|
-
prerelease: false
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
93
|
-
requirements:
|
94
|
-
- - ">="
|
95
|
-
- !ruby/object:Gem::Version
|
96
|
-
version: '0'
|
97
83
|
description: Validations for Active Storage (presence)
|
98
84
|
email:
|
99
85
|
- igorkasyanchuk@gmail.com
|
@@ -106,7 +92,12 @@ files:
|
|
106
92
|
- Rakefile
|
107
93
|
- config/locales/de.yml
|
108
94
|
- config/locales/en.yml
|
95
|
+
- config/locales/fr.yml
|
96
|
+
- config/locales/ja.yml
|
109
97
|
- config/locales/pt-BR.yml
|
98
|
+
- config/locales/ru.yml
|
99
|
+
- config/locales/tr.yml
|
100
|
+
- config/locales/uk.yml
|
110
101
|
- lib/active_storage_validations.rb
|
111
102
|
- lib/active_storage_validations/aspect_ratio_validator.rb
|
112
103
|
- lib/active_storage_validations/attached_validator.rb
|
@@ -114,6 +105,11 @@ files:
|
|
114
105
|
- lib/active_storage_validations/dimension_validator.rb
|
115
106
|
- lib/active_storage_validations/engine.rb
|
116
107
|
- lib/active_storage_validations/limit_validator.rb
|
108
|
+
- lib/active_storage_validations/matchers.rb
|
109
|
+
- lib/active_storage_validations/matchers/attached_validator_matcher.rb
|
110
|
+
- lib/active_storage_validations/matchers/content_type_validator_matcher.rb
|
111
|
+
- lib/active_storage_validations/matchers/dimension_validator_matcher.rb
|
112
|
+
- lib/active_storage_validations/matchers/size_validator_matcher.rb
|
117
113
|
- lib/active_storage_validations/metadata.rb
|
118
114
|
- lib/active_storage_validations/railtie.rb
|
119
115
|
- lib/active_storage_validations/size_validator.rb
|
@@ -138,7 +134,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
134
|
- !ruby/object:Gem::Version
|
139
135
|
version: '0'
|
140
136
|
requirements: []
|
141
|
-
rubygems_version: 3.0.
|
137
|
+
rubygems_version: 3.0.3
|
142
138
|
signing_key:
|
143
139
|
specification_version: 4
|
144
140
|
summary: Validations for Active Storage
|