active_storage_validations 0.8.6 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 354dd29a7d41df8c36694ecb4424869a5a6c0c7add98a32186995199c036ae23
4
- data.tar.gz: db75c2027d7e9fe55150223007b414957c67bdf1d3ccd5a58fcbb38daf836e1a
3
+ metadata.gz: 0475512c246a633adcc226c575f66826ffa09df688fda330f2b862d7229a3e3a
4
+ data.tar.gz: c52cbf3aa8527ddfc7e17f8619d63f1f3b008360e969f054a129d8e0f6af2e51
5
5
  SHA512:
6
- metadata.gz: c919243c4a4791c4615d5b3112c896462ab34125ed3b4974a0ad1d29bf53b8521a95dc5c34636ad11025f607247794f2c29772217f2fc130771493330b569bd1
7
- data.tar.gz: 9ecc389a981df18f6663e0ae112df499e9bf56e5f0f99a7a086c3cf59368625bc530e4f0c5add8d25f1e783ab8727aaa34bdf0ec3ca197be7035e3931a93fd44
6
+ metadata.gz: f472fa75d9d021f8ab55bff5dd900d738449884f9bf6ee72d953bb34c988acf01904c29dca7586f5d82ad192b9914bfd9e6f365d413615ef1b3aa18b9635cbb1
7
+ data.tar.gz: 8f1b3da90613ff72ab85aaa6faf6fe7403b132e1806c2e4f60156cf95d9f9dfd1dac20d950c7eb0cda64229427deb51d1ce2e4b1742020040c78853c8cdc1bba
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 valiation method.
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
 
@@ -117,7 +117,7 @@ end
117
117
 
118
118
  ## Internationalization (I18n)
119
119
 
120
- Active Storage Validations use I18n for errors messages. For this add there keys in your translation file:
120
+ Active Storage Validations uses I18n for error messages. For this, add these keys in your translation file:
121
121
 
122
122
  ```yml
123
123
  en:
@@ -137,19 +137,18 @@ en:
137
137
  dimension_height_less_than_or_equal_to: "height must be less than or equal to %{length} pixel."
138
138
  dimension_width_equal_to: "width must be equal to %{length} pixel."
139
139
  dimension_height_equal_to: "height must be equal to %{length} pixel."
140
- aspect_ratio_not_square: "doesn't a square image"
141
- aspect_ratio_not_portrait: "doesn't contain a portrait image"
142
- aspect_ratio_not_landscape: "doesn't contain a landscape image"
143
- aspect_ratio_is_not: "doesn't contain aspect ratio of %{aspect_ratio}"
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}"
144
144
  aspect_ratio_unknown: "has an unknown aspect ratio"
145
-
146
145
  ```
147
146
 
148
- 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:
149
148
 
150
- 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_type" containing the list of authorized content types.
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.
151
150
 
152
- It's variables are not used by default to leave the choice to the user.
151
+ The variables are not used by default to leave the choice to the user.
153
152
 
154
153
  For example :
155
154
 
@@ -189,20 +188,106 @@ Very simple example of validation with file attached, content type check and cus
189
188
 
