active_storage_validations 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -13
  3. data/config/locales/de.yml +5 -1
  4. data/config/locales/en.yml +5 -1
  5. data/config/locales/es.yml +5 -1
  6. data/config/locales/fr.yml +5 -1
  7. data/config/locales/it.yml +5 -1
  8. data/config/locales/ja.yml +5 -1
  9. data/config/locales/nl.yml +5 -1
  10. data/config/locales/pl.yml +5 -1
  11. data/config/locales/pt-BR.yml +6 -1
  12. data/config/locales/ru.yml +5 -1
  13. data/config/locales/sv.yml +23 -0
  14. data/config/locales/tr.yml +5 -1
  15. data/config/locales/uk.yml +5 -1
  16. data/config/locales/vi.yml +5 -1
  17. data/config/locales/zh-CN.yml +5 -1
  18. data/lib/active_storage_validations/aspect_ratio_validator.rb +19 -16
  19. data/lib/active_storage_validations/attached_validator.rb +4 -3
  20. data/lib/active_storage_validations/content_type_validator.rb +4 -3
  21. data/lib/active_storage_validations/dimension_validator.rb +34 -18
  22. data/lib/active_storage_validations/error_handler.rb +18 -0
  23. data/lib/active_storage_validations/limit_validator.rb +8 -6
  24. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +12 -2
  25. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +22 -2
  26. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +11 -7
  27. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +27 -18
  28. data/lib/active_storage_validations/metadata.rb +23 -11
  29. data/lib/active_storage_validations/processable_image_validator.rb +5 -10
  30. data/lib/active_storage_validations/size_validator.rb +8 -5
  31. data/lib/active_storage_validations/version.rb +1 -1
  32. data/lib/active_storage_validations.rb +3 -0
  33. metadata +19 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 267c083e1e37ff53853d928bf3df0a69b8c9cf0662c3cf744f99cde3d5809125
4
- data.tar.gz: 36554892e5c2df7ad812119b6acfae985e4adbe1dd8f7c29699f8f9ba69f0506
3
+ metadata.gz: f41ec7bce11523af0deebbac7aac427c19a0c5f9788c3719ff94122468781a79
4
+ data.tar.gz: aef955507fb8c8b3a42a27820ea213347f03c49373eef27b44a99e764456b77d
5
5
  SHA512:
