active_storage_validations 0.8.6 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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