190
189
  [![Sample](https://raw.githubusercontent.com/igorkasyanchuk/active_storage_validations/master/docs/preview.png)](https://raw.githubusercontent.com/igorkasyanchuk/active_storage_validations/master/docs/preview.png)
191
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
+
192
277
  ## Todo
193
278
 
194
279
  * verify with remote storages (s3, etc)
195
280
  * verify how it works with direct upload
196
281
  * better error message when content_size is invalid
197
282
  * add more translations
198
- * add aspect ratio validation
199
283
 
200
284
  ## Tests & Contributing
201
285
 
202
286
  To run tests in root folder of gem:
203
287
 
204
288
  * `BUNDLE_GEMFILE=gemfiles/rails_5_2.gemfile bundle exec rake test` to run for Rails 5.2
205
- * `BUNDLE_GEMFILE=gemfiles/rails_6.0.gemfile bundle exec rake test` to run for Rails 6.0
289
+ * `BUNDLE_GEMFILE=gemfiles/rails_6_0.gemfile bundle exec rake test` to run for Rails 6.0
290
+ * `BUNDLE_GEMFILE=gemfiles/rails_6_1.gemfile bundle exec rake test` to run for Rails 6.1 (TESTS ARE NOT READY, migration in DB is needed for specs)
206
291
 
207
292
  To play with app `cd test/dummy` and `rails s -b 0.0.0.0` (before `rails db:migrate`).
208
293
 
@@ -210,12 +295,13 @@ To play with app `cd test/dummy` and `rails s -b 0.0.0.0` (before `rails db:migr
210
295
 
211
296
  - 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:
212
297
 
213
- ```
298
+ ```erb
214
299
  <% if @user.avatar.attached? && @user.avatar.attachment.blob.present? && @user.avatar.attachment.blob.persisted? %>
215
300
  <%= image_tag @user.avatar %>
216
301
  <% end %>
217
302
  ```
218
- This is Rails issue. And according to commits it will be fixed in Rails 6.
303
+
304
+ This is a Rails issue, and is fixed in Rails 6.
219
305
 
220
306
  ## Contributing
221
307
  You are welcome to contribute.
@@ -240,6 +326,16 @@ You are welcome to contribute.
240
326
  - https://github.com/UICJohn
241
327
  - https://github.com/giovannibonetti
242
328
  - https://github.com/dlepage
329
+ - https://github.com/StefSchenkelaars
330
+ - https://github.com/willnet
331
+ - https://github.com/mohanklein
332
+ - https://github.com/High5Apps
333
+ - https://github.com/mschnitzer
334
+ - https://github.com/sinankeskin
335
+ - https://github.com/alejandrodevs
336
+ - https://github.com/molfar
337
+ - https://github.com/connorshea
338
+ - https://github.com/yshmarov
243
339
 
244
340
  ## License
245
341
 
@@ -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 von %{file_size} ist außerhalb des erlaubten Bereichs"
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 nicht ein gültigen Bild"
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: "Breite ist nicht zwischen %{min} und %{max} Pixel enthalten"
11
- dimension_height_inclusion: "Höhe ist nicht zwischen %{min} und %{max} Pixel enthalten"
12
- dimension_width_greater_than_or_equal_to: "Breite muss größer oder gleich %{length} Pixel sein"
13
- dimension_height_greater_than_or_equal_to: "Höhe muss größer oder gleich %{length} Pixel sein"
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: "Breite muss genau %{length} Pixel sein"
17
- dimension_height_equal_to: "Höhe muss genau %{length} Pixel sein"
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
+ es:
2
+ errors:
3
+ messages:
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"
6
+ limit_out_of_range: "el número total está fuera de rango"
7
+ image_metadata_missing: "no es una imagen válida"
8
+ dimension_min_inclusion: "debe ser mayor o igual a %{width} x %{height} pixel"
9
+ dimension_max_inclusion: "debe ser menor o igual a %{width} x %{height} pixel"
10
+ dimension_width_inclusion: "el ancho no se incluye entre %{min} y %{max} pixel"
11
+ dimension_height_inclusion: "la altura no se incluye entre %{min} y %{max} pixel"
12
+ dimension_width_greater_than_or_equal_to: "el ancho debe ser mayor o igual a %{length} pixel"
13
+ dimension_height_greater_than_or_equal_to: "la altura debe ser mayor o igual a %{length} pixel"
14
+ dimension_width_less_than_or_equal_to: "el ancho debe ser menor o igual a %{length} pixel"
15
+ dimension_height_less_than_or_equal_to: "la altura debe ser menor o igual a %{length} pixel"
16
+ dimension_width_equal_to: "el ancho debe ser igual a %{length} pixel"
17
+ dimension_height_equal_to: "la altura debe ser igual a %{length} pixel"
18
+ aspect_ratio_not_square: "debe ser una imagen cuadrada"
19
+ aspect_ratio_not_portrait: "debe ser una imagen de retrato"
20
+ aspect_ratio_not_landscape: "debe ser una imagen de paisaje"
21
+ aspect_ratio_is_not: "debe tener una relación de aspecto de %{aspect_ratio}"
22
+ aspect_ratio_unknown: "tiene una relación de aspecto desconocida"
@@ -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
+ nl:
2
+ errors:
3
+ messages:
4
+ content_type_invalid: "heeft een ongeldig inhoudstype"
5
+ file_size_out_of_range: "bestandsgrootte %{file_size} valt niet tussen het vereiste bereik"
6
+ limit_out_of_range: "totaal aantal valt buiten het vereiste bereik"
7
+ image_metadata_missing: "is geen geldige afbeelding"
8
+ dimension_min_inclusion: "moet groter of gelijk zijn aan %{width} x %{height} pixels"
9
+ dimension_max_inclusion: "moet kleiner of gelijk zijn aan %{width} x %{height} pixels"
10
+ dimension_width_inclusion: "breedte niet tussen de %{min} en %{max} pixels"
11
+ dimension_height_inclusion: "hoogte niet tussen de %{min} en %{max} pixels"
12
+ dimension_width_greater_than_or_equal_to: "breedte moet groter of gelijk zijn aan %{length} pixels"
13
+ dimension_height_greater_than_or_equal_to: "hoogte moet groter of gelijk zijn aan %{length} pixels"
14
+ dimension_width_less_than_or_equal_to: "breedte moet kleiner of gelijk zijn aan %{length} pixels"
15
+ dimension_height_less_than_or_equal_to: "hoogte moet kleiner of gelijk zijn aan %{length} pixels"
16
+ dimension_width_equal_to: "breedte moet gelijk zijn aan %{length} pixels"
17
+ dimension_height_equal_to: "hoogte moet gelijk zijn aan %{length} pixels"
18
+ aspect_ratio_not_square: "moet een vierkante afbeelding zijn"
19
+ aspect_ratio_not_portrait: "moet een staande afbeelding zijn"
20
+ aspect_ratio_not_landscape: "moet een liggende afbeelding zijn"
21
+ aspect_ratio_is_not: "moet een beeldverhouding hebben van %{aspect_ratio}"
22
+ aspect_ratio_unknown: "heeft een onbekende beeldverhouding"
@@ -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ı"
@@ -1,22 +1,22 @@
1
1
  uk:
2
2
  errors:
3
3
  messages:
4
- content_type_invalid: "має неприпустимий тип вмісту"
5
- file_size_out_of_range: "розмір %{file_size} більше необхідного"
6
- limit_out_of_range: "кількість файлів більше необхідного"
7
- image_metadata_missing: "не є допустимим зображенням"
8
- dimension_min_inclusion: "мусить бути більше або дорівнювати %{width} x %{height} пікселям"
9
- dimension_max_inclusion: "мусить бути менше або дорівнювати %{width} x %{height} пікселям"
10
- dimension_width_inclusion: "ширина не включена між %{min} і %{max} пікселям"
11
- dimension_height_inclusion: "висота не включена між %{min} і %{max} пікселям"
12
- dimension_width_greater_than_or_equal_to: "ширина мусить бути більше або дорівнювати %{length} пікселям"
13
- dimension_height_greater_than_or_equal_to: "висота мусить бути більше або дорівнювати %{length} пікселям"
14
- dimension_width_less_than_or_equal_to: "ширина мусить бути менше або дорівнювати %{length} пікселям"
15
- dimension_height_less_than_or_equal_to: "висота мусить бути менше або дорівнювати %{length} пікселям"
16
- dimension_width_equal_to: "ширина мусить дорівнювати %{length} пікселям"
17
- dimension_height_equal_to: "висота мусить дорівнювати %{length} пікселям"
18
- aspect_ratio_not_square: "мусить бути квадратне зображення"
19
- aspect_ratio_not_portrait: "мусить бути портретне зображення"
20
- aspect_ratio_not_landscape: "мусить бути пейзажне зображення"
21
- aspect_ratio_is_not: "мусить мати співвідношення сторін %{aspect_ratio}"
22
- aspect_ratio_unknown: "має невідоме співвідношення сторін"
4
+ 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: "має невідоме співвідношення сторін"
@@ -14,7 +14,7 @@ module ActiveStorageValidations
14
14
  next if is_valid?(file)
15
15
 
16
16
  errors_options[:content_type] = content_type(file)
17
- record.errors.add(attribute, :content_type_invalid, errors_options)
17
+ record.errors.add(attribute, :content_type_invalid, **errors_options)
18
18
  break
19
19
  end
20
20
  end
@@ -123,7 +123,7 @@ module ActiveStorageValidations
123
123
  key = options[:message].presence || type
124
124
  return if record.errors.added?(attribute, key)
125
125
  record.errors.add(attribute, key, *attrs)
126
- end
126
+ end
127
127
 
128
128
  end
129
129
  end
@@ -17,7 +17,7 @@ module ActiveStorageValidations
17
17
  errors_options = { min: options[:min], max: options[:max] }
18
18
 
19
19
  return true if files_count_valid?(files.count)
20
- record.errors.add(attribute, options[:message].presence || :limit_out_of_range, errors_options)
20
+ record.errors.add(attribute, options[:message].presence || :limit_out_of_range, **errors_options)
21
21
  end
22
22
 
23
23
  def files_count_valid?(count)
@@ -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,59 @@
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
+ responds_to_methods && valid_when_attached && invalid_when_not_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 responds_to_methods
34
+ @subject.respond_to?(@attribute_name) &&
35
+ @subject.public_send(@attribute_name).respond_to?(:attach) &&
36
+ @subject.public_send(@attribute_name).respond_to?(:detach)
37
+ end
38
+
39
+ def valid_when_attached
40
+ @subject.public_send(@attribute_name).attach(attachable) unless @subject.public_send(@attribute_name).attached?
41
+ @subject.validate
42
+ @subject.errors.details[@attribute_name].exclude?(error: :blank)
43
+ end
44
+
45
+ def invalid_when_not_attached
46
+ @subject.public_send(@attribute_name).detach
47
+ # Unset the direct relation since `detach` on an unpersisted record does not set `attached?` to false.
48
+ @subject.public_send("#{@attribute_name}=", nil)
49
+
50
+ @subject.validate
51
+ @subject.errors.details[@attribute_name].include?(error: :blank)
52
+ end
53
+
54
+ def attachable
55
+ { io: Tempfile.new('.'), filename: 'dummy.txt', content_type: 'text/plain' }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,101 @@
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
+ responds_to_methods && 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 responds_to_methods
50
+ @subject.respond_to?(@attribute_name) &&
51
+ @subject.public_send(@attribute_name).respond_to?(:attach) &&
52
+ @subject.public_send(@attribute_name).respond_to?(:detach)
53
+ end
54
+
55
+ def allowed_types
56
+ @allowed_types || []
57
+ end
58
+
59
+ def rejected_types
60
+ @rejected_types || (Mime::LOOKUP.keys - allowed_types)
61
+ end
62
+
63
+ def allowed_types_allowed?
64
+ @missing_allowed_types ||= allowed_types.reject { |type| type_allowed?(type) }
65
+ @missing_allowed_types.none?
66
+ end
67
+
68
+ def rejected_types_rejected?
69
+ @missing_rejected_types ||= rejected_types.select { |type| type_allowed?(type) }
70
+ @missing_rejected_types.none?
71
+ end
72
+
73
+ def accepted_types_and_failures
74
+ if @missing_allowed_types.present?
75
+ "#{@missing_allowed_types.join(", ")} were rejected."
76
+ else
77
+ "All were accepted successfully."
78
+ end
79
+ end
80
+
81
+ def rejected_types_and_failures
82
+ if @missing_rejected_types.present?
83
+ "#{@missing_rejected_types.join(", ")} were accepted."
84
+ else
85
+ "All were rejected successfully."
86
+ end
87
+ end
88
+
89
+ def type_allowed?(type)
90
+ @subject.public_send(@attribute_name).attach(attachment_for(type))
91
+ @subject.validate
92
+ @subject.errors.details[@attribute_name].all? { |error| error[:error] != :content_type_invalid }
93
+ end
94
+
95
+ def attachment_for(type)
96
+ suffix = type.to_s.split('/').last
97
+ { io: Tempfile.new('.'), filename: "test.#{suffix}", content_type: type }
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,145 @@
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
+ responds_to_methods &&
62
+ width_smaller_than_min? && width_larger_than_min? && width_smaller_than_max? && width_larger_than_max? && width_equals? &&
63
+ height_smaller_than_min? && height_larger_than_min? && height_smaller_than_max? && height_larger_than_max? && height_equals?
64
+ end
65
+
66
+ def failure_message
67
+ <<~MESSAGE
68
+ is expected to validate dimensions of #{@attribute_name}
69
+ width between #{@width_min} and #{@width_max}
70
+ height between #{@height_min} and #{@height_max}
71
+ MESSAGE
72
+ end
73
+
74
+ protected
75
+
76
+ def responds_to_methods
77
+ @subject.respond_to?(@attribute_name) &&
78
+ @subject.public_send(@attribute_name).respond_to?(:attach) &&
79
+ @subject.public_send(@attribute_name).respond_to?(:detach)
80
+ end
81
+
82
+ def valid_width
83
+ ((@width_min || 0) + (@width_max || 2000)) / 2
84
+ end
85
+
86
+ def valid_height
87
+ ((@height_min || 0) + (@height_max || 2000)) / 2
88
+ end
89
+
90
+ def width_smaller_than_min?
91
+ @width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height, 'width')
92
+ end
93
+
94
+ def width_larger_than_min?
95
+ @width_min.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_min + 1, valid_height, 'width')
96
+ end
97
+
98
+ def width_smaller_than_max?
99
+ @width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height, 'width')
100
+ end
101
+
102
+ def width_larger_than_max?
103
+ @width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height, 'width')
104
+ end
105
+
106
+ def width_equals?
107
+ @width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height, 'width')
108
+ end
109
+
110
+ def height_smaller_than_min?
111
+ @height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1, 'height')
112
+ end
113
+
114
+ def height_larger_than_min?
115
+ @height_min.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_min + 1, 'height')
116
+ end
117
+
118
+ def height_smaller_than_max?
119
+ @height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1, 'height')
120
+ end
121
+
122
+ def height_larger_than_max?
123
+ @height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1, 'height')
124
+ end
125
+
126
+ def height_equals?
127
+ @height_min.nil? || @height_min != @height_max || passes_validation_with_dimensions(valid_width, @height_min, 'height')
128
+ end
129
+
130
+ def passes_validation_with_dimensions(width, height, check)
131
+ @subject.public_send(@attribute_name).attach attachment_for(width, height)
132
+
133
+ attachment = @subject.public_send(@attribute_name)
134
+ Matchers.mock_metadata(attachment, width, height) do
135
+ @subject.validate
136
+ @subject.errors.details[@attribute_name].all? { |error| error[:error].to_s.exclude?("dimension_#{check}") }
137
+ end
138
+ end
139
+
140
+ def attachment_for(width, height)
141
+ { io: Tempfile.new('Hello world!'), filename: 'test.png', content_type: 'image/png' }
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,93 @@
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
+ responds_to_methods && 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 responds_to_methods
62
+ @subject.respond_to?(@attribute_name) &&
63
+ @subject.public_send(@attribute_name).respond_to?(:attach) &&
64
+ @subject.public_send(@attribute_name).respond_to?(:detach)
65
+ end
66
+
67
+ def lower_than_low?
68
+ @low.nil? || !passes_validation_with_size(@low - 1)
69
+ end
70
+
71
+ def higher_than_low?
72
+ @low.nil? || passes_validation_with_size(@low + 1)
73
+ end
74
+
75
+ def lower_than_high?
76
+ @high.nil? || @high == Float::INFINITY || passes_validation_with_size(@high - 1)
77
+ end
78
+
79
+ def higher_than_high?
80
+ @high.nil? || @high == Float::INFINITY || !passes_validation_with_size(@high + 1)
81
+ end
82
+
83
+ def passes_validation_with_size(new_size)
84
+ io = Tempfile.new('Hello world!')
85
+ Matchers.stub_method(io, :size, new_size) do
86
+ @subject.public_send(@attribute_name).attach(io: io, filename: 'test.png', content_type: 'image/pg')
87
+ @subject.validate
88
+ @subject.errors.details[@attribute_name].all? { |error| error[:error] != :file_size_out_of_range }
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -19,11 +19,22 @@ module ActiveStorageValidations
19
19
  private