6
- metadata.gz: 6cbe5d7521afc8680a9df584968c4e3da7c5e1602efc30f60dc6890a54bb9d609daf7e7eaa0924d6d7971f13e9c763e4063c228d14645b8c5261f1702c4a21c9
7
- data.tar.gz: 989e058c095e07790f647af2582355dddd715101b6dce10d4b521865362c5857f8df869270ae88e5eb58d20fa698ed470f4b181517ac016439d45f0e30049e21
6
+ metadata.gz: 19b37c9826cda8b8864631c77e5a3eabf1f8164279ad2c64beebd5f60b82e03dc39bdb466d516fa161b81b9ca3b0d98913bd04b32950e3b98b3ab28632941a91
7
+ data.tar.gz: d12ffa26482ae3e3feead8c3b658f0aa4331dd08a16b87ba9d7e639bc78a25499bda8ba27ae81c0db39d7d0c9bd59bc9f136b56500d97ae5a0f8b2bd85a5f112
data/README.md CHANGED
@@ -6,6 +6,7 @@
6
6
  [![MiniTest](https://github.com/igorkasyanchuk/active_storage_validations/workflows/MiniTest/badge.svg)](https://github.com/igorkasyanchuk/active_storage_validations/actions)
7
7
  [![RailsJazz](https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/my_other.svg?raw=true)](https://www.railsjazz.com)
8
8
  [![https://www.patreon.com/igorkasyanchuk](https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/patron.svg?raw=true)](https://www.patreon.com/igorkasyanchuk)
9
+ [![Listed on OpenSource-Heroes.com](https://opensource-heroes.com/badge-v1.svg)](https://opensource-heroes.com/r/igorkasyanchuk/active_storage_validations)
9
10
 
10
11
  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.
11
12
 
@@ -148,7 +149,11 @@ en:
148
149
  errors:
149
150
  messages:
150
151
  content_type_invalid: "has an invalid content type"
151
- file_size_out_of_range: "size %{file_size} is not between required range"
152
+ file_size_not_less_than: "file size must be less than %{max_size} (current size is %{file_size})"
153
+ file_size_not_less_than_or_equal_to: "file size must be less than or equal to %{max_size} (current size is %{file_size})"
154
+ file_size_not_greater_than: "file size must be greater than %{min_size} (current size is %{file_size})"
155
+ file_size_not_greater_than_or_equal_to: "file size must be greater than or equal to %{min_size} (current size is %{file_size})"
156
+ file_size_not_between: "file size must be between %{min_size} and %{max_size} (current size is %{file_size})"
152
157
  limit_out_of_range: "total number is out of range"
153
158
  image_metadata_missing: "is not a valid image"
154
159
  dimension_min_inclusion: "must be greater than or equal to %{width} x %{height} pixel."
@@ -171,17 +176,21 @@ en:
171
176
 
172
177
  In some cases, Active Storage Validations provides variables to help you customize messages:
173
178
 
174
- 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.
175
-
176
- The variables are not used by default to leave the choice to the user.
179
+ ### Content type
180
+ The `content_type_invalid` key has two variables that you can use:
181
+ - `content_type` containing the content type of the sent file
182
+ - `authorized_types` containing the list of authorized content types
177
183
 
178
184
  For example :
179
185
 
180
186
  ```yml
181
- content_type_invalid: "has an invalid content type : %{content_type}"
187
+ content_type_invalid: "has an invalid content type : %{content_type}, authorized types are %{authorized_types}"
182
188
  ```
183
189
 
184
- Also the "limit_out_of_range" key supports two variables the "min" and "max".
190
+ ### Number of files
191
+ The `limit_out_of_range` key supports two variables that you can use:
192
+ - `min` containing the minimum number of files
193
+ - `max` containing the maximum number of files
185
194
 
186
195
  For example :
187
196
 
@@ -189,6 +198,28 @@ For example :
189
198
  limit_out_of_range: "total number is out of range. range: [%{min}, %{max}]"
190
199
  ```
191
200
 
201
+ ### File size
202
+ The keys starting with `file_size_not_` support three variables that you can use:
203
+ - `file_size` containing the current file size
204
+ - `min` containing the minimum file size
205
+ - `max` containing the maxmimum file size
206
+
207
+ For example :
208
+
209
+ ```yml
210
+ file_size_not_between: "file size must be between %{min_size} and %{max_size} (current size is %{file_size})"
211
+ ```
212
+
213
+ ### Aspect ratio
214
+ The keys starting with `aspect_ratio_` support one variable that you can use:
215
+ - `aspect_ratio` containing the expected aspect ratio, especially usefull for custom aspect ratio
216
+
217
+ For example :
218
+
219
+ ```yml
220
+ aspect_ratio_is_not: "must be a %{aspect_ratio} image"
221
+ ```
222
+
192
223
  ## Installation
193
224
 
194
225
  Add this line to your application's Gemfile:
@@ -239,9 +270,11 @@ Example (Note that the options are chainable):
239
270
  ```ruby
240
271
  describe User do
241
272
  it { is_expected.to validate_attached_of(:avatar) }
273
+ it { is_expected.to validate_attached_of(:avatar).with_message('must not be blank') }
242
274
 
243
275
  it { is_expected.to validate_content_type_of(:avatar).allowing('image/png', 'image/gif') }
244
276
  it { is_expected.to validate_content_type_of(:avatar).rejecting('text/plain', 'text/xml') }
277
+ it { is_expected.to validate_content_type_of(:avatar).rejecting('text/plain', 'text/xml').with_message("must be an authorized type") }
245
278
 
246
279
  it { is_expected.to validate_dimensions_of(:avatar).width(250) }
247
280
  it { is_expected.to validate_dimensions_of(:avatar).height(200) }
@@ -256,6 +289,7 @@ describe User do
256
289
  it { is_expected.to validate_size_of(:avatar).less_than(50.kilobytes) }
257
290
  it { is_expected.to validate_size_of(:avatar).less_than_or_equal_to(50.kilobytes) }
258
291
  it { is_expected.to validate_size_of(:avatar).greater_than(1.kilobyte) }
292
+ it { is_expected.to validate_size_of(:avatar).greater_than(1.kilobyte).with_message('is not in required file size range') }
259
293
  it { is_expected.to validate_size_of(:avatar).greater_than_or_equal_to(1.kilobyte) }
260
294
  it { is_expected.to validate_size_of(:avatar).between(100..500.kilobytes) }
261
295
  end
@@ -281,9 +315,11 @@ Example (Note that the options are chainable):
281
315
  ```ruby
282
316
  class UserTest < ActiveSupport::TestCase
283
317
  should validate_attached_of(:avatar)
318
+ should validate_attached_of(:avatar).with_message('must not be blank')
284
319
 
285
320
  should validate_content_type_of(:avatar).allowing('image/png', 'image/gif')
286
321
  should validate_content_type_of(:avatar).rejecting('text/plain', 'text/xml')
322
+ should validate_content_type_of(:avatar).rejecting('text/plain', 'text/xml').with_message("must be an authorized type")
287
323
 
288
324
  should validate_dimensions_of(:avatar).width(250)
289
325
  should validate_dimensions_of(:avatar).height(200)
@@ -298,6 +334,7 @@ class UserTest < ActiveSupport::TestCase
298
334
  should validate_size_of(:avatar).less_than(50.kilobytes)
299
335
  should validate_size_of(:avatar).less_than_or_equal_to(50.kilobytes)
300
336
  should validate_size_of(:avatar).greater_than(1.kilobyte)
337
+ should validate_size_of(:avatar).greater_than(1.kilobyte).with_message('is not in required file size range')
301
338
  should validate_size_of(:avatar).greater_than_or_equal_to(1.kilobyte)
302
339
  should validate_size_of(:avatar).between(100..500.kilobytes)
303
340
  end
@@ -307,7 +344,6 @@ end
307
344
 
308
345
  * verify with remote storages (s3, etc)
309
346
  * verify how it works with direct upload
310
- * better error message when content_size is invalid
311
347
  * add more translations
312
348
 
313
349
  ## Tests & Contributing
@@ -345,8 +381,12 @@ BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test
345
381
  This is a Rails issue, and is fixed in Rails 6.
346
382
 
347
383
  ## Contributing
384
+
348
385
  You are welcome to contribute.
349
386
 
387
+ [<img src="https://opensource-heroes.com/svg/embed/igorkasyanchuk/active_storage_validations"
388
+ />](https://opensource-heroes.com/r/igorkasyanchuk/active_storage_validations)
389
+
350
390
  ## Contributors (BIG THANK YOU)
351
391
  - https://github.com/schweigert
352
392
  - https://github.com/tleneveu
@@ -394,12 +434,19 @@ You are welcome to contribute.
394
434
  - https://github.com/NARKOZ
395
435
  - https://github.com/stephensolis
396
436
  - 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
437
+ - https://github.com/Animesh-Ghosh
438
+ - https://github.com/gr8bit
439
+ - https://github.com/codegeek319
440
+ - https://github.com/clwy-cn
441
+ - https://github.com/kukicola
442
+ - https://github.com/sobrinho
443
+ - https://github.com/iainbeeston
444
+ - https://github.com/marckohlbrugge
445
+ - https://github.com/Mth0158
446
+ - https://github.com/technicalpickles
447
+ - https://github.com/ricsdeol
448
+ - https://github.com/Fonsan
449
+
403
450
 
404
451
  ## License
405
452
 
@@ -2,7 +2,11 @@ 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 %{file_size} liegt nicht im erlaubten Bereich"
5
+ file_size_not_less_than: "Dateigröße muss kleiner als %{max_size} sein (aktuelle Dateigröße ist %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "Dateigröße muss kleiner oder gleich %{max_size} sein (aktuelle Dateigröße ist %{file_size})"
7
+ file_size_not_greater_than: "Dateigröße muss größer als %{min_size} sein (aktuelle Dateigröße ist %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "Dateigröße muss größer oder gleich %{min_size} sein (aktuelle Dateigröße ist %{file_size})"
9
+ file_size_not_between: "Dateigröße muss zwischen %{min_size} und %{max_size} liegen (aktuelle Dateigröße ist %{file_size})"
6
10
  limit_out_of_range: "Anzahl ist außerhalb des gültigen Bereichs"
7
11
  image_metadata_missing: "ist kein gültiges Bild"
8
12
  dimension_min_inclusion: "muss größer oder gleich %{width} x %{height} Pixel sein"
@@ -2,7 +2,11 @@ en:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "has an invalid content type"
5
- file_size_out_of_range: "size %{file_size} is not between required range"
5
+ file_size_not_less_than: "file size must be less than %{max_size} (current size is %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "file size must be less than or equal to %{max_size} (current size is %{file_size})"
7
+ file_size_not_greater_than: "file size must be greater than %{min_size} (current size is %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "file size must be greater than or equal to %{min_size} (current size is %{file_size})"
9
+ file_size_not_between: "file size must be between %{min_size} and %{max_size} (current size is %{file_size})"
6
10
  limit_out_of_range: "total number is out of range"
7
11
  image_metadata_missing: "is not a valid image"
8
12
  dimension_min_inclusion: "must be greater than or equal to %{width} x %{height} pixel"
@@ -2,7 +2,11 @@ es:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "tiene un tipo de contenido inválido"
5
- file_size_out_of_range: "tamaño %{file_size} no está entre el rango requerido"
5
+ file_size_not_less_than: "el tamaño del archivo debe ser inferior a %{max_size} (el tamaño actual es %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "el tamaño del archivo debe ser menor o igual a %{max_size} (el tamaño actual es %{file_size})"
7
+ file_size_not_greater_than: "el tamaño del archivo debe ser mayor que %{min_size} (el tamaño actual es %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "el tamaño del archivo debe ser mayor o igual a %{min_size} (el tamaño actual es %{file_size})"
9
+ file_size_not_between: "el tamaño del archivo debe estar entre %{min_size} y %{max_size} (el tamaño actual es %{file_size})"
6
10
  limit_out_of_range: "el número total está fuera de rango"
7
11
  image_metadata_missing: "no es una imagen válida"
8
12
  dimension_min_inclusion: "debe ser mayor o igual a %{width} x %{height} pixel"
@@ -2,7 +2,11 @@ fr:
2
2
  errors:
3
3
  messages:
4
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"
5
+ file_size_not_less_than: "la taille du fichier doit être inférieure à %{max_size} (la taille actuelle est %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "la taille du fichier doit être inférieure ou égale à %{max_size} (la taille actuelle est %{file_size})"
7
+ file_size_not_greater_than: "la taille du fichier doit être supérieure à %{min_size} (la taille actuelle est %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "la taille du fichier doit être supérieure ou égale à %{min_size} (la taille actuelle est %{file_size})"
9
+ file_size_not_between: "la taille du fichier doit être comprise entre %{min_size} et %{max_size} (la taille actuelle est %{file_size})"
6
10
  limit_out_of_range: "le nombre total est hors limites"
7
11
  image_metadata_missing: "n'est pas une image valide"
8
12
  dimension_min_inclusion: "doit être supérieur ou égal à %{width} x %{height} pixels"
@@ -2,7 +2,11 @@ it:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "ha un tipo di contenuto non valido"
5
- file_size_out_of_range: "la dimensione del file %{file_size} non è compresa nell’intervallo consentito"
5
+ file_size_not_less_than: "la dimensione del file deve essere inferiore a %{max_size} (la dimensione attuale è %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "la dimensione del file deve essere minore o uguale a %{max_size} (la dimensione attuale è %{file_size})"
7
+ file_size_not_greater_than: "la dimensione del file deve essere maggiore di %{min_size} (la dimensione attuale è %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "la dimensione del file deve essere maggiore o uguale a %{min_size} (la dimensione attuale è %{file_size})"
9
+ file_size_not_between: "la dimensione del file deve essere compresa tra %{min_size} e %{max_size} (la dimensione attuale è %{file_size})"
6
10
  limit_out_of_range: "il valore è al di fuori dell’intervallo consentito"
7
11
  image_metadata_missing: "non è un'immagine valida"
8
12
  dimension_min_inclusion: "deve essere maggiore o uguale a %{width} x %{height} pixel"
@@ -2,7 +2,11 @@ ja:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "のContent Typeが不正です"
5
- file_size_out_of_range: "の容量 %{file_size} が許容範囲外です"
5
+ file_size_not_less_than: "ファイル サイズは %{max_size} 未満にする必要があります (現在のサイズは %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "ファイル サイズは %{max_size} 以下である必要があります (現在のサイズは %{file_size})"
7
+ file_size_not_greater_than: "ファイル サイズは %{min_size} より大きい必要があります (現在のサイズは %{file_size} です)"
8
+ file_size_not_greater_than_or_equal_to: "ファイル サイズは %{min_size} 以上である必要があります (現在のサイズは %{file_size})"
9
+ file_size_not_between: "ファイル サイズは %{min_size} から %{max_size} の間でなければなりません (現在のサイズは %{file_size} です)"
6
10
  limit_out_of_range: "の数が許容範囲外です"
7
11
  image_metadata_missing: "は不正な画像です"
8
12
  dimension_min_inclusion: "は %{width} x %{height} ピクセル以上の大きさにしてください"
@@ -2,7 +2,11 @@ nl:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "heeft een ongeldig inhoudstype"
5
- file_size_out_of_range: "bestandsgrootte %{file_size} valt niet tussen het vereiste bereik"
5
+ file_size_not_less_than: "bestandsgrootte moet kleiner zijn dan %{max_size} (huidige grootte is %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "bestandsgrootte moet kleiner zijn dan of gelijk zijn aan %{max_size} (huidige grootte is %{file_size})"
7
+ file_size_not_greater_than: "bestandsgrootte moet groter zijn dan %{min_size} (huidige grootte is %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "bestandsgrootte moet groter zijn dan of gelijk zijn aan %{min_size} (huidige grootte is %{file_size})"
9
+ file_size_not_between: "bestandsgrootte moet tussen %{min_size} en %{max_size} liggen (huidige grootte is %{file_size})"
6
10
  limit_out_of_range: "totaal aantal valt buiten het vereiste bereik"
7
11
  image_metadata_missing: "is geen geldige afbeelding"
8
12
  dimension_min_inclusion: "moet groter of gelijk zijn aan %{width} x %{height} pixels"
@@ -2,7 +2,11 @@ pl:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "jest nieprawidłowego typu"
5
- file_size_out_of_range: "rozmiar %{file_size} nie zawiera się w dopuszczalnym zakresie"
5
+ file_size_not_less_than: "rozmiar pliku musi być mniejszy niż %{max_size} (obecny rozmiar to %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "rozmiar pliku musi być mniejszy lub równy %{max_size} (obecny rozmiar to %{file_size})"
7
+ file_size_not_greater_than: "rozmiar pliku musi być większy niż %{min_size} (obecny rozmiar to %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "rozmiar pliku musi być większy lub równy %{min_size} (obecny rozmiar to %{file_size})"
9
+ file_size_not_between: "rozmiar pliku musi mieścić się w przedziale od %{min_size} do %{max_size} (obecny rozmiar to %{file_size})"
6
10
  limit_out_of_range: "ilość przekracza dopuszczalny zakres"
7
11
  image_metadata_missing: "nie jest prawidłowym obrazem"
8
12
  dimension_min_inclusion: "musi być równe lub większe od %{width} x %{height} pixeli"
@@ -2,7 +2,11 @@ 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: "tem tamanho %{file_size} e está fora da faixa de tamanho válida"
5
+ file_size_not_less_than: "o tamanho do arquivo deve ser menor que %{max_size} (o tamanho atual é %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "o tamanho do arquivo deve ser menor ou igual a %{max_size} (o tamanho atual é %{file_size})"
7
+ file_size_not_greater_than: "o tamanho do arquivo deve ser maior que %{min_size} (o tamanho atual é %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "o tamanho do arquivo deve ser maior ou igual a %{min_size} (o tamanho atual é %{file_size})"
9
+ file_size_not_between: "o tamanho do arquivo deve estar entre %{min_size} e %{max_size} (o tamanho atual é %{file_size})"
6
10
  limit_out_of_range: "o número total está fora do limite"
7
11
  image_metadata_missing: "não é uma imagem válida"
8
12
  dimension_min_inclusion: "deve ser maior ou igual a %{width} x %{height} pixels"
@@ -20,3 +24,4 @@ pt-BR:
20
24
  aspect_ratio_not_landscape: "não está no formato paisagem"
21
25
  aspect_ratio_is_not: "não contém uma proporção de %{aspect_ratio}"
22
26
  aspect_ratio_unknown: "não tem uma proporção definida"
27
+ image_not_processable: "não é uma imagem válida"
@@ -2,7 +2,11 @@ ru:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "имеет недопустимый тип содержимого"
5
- file_size_out_of_range: "размер %{file_size} больше требуемого"
5
+ file_size_not_less_than: "размер файла должен быть меньше %{max_size} (текущий размер %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "размер файла должен быть меньше или равен %{max_size} (текущий размер %{file_size})"
7
+ file_size_not_greater_than: "размер файла должен быть больше %{min_size} (текущий размер %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "размер файла должен быть больше или равен %{min_size} (текущий размер %{file_size})"
9
+ file_size_not_between: "размер файла должен быть между %{min_size} и %{max_size} (текущий размер %{file_size})"
6
10
  limit_out_of_range: "количество файлов больше требуемого"
7
11
  image_metadata_missing: "не является допустимым изображением"
8
12
  dimension_min_inclusion: "должен быть больше или равно %{width} x %{height} пикселям"
@@ -0,0 +1,23 @@
1
+ sv:
2
+ errors:
3
+ messages:
4
+ content_type_invalid: "Har en ogiltig filtyp"
5
+ file_size_out_of_range: "storleken %{file_size} är utanför det krävda intervallet"
6
+ limit_out_of_range: "antalet filer är utanför det godkända intervallet"
7
+ image_metadata_missing: "bilden sankar metadata"
8
+ dimension_min_inclusion: "måste minst vara %{width} x %{height} pixlar"
9
+ dimension_max_inclusion: "måste vara mindre eller lika med %{width} x %{height} pixlar"
10
+ dimension_width_inclusion: "bredden är utanför %{min} till %{max} pixlar"
11
+ dimension_height_inclusion: "höjden är utanför %{min} till %{max} pixlar"
12
+ dimension_width_greater_than_or_equal_to: "bredden måste minst vara %{length} pixlar"
13
+ dimension_height_greater_than_or_equal_to: "höjden måste minst vara %{length} pixlar"
14
+ dimension_width_less_than_or_equal_to: "bredden får max vara %{length} pixlar"
15
+ dimension_height_less_than_or_equal_to: "höjden får max vara %{length} pixlar"
16
+ dimension_width_equal_to: "bredden måste vara %{length} pixlar"
17
+ dimension_height_equal_to: "höjden måste vara %{length} pixlar"
18
+ aspect_ratio_not_square: "måste vara en kvadratisk bild"
19
+ aspect_ratio_not_portrait: "måste vara en porträttorienterad bild"
20
+ aspect_ratio_not_landscape: "måste vara en landskapsorienterad bild"
21
+ aspect_ratio_is_not: "måste ha en följande aspect ratio %{aspect_ratio}"
22
+ aspect_ratio_unknown: "har en okänd aspect ratio"
23
+ image_not_processable: "är inte en giltig bild"
@@ -2,7 +2,11 @@ tr:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "geçersiz dosya tipine sahip"
5
- file_size_out_of_range: "dosya boyutu %{file_size} gerekli aralık dışında"
5
+ file_size_not_less_than: "dosya boyutu %{max_size} boyutundan küçük olmalıdır (geçerli boyut %{file_size}'dir)"
6
+ file_size_not_less_than_or_equal_to: "dosya boyutu %{max_size} değerinden küçük veya ona eşit olmalıdır (geçerli boyut %{file_size}'dir)"
7
+ file_size_not_greater_than: "dosya boyutu %{min_size} boyutundan büyük olmalıdır (geçerli boyut %{file_size}'dir)"
8
+ file_size_not_greater_than_or_equal_to: "dosya boyutu %{min_size} değerinden büyük veya eşit olmalıdır (geçerli boyut %{file_size}'dir)"
9
+ file_size_not_between: "dosya boyutu %{min_size} ile %{max_size} arasında olmalıdır (geçerli boyut %{file_size}'dir)"
6
10
  limit_out_of_range: "toplam miktar aralık dışında"
7
11
  image_metadata_missing: "geçerli bir imaj değil"
8
12
  dimension_min_inclusion: "%{width} x %{height} piksele eşit ya da büyük olmalı"
@@ -2,7 +2,11 @@ uk:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "має неприпустимий тип вмісту"
5
- file_size_out_of_range: "розмір %{file_size} більше необхідного"
5
+ file_size_not_less_than: "розмір файлу має бути менше %{max_size} (поточний розмір %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "розмір файлу має бути меншим або дорівнювати %{max_size} (поточний розмір %{file_size})"
7
+ file_size_not_greater_than: "розмір файлу має бути більшим ніж %{min_size} (поточний розмір %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "розмір файлу має бути більшим або дорівнювати %{min_size} (поточний розмір %{file_size})"
9
+ file_size_not_between: "розмір файлу має бути від %{min_size} до %{max_size} (поточний розмір %{file_size})"
6
10
  limit_out_of_range: "кількість файлів більше необхідного"
7
11
  image_metadata_missing: "не є допустимим зображенням"
8
12
  dimension_min_inclusion: "мусить бути більше або дорівнювати %{width} x %{height} пікселям"
@@ -2,7 +2,11 @@ vi:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "tệp không hợp lệ"
5
- file_size_out_of_range: "kích thước %{file_size} vượt giới hạn"
5
+ file_size_not_less_than: "kích thước tệp phải nhỏ hơn %{max_size} (kích thước hiện tại là %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "kích thước tệp phải nhỏ hơn hoặc bằng %{max_size} (kích thước hiện tại là %{file_size})"
7
+ file_size_not_greater_than: "kích thước tệp phải lớn hơn %{min_size} (kích thước hiện tại là %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "kích thước tệp phải lớn hơn hoặc bằng %{min_size} (kích thước hiện tại là %{file_size})"
9
+ file_size_not_between: "kích thước tệp phải nằm trong khoảng từ %{min_size} đến %{max_size} (kích thước hiện tại là %{file_size})"
6
10
  limit_out_of_range: "tổng số tệp vượt giới hạn"
7
11
  image_metadata_missing: "không phải là ảnh"
8
12
  dimension_min_inclusion: "phải lớn hơn hoặc bằng %{width} x %{height} pixel"
@@ -2,7 +2,11 @@ zh-CN:
2
2
  errors:
3
3
  messages:
4
4
  content_type_invalid: "文件类型错误"
5
- file_size_out_of_range: "文件大小 %{file_size} 超出限定范围"
5
+ file_size_not_less_than: "文件大小必须小于 %{max_size}(当前大小为 %{file_size})"
6
+ file_size_not_less_than_or_equal_to: "文件大小必须小于或等于 %{max_size}(当前大小为 %{file_size})"
7
+ file_size_not_greater_than: "文件大小必须大于 %{min_size}(当前大小为 %{file_size})"
8
+ file_size_not_greater_than_or_equal_to: "文件大小必须大于或等于 %{min_size}(当前大小为 %{file_size})"
9
+ file_size_not_between: "文件大小必须介于 %{min_size} 和 %{max_size} 之间(当前大小为 %{file_size})"
6
10
  limit_out_of_range: "文件数超出限定范围"
7
11
  image_metadata_missing: "不是有效的图像"
8
12
  dimension_min_inclusion: "必须大于或等于 %{width} x %{height} 像素"
@@ -5,13 +5,15 @@ require_relative 'metadata.rb'
5
5
  module ActiveStorageValidations
6
6
  class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
7
7
  include OptionProcUnfolding
8
+ include ErrorHandler
8
9
 
9
10
  AVAILABLE_CHECKS = %i[with].freeze
10
11
  PRECISION = 3
11
12
 
12
13
  def check_validity!
13
- return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
14
- raise ArgumentError, 'You must pass :with to the validator'
14
+ unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
15
+ raise ArgumentError, 'You must pass :with to the validator'
16
+ end
15
17
  end
16
18
 
17
19
  if Rails.gem_version >= Gem::Version.new('6.0.0')
@@ -53,23 +55,30 @@ module ActiveStorageValidations
53
55
 
54
56
  def is_valid?(record, attribute, metadata)
55
57
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
58
+ errors_options = initialize_error_options(options)
59
+
56
60
  if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
57
- add_error(record, attribute, :image_metadata_missing, flat_options[:with])
61
+ errors_options[:aspect_ratio] = flat_options[:with]
62
+
63
+ add_error(record, attribute, :image_metadata_missing, **errors_options)
58
64
  return false
59
65
  end
60
66
 
61
67
  case flat_options[:with]
62
68
  when :square
63
69
  return true if metadata[:width] == metadata[:height]
64
- add_error(record, attribute, :aspect_ratio_not_square, flat_options[:with])
70
+ errors_options[:aspect_ratio] = flat_options[:with]
71
+ add_error(record, attribute, :aspect_ratio_not_square, **errors_options)
65
72
 
66
73
  when :portrait
67
74
  return true if metadata[:height] > metadata[:width]
68
- add_error(record, attribute, :aspect_ratio_not_portrait, flat_options[:with])
75
+ errors_options[:aspect_ratio] = flat_options[:with]
76
+ add_error(record, attribute, :aspect_ratio_not_portrait, **errors_options)
69
77
 
70
78
  when :landscape
71
79
  return true if metadata[:width] > metadata[:height]
72
- add_error(record, attribute, :aspect_ratio_not_landscape, flat_options[:with])
80
+ errors_options[:aspect_ratio] = flat_options[:with]
81
+ add_error(record, attribute, :aspect_ratio_not_landscape, **errors_options)
73
82
 
74
83
  else
75
84
  if flat_options[:with] =~ /is_(\d*)_(\d*)/
@@ -78,20 +87,14 @@ module ActiveStorageValidations
78
87
 
79
88
  return true if (x.to_f / y).round(PRECISION) == (metadata[:width].to_f / metadata[:height]).round(PRECISION)
80
89
 
81
- add_error(record, attribute, :aspect_ratio_is_not, "#{x}x#{y}")
90
+ errors_options[:aspect_ratio] = "#{x}x#{y}"
91
+ add_error(record, attribute, :aspect_ratio_is_not, **errors_options)
82
92
  else
83
- add_error(record, attribute, :aspect_ratio_unknown, flat_options[:with])
93
+ errors_options[:aspect_ratio] = flat_options[:with]
94
+ add_error(record, attribute, :aspect_ratio_unknown, **errors_options)
84
95
  end
85
96
  end
86
97
  false
87
98
  end
88
-
89
-
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)
94
- end
95
-
96
99
  end
97
100
  end
@@ -2,13 +2,14 @@
2
2
 
3
3
  module ActiveStorageValidations
4
4
  class AttachedValidator < ActiveModel::EachValidator # :nodoc:
5
+ include ErrorHandler
6
+
5
7
  def validate_each(record, attribute, _value)
6
8
  return if record.send(attribute).attached?
7
9
 
8
- errors_options = {}
9
- errors_options[:message] = options[:message] if options[:message].present?
10
+ errors_options = initialize_error_options(options)
10
11
 
11
- record.errors.add(attribute, :blank, **errors_options)
12
+ add_error(record, attribute, :blank, **errors_options)
12
13
  end
13
14
  end
14
15
  end
@@ -3,6 +3,7 @@
3
3
  module ActiveStorageValidations
4
4
  class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
5
5
  include OptionProcUnfolding
6
+ include ErrorHandler
6
7
 
7
8
  AVAILABLE_CHECKS = %i[with in].freeze
8
9
 
@@ -14,14 +15,14 @@ module ActiveStorageValidations
14
15
 
15
16
  files = Array.wrap(record.send(attribute))
16
17
 
17
- errors_options = { authorized_types: types_to_human_format(types) }
18
- errors_options[:message] = options[:message] if options[:message].present?
18
+ errors_options = initialize_error_options(options)
19
+ errors_options[:authorized_types] = types_to_human_format(types)
19
20
 
20
21
  files.each do |file|
21
22
  next if is_valid?(file, types)
22
23
 
23
24
  errors_options[:content_type] = content_type(file)
24
- record.errors.add(attribute, :content_type_invalid, **errors_options)
25
+ add_error(record, attribute, :content_type_invalid, **errors_options)
25
26
  break
26
27
  end
27
28
  end
@@ -5,6 +5,7 @@ require_relative 'metadata.rb'
5
5
  module ActiveStorageValidations
6
6
  class DimensionValidator < ActiveModel::EachValidator # :nodoc
7
7
  include OptionProcUnfolding
8
+ include ErrorHandler
8
9
 
9
10
  AVAILABLE_CHECKS = %i[width height min max].freeze
10
11
 
@@ -32,8 +33,9 @@ module ActiveStorageValidations
32
33
 
33
34
 
34
35
  def check_validity!
35
- return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
36
- raise ArgumentError, 'You must pass either :width, :height, :min or :max to the validator'
36
+ unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
37
+ raise ArgumentError, 'You must pass either :width, :height, :min or :max to the validator'
38
+ end
37
39
  end
38
40
 
39
41
 
@@ -70,9 +72,11 @@ module ActiveStorageValidations
70
72
 
71
73
  def is_valid?(record, attribute, file_metadata)
72
74
  flat_options = process_options(record)
75
+ errors_options = initialize_error_options(options)
76
+
73
77
  # Validation fails unless file metadata contains valid width and height.
74
78
  if file_metadata[:width].to_i <= 0 || file_metadata[:height].to_i <= 0
75
- add_error(record, attribute, :image_metadata_missing)
79
+ add_error(record, attribute, :image_metadata_missing, **errors_options)
76
80
  return false
77
81
  end
78
82
 
@@ -82,39 +86,58 @@ module ActiveStorageValidations
82
86
  (flat_options[:width][:min] && file_metadata[:width] < flat_options[:width][:min]) ||
83
87
  (flat_options[:height][:min] && file_metadata[:height] < flat_options[:height][:min])
84
88
  )
85
- add_error(record, attribute, :dimension_min_inclusion, width: flat_options[:width][:min], height: flat_options[:height][:min])
89
+ errors_options[:width] = flat_options[:width][:min]
90
+ errors_options[:height] = flat_options[:height][:min]
91
+
92
+ add_error(record, attribute, :dimension_min_inclusion, **errors_options)
86
93
  return false
87
94
  end
88
95
  if flat_options[:max] && (
89
96
  (flat_options[:width][:max] && file_metadata[:width] > flat_options[:width][:max]) ||
90
97
  (flat_options[:height][:max] && file_metadata[:height] > flat_options[:height][:max])
91
98
  )
92
- add_error(record, attribute, :dimension_max_inclusion, width: flat_options[:width][:max], height: flat_options[:height][:max])
99
+ errors_options[:width] = flat_options[:width][:max]
100
+ errors_options[:height] = flat_options[:height][:max]
101
+
102
+ add_error(record, attribute, :dimension_max_inclusion, **errors_options)
93
103
  return false
94
104
  end
95
105
 
96
106
  # Validation based on checks :width and :height.
97
107
  else
98
108
  width_or_height_invalid = false
109
+
99
110
  [:width, :height].each do |length|
100
111
  next unless flat_options[length]
101
112
  if flat_options[length].is_a?(Hash)
102
113
  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])
114
+ error_type = :"dimension_#{length}_inclusion"
115
+ errors_options[:min] = flat_options[length][:min]
116
+ errors_options[:max] = flat_options[length][:max]
117
+
118
+ add_error(record, attribute, error_type, **errors_options)
104
119
  width_or_height_invalid = true
105
120
  else
106
121
  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])
122
+ error_type = :"dimension_#{length}_greater_than_or_equal_to"
123
+ errors_options[:length] = flat_options[length][:min]
124
+
125
+ add_error(record, attribute, error_type, **errors_options)
108
126
  width_or_height_invalid = true
109
- end
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])
127
+ elsif flat_options[length][:max] && file_metadata[length] > flat_options[length][:max]
128
+ error_type = :"dimension_#{length}_less_than_or_equal_to"
129
+ errors_options[:length] = flat_options[length][:max]
130
+
131
+ add_error(record, attribute, error_type, **errors_options)
112
132
  width_or_height_invalid = true
113
133
  end
114
134
  end
115
135
  else
116
136
  if file_metadata[length] != flat_options[length]
117
- add_error(record, attribute, :"dimension_#{length}_equal_to", length: flat_options[length])
137
+ error_type = :"dimension_#{length}_equal_to"
138
+ errors_options[:length] = flat_options[length]
139
+
140
+ add_error(record, attribute, error_type, **errors_options)
118
141
  width_or_height_invalid = true
119
142
  end
120
143
  end
@@ -125,12 +148,5 @@ module ActiveStorageValidations
125
148
 
126
149
  true # valid file
127
150
  end
128
-
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)
133
- end
134
-
135
151
  end
136
152
  end
@@ -0,0 +1,18 @@
1
+ module ActiveStorageValidations
2
+ module ErrorHandler
3
+
4
+ def initialize_error_options(options)
5
+ {
6
+ message: (options[:message] if options[:message].present?)
7
+ }
8
+ end
9
+
10
+ def add_error(record, attribute, default_message, **errors_options)
11
+ message = errors_options[:message].presence || default_message
12
+ return if record.errors.added?(attribute, message)
13
+
14
+ record.errors.add(attribute, message, **errors_options)
15
+ end
16
+
17
+ end
18
+ end
@@ -3,23 +3,25 @@
3
3
  module ActiveStorageValidations
4
4
  class LimitValidator < ActiveModel::EachValidator # :nodoc:
5
5
  include OptionProcUnfolding
6
+ include ErrorHandler
6
7
 
7
8
  AVAILABLE_CHECKS = %i[max min].freeze
8
9
 
9
10
  def check_validity!
10
- return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
11
- raise ArgumentError, 'You must pass either :max or :min to the validator'
11
+ unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
12
+ raise ArgumentError, 'You must pass either :max or :min to the validator'
13
+ end
12
14
  end
13
15
 
14
16
  def validate_each(record, attribute, _)
15
- return true unless record.send(attribute).attached?
16
-
17
17
  files = Array.wrap(record.send(attribute)).compact.uniq
18
18
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
19
- errors_options = { min: flat_options[:min], max: flat_options[:max] }
19
+ errors_options = initialize_error_options(options)
20
+ errors_options[:min] = flat_options[:min]
21
+ errors_options[:max] = flat_options[:max]
20
22
 
21
23
  return true if files_count_valid?(files.count, flat_options)
22
- record.errors.add(attribute, options[:message].presence || :limit_out_of_range, **errors_options)
24
+ add_error(record, attribute, :limit_out_of_range, **errors_options)
23
25
  end
24
26
 
25
27
  def files_count_valid?(count, flat_options)
@@ -9,12 +9,18 @@ module ActiveStorageValidations
9
9
  class AttachedValidatorMatcher
10
10
  def initialize(attribute_name)
11
11
  @attribute_name = attribute_name
12
+ @custom_message = nil
12
13
  end
13
14
 
14
15
  def description
15
16
  "validate #{@attribute_name} must be attached"
16
17
  end
17
18
 
19
+ def with_message(message)
20
+ @custom_message = message
21
+ self
22
+ end
23
+
18
24
  def matches?(subject)
19
25
  @subject = subject.is_a?(Class) ? subject.new : subject
20
26
  responds_to_methods && valid_when_attached && invalid_when_not_attached
@@ -39,7 +45,7 @@ module ActiveStorageValidations
39
45
  def valid_when_attached
40
46
  @subject.public_send(@attribute_name).attach(attachable) unless @subject.public_send(@attribute_name).attached?
41
47
  @subject.validate
42
- @subject.errors.details[@attribute_name].exclude?(error: :blank)
48
+ @subject.errors.details[@attribute_name].exclude?(error: error_message)
43
49
  end
44
50
 
45
51
  def invalid_when_not_attached
@@ -48,7 +54,11 @@ module ActiveStorageValidations
48
54
  @subject.public_send("#{@attribute_name}=", nil)
49
55
 
50
56
  @subject.validate
51
- @subject.errors.details[@attribute_name].include?(error: :blank)
57
+ @subject.errors.details[@attribute_name].include?(error: error_message)
58
+ end
59
+
60
+ def error_message
61
+ @custom_message || :blank
52
62
  end
53
63
 
54
64
  def attachable
@@ -11,6 +11,7 @@ module ActiveStorageValidations
11
11
  class ContentTypeValidatorMatcher
12
12
  def initialize(attribute_name)
13
13
  @attribute_name = attribute_name
14
+ @custom_message = nil
14
15
  end
15
16
 
16
17
  def description
@@ -27,9 +28,14 @@ module ActiveStorageValidations
27
28
  self
28
29
  end
29
30
 
31
+ def with_message(message)
32
+ @custom_message = message
33
+ self
34
+ end
35
+
30
36
  def matches?(subject)
31
37
  @subject = subject.is_a?(Class) ? subject.new : subject
32
- responds_to_methods && allowed_types_allowed? && rejected_types_rejected?
38
+ responds_to_methods && allowed_types_allowed? && rejected_types_rejected? && validate_error_message?
33
39
  end
34
40
 
35
41
  def failure_message
@@ -77,7 +83,21 @@ module ActiveStorageValidations
77
83
  def type_allowed?(type)
78
84
  @subject.public_send(@attribute_name).attach(attachment_for(type))
79
85
  @subject.validate
80
- @subject.errors.details[@attribute_name].all? { |error| error[:error] != :content_type_invalid }
86
+ @subject.errors.details[@attribute_name].none? do |error|
87
+ error[:error].to_s.include?(error_message)
88
+ end
89
+ end
90
+
91
+ def validate_error_message?
92
+ @subject.public_send(@attribute_name).attach(attachment_for('fake/fake'))
93
+ @subject.validate
94
+ @subject.errors.details[@attribute_name].all? do |error|
95
+ error[:error].to_s.include?(error_message)
96
+ end
97
+ end
98
+
99
+ def error_message
100
+ @custom_message || :content_type_invalid.to_s
81
101
  end
82
102
 
83
103
  def attachment_for(type)
@@ -65,8 +65,8 @@ module ActiveStorageValidations
65
65
  def matches?(subject)
66
66
  @subject = subject.is_a?(Class) ? subject.new : subject
67
67
  responds_to_methods &&
68
- width_smaller_than_min? && width_larger_than_min? && width_smaller_than_max? && width_larger_than_max? && width_equals? &&
69
- height_smaller_than_min? && height_larger_than_min? && height_smaller_than_max? && height_larger_than_max? && height_equals?
68
+ width_not_smaller_than_min? && width_larger_than_min? && width_smaller_than_max? && width_not_larger_than_max? && width_equals? &&
69
+ height_not_smaller_than_min? && height_larger_than_min? && height_smaller_than_max? && height_not_larger_than_max? && height_equals?
70
70
  end
71
71
 
72
72
  def failure_message
@@ -93,7 +93,7 @@ module ActiveStorageValidations
93
93
  ((@height_min || 0) + (@height_max || 2000)) / 2
94
94
  end
95
95
 
96
- def width_smaller_than_min?
96
+ def width_not_smaller_than_min?
97
97
  @width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height, 'width')
98
98
  end
99
99
 
@@ -105,7 +105,7 @@ module ActiveStorageValidations
105
105
  @width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height, 'width')
106
106
  end
107
107
 
108
- def width_larger_than_max?
108
+ def width_not_larger_than_max?
109
109
  @width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height, 'width')
110
110
  end
111
111
 
@@ -113,7 +113,7 @@ module ActiveStorageValidations
113
113
  @width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height, 'width')
114
114
  end
115
115
 
116
- def height_smaller_than_min?
116
+ def height_not_smaller_than_min?
117
117
  @height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1, 'height')
118
118
  end
119
119
 
@@ -125,7 +125,7 @@ module ActiveStorageValidations
125
125
  @height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1, 'height')
126
126
  end
127
127
 
128
- def height_larger_than_max?
128
+ def height_not_larger_than_max?
129
129
  @height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1, 'height')
130
130
  end
131
131
 
@@ -140,7 +140,11 @@ module ActiveStorageValidations
140
140
  Matchers.mock_metadata(attachment, width, height) do
141
141
  @subject.validate
142
142
  exclude_error_message = @custom_message || "dimension_#{check}"
143
- @subject.errors.details[@attribute_name].all? { |error| error[:error].to_s.exclude?(exclude_error_message) }
143
+ @subject.errors.details[@attribute_name].none? do |error|
144
+ error[:error].to_s.include?(exclude_error_message) ||
145
+ error[:error].to_s.include?("dimension_min") ||
146
+ error[:error].to_s.include?("dimension_max")
147
+ end
144
148
  end
145
149
  end
146
150
 
@@ -11,7 +11,8 @@ module ActiveStorageValidations
11
11
  class SizeValidatorMatcher
12
12
  def initialize(attribute_name)
13
13
  @attribute_name = attribute_name
14
- @low = @high = nil
14
+ @min = @max = nil
15
+ @custom_message = nil
15
16
  end
16
17
 
17
18
  def description
@@ -19,41 +20,46 @@ module ActiveStorageValidations
19
20
  end
20
21
 
21
22
  def less_than(size)
22
- @high = size - 1.byte
23
+ @max = size - 1.byte
23
24
  self
24
25
  end
25
26
 
26
27
  def less_than_or_equal_to(size)
27
- @high = size
28
+ @max = size
28
29
  self
29
30
  end
30
31
 
31
32
  def greater_than(size)
32
- @low = size + 1.byte
33
+ @min = size + 1.byte
33
34
  self
34
35
  end
35
36
 
36
37
  def greater_than_or_equal_to(size)
37
- @low = size
38
+ @min = size
38
39
  self
39
40
  end
40
41
 
41
42
  def between(range)
42
- @low, @high = range.first, range.last
43
+ @min, @max = range.first, range.last
44
+ self
45
+ end
46
+
47
+ def with_message(message)
48
+ @custom_message = message
43
49
  self
44
50
  end
45
51
 
46
52
  def matches?(subject)
47
53
  @subject = subject.is_a?(Class) ? subject.new : subject
48
- responds_to_methods && lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
54
+ responds_to_methods && not_lower_than_min? && higher_than_min? && lower_than_max? && not_higher_than_max?
49
55
  end
50
56
 
51
57
  def failure_message
52
- "is expected to validate file size of #{@attribute_name} to be between #{@low} and #{@high} bytes"
58
+ "is expected to validate file size of #{@attribute_name} to be between #{@min} and #{@max} bytes"
53
59
  end
54
60
 
55
61
  def failure_message_when_negated
56
- "is expected to not validate file size of #{@attribute_name} to be between #{@low} and #{@high} bytes"
62
+ "is expected to not validate file size of #{@attribute_name} to be between #{@min} and #{@max} bytes"
57
63
  end
58
64
 
59
65
  protected
@@ -64,20 +70,20 @@ module ActiveStorageValidations
64
70
  @subject.public_send(@attribute_name).respond_to?(:detach)
65
71
  end
66
72
 
67
- def lower_than_low?
68
- @low.nil? || !passes_validation_with_size(@low - 1)
73
+ def not_lower_than_min?
74
+ @min.nil? || !passes_validation_with_size(@min - 1)
69
75
  end
70
76
 
71
- def higher_than_low?
72
- @low.nil? || passes_validation_with_size(@low + 1)
77
+ def higher_than_min?
78
+ @min.nil? || passes_validation_with_size(@min + 1)
73
79
  end
74
80
 
75
- def lower_than_high?
76
- @high.nil? || @high == Float::INFINITY || passes_validation_with_size(@high - 1)
81
+ def lower_than_max?
82
+ @max.nil? || @max == Float::INFINITY || passes_validation_with_size(@max - 1)
77
83
  end
78
84
 
79
- def higher_than_high?
80
- @high.nil? || @high == Float::INFINITY || !passes_validation_with_size(@high + 1)
85
+ def not_higher_than_max?
86
+ @max.nil? || @max == Float::INFINITY || !passes_validation_with_size(@max + 1)
81
87
  end
82
88
 
83
89
  def passes_validation_with_size(new_size)
@@ -85,7 +91,10 @@ module ActiveStorageValidations
85
91
  Matchers.stub_method(io, :size, new_size) do
86
92
  @subject.public_send(@attribute_name).attach(io: io, filename: 'test.png', content_type: 'image/pg')
87
93
  @subject.validate
88
- @subject.errors.details[@attribute_name].all? { |error| error[:error] != :file_size_out_of_range }
94
+ exclude_error_message = @custom_message || "file_size_not_"
95
+ @subject.errors.details[@attribute_name].none? do |error|
96
+ error[:error].to_s.include?(exclude_error_message)
97
+ end
89
98
  end
90
99
  end
91
100
  end
@@ -75,17 +75,10 @@ module ActiveStorageValidations
75
75
  tempfile.flush
76
76
  tempfile.rewind
77
77
 
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
78
+ image = new_image_from_path(tempfile.path)
83
79
  else
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
80
+ file_path = read_file_path
81
+ image = new_image_from_path(file_path)
89
82
  end
90
83
 
91
84
 
@@ -101,6 +94,14 @@ module ActiveStorageValidations
101
94
  image = nil
102
95
  end
103
96
 
97
+ def new_image_from_path(path)
98
+ if image_processor == :vips && defined?(Vips) && (Vips::get_suffixes.include?(File.extname(path).downcase) || !Vips::respond_to?(:vips_foreign_get_suffixes))
99
+ Vips::Image.new_from_file(path)
100
+ elsif defined?(MiniMagick)
101
+ MiniMagick::Image.new(path)
102
+ end
103
+ end
104
+
104
105
  def valid_image?(image)
105
106
  return false unless image
106
107
 
@@ -125,7 +126,18 @@ module ActiveStorageValidations
125
126
  when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
126
127
  file.path
127
128
  when Hash
128
- File.open(file.fetch(:io)).path
129
+ io = file.fetch(:io)
130
+ if io.is_a?(StringIO)
131
+ tempfile = Tempfile.new([File.basename(file[:filename], '.*'), File.extname(file[:filename])])
132
+ tempfile.binmode
133
+ IO.copy_stream(io, tempfile)
134
+ io.rewind
135
+ tempfile.flush
136
+ tempfile.rewind
137
+ tempfile.path
138
+ else
139
+ File.open(io).path
140
+ end
129
141
  else
130
142
  raise "Something wrong with params."
131
143
  end
@@ -5,18 +5,21 @@ require_relative 'metadata.rb'
5
5
  module ActiveStorageValidations
6
6
  class ProcessableImageValidator < ActiveModel::EachValidator # :nodoc
7
7
  include OptionProcUnfolding
8
+ include ErrorHandler
8
9
 
9
10
  if Rails.gem_version >= Gem::Version.new('6.0.0')
10
11
  def validate_each(record, attribute, _value)
11
12
  return true unless record.send(attribute).attached?
12
13
 
14
+ errors_options = initialize_error_options(options)
15
+
13
16
  changes = record.attachment_changes[attribute.to_s]
14
17
  return true if changes.blank?
15
18
 
16
19
  files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
17
20
 
18
21
  files.each do |file|
19
- add_error(record, attribute, :image_not_processable) unless Metadata.new(file).valid?
22
+ add_error(record, attribute, :image_not_processable, **errors_options) unless Metadata.new(file).valid?
20
23
  end
21
24
  end
22
25
  else
@@ -27,17 +30,9 @@ module ActiveStorageValidations
27
30
  files = Array.wrap(record.send(attribute))
28
31
 
29
32
  files.each do |file|
30
- add_error(record, attribute, :image_not_processable) unless Metadata.new(file).valid?
33
+ add_error(record, attribute, :image_not_processable, **errors_options) unless Metadata.new(file).valid?
31
34
  end
32
35
  end
33
36
  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
37
  end
43
38
  end
@@ -3,14 +3,16 @@
3
3
  module ActiveStorageValidations
4
4
  class SizeValidator < ActiveModel::EachValidator # :nodoc:
5
5
  include OptionProcUnfolding
6
+ include ErrorHandler
6
7
 
7
8
  delegate :number_to_human_size, to: ActiveSupport::NumberHelper
8
9
 
9
10
  AVAILABLE_CHECKS = %i[less_than less_than_or_equal_to greater_than greater_than_or_equal_to between].freeze
10
11
 
11
12
  def check_validity!
12
- return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
13
- raise ArgumentError, 'You must pass either :less_than(_or_equal_to), :greater_than(_or_equal_to), or :between to the validator'
13
+ unless AVAILABLE_CHECKS.one? { |argument| options.key?(argument) }
14
+ raise ArgumentError, 'You must pass either :less_than(_or_equal_to), :greater_than(_or_equal_to), or :between to the validator'
15
+ end
14
16
  end
15
17
 
16
18
  def validate_each(record, attribute, _value)
@@ -19,8 +21,8 @@ module ActiveStorageValidations
19
21
 
20
22
  files = Array.wrap(record.send(attribute))
21
23
 
22
- errors_options = {}
23
- errors_options[:message] = options[:message] if options[:message].present?
24
+ errors_options = initialize_error_options(options)
25
+
24
26
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
25
27
 
26
28
  files.each do |file|
@@ -29,8 +31,9 @@ module ActiveStorageValidations
29
31
  errors_options[:file_size] = number_to_human_size(file.blob.byte_size)
30
32
  errors_options[:min_size] = number_to_human_size(min_size(flat_options))
31
33
  errors_options[:max_size] = number_to_human_size(max_size(flat_options))
34
+ error_type = "file_size_not_#{flat_options.keys.first}".to_sym
32
35
 
33
- record.errors.add(attribute, :file_size_out_of_range, **errors_options)
36
+ add_error(record, attribute, error_type, **errors_options)
34
37
  break
35
38
  end
36
39
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageValidations
4
- VERSION = '1.0.3'
4
+ VERSION = '1.1.0'
5
5
  end
@@ -1,8 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_model'
4
+
3
5
  require 'active_storage_validations/railtie'
4
6
  require 'active_storage_validations/engine'
5
7
  require 'active_storage_validations/option_proc_unfolding'
8
+ require 'active_storage_validations/error_handler'
6
9
  require 'active_storage_validations/attached_validator'
7
10
  require 'active_storage_validations/content_type_validator'
8
11
  require 'active_storage_validations/size_validator'
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: 1.0.3
4
+ version: 1.1.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: 2022-10-28 00:00:00.000000000 Z
11
+ date: 2023-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: simplecov
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'
167
181
  - !ruby/object:Gem::Dependency
168
182
  name: globalid
169
183
  requirement: !ruby/object:Gem::Requirement
@@ -198,6 +212,7 @@ files:
198
212
  - config/locales/pl.yml
199
213
  - config/locales/pt-BR.yml
200
214
  - config/locales/ru.yml
215
+ - config/locales/sv.yml
201
216
  - config/locales/tr.yml
202
217
  - config/locales/uk.yml
203
218
  - config/locales/vi.yml
@@ -208,6 +223,7 @@ files:
208
223
  - lib/active_storage_validations/content_type_validator.rb
209
224
  - lib/active_storage_validations/dimension_validator.rb
210
225
  - lib/active_storage_validations/engine.rb
226
+ - lib/active_storage_validations/error_handler.rb
211
227
  - lib/active_storage_validations/limit_validator.rb
212
228
  - lib/active_storage_validations/matchers.rb
213
229
  - lib/active_storage_validations/matchers/attached_validator_matcher.rb
@@ -240,7 +256,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
240
256
  - !ruby/object:Gem::Version
241
257
  version: '0'
242
258
  requirements: []
243
- rubygems_version: 3.2.3
259
+ rubygems_version: 3.4.10
244
260
  signing_key:
245
261
  specification_version: 4
246
262
  summary: Validations for Active Storage