active_storage_validations 0.8.5 → 0.9.0

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