20
20
 
21
21
  def read_image
22
- if file.is_a?(String)
23
- blob = ActiveStorage::Blob.find_signed(file)
22
+ is_string = file.is_a?(String)
23
+ if is_string || file.is_a?(ActiveStorage::Blob)
24
+ if is_string
25
+ # If Rails 5.2 or 6.0, use `find_signed`
26
+ if Rails::VERSION::MAJOR < 6 || (Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR == 0)
27
+ blob = ActiveStorage::Blob.find_signed(file)
28
+ # If Rails 6.1 or higher, use `find_signed!`
29
+ elsif Rails::VERSION::MAJOR > 6 || (Rails::VERSION::MAJOR == 6 && Rails::VERSION::MINOR >= 1)
30
+ blob = ActiveStorage::Blob.find_signed!(file)
31
+ end
32
+ else
33
+ blob = file
34
+ end
24
35
 
25
36
  tempfile = Tempfile.new(["ActiveStorage-#{blob.id}-", blob.filename.extension_with_delimiter])
26
- tempfile.binmode
37
+ tempfile.binmode
27
38
 
28
39
  blob.download do |chunk|
29
40
  tempfile.write(chunk)
@@ -25,7 +25,7 @@ module ActiveStorageValidations
25
25
  next if content_size_valid?(file.blob.byte_size)
