active_storage_validations 1.0.4 → 3.0.2
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 +4 -4
- data/README.md +785 -245
- data/config/locales/da.yml +63 -0
- data/config/locales/de.yml +60 -19
- data/config/locales/en-GB.yml +63 -0
- data/config/locales/en.yml +60 -20
- data/config/locales/es.yml +60 -19
- data/config/locales/fr.yml +60 -19
- data/config/locales/it.yml +60 -19
- data/config/locales/ja.yml +60 -19
- data/config/locales/nl.yml +60 -19
- data/config/locales/pl.yml +60 -19
- data/config/locales/pt-BR.yml +60 -19
- data/config/locales/ru.yml +60 -19
- data/config/locales/sv.yml +63 -0
- data/config/locales/tr.yml +60 -19
- data/config/locales/uk.yml +60 -19
- data/config/locales/vi.yml +60 -19
- data/config/locales/zh-CN.yml +60 -19
- data/lib/active_storage_validations/analyzer/audio_analyzer.rb +58 -0
- data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +60 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +46 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +56 -0
- data/lib/active_storage_validations/analyzer/image_analyzer.rb +49 -0
- data/lib/active_storage_validations/analyzer/null_analyzer.rb +18 -0
- data/lib/active_storage_validations/analyzer/pdf_analyzer.rb +89 -0
- data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +61 -0
- data/lib/active_storage_validations/analyzer/video_analyzer.rb +130 -0
- data/lib/active_storage_validations/analyzer.rb +88 -0
- data/lib/active_storage_validations/aspect_ratio_validator.rb +157 -97
- data/lib/active_storage_validations/attached_validator.rb +22 -5
- data/lib/active_storage_validations/base_comparison_validator.rb +83 -0
- data/lib/active_storage_validations/content_type_validator.rb +219 -31
- data/lib/active_storage_validations/dimension_validator.rb +187 -97
- data/lib/active_storage_validations/duration_validator.rb +70 -0
- data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +56 -0
- data/lib/active_storage_validations/extensors/asv_marcelable.rb +12 -0
- data/lib/active_storage_validations/limit_validator.rb +76 -9
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +48 -25
- data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +150 -0
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +98 -39
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +93 -55
- data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +39 -0
- data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +127 -0
- data/lib/active_storage_validations/matchers/pages_validator_matcher.rb +39 -0
- data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +78 -0
- data/lib/active_storage_validations/matchers/shared/asv_active_storageable.rb +19 -0
- data/lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb +28 -0
- data/lib/active_storage_validations/matchers/shared/asv_attachable.rb +72 -0
- data/lib/active_storage_validations/matchers/shared/asv_contextable.rb +57 -0
- data/lib/active_storage_validations/matchers/shared/asv_messageable.rb +28 -0
- data/lib/active_storage_validations/matchers/shared/asv_rspecable.rb +27 -0
- data/lib/active_storage_validations/matchers/shared/asv_validatable.rb +56 -0
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +17 -71
- data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +47 -0
- data/lib/active_storage_validations/matchers.rb +17 -21
- data/lib/active_storage_validations/pages_validator.rb +61 -0
- data/lib/active_storage_validations/processable_file_validator.rb +37 -0
- data/lib/active_storage_validations/railtie.rb +14 -0
- data/lib/active_storage_validations/shared/asv_active_storageable.rb +30 -0
- data/lib/active_storage_validations/shared/asv_analyzable.rb +89 -0
- data/lib/active_storage_validations/shared/asv_attachable.rb +236 -0
- data/lib/active_storage_validations/shared/asv_errorable.rb +64 -0
- data/lib/active_storage_validations/shared/asv_loggable.rb +11 -0
- data/lib/active_storage_validations/shared/asv_optionable.rb +29 -0
- data/lib/active_storage_validations/shared/asv_symbolizable.rb +14 -0
- data/lib/active_storage_validations/size_validator.rb +24 -41
- data/lib/active_storage_validations/total_size_validator.rb +52 -0
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +27 -13
- metadata +113 -31
- data/lib/active_storage_validations/metadata.rb +0 -151
- data/lib/active_storage_validations/option_proc_unfolding.rb +0 -16
- data/lib/active_storage_validations/processable_image_validator.rb +0 -43
data/README.md
CHANGED
|
@@ -6,228 +6,773 @@
|
|
|
6
6
|
[](https://github.com/igorkasyanchuk/active_storage_validations/actions)
|
|
7
7
|
[](https://www.railsjazz.com)
|
|
8
8
|
[](https://www.patreon.com/igorkasyanchuk)
|
|
9
|
-
[](https://opensource-heroes.com/r/igorkasyanchuk/active_storage_validations)
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
[](https://buymeacoffee.com/igorkasyanchuk)
|
|
11
|
+
|
|
12
|
+
Active Storage Validations is a gem that allows you to add validations for Active Storage attributes.
|
|
13
|
+
|
|
14
|
+
This gems is doing it right for you! Just use `validates :avatar, attached: true, content_type: 'image/png'` and that's it!
|
|
15
|
+
|
|
16
|
+
## Table of Contents
|
|
17
|
+
|
|
18
|
+
- [Getting started](#getting-started)
|
|
19
|
+
- [Installation](#installation)
|
|
20
|
+
- [Error messages (I18n)](#error-messages-i18n)
|
|
21
|
+
- [Using image metadata validators](#using-image-metadata-validators)
|
|
22
|
+
- [Using video and audio metadata validators](#using-video-and-audio-metadata-validators)
|
|
23
|
+
- [Using pdf metadata validators](#using-pdf-metadata-validators)
|
|
24
|
+
- [Using content type spoofing protection validator option](#using-content-type-spoofing-protection-validator-option)
|
|
25
|
+
- [Validators](#validators)
|
|
26
|
+
- [Attached](#attached)
|
|
27
|
+
- [Limit](#limit)
|
|
28
|
+
- [Content type](#content-type)
|
|
29
|
+
- [Size](#size)
|
|
30
|
+
- [Total size](#total-size)
|
|
31
|
+
- [Dimension](#dimension)
|
|
32
|
+
- [Duration](#duration)
|
|
33
|
+
- [Aspect ratio](#aspect-ratio)
|
|
34
|
+
- [Processable file](#processable-file)
|
|
35
|
+
- [Pages](#pages)
|
|
36
|
+
- [Upgrading from 1.x to 2.x](#upgrading-from-1x-to-2x)
|
|
37
|
+
- [Upgrading from 2.x to 3.x](#upgrading-from-2x-to-3x)
|
|
38
|
+
- [Internationalization (I18n)](#internationalization-i18n)
|
|
39
|
+
- [Test matchers](#test-matchers)
|
|
40
|
+
- [Contributing](#contributing)
|
|
41
|
+
- [Additional information](#additional-information)
|
|
42
|
+
|
|
43
|
+
## Getting started
|
|
44
|
+
|
|
45
|
+
### Installation
|
|
46
|
+
|
|
47
|
+
Active Storage Validations work with Rails 6.1.4 onwards. Add this line to your application's Gemfile:
|
|
12
48
|
|
|
13
|
-
|
|
49
|
+
```ruby
|
|
50
|
+
gem 'active_storage_validations'
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
And then execute:
|
|
54
|
+
|
|
55
|
+
```sh
|
|
56
|
+
$ bundle
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Error messages (I18n)
|
|
60
|
+
|
|
61
|
+
Once you have installed the gem, I18n error messages will be added automatically to your app. See [Internationalization (I18n)](#internationalization-i18n) section for more details.
|
|
62
|
+
|
|
63
|
+
### Using image metadata validators
|
|
64
|
+
|
|
65
|
+
Optionally, to use the image metadata validators (`dimension`, `aspect_ratio` and `processable_file`), you will have to add one of the corresponding gems:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
gem 'mini_magick', '>= 4.9.5'
|
|
69
|
+
# Or
|
|
70
|
+
gem 'ruby-vips', '>= 2.1.0'
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Plus, you have to be sure to have the corresponding command-line tool installed on your system. For example, to use `mini_magick` gem, you need to have `imagemagick` installed on your system (both on your local and in your CI / production environments).
|
|
74
|
+
|
|
75
|
+
### Using video and audio metadata validators
|
|
76
|
+
|
|
77
|
+
To use the video and audio metadata validators (`dimension`, `aspect_ratio`, `processable_file` and `duration`), you will not need to add any gems. However you will need to have the `ffmpeg` command-line tool installed on your system (once again, be sure to have it installed both on your local and in your CI / production environments).
|
|
78
|
+
|
|
79
|
+
### Using pdf metadata validators
|
|
80
|
+
|
|
81
|
+
To use the pdf metadata validators (`dimension`, `aspect_ratio`, `processable_file` and `pages`), you will not need to add any gems. However you will need to have the `poppler` tool installed on your system (once again, be sure to have it installed both on your local and in your CI / production environments).
|
|
14
82
|
|
|
15
|
-
|
|
83
|
+
### Using content type spoofing protection validator option
|
|
16
84
|
|
|
17
|
-
|
|
18
|
-
* validates content type
|
|
19
|
-
* validates size of files
|
|
20
|
-
* validates dimension of images/videos
|
|
21
|
-
* validates number of uploaded files (min/max required)
|
|
22
|
-
* validates aspect ratio (if square, portrait, landscape, is_16_9, ...)
|
|
23
|
-
* validates if file can be processed by MiniMagick or Vips
|
|
24
|
-
* custom error messages
|
|
25
|
-
* allow procs for dynamic determination of values
|
|
85
|
+
To use the `spoofing_protection` option with the `content_type` validator, you only need to have the UNIX `file` command on your system.
|
|
26
86
|
|
|
27
|
-
|
|
87
|
+
If you want some inspiration about how to add `imagemagick`, `libvips`, `ffmpeg` or `poppler` to your docker image, you can check how we do it for the gem CI (https://github.com/igorkasyanchuk/active_storage_validations/blob/master/.github/workflows/main.yml)
|
|
28
88
|
|
|
29
|
-
|
|
89
|
+
## Validators
|
|
30
90
|
|
|
91
|
+
**List of validators:**
|
|
92
|
+
- [Attached](#attached): validates if file(s) attached
|
|
93
|
+
- [Limit](#limit): validates number of uploaded files
|
|
94
|
+
- [Content type](#content-type): validates file content type
|
|
95
|
+
- [Size](#size): validates file size
|
|
96
|
+
- [Total size](#total-size): validates total file size for several files
|
|
97
|
+
- [Dimension](#dimension): validates image / video dimensions
|
|
98
|
+
- [Duration](#duration): validates video / audio duration
|
|
99
|
+
- [Aspect ratio](#aspect-ratio): validates image / video aspect ratio
|
|
100
|
+
- [Processable file](#processable-file): validates if a file can be processed
|
|
101
|
+
- [Pages](#pages): validates pdf number of pages
|
|
102
|
+
<br>
|
|
103
|
+
<br>
|
|
104
|
+
|
|
105
|
+
**Proc usage**<br>
|
|
106
|
+
Every validator can use procs instead of values in all the validator examples:
|
|
31
107
|
```ruby
|
|
32
108
|
class User < ApplicationRecord
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
validates :name, presence: true
|
|
38
|
-
|
|
39
|
-
validates :avatar, attached: true, content_type: 'image/png',
|
|
40
|
-
dimension: { width: 200, height: 200 }
|
|
41
|
-
validates :photos, attached: true, content_type: ['image/png', 'image/jpeg'],
|
|
42
|
-
dimension: { width: { min: 800, max: 2400 },
|
|
43
|
-
height: { min: 600, max: 1800 }, message: 'is not given between dimension' }
|
|
44
|
-
validates :image, attached: true,
|
|
45
|
-
processable_image: true,
|
|
46
|
-
content_type: ['image/png', 'image/jpeg'],
|
|
47
|
-
aspect_ratio: :landscape
|
|
109
|
+
has_many_attached :files
|
|
110
|
+
|
|
111
|
+
validates :files, limit: { max: -> (record) { record.admin? ? 100 : 10 } }
|
|
48
112
|
end
|
|
49
113
|
```
|
|
50
114
|
|
|
51
|
-
|
|
115
|
+
**Performance optimization**<br>
|
|
116
|
+
Some validators rely on an expensive operation (metadata analysis and content type analysis). To mitigate the performance cost, the gem leverages the `ActiveStorage::Blob.metadata` method to store retrieved metadata. Therefore, once the file has been analyzed by our gem, the expensive analysis operation will not be triggered again for new validations.
|
|
117
|
+
|
|
118
|
+
As stated in the Rails documentation: "Blobs are intended to be immutable in so far as their reference to a specific file goes". We based our performance optimization on the same assumption, so if you do not follow it, the gem will not work as expected.
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
### Attached
|
|
123
|
+
|
|
124
|
+
Validates if the attachment is present.
|
|
52
125
|
|
|
126
|
+
#### Options
|
|
127
|
+
|
|
128
|
+
The `attached` validator has no options.
|
|
129
|
+
|
|
130
|
+
#### Examples
|
|
131
|
+
|
|
132
|
+
Use it like this:
|
|
53
133
|
```ruby
|
|
54
|
-
class
|
|
55
|
-
has_one_attached :
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
has_many_attached :documents
|
|
59
|
-
|
|
60
|
-
validates :title, presence: true
|
|
61
|
-
|
|
62
|
-
validates :logo, attached: true, size: { less_than: 100.megabytes , message: 'is too large' }
|
|
63
|
-
validates :preview, attached: true, size: { between: 1.kilobyte..100.megabytes , message: 'is not given between size' }
|
|
64
|
-
validates :attachment, attached: true, content_type: { in: 'application/pdf', message: 'is not a PDF' }
|
|
65
|
-
validates :documents, limit: { min: 1, max: 3 }
|
|
134
|
+
class User < ApplicationRecord
|
|
135
|
+
has_one_attached :avatar
|
|
136
|
+
|
|
137
|
+
validates :avatar, attached: true # ensures that avatar has an attached file
|
|
66
138
|
end
|
|
67
139
|
```
|
|
68
140
|
|
|
69
|
-
|
|
141
|
+
#### Error messages (I18n)
|
|
142
|
+
|
|
143
|
+
```yml
|
|
144
|
+
en:
|
|
145
|
+
errors:
|
|
146
|
+
messages:
|
|
147
|
+
blank: "can't be blank"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
The error message for this validator relies on Rails own `blank` error message.
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
### Limit
|
|
70
155
|
|
|
71
|
-
|
|
156
|
+
Validates the number of uploaded files.
|
|
72
157
|
|
|
158
|
+
#### Options
|
|
159
|
+
|
|
160
|
+
The `limit` validator has 2 possible options:
|
|
161
|
+
- `min`: defines the minimum allowed number of files
|
|
162
|
+
- `max`: defines the maximum allowed number of files
|
|
163
|
+
|
|
164
|
+
#### Examples
|
|
165
|
+
|
|
166
|
+
Use it like this:
|
|
73
167
|
```ruby
|
|
74
168
|
class User < ApplicationRecord
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
validates :avatar, attached: true, content_type: :png # Marcel::Magic.by_extension(:png).to_s => 'image/png'
|
|
79
|
-
# Rails <= 6.1.3; MimeMagic.by_extension(:png).to_s => 'image/png'
|
|
80
|
-
# or
|
|
81
|
-
validates :photos, attached: true, content_type: [:png, :jpg, :jpeg]
|
|
82
|
-
# or
|
|
83
|
-
validates :avatar, content_type: /\Aimage\/.*\z/
|
|
169
|
+
has_many_attached :certificates
|
|
170
|
+
|
|
171
|
+
validates :certificates, limit: { min: 1, max: 10 } # restricts the number of files to between 1 and 10
|
|
84
172
|
end
|
|
85
173
|
```
|
|
86
174
|
|
|
87
|
-
|
|
175
|
+
#### Error messages (I18n)
|
|
176
|
+
|
|
177
|
+
```yml
|
|
178
|
+
en:
|
|
179
|
+
errors:
|
|
180
|
+
messages:
|
|
181
|
+
limit_out_of_range:
|
|
182
|
+
zero: "no files attached (must have between %{min} and %{max} files)"
|
|
183
|
+
one: "only 1 file attached (must have between %{min} and %{max} files)"
|
|
184
|
+
other: "total number of files must be between %{min} and %{max} files (there are %{count} files attached)"
|
|
185
|
+
limit_min_not_reached:
|
|
186
|
+
zero: "no files attached (must have at least %{min} files)"
|
|
187
|
+
one: "only 1 file attached (must have at least %{min} files)"
|
|
188
|
+
other: "%{count} files attached (must have at least %{min} files)"
|
|
189
|
+
limit_max_exceeded:
|
|
190
|
+
zero: "no files attached (maximum is %{max} files)"
|
|
191
|
+
one: "too many files attached (maximum is %{max} files, got %{count})"
|
|
192
|
+
other: "too many files attached (maximum is %{max} files, got %{count})"
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
The `limit` validator error messages expose 3 values that you can use:
|
|
196
|
+
- `min` containing the minimum allowed number of files (e.g. `1`)
|
|
197
|
+
- `max` containing the maximum allowed number of files (e.g. `10`)
|
|
198
|
+
- `count` containing the current number of files (e.g. `5`)
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### Content type
|
|
203
|
+
|
|
204
|
+
Validates if the attachment has an allowed content type.
|
|
88
205
|
|
|
206
|
+
#### Options
|
|
207
|
+
|
|
208
|
+
The `content_type` validator has 3 possible options:
|
|
209
|
+
- `with`: defines the allowed content type (string, symbol or regex)
|
|
210
|
+
- `in`: defines the allowed content types (array of strings or symbols)
|
|
211
|
+
- `spoofing_protection`: enables content type spoofing protection (boolean, defaults to `false`)
|
|
212
|
+
|
|
213
|
+
As mentioned above, this validator can define content types in several ways:
|
|
214
|
+
- String: `image/png` or `png`
|
|
215
|
+
- Symbol: `:png`
|
|
216
|
+
- Regex: `/\Avideo\/.*\z/`
|
|
217
|
+
|
|
218
|
+
#### Examples
|
|
219
|
+
|
|
220
|
+
Use it like this:
|
|
89
221
|
```ruby
|
|
90
222
|
class User < ApplicationRecord
|
|
91
223
|
has_one_attached :avatar
|
|
92
|
-
has_many_attached :photos
|
|
93
224
|
|
|
94
|
-
validates :avatar,
|
|
95
|
-
validates :
|
|
225
|
+
validates :avatar, content_type: 'image/png' # only allows PNG images
|
|
226
|
+
validates :avatar, content_type: :png # only allows PNG images, same as { with: :png }
|
|
227
|
+
validates :avatar, content_type: /\Avideo\/.*\z/ # only allows video files
|
|
228
|
+
validates :avatar, content_type: ['image/png', 'image/jpeg'] # only allows PNG and JPEG images
|
|
229
|
+
validates :avatar, content_type: { in: [:png, :jpeg], spoofing_protection: true } # only allows PNG, JPEG and their variants, with spoofing protection enabled
|
|
96
230
|
end
|
|
97
231
|
```
|
|
98
232
|
|
|
99
|
-
|
|
233
|
+
#### Best practices
|
|
100
234
|
|
|
235
|
+
When using the `content_type` validator, it is recommended to reflect the allowed content types in the html [`accept`](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept) attribute in the corresponding file field in your views. This will prevent users from trying to upload files with not allowed content types (however it is only an UX improvement, a malicious user can still try to upload files with not allowed content types therefore the backend validation).
|
|
236
|
+
|
|
237
|
+
For example, if you want to allow PNG and JPEG images only, you can do this:
|
|
101
238
|
```ruby
|
|
102
239
|
class User < ApplicationRecord
|
|
240
|
+
ACCEPTED_CONTENT_TYPES = ['image/png', 'image/jpeg'].freeze
|
|
241
|
+
|
|
103
242
|
has_one_attached :avatar
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
validates :avatar, dimension: { min: 200..100 }
|
|
107
|
-
# Equivalent to:
|
|
108
|
-
# validates :avatar, dimension: { width: { min: 200 }, height: { min: 100 } }
|
|
109
|
-
validates :photos, dimension: { min: 200..100, max: 400..200 }
|
|
110
|
-
# Equivalent to:
|
|
111
|
-
# validates :avatar, dimension: { width: { min: 200, max: 400 }, height: { min: 100, max: 200 } }
|
|
243
|
+
|
|
244
|
+
validates :avatar, content_type: ACCEPTED_CONTENT_TYPES
|
|
112
245
|
end
|
|
113
246
|
```
|
|
114
247
|
|
|
115
|
-
|
|
248
|
+
```erb
|
|
249
|
+
<%= form_with model: @user do |f| %>
|
|
250
|
+
<%= f.file_field :avatar,
|
|
251
|
+
accept: ACCEPTED_CONTENT_TYPES.join(',') %>
|
|
252
|
+
<% end %>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
#### Content type shorthands
|
|
256
|
+
|
|
257
|
+
If you choose to use a content_type 'shorthand' (like `png`), note that it will be converted to a full content type using `Marcel::MimeType.for` under the hood. Therefore, you should check if the content_type is registered by [`Marcel::EXTENSIONS`](https://github.com/rails/marcel/blob/main/lib/marcel/tables.rb). If it's not, you can register it by adding the following code to your `config/initializers/mime_types.rb` file:
|
|
116
258
|
|
|
259
|
+
```ruby
|
|
260
|
+
Marcel::MimeType.extend "application/ino", extensions: %w(ino), parents: "text/plain" # Registering arduino INO files
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Be sure to at least include one the `extensions`, `parents` or `magic` option, otherwise the content type will not be registered.
|
|
264
|
+
|
|
265
|
+
#### Content type spoofing protection
|
|
266
|
+
|
|
267
|
+
By default, the gem does not prevent content type spoofing. You can enable it by setting the `spoofing_protection` option to `true` in your validator options.
|
|
268
|
+
|
|
269
|
+
<details>
|
|
270
|
+
<summary>
|
|
271
|
+
What is content type spoofing?
|
|
272
|
+
</summary>
|
|
273
|
+
|
|
274
|
+
File content type spoofing happens when an ill-intentioned user uploads a file which hides its true content type by faking its extension and its declared content type value. For example, a user may try to upload a `.exe` file (application/x-msdownload content type) dissimulated as a `.jpg` file (image/jpeg content type).
|
|
275
|
+
</details>
|
|
276
|
+
|
|
277
|
+
<details>
|
|
278
|
+
<summary>
|
|
279
|
+
How do we prevent it?
|
|
280
|
+
</summary>
|
|
281
|
+
|
|
282
|
+
The spoofing protection relies on both the UNIX `file` command and `Marcel` gem. Be careful, since it needs to load the whole file io to perform the analysis, it will use a lot of RAM for very large files. Therefore it could be a wise decision not to enable it in this case.
|
|
283
|
+
|
|
284
|
+
Take note that the `file` analyzer will not find the exactly same content type as the ActiveStorage blob (ActiveStorage content type detection relies on a different logic using first 4kb of content + filename + extension). To handle this issue, we consider a close parent content type to be a match. For example, for an ActiveStorage blob which content type is `video/x-ms-wmv`, the `file` analyzer will probably detect a `video/x-ms-asf` content type, this will be considered as a valid match because these 2 content types are closely related. The correlation mapping is based on `Marcel::TYPE_PARENTS` table.
|
|
285
|
+
</details>
|
|
286
|
+
|
|
287
|
+
<details>
|
|
288
|
+
<summary>
|
|
289
|
+
Edge cases
|
|
290
|
+
</summary>
|
|
291
|
+
|
|
292
|
+
The difficulty to accurately predict a mime type may generate false positives, if so there are two solutions available:
|
|
293
|
+
- If the ActiveStorage blob content type is closely related to the detected content type using the `file` analyzer, you can enhance `Marcel::TYPE_PARENTS` mapping using `Marcel::MimeType.extend "application/x-rar-compressed", parents: %(application/x-rar)` in the `config/initializers/mime_types.rb` file. (Please drop an issue so we can add it to the gem for everyone!)
|
|
294
|
+
- If the ActiveStorage blob content type is not closely related, you still can disable the content type spoofing protection in the validator, if so, please drop us an issue so we can fix it for everyone!
|
|
295
|
+
</details>
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
#### Error messages (I18n)
|
|
299
|
+
|
|
300
|
+
```yml
|
|
301
|
+
en:
|
|
302
|
+
errors:
|
|
303
|
+
messages:
|
|
304
|
+
content_type_invalid:
|
|
305
|
+
one: "has an invalid content type (authorized content type is %{authorized_human_content_types})"
|
|
306
|
+
other: "has an invalid content type (authorized content types are %{authorized_human_content_types})"
|
|
307
|
+
content_type_spoofed:
|
|
308
|
+
one: "has a content type that is not equivalent to the one that is detected through its content (authorized content type is %{authorized_human_content_types})"
|
|
309
|
+
other: "has a content type that is not equivalent to the one that is detected through its content (authorized content types are %{authorized_human_content_types})"
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The `content_type` validator error messages expose 7 values that you can use:
|
|
313
|
+
- `content_type` containing the content type of the sent file (e.g. `image/png`)
|
|
314
|
+
- `human_content_type` containing a more user-friendly version of the sent file content type (e.g. 'TXT' for 'text/plain')
|
|
315
|
+
- `detected_content_type` containing the detected content type of the sent file using `spoofing_protection` option (e.g. `image/png`)
|
|
316
|
+
- `detected_human_content_type` containing a more user-friendly version of the sent file detected content type using `spoofing_protection` option (e.g. 'TXT' for 'text/plain')
|
|
317
|
+
- `authorized_human_content_types` containing the list of authorized content types (e.g. 'PNG, JPEG' for `['image/png', 'image/jpeg']`)
|
|
318
|
+
- `count` containing the number of authorized content types (e.g. `2`)
|
|
319
|
+
- `filename` containing the filename
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
### Size
|
|
324
|
+
|
|
325
|
+
Validates each attached file size.
|
|
326
|
+
|
|
327
|
+
#### Options
|
|
328
|
+
|
|
329
|
+
The `size` validator has 5 possible options:
|
|
330
|
+
- `less_than`: defines the strict maximum allowed file size
|
|
331
|
+
- `less_than_or_equal_to`: defines the maximum allowed file size
|
|
332
|
+
- `greater_than`: defines the strict minimum allowed file size
|
|
333
|
+
- `greater_than_or_equal_to`: defines the minimum allowed file size
|
|
334
|
+
- `between`: defines the allowed file size range
|
|
335
|
+
- `equal_to`: defines the allowed file size
|
|
336
|
+
|
|
337
|
+
#### Examples
|
|
338
|
+
|
|
339
|
+
Use it like this:
|
|
117
340
|
```ruby
|
|
118
341
|
class User < ApplicationRecord
|
|
119
342
|
has_one_attached :avatar
|
|
120
|
-
has_one_attached :photo
|
|
121
|
-
has_many_attached :photos
|
|
122
343
|
|
|
123
|
-
validates :avatar,
|
|
124
|
-
validates :
|
|
344
|
+
validates :avatar, size: { less_than: 2.megabytes } # restricts the file size to < 2MB
|
|
345
|
+
validates :avatar, size: { less_than_or_equal_to: 2.megabytes } # restricts the file size to <= 2MB
|
|
346
|
+
validates :avatar, size: { greater_than: 1.kilobyte } # restricts the file size to > 1KB
|
|
347
|
+
validates :avatar, size: { greater_than_or_equal_to: 1.kilobyte } # restricts the file size to >= 1KB
|
|
348
|
+
validates :avatar, size: { between: 1.kilobyte..2.megabytes } # restricts the file size to between 1KB and 2MB
|
|
349
|
+
validates :avatar, size: { equal_to: 1.megabyte } # restricts the file size to exactly 1MB
|
|
350
|
+
end
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
#### Best practices
|
|
125
354
|
|
|
126
|
-
|
|
127
|
-
|
|
355
|
+
It is always a good practice to limit the maximum file size to a reasonable value (like 2MB for avatar images). This helps prevent server storage issues, reduces upload/download times, and ensures better performance. Large files can consume excessive bandwidth and storage space, potentially impacting both server resources and user experience.
|
|
356
|
+
Plus, not setting a size limit inside your Rails app might lead into your server throwing a `413 Content Too Large` error, which is not as nice as a Rails validation error.
|
|
357
|
+
|
|
358
|
+
#### Error messages (I18n)
|
|
359
|
+
|
|
360
|
+
```yml
|
|
361
|
+
en:
|
|
362
|
+
errors:
|
|
363
|
+
messages:
|
|
364
|
+
file_size_not_less_than: "file size must be less than %{max} (current size is %{file_size})"
|
|
365
|
+
file_size_not_less_than_or_equal_to: "file size must be less than or equal to %{max} (current size is %{file_size})"
|
|
366
|
+
file_size_not_greater_than: "file size must be greater than %{min} (current size is %{file_size})"
|
|
367
|
+
file_size_not_greater_than_or_equal_to: "file size must be greater than or equal to %{min} (current size is %{file_size})"
|
|
368
|
+
file_size_not_between: "file size must be between %{min} and %{max} (current size is %{file_size})"
|
|
369
|
+
file_size_not_equal_to: "file size must be equal to %{exact} (current size is %{file_size})"
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
The `size` validator error messages expose 4 values that you can use:
|
|
373
|
+
- `file_size` containing the current file size (e.g. `1.5MB`)
|
|
374
|
+
- `min` containing the minimum allowed file size (e.g. `1KB`)
|
|
375
|
+
- `exact` containing the allowed file size (e.g. `1MB`)
|
|
376
|
+
- `max` containing the maximum allowed file size (e.g. `2MB`)
|
|
377
|
+
- `filename` containing the current file name
|
|
378
|
+
|
|
379
|
+
---
|
|
380
|
+
|
|
381
|
+
### Total size
|
|
382
|
+
|
|
383
|
+
Validates the total file size for several files.
|
|
384
|
+
|
|
385
|
+
#### Options
|
|
386
|
+
|
|
387
|
+
The `total_size` validator has 5 possible options:
|
|
388
|
+
- `less_than`: defines the strict maximum allowed total file size
|
|
389
|
+
- `less_than_or_equal_to`: defines the maximum allowed total file size
|
|
390
|
+
- `greater_than`: defines the strict minimum allowed total file size
|
|
391
|
+
- `greater_than_or_equal_to`: defines the minimum allowed total file size
|
|
392
|
+
- `between`: defines the allowed total file size range
|
|
393
|
+
- `equal_to`: defines the allowed total file size
|
|
394
|
+
|
|
395
|
+
#### Examples
|
|
396
|
+
|
|
397
|
+
Use it like this:
|
|
398
|
+
```ruby
|
|
399
|
+
class User < ApplicationRecord
|
|
400
|
+
has_many_attached :certificates
|
|
401
|
+
|
|
402
|
+
validates :certificates, total_size: { less_than: 10.megabytes } # restricts the total size to < 10MB
|
|
403
|
+
validates :certificates, total_size: { less_than_or_equal_to: 10.megabytes } # restricts the total size to <= 10MB
|
|
404
|
+
validates :certificates, total_size: { greater_than: 1.kilobyte } # restricts the total size to > 1KB
|
|
405
|
+
validates :certificates, total_size: { greater_than_or_equal_to: 1.kilobyte } # restricts the total size to >= 1KB
|
|
406
|
+
validates :certificates, total_size: { between: 1.kilobyte..10.megabytes } # restricts the total size to between 1KB and 10MB
|
|
407
|
+
validates :certificates, total_size: { equal_to: 1.megabyte } # restricts the total file size to exactly 1MB
|
|
128
408
|
end
|
|
129
409
|
```
|
|
130
410
|
|
|
131
|
-
|
|
411
|
+
#### Error messages (I18n)
|
|
132
412
|
|
|
133
|
-
|
|
413
|
+
```yml
|
|
414
|
+
en:
|
|
415
|
+
errors:
|
|
416
|
+
messages:
|
|
417
|
+
total_file_size_not_less_than: "total file size must be less than %{max} (current size is %{total_file_size})"
|
|
418
|
+
total_file_size_not_less_than_or_equal_to: "total file size must be less than or equal to %{max} (current size is %{total_file_size})"
|
|
419
|
+
total_file_size_not_greater_than: "total file size must be greater than %{min} (current size is %{total_file_size})"
|
|
420
|
+
total_file_size_not_greater_than_or_equal_to: "total file size must be greater than or equal to %{min} (current size is %{total_file_size})"
|
|
421
|
+
total_file_size_not_between: "total file size must be between %{min} and %{max} (current size is %{total_file_size})"
|
|
422
|
+
total_file_size_not_equal_to: "total file size must be equal to %{exact} (current size is %{total_file_size})"
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
The `total_size` validator error messages expose 4 values that you can use:
|
|
426
|
+
- `total_file_size` containing the current total file size (e.g. `1.5MB`)
|
|
427
|
+
- `min` containing the minimum allowed total file size (e.g. `1KB`)
|
|
428
|
+
- `exact` containing the allowed total file size (e.g. `1MB`)
|
|
429
|
+
- `max` containing the maximum allowed total file size (e.g. `2MB`)
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
### Dimension
|
|
434
|
+
|
|
435
|
+
Validates the dimension of the attached image / video files.
|
|
436
|
+
It can also be used for pdf files, but it will only analyze the pdf first page, and will assume a DPI of 72.
|
|
437
|
+
(be sure to have the right dependencies installed as mentioned in [installation](#installation))
|
|
438
|
+
|
|
439
|
+
#### Options
|
|
440
|
+
|
|
441
|
+
The `dimension` validator has several possible options:
|
|
442
|
+
- `width`: defines the allowed width (integer)
|
|
443
|
+
- `min`: defines the minimum allowed width (integer)
|
|
444
|
+
- `max`: defines the maximum allowed width (integer)
|
|
445
|
+
- `in`: defines the allowed width range (range)
|
|
446
|
+
- `height`: defines the allowed height (integer)
|
|
447
|
+
- `min`: defines the minimum allowed height (integer)
|
|
448
|
+
- `max`: defines the maximum allowed height (integer)
|
|
449
|
+
- `in`: defines the allowed height range (range)
|
|
450
|
+
- `min`: defines the minimum allowed width and height (range)
|
|
451
|
+
- `max`: defines the maximum allowed width and height (range)
|
|
452
|
+
|
|
453
|
+
#### Examples
|
|
454
|
+
|
|
455
|
+
Use it like this:
|
|
134
456
|
```ruby
|
|
135
457
|
class User < ApplicationRecord
|
|
136
|
-
|
|
458
|
+
has_one_attached :avatar
|
|
137
459
|
|
|
138
|
-
validates :
|
|
460
|
+
validates :avatar, dimension: { width: 100 } # restricts the width to 100 pixels
|
|
461
|
+
validates :avatar, dimension: { width: { min: 80, max: 100 } } # restricts the width to between 80 and 100 pixels
|
|
462
|
+
validates :avatar, dimension: { width: { in: 80..100 } } # restricts the width to between 80 and 100 pixels
|
|
463
|
+
validates :avatar, dimension: { height: 100 } # restricts the height to 100 pixels
|
|
464
|
+
validates :avatar, dimension: { height: { min: 600, max: 1800 } } # restricts the height to between 600 and 1800 pixels
|
|
465
|
+
validates :avatar, dimension: { height: { in: 600..1800 } } # restricts the height to between 600 and 1800 pixels
|
|
466
|
+
validates :avatar, dimension: { min: 80..600, max: 100..1800 } # restricts the width to between 80 and 100 pixels, and the height to between 600 and 1800 pixels
|
|
139
467
|
end
|
|
468
|
+
```
|
|
140
469
|
|
|
470
|
+
#### Error messages (I18n)
|
|
471
|
+
|
|
472
|
+
```yml
|
|
473
|
+
en:
|
|
474
|
+
errors:
|
|
475
|
+
messages:
|
|
476
|
+
dimension_min_not_included_in: "must be greater than or equal to %{width} x %{height} pixels"
|
|
477
|
+
dimension_max_not_included_in: "must be less than or equal to %{width} x %{height} pixels"
|
|
478
|
+
dimension_width_not_included_in: "width is not included between %{min} and %{max} pixels"
|
|
479
|
+
dimension_height_not_included_in: "height is not included between %{min} and %{max} pixels"
|
|
480
|
+
dimension_width_not_greater_than_or_equal_to: "width must be greater than or equal to %{length} pixels"
|
|
481
|
+
dimension_height_not_greater_than_or_equal_to: "height must be greater than or equal to %{length} pixels"
|
|
482
|
+
dimension_width_not_less_than_or_equal_to: "width must be less than or equal to %{length} pixels"
|
|
483
|
+
dimension_height_not_less_than_or_equal_to: "height must be less than or equal to %{length} pixels"
|
|
484
|
+
dimension_width_not_equal_to: "width must be equal to %{length} pixels"
|
|
485
|
+
dimension_height_not_equal_to: "height must be equal to %{length} pixels"
|
|
486
|
+
media_metadata_missing: "is not a valid media file"
|
|
141
487
|
```
|
|
142
488
|
|
|
143
|
-
|
|
489
|
+
The `dimension` validator error messages expose 6 values that you can use:
|
|
490
|
+
- `min` containing the minimum width or height allowed
|
|
491
|
+
- `max` containing the maximum width or height allowed
|
|
492
|
+
- `width` containing the minimum or maximum width allowed
|
|
493
|
+
- `height` containing the minimum or maximum width allowed
|
|
494
|
+
- `length` containing the exact width or height allowed
|
|
495
|
+
- `filename` containing the current filename in error
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
### Duration
|
|
500
|
+
|
|
501
|
+
Validates the duration of the attached audio / video files.
|
|
502
|
+
(be sure to have the right dependencies installed as mentioned in [installation](#installation))
|
|
503
|
+
|
|
504
|
+
#### Options
|
|
505
|
+
|
|
506
|
+
The `duration` validator has 5 possible options:
|
|
507
|
+
- `less_than`: defines the strict maximum allowed file duration
|
|
508
|
+
- `less_than_or_equal_to`: defines the maximum allowed file duration
|
|
509
|
+
- `greater_than`: defines the strict minimum allowed file duration
|
|
510
|
+
- `greater_than_or_equal_to`: defines the minimum allowed file duration
|
|
511
|
+
- `between`: defines the allowed file duration range
|
|
512
|
+
- `equal_to`: defines the allowed duration
|
|
513
|
+
|
|
514
|
+
#### Examples
|
|
515
|
+
|
|
516
|
+
Use it like this:
|
|
517
|
+
```ruby
|
|
518
|
+
class User < ApplicationRecord
|
|
519
|
+
has_one_attached :intro_song
|
|
520
|
+
|
|
521
|
+
validates :intro_song, duration: { less_than: 2.minutes } # restricts the file duration to < 2 minutes
|
|
522
|
+
validates :intro_song, duration: { less_than_or_equal_to: 2.minutes } # restricts the file duration to <= 2 minutes
|
|
523
|
+
validates :intro_song, duration: { greater_than: 1.second } # restricts the file duration to > 1 second
|
|
524
|
+
validates :intro_song, duration: { greater_than_or_equal_to: 1.second } # restricts the file duration to >= 1 second
|
|
525
|
+
validates :intro_song, duration: { between: 1.second..2.minutes } # restricts the file duration to between 1 second and 2 minutes
|
|
526
|
+
validates :intro_song, duration: { equal_to: 1.minute } # restricts the duration to exactly 1 minute
|
|
527
|
+
end
|
|
528
|
+
```
|
|
144
529
|
|
|
145
|
-
|
|
530
|
+
#### Error messages (I18n)
|
|
146
531
|
|
|
147
532
|
```yml
|
|
148
533
|
en:
|
|
149
534
|
errors:
|
|
150
535
|
messages:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
536
|
+
duration_not_less_than: "duration must be less than %{max} (current duration is %{duration})"
|
|
537
|
+
duration_not_less_than_or_equal_to: "duration must be less than or equal to %{max} (current duration is %{duration})"
|
|
538
|
+
duration_not_greater_than: "duration must be greater than %{min} (current duration is %{duration})"
|
|
539
|
+
duration_not_greater_than_or_equal_to: "duration must be greater than or equal to %{min} (current duration is %{duration})"
|
|
540
|
+
duration_not_between: "duration must be between %{min} and %{max} (current duration is %{duration})"
|
|
541
|
+
duration_not_equal_to: "duration must be equal to %{exact} (current duration is %{duration})"
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
The `duration` validator error messages expose 4 values that you can use:
|
|
545
|
+
- `duration` containing the current duration size (e.g. `2 minutes`)
|
|
546
|
+
- `min` containing the minimum allowed duration size (e.g. `1 second`)
|
|
547
|
+
- `exact` containing the allowed duration (e.g. `3 seconds`)
|
|
548
|
+
- `max` containing the maximum allowed duration size (e.g. `2 minutes`)
|
|
549
|
+
- `filename` containing the current file name
|
|
550
|
+
|
|
551
|
+
---
|
|
552
|
+
|
|
553
|
+
### Aspect ratio
|
|
554
|
+
|
|
555
|
+
Validates the aspect ratio of the attached image / video files.
|
|
556
|
+
It can also be used for pdf files, but it will only analyze the pdf first page.
|
|
557
|
+
(be sure to have the right dependencies installed as mentioned in [installation](#installation))
|
|
558
|
+
|
|
559
|
+
#### Options
|
|
560
|
+
|
|
561
|
+
The `aspect_ratio` validator has several options:
|
|
562
|
+
- `with`: defines the allowed aspect ratio (e.g. `:is_16/9`)
|
|
563
|
+
- `in`: defines the allowed aspect ratios (e.g. `%i[square landscape]`)
|
|
564
|
+
|
|
565
|
+
This validator can define aspect ratios in several ways:
|
|
566
|
+
- Symbols:
|
|
567
|
+
- prebuilt aspect ratios: `:square`, `:portrait`, `:landscape`
|
|
568
|
+
- custom aspect ratios (it must be of type `is_xx_yy`): `:is_16_9`, `:is_4_3`, etc.
|
|
569
|
+
|
|
570
|
+
#### Examples
|
|
571
|
+
|
|
572
|
+
Use it like this:
|
|
573
|
+
```ruby
|
|
574
|
+
class User < ApplicationRecord
|
|
575
|
+
has_one_attached :avatar
|
|
576
|
+
|
|
577
|
+
validates :avatar, aspect_ratio: :square # restricts the aspect ratio to 1:1
|
|
578
|
+
validates :avatar, aspect_ratio: :portrait # restricts the aspect ratio to x:y where y > x
|
|
579
|
+
validates :avatar, aspect_ratio: :landscape # restricts the aspect ratio to x:y where x > y
|
|
580
|
+
validates :avatar, aspect_ratio: :is_16_9 # restricts the aspect ratio to 16:9
|
|
581
|
+
validates :avatar, aspect_ratio: %i[square is_16_9] # restricts the aspect ratio to 1:1 and 16:9
|
|
582
|
+
end
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
#### Error messages (I18n)
|
|
180
586
|
|
|
181
587
|
```yml
|
|
182
|
-
|
|
588
|
+
en:
|
|
589
|
+
errors:
|
|
590
|
+
messages:
|
|
591
|
+
aspect_ratio_not_square: "must be square (current file is %{width}x%{height}px)"
|
|
592
|
+
aspect_ratio_not_portrait: "must be portrait (current file is %{width}x%{height}px)"
|
|
593
|
+
aspect_ratio_not_landscape: "must be landscape (current file is %{width}x%{height}px)"
|
|
594
|
+
aspect_ratio_not_x_y: "must be %{authorized_aspect_ratios} (current file is %{width}x%{height}px)"
|
|
595
|
+
aspect_ratio_invalid: "has an invalid aspect ratio (valid aspect ratios are %{authorized_aspect_ratios})"
|
|
596
|
+
media_metadata_missing: "is not a valid media file"
|
|
183
597
|
```
|
|
184
598
|
|
|
185
|
-
|
|
599
|
+
The `aspect_ratio` validator error messages expose 4 values that you can use:
|
|
600
|
+
- `authorized_aspect_ratios` containing the authorized aspect ratios
|
|
601
|
+
- `width` containing the current width of the image/video
|
|
602
|
+
- `height` containing the current height of the image/video
|
|
603
|
+
- `filename` containing the current filename in error
|
|
604
|
+
|
|
605
|
+
---
|
|
186
606
|
|
|
187
|
-
|
|
607
|
+
### Processable file
|
|
608
|
+
|
|
609
|
+
Validates if the attached files can be processed by MiniMagick or Vips (image), ffmpeg (video/audio) or poppler (pdf).
|
|
610
|
+
(be sure to have the right dependencies installed as mentioned in [installation](#installation))
|
|
611
|
+
|
|
612
|
+
#### Options
|
|
613
|
+
|
|
614
|
+
The `processable_file` validator has no options.
|
|
615
|
+
|
|
616
|
+
#### Examples
|
|
617
|
+
|
|
618
|
+
Use it like this:
|
|
619
|
+
```ruby
|
|
620
|
+
class User < ApplicationRecord
|
|
621
|
+
has_one_attached :avatar
|
|
622
|
+
|
|
623
|
+
validates :avatar, processable_file: true # ensures that the file is processable by MiniMagick or Vips (image) or ffmpeg (video/audio)
|
|
624
|
+
end
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
#### Error messages (I18n)
|
|
188
628
|
|
|
189
629
|
```yml
|
|
190
|
-
|
|
630
|
+
en:
|
|
631
|
+
errors:
|
|
632
|
+
messages:
|
|
633
|
+
file_not_processable: "is not identified as a valid media file"
|
|
191
634
|
```
|
|
192
635
|
|
|
193
|
-
|
|
636
|
+
The `processable_file` validator error messages expose 1 value that you can use:
|
|
637
|
+
- `filename` containing the current filename in error
|
|
194
638
|
|
|
195
|
-
|
|
639
|
+
---
|
|
196
640
|
|
|
197
|
-
|
|
198
|
-
# Rails 5.2 and Rails 6
|
|
199
|
-
gem 'active_storage_validations'
|
|
641
|
+
### Pages
|
|
200
642
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
643
|
+
Validates each attached pdf file number of pages.
|
|
644
|
+
(be sure to have the right dependencies installed as mentioned in [installation](#installation))
|
|
645
|
+
|
|
646
|
+
#### Options
|
|
647
|
+
|
|
648
|
+
The `pages` validator has 6 possible options:
|
|
649
|
+
- `less_than`: defines the strict maximum allowed number of pages
|
|
650
|
+
- `less_than_or_equal_to`: defines the maximum allowed number of pages
|
|
651
|
+
- `greater_than`: defines the strict minimum allowed number of pages
|
|
652
|
+
- `greater_than_or_equal_to`: defines the minimum allowed number of pages
|
|
653
|
+
- `between`: defines the allowed number of pages range
|
|
654
|
+
- `equal_to`: defines the allowed number of pages
|
|
655
|
+
|
|
656
|
+
#### Examples
|
|
657
|
+
|
|
658
|
+
Use it like this:
|
|
659
|
+
```ruby
|
|
660
|
+
class User < ApplicationRecord
|
|
661
|
+
has_one_attached :contract
|
|
662
|
+
|
|
663
|
+
validates :contract, pages: { less_than: 2 } # restricts the number of pages to < 2
|
|
664
|
+
validates :contract, pages: { less_than_or_equal_to: 2 } # restricts the number of pages to <= 2
|
|
665
|
+
validates :contract, pages: { greater_than: 1 } # restricts the number of pages to > 1
|
|
666
|
+
validates :contract, pages: { greater_than_or_equal_to: 1 } # restricts the number of pages to >= 1
|
|
667
|
+
validates :contract, pages: { between: 1..2 } # restricts the number of pages to between 1 and 2
|
|
668
|
+
validates :contract, pages: { equal_to: 1 } # restricts the number of pages to exactly 1
|
|
669
|
+
end
|
|
205
670
|
```
|
|
206
671
|
|
|
207
|
-
|
|
672
|
+
#### Error messages (I18n)
|
|
208
673
|
|
|
209
|
-
```
|
|
210
|
-
|
|
674
|
+
```yml
|
|
675
|
+
en:
|
|
676
|
+
errors:
|
|
677
|
+
messages:
|
|
678
|
+
pages_not_less_than: "page count must be less than %{max} (current page count is %{pages})"
|
|
679
|
+
pages_not_less_than_or_equal_to: "page count must be less than or equal to %{max} (current page count is %{pages})"
|
|
680
|
+
pages_not_greater_than: "page count must be greater than %{min} (current page count is %{pages})"
|
|
681
|
+
pages_not_greater_than_or_equal_to: "page count must be greater than or equal to %{min} (current page count is %{pages})"
|
|
682
|
+
pages_not_between: "page count must be between %{min} and %{max} (current page count is %{pages})"
|
|
683
|
+
pages_not_equal_to: "page count must be equal to %{exact} (current page count is %{pages})"
|
|
211
684
|
```
|
|
212
685
|
|
|
213
|
-
|
|
686
|
+
The `pages` validator error messages expose 5 values that you can use:
|
|
687
|
+
- `pages` containing the current file number of pages (e.g. `7`)
|
|
688
|
+
- `min` containing the minimum allowed number of pages (e.g. `1`)
|
|
689
|
+
- `exact` containing the allowed number of pages (e.g. `3`)
|
|
690
|
+
- `max` containing the maximum allowed number of pages (e.g. `5`)
|
|
691
|
+
- `filename` containing the current file name
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## Upgrading from 1.x to 2.x
|
|
696
|
+
|
|
697
|
+
If you are upgrading from 1.x to 2.x, you will be pleased to note that a lot of things have been added and improved!
|
|
698
|
+
|
|
699
|
+
Added features:
|
|
700
|
+
- `duration` validator has been added for audio / video files
|
|
701
|
+
- `dimension` validator now supports videos
|
|
702
|
+
- `aspect_ratio` validator now supports videos
|
|
703
|
+
- `processable_image` validator is now `processable_file` validator and supports image/video/audio
|
|
704
|
+
- Major performance improvement have been added: we now only perform the expensive io analysis operation on the newly attached files. For previously attached files, we validate them using Rails `ActiveStorage::Blob#metadata` internal mecanism ([more here](https://github.com/rails/rails/blob/main/activestorage/app/models/active_storage/blob/analyzable.rb)).
|
|
705
|
+
- All error messages have been given an upgrade and new variables that you can use
|
|
706
|
+
|
|
707
|
+
But this major version bump also comes with some breaking changes. Below are the main breaking changes you need to be aware of:
|
|
708
|
+
- Error messages
|
|
709
|
+
- We advise you to replace all the v1 translations by the new v2 rather than changing them one by one. A majority of messages have been completely rewritten to be more consistent and easier to understand.
|
|
710
|
+
- If you wish to change them one by one, here is the list of changes to make:
|
|
711
|
+
- Some validator errors have been totally changed:
|
|
712
|
+
- `limit` validator keys have been totally reworked
|
|
713
|
+
- `dimension` validator keys have been totally reworked
|
|
714
|
+
- `content_type` validator keys have been totally reworked
|
|
715
|
+
- `processable_image` validator keys have been totally reworked
|
|
716
|
+
- Some keys have been changed:
|
|
717
|
+
- `image_metadata_missing` has been replaced by `media_metadata_missing`
|
|
718
|
+
- `aspect_ratio_is_not` has been replaced by `aspect_ratio_not_x_y`
|
|
719
|
+
- Some error messages variables names have been changed to improve readability:
|
|
720
|
+
- `aspect_ratio` validator:
|
|
721
|
+
- `aspect_ratio` has been replaced by `authorized_aspect_ratios`
|
|
722
|
+
- `content_type` validator:
|
|
723
|
+
- `authorized_types` has been replaced by `authorized_human_content_types`
|
|
724
|
+
- `size` validator:
|
|
725
|
+
- `min_size` has been replaced by `min`
|
|
726
|
+
- `max_size` has been replaced by `max`
|
|
727
|
+
- `total_size` validator:
|
|
728
|
+
- `min_size` has been replaced by `min`
|
|
729
|
+
- `max_size` has been replaced by `max`
|
|
730
|
+
|
|
731
|
+
- `content_type` validator
|
|
732
|
+
- The `:in` option now only accepts 'valid' content types (ie content types deemed by Marcel as valid).
|
|
733
|
+
- The check was mistakenly only performed on the `:with` option previously. Therefore, invalid content types were accepted in the `:in` option, which is not the expected behavior.
|
|
734
|
+
- This might break some cases when you had for example `content_type: ['image/png', 'image/jpg']`, because `image/jpg` is not a valid content type, it should be replaced by `image/jpeg`.
|
|
735
|
+
- An `ArgumentError` is now raised if `image/jpg` is used to make it easier to fix. You should now only use `image/jpeg`.
|
|
736
|
+
|
|
737
|
+
- `processable_image` validator
|
|
738
|
+
- The validator has been replaced by `processable_file` validator, be sure to replace `processable_image: true` to `processable_file: true`
|
|
739
|
+
- The associated matcher has also been updated accordingly, be sure to replace `validate_processable_image_of` to `validate_processable_file_of`
|
|
740
|
+
|
|
741
|
+
## Upgrading from 2.x to 3.x
|
|
742
|
+
|
|
743
|
+
Version 3 comes with the ability to support single page pdf `dimension` / `aspect_ratio` analysis, we had to make a breaking change:
|
|
744
|
+
- To analyze PDFs, you must install the `poppler` PDF processing dependency
|
|
745
|
+
- It's a Rails-supported PDF processing dependency (https://guides.rubyonrails.org/active_storage_overview.html#requirements)
|
|
746
|
+
- To install it, check their documentation at this [link](https://pdf2image.readthedocs.io/en/latest/installation.html).
|
|
747
|
+
- To check if it's installed, execute `pdftoppm -h`.
|
|
748
|
+
- To install this tool in your CI / production environments, you can check how we do it in our own CI (https://github.com/igorkasyanchuk/active_storage_validations/blob/master/.github/workflows/main.yml)
|
|
749
|
+
|
|
750
|
+
We also added the `pages` validator to validate pdf number of pages, and the `equal_to` option to `duration`, `size` and `total_size` validators.
|
|
751
|
+
|
|
752
|
+
Note that, if you do not perform these metadata validations on pdfs, the gem will work the same as in version 2.
|
|
753
|
+
|
|
754
|
+
## Internationalization (I18n)
|
|
755
|
+
|
|
756
|
+
Active Storage Validations uses I18n for error messages. The error messages are automatically loaded in your Rails app if your language translations are present in the gem.
|
|
214
757
|
|
|
215
|
-
|
|
758
|
+
Translation files are available [here](https://github.com/igorkasyanchuk/active_storage_validations/tree/master/config/locales). We currently have translations for `da`, `de`, `en`, `en-GB`, `es`, `fr`, `it`, `ja`, `nl`, `pl`, `pt-BR`, `ru`, `sv`, `tr`, `uk`, `vi` and `zh-CN`. Feel free to drop a PR to add your language ✌️.
|
|
216
759
|
|
|
217
|
-
|
|
760
|
+
If you wish to customize the error messages, just copy, paste and update the translation files into your application locales.
|
|
218
761
|
|
|
219
762
|
## Test matchers
|
|
220
|
-
|
|
763
|
+
|
|
764
|
+
The gem also provides RSpec-compatible and Minitest-compatible matchers for testing the validators.
|
|
221
765
|
|
|
222
766
|
### RSpec
|
|
223
767
|
|
|
768
|
+
#### Setup
|
|
224
769
|
In `spec_helper.rb`, you'll need to require the matchers:
|
|
225
770
|
|
|
226
771
|
```ruby
|
|
227
772
|
require 'active_storage_validations/matchers'
|
|
228
773
|
```
|
|
229
774
|
|
|
230
|
-
And
|
|
775
|
+
And include the module:
|
|
231
776
|
|
|
232
777
|
```ruby
|
|
233
778
|
RSpec.configure do |config|
|
|
@@ -235,41 +780,110 @@ RSpec.configure do |config|
|
|
|
235
780
|
end
|
|
236
781
|
```
|
|
237
782
|
|
|
238
|
-
|
|
783
|
+
#### Matchers
|
|
784
|
+
Matcher methods available:
|
|
239
785
|
|
|
240
786
|
```ruby
|
|
241
787
|
describe User do
|
|
788
|
+
# aspect_ratio:
|
|
789
|
+
# #allowing, #rejecting
|
|
790
|
+
it { is_expected.to validate_aspect_ratio_of(:avatar).allowing(:square, :portrait) } # possible to use an Array or *splatted array
|
|
791
|
+
it { is_expected.to validate_aspect_ratio_of(:avatar).rejecting(:square, :landscape) } # possible to use an Array or *splatted array
|
|
792
|
+
|
|
793
|
+
# attached
|
|
242
794
|
it { is_expected.to validate_attached_of(:avatar) }
|
|
243
795
|
|
|
244
|
-
|
|
245
|
-
it { is_expected.to
|
|
796
|
+
# processable_file
|
|
797
|
+
it { is_expected.to validate_processable_file_of(:avatar) }
|
|
798
|
+
|
|
799
|
+
# limit
|
|
800
|
+
# #min, #max
|
|
801
|
+
it { is_expected.to validate_limits_of(:avatar).min(1) }
|
|
802
|
+
it { is_expected.to validate_limits_of(:avatar).max(5) }
|
|
803
|
+
|
|
804
|
+
# content_type:
|
|
805
|
+
# #allowing, #rejecting
|
|
806
|
+
it { is_expected.to validate_content_type_of(:avatar).allowing('image/png', 'image/gif') } # possible to use an Array or *splatted array
|
|
807
|
+
it { is_expected.to validate_content_type_of(:avatar).rejecting('text/plain', 'text/xml') } # possible to use an Array or *splatted array
|
|
246
808
|
|
|
809
|
+
# dimension:
|
|
810
|
+
# #width, #height, #width_min, #height_min, #width_max, #height_max, #width_between, #height_between
|
|
247
811
|
it { is_expected.to validate_dimensions_of(:avatar).width(250) }
|
|
248
812
|
it { is_expected.to validate_dimensions_of(:avatar).height(200) }
|
|
249
|
-
it { is_expected.to validate_dimensions_of(:avatar).width(250).height(200).with_message('Invalid dimensions.') }
|
|
250
813
|
it { is_expected.to validate_dimensions_of(:avatar).width_min(200) }
|
|
251
|
-
it { is_expected.to validate_dimensions_of(:avatar).width_max(500) }
|
|
252
814
|
it { is_expected.to validate_dimensions_of(:avatar).height_min(100) }
|
|
815
|
+
it { is_expected.to validate_dimensions_of(:avatar).width_max(500) }
|
|
253
816
|
it { is_expected.to validate_dimensions_of(:avatar).height_max(300) }
|
|
254
817
|
it { is_expected.to validate_dimensions_of(:avatar).width_between(200..500) }
|
|
255
818
|
it { is_expected.to validate_dimensions_of(:avatar).height_between(100..300) }
|
|
256
819
|
|
|
820
|
+
# size:
|
|
821
|
+
# #less_than, #less_than_or_equal_to, #greater_than, #greater_than_or_equal_to, #between, #equal_to
|
|
257
822
|
it { is_expected.to validate_size_of(:avatar).less_than(50.kilobytes) }
|
|
258
823
|
it { is_expected.to validate_size_of(:avatar).less_than_or_equal_to(50.kilobytes) }
|
|
259
824
|
it { is_expected.to validate_size_of(:avatar).greater_than(1.kilobyte) }
|
|
260
825
|
it { is_expected.to validate_size_of(:avatar).greater_than_or_equal_to(1.kilobyte) }
|
|
261
826
|
it { is_expected.to validate_size_of(:avatar).between(100..500.kilobytes) }
|
|
827
|
+
it { is_expected.to validate_size_of(:avatar).equal_to(5.megabytes) }
|
|
828
|
+
|
|
829
|
+
# total_size:
|
|
830
|
+
# #less_than, #less_than_or_equal_to, #greater_than, #greater_than_or_equal_to, #between, #equal_to
|
|
831
|
+
it { is_expected.to validate_total_size_of(:avatar).less_than(50.kilobytes) }
|
|
832
|
+
it { is_expected.to validate_total_size_of(:avatar).less_than_or_equal_to(50.kilobytes) }
|
|
833
|
+
it { is_expected.to validate_total_size_of(:avatar).greater_than(1.kilobyte) }
|
|
834
|
+
it { is_expected.to validate_total_size_of(:avatar).greater_than_or_equal_to(1.kilobyte) }
|
|
835
|
+
it { is_expected.to validate_total_size_of(:avatar).between(100..500.kilobytes) }
|
|
836
|
+
it { is_expected.to validate_total_size_of(:avatar).equal_to(5.megabytes) }
|
|
837
|
+
|
|
838
|
+
# duration:
|
|
839
|
+
# #less_than, #less_than_or_equal_to, #greater_than, #greater_than_or_equal_to, #between, #equal_to
|
|
840
|
+
it { is_expected.to validate_duration_of(:introduction).less_than(50.seconds) }
|
|
841
|
+
it { is_expected.to validate_duration_of(:introduction).less_than_or_equal_to(50.seconds) }
|
|
842
|
+
it { is_expected.to validate_duration_of(:introduction).greater_than(1.minute) }
|
|
843
|
+
it { is_expected.to validate_duration_of(:introduction).greater_than_or_equal_to(1.minute) }
|
|
844
|
+
it { is_expected.to validate_duration_of(:introduction).between(100..500.seconds) }
|
|
845
|
+
it { is_expected.to validate_duration_of(:avatar).equal_to(5.minutes) }
|
|
846
|
+
|
|
847
|
+
# pages:
|
|
848
|
+
# #less_than, #less_than_or_equal_to, #greater_than, #greater_than_or_equal_to, #between, #equal_to
|
|
849
|
+
it { is_expected.to validate_pages_of(:contract).less_than(50) }
|
|
850
|
+
it { is_expected.to validate_pages_of(:contract).less_than_or_equal_to(50) }
|
|
851
|
+
it { is_expected.to validate_pages_of(:contract).greater_than(5) }
|
|
852
|
+
it { is_expected.to validate_pages_of(:contract).greater_than_or_equal_to(5) }
|
|
853
|
+
it { is_expected.to validate_pages_of(:contract).between(100..500) }
|
|
854
|
+
it { is_expected.to validate_pages_of(:contract).equal_to(5) }
|
|
855
|
+
end
|
|
856
|
+
```
|
|
857
|
+
(Note that matcher methods are chainable)
|
|
858
|
+
|
|
859
|
+
All matchers can currently be customized with Rails validation options:
|
|
860
|
+
|
|
861
|
+
```ruby
|
|
862
|
+
describe User do
|
|
863
|
+
# :allow_blank
|
|
864
|
+
it { is_expected.to validate_attached_of(:avatar).allow_blank }
|
|
865
|
+
|
|
866
|
+
# :on
|
|
867
|
+
it { is_expected.to validate_attached_of(:avatar).on(:update) }
|
|
868
|
+
it { is_expected.to validate_attached_of(:avatar).on(%i[update custom]) }
|
|
869
|
+
|
|
870
|
+
# :message
|
|
871
|
+
it { is_expected.to validate_dimensions_of(:avatar).width(250).with_message('Invalid dimensions.') }
|
|
262
872
|
end
|
|
263
873
|
```
|
|
264
874
|
|
|
265
875
|
### Minitest
|
|
266
|
-
|
|
876
|
+
|
|
877
|
+
#### Setup
|
|
878
|
+
To use the matchers, make sure you have the [shoulda-context](https://github.com/thoughtbot/shoulda-context) gem up and running.
|
|
879
|
+
|
|
880
|
+
You need to require the matchers:
|
|
267
881
|
|
|
268
882
|
```ruby
|
|
269
883
|
require 'active_storage_validations/matchers'
|
|
270
884
|
```
|
|
271
885
|
|
|
272
|
-
And
|
|
886
|
+
And extend the module:
|
|
273
887
|
|
|
274
888
|
```ruby
|
|
275
889
|
class ActiveSupport::TestCase
|
|
@@ -277,136 +891,62 @@ class ActiveSupport::TestCase
|
|
|
277
891
|
end
|
|
278
892
|
```
|
|
279
893
|
|
|
280
|
-
|
|
894
|
+
#### Matchers
|
|
895
|
+
Then you can use the matchers with the syntax specified in the RSpec section, just use `should validate_method` instead of `it { is_expected_to validate_method }` as specified in the [shoulda-context](https://github.com/thoughtbot/shoulda-context) gem.
|
|
281
896
|
|
|
282
|
-
```ruby
|
|
283
|
-
class UserTest < ActiveSupport::TestCase
|
|
284
|
-
should validate_attached_of(:avatar)
|
|
285
|
-
|
|
286
|
-
should validate_content_type_of(:avatar).allowing('image/png', 'image/gif')
|
|
287
|
-
should validate_content_type_of(:avatar).rejecting('text/plain', 'text/xml')
|
|
288
|
-
|
|
289
|
-
should validate_dimensions_of(:avatar).width(250)
|
|
290
|
-
should validate_dimensions_of(:avatar).height(200)
|
|
291
|
-
should validate_dimensions_of(:avatar).width(250).height(200).with_message('Invalid dimensions.')
|
|
292
|
-
should validate_dimensions_of(:avatar).width_min(200)
|
|
293
|
-
should validate_dimensions_of(:avatar).width_max(500)
|
|
294
|
-
should validate_dimensions_of(:avatar).height_min(100)
|
|
295
|
-
should validate_dimensions_of(:avatar).height_max(300)
|
|
296
|
-
should validate_dimensions_of(:avatar).width_between(200..500)
|
|
297
|
-
should validate_dimensions_of(:avatar).height_between(100..300)
|
|
298
|
-
|
|
299
|
-
should validate_size_of(:avatar).less_than(50.kilobytes)
|
|
300
|
-
should validate_size_of(:avatar).less_than_or_equal_to(50.kilobytes)
|
|
301
|
-
should validate_size_of(:avatar).greater_than(1.kilobyte)
|
|
302
|
-
should validate_size_of(:avatar).greater_than_or_equal_to(1.kilobyte)
|
|
303
|
-
should validate_size_of(:avatar).between(100..500.kilobytes)
|
|
304
|
-
end
|
|
305
|
-
```
|
|
306
897
|
|
|
307
|
-
##
|
|
898
|
+
## Contributing
|
|
308
899
|
|
|
309
|
-
|
|
310
|
-
* verify how it works with direct upload
|
|
311
|
-
* better error message when content_size is invalid
|
|
312
|
-
* add more translations
|
|
900
|
+
If you want to contribute to the project, you will have to fork the repository and create a new branch from the `master` branch. Then build your feature, or fix the issue, and create a pull request. Be sure to add tests for your changes.
|
|
313
901
|
|
|
314
|
-
|
|
902
|
+
Before submitting your pull request, run the tests to make sure everything works as expected.
|
|
315
903
|
|
|
316
|
-
To run tests in root folder of gem:
|
|
904
|
+
To run the gem tests, launch the following commands in the root folder of gem repository:
|
|
317
905
|
|
|
318
|
-
* `BUNDLE_GEMFILE=gemfiles/
|
|
319
|
-
* `BUNDLE_GEMFILE=gemfiles/rails_6_1.gemfile bundle exec rake test` to run for Rails 6.1
|
|
906
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_6_1_4.gemfile bundle exec rake test` to run for Rails 6.1.4
|
|
320
907
|
* `BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test` to run for Rails 7.0
|
|
321
|
-
* `BUNDLE_GEMFILE=gemfiles/
|
|
908
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test` to run for Rails 7.1
|
|
909
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test` to run for Rails 7.2
|
|
910
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rake test` to run for Rails 8.0
|
|
911
|
+
* `BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test` to run for Rails main
|
|
322
912
|
|
|
323
913
|
Snippet to run in console:
|
|
324
914
|
|
|
325
|
-
```
|
|
326
|
-
BUNDLE_GEMFILE=gemfiles/
|
|
327
|
-
BUNDLE_GEMFILE=gemfiles/rails_6_1.gemfile bundle
|
|
915
|
+
```bash
|
|
916
|
+
BUNDLE_GEMFILE=gemfiles/rails_6_1_4.gemfile bundle
|
|
328
917
|
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle
|
|
918
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle
|
|
919
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle
|
|
920
|
+
BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle
|
|
329
921
|
BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle
|
|
330
|
-
BUNDLE_GEMFILE=gemfiles/
|
|
331
|
-
BUNDLE_GEMFILE=gemfiles/rails_6_1.gemfile bundle exec rake test
|
|
922
|
+
BUNDLE_GEMFILE=gemfiles/rails_6_1_4.gemfile bundle exec rake test
|
|
332
923
|
BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test
|
|
924
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_1.gemfile bundle exec rake test
|
|
925
|
+
BUNDLE_GEMFILE=gemfiles/rails_7_2.gemfile bundle exec rake test
|
|
926
|
+
BUNDLE_GEMFILE=gemfiles/rails_8_0.gemfile bundle exec rake test
|
|
333
927
|
BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test
|
|
334
928
|
```
|
|
335
929
|
|
|
336
|
-
|
|
930
|
+
Tips:
|
|
931
|
+
- To focus a specific test, use the `focus` class method provided by [minitest-focus](https://github.com/minitest/minitest-focus)
|
|
932
|
+
- To focus a specific file, use the TEST option provided by minitest, e.g. to only run `size_validator_test.rb` file you will launch the following command: `bundle exec rake test TEST=test/validators/size_validator_test.rb`
|
|
337
933
|
|
|
338
|
-
- 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:
|
|
339
934
|
|
|
340
|
-
|
|
341
|
-
<% if @user.avatar.attached? && @user.avatar.attachment.blob.present? && @user.avatar.attachment.blob.persisted? %>
|
|
342
|
-
<%= image_tag @user.avatar %>
|
|
343
|
-
<% end %>
|
|
344
|
-
```
|
|
935
|
+
## Additional information
|
|
345
936
|
|
|
346
|
-
|
|
937
|
+
### Contributors (BIG THANK YOU!)
|
|
347
938
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
- https://github.com/tleneveu
|
|
354
|
-
- https://github.com/reckerswartz
|
|
355
|
-
- https://github.com/Uysim
|
|
356
|
-
- https://github.com/D-system
|
|
357
|
-
- https://github.com/ivanelrey
|
|
358
|
-
- https://github.com/phlegx
|
|
359
|
-
- https://github.com/rr-dev
|
|
360
|
-
- https://github.com/dsmalko
|
|
361
|
-
- https://github.com/danderozier
|
|
362
|
-
- https://github.com/cseelus
|
|
363
|
-
- https://github.com/vkinelev
|
|
364
|
-
- https://github.com/reed
|
|
365
|
-
- https://github.com/connorshea
|
|
366
|
-
- https://github.com/Atul9
|
|
367
|
-
- https://github.com/victorbueno
|
|
368
|
-
- https://github.com/UICJohn
|
|
369
|
-
- https://github.com/giovannibonetti
|
|
370
|
-
- https://github.com/dlepage
|
|
371
|
-
- https://github.com/StefSchenkelaars
|
|
372
|
-
- https://github.com/willnet
|
|
373
|
-
- https://github.com/mohanklein
|
|
374
|
-
- https://github.com/High5Apps
|
|
375
|
-
- https://github.com/mschnitzer
|
|
376
|
-
- https://github.com/sinankeskin
|
|
377
|
-
- https://github.com/alejandrodevs
|
|
378
|
-
- https://github.com/molfar
|
|
379
|
-
- https://github.com/connorshea
|
|
380
|
-
- https://github.com/yshmarov
|
|
381
|
-
- https://github.com/fongfan999
|
|
382
|
-
- https://github.com/cooperka
|
|
383
|
-
- https://github.com/dolarsrg
|
|
384
|
-
- https://github.com/jayshepherd
|
|
385
|
-
- https://github.com/ohbarye
|
|
386
|
-
- https://github.com/randsina
|
|
387
|
-
- https://github.com/vietqhoang
|
|
388
|
-
- https://github.com/kemenaran
|
|
389
|
-
- https://github.com/jrmhaig
|
|
390
|
-
- https://github.com/tagliala
|
|
391
|
-
- https://github.com/evedovelli
|
|
392
|
-
- https://github.com/JuanVqz
|
|
393
|
-
- https://github.com/luiseugenio
|
|
394
|
-
- https://github.com/equivalent
|
|
395
|
-
- https://github.com/NARKOZ
|
|
396
|
-
- https://github.com/stephensolis
|
|
397
|
-
- https://github.com/kwent
|
|
398
|
-
- https://github.com/Animesh-Ghosh
|
|
399
|
-
- https://github.com/gr8bit
|
|
400
|
-
- https://github.com/codegeek319
|
|
401
|
-
- https://github.com/clwy-cn
|
|
402
|
-
- https://github.com/kukicola
|
|
403
|
-
- https://github.com/sobrinho
|
|
404
|
-
- https://github.com/iainbeeston
|
|
405
|
-
- https://github.com/marckohlbrugge
|
|
406
|
-
|
|
407
|
-
## License
|
|
939
|
+
We have a long list of valued contributors. Check them all at:
|
|
940
|
+
|
|
941
|
+
https://github.com/igorkasyanchuk/active_storage_validations/graphs/contributors
|
|
942
|
+
|
|
943
|
+
### License
|
|
408
944
|
|
|
409
945
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
410
946
|
|
|
947
|
+
<br>
|
|
948
|
+
|
|
411
949
|
[<img src="https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/more_gems.png?raw=true"
|
|
412
950
|
/>](https://www.railsjazz.com/?utm_source=github&utm_medium=bottom&utm_campaign=active_storage_validations)
|
|
951
|
+
|
|
952
|
+
[](https://buymeacoffee.com/igorkasyanchuk)
|