active_storage_validations 0.8.5 → 0.9.0

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: 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