26
26
 
27
27
  errors_options[:file_size] = number_to_human_size(file.blob.byte_size)
28
- record.errors.add(attribute, :file_size_out_of_range, errors_options)
28
+ record.errors.add(attribute, :file_size_out_of_range, **errors_options)
29
29
  break
30
30
  end
31
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageValidations
4
- VERSION = '0.8.6'
4
+ VERSION = '0.9.1'
5
5
  end
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.8.6
4
+ version: 0.9.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-01-20 00:00:00.000000000 Z
11
+ date: 2020-12-28 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,9 +92,13 @@ files:
106
92
  - Rakefile
107
93
  - config/locales/de.yml
108
94
  - config/locales/en.yml
95
+ - config/locales/es.yml
109
96
  - config/locales/fr.yml
97
+ - config/locales/ja.yml
98
+ - config/locales/nl.yml
110
99
  - config/locales/pt-BR.yml
111
100
  - config/locales/ru.yml
101
+ - config/locales/tr.yml
112
102
  - config/locales/uk.yml
113
103
  - lib/active_storage_validations.rb
114
104
  - lib/active_storage_validations/aspect_ratio_validator.rb
@@ -117,6 +107,11 @@ files:
117
107
  - lib/active_storage_validations/dimension_validator.rb
118
108
  - lib/active_storage_validations/engine.rb
119
109
  - lib/active_storage_validations/limit_validator.rb
110
+ - lib/active_storage_validations/matchers.rb
111
+ - lib/active_storage_validations/matchers/attached_validator_matcher.rb
112
+ - lib/active_storage_validations/matchers/content_type_validator_matcher.rb
113
+ - lib/active_storage_validations/matchers/dimension_validator_matcher.rb
114
+ - lib/active_storage_validations/matchers/size_validator_matcher.rb
120
115
  - lib/active_storage_validations/metadata.rb
121
116
  - lib/active_storage_validations/railtie.rb
122
117
  - lib/active_storage_validations/size_validator.rb
@@ -141,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
141
136
  - !ruby/object:Gem::Version
142
137
  version: '0'
143
138
  requirements: []
144
- rubygems_version: 3.0.6
139
+ rubygems_version: 3.0.3
145
140
  signing_key:
146
141
  specification_version: 4
147
142
  summary: Validations for Active Storage