active_storage_validations 4.0.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +71 -8
- data/config/locales/da.yml +2 -0
- data/config/locales/de.yml +2 -0
- data/config/locales/en-GB.yml +2 -0
- data/config/locales/en.yml +2 -0
- data/config/locales/es.yml +2 -0
- data/config/locales/fr.yml +2 -0
- data/config/locales/it.yml +2 -0
- data/config/locales/ja.yml +2 -0
- data/config/locales/nl.yml +2 -0
- data/config/locales/pl.yml +2 -0
- data/config/locales/pt-BR.yml +2 -0
- data/config/locales/ru.yml +2 -0
- data/config/locales/sv.yml +4 -2
- data/config/locales/tr.yml +2 -0
- data/config/locales/uk.yml +2 -0
- data/config/locales/vi.yml +2 -0
- data/config/locales/zh-CN.yml +2 -0
- data/lib/active_storage_validations/analyzer/audio_analyzer.rb +7 -1
- data/lib/active_storage_validations/analyzer/content_type_analyzer/file.rb +0 -2
- data/lib/active_storage_validations/analyzer/content_type_analyzer/magika.rb +0 -2
- data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +3 -0
- data/lib/active_storage_validations/analyzer/image_analyzer.rb +16 -6
- data/lib/active_storage_validations/analyzer/pdf_analyzer.rb +11 -2
- data/lib/active_storage_validations/analyzer.rb +2 -35
- data/lib/active_storage_validations/aspect_ratio_validator.rb +21 -15
- data/lib/active_storage_validations/asv_attachable_adapter.rb +221 -0
- data/lib/active_storage_validations/content_type_validator.rb +40 -64
- data/lib/active_storage_validations/duration_validator.rb +3 -1
- data/lib/active_storage_validations/engine.rb +3 -0
- data/lib/active_storage_validations/extensors/asv_marcelable.rb +5 -5
- data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +11 -4
- data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +2 -1
- data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +1 -0
- data/lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb +1 -1
- data/lib/active_storage_validations/matchers/with_audio_validator_matcher.rb +114 -0
- data/lib/active_storage_validations/matchers.rb +22 -10
- data/lib/active_storage_validations/shared/asv_analyzable.rb +51 -8
- data/lib/active_storage_validations/shared/asv_attachable.rb +14 -97
- data/lib/active_storage_validations/shared/asv_errorable.rb +9 -8
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations/with_audio_validator.rb +43 -0
- data/lib/active_storage_validations.rb +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5a6bd490e0f58921da42838a5df629556c0e9f84a01c114b8772ac195afea917
|
|
4
|
+
data.tar.gz: 397f02aeea00242f94af79a422d37dbcd90c1e07946107bf08f2dccf1da0c793
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ffc3c1b5b9a722dbc2b33d57560fdc41a2efb5ec68095883e15d251024e073f5d34342a2c0f10515794bd1ea6aeed1de593ca0e345af9cfb130d2d424543bf9a
|
|
7
|
+
data.tar.gz: aaf819d38fd5e91de9eeb68248b9d8e4f0faf733a9b5773a682819bb19933b6e740336315842914a1962b9ef2c39f2b9637fd3c481e584dd9c4f566237f0ace5
|
data/README.md
CHANGED
|
@@ -31,6 +31,7 @@ This gem is doing it right for you! Just use `validates :avatar, attached: true,
|
|
|
31
31
|
- [Total size](#total-size)
|
|
32
32
|
- [Dimension](#dimension)
|
|
33
33
|
- [Duration](#duration)
|
|
34
|
+
- [With audio](#with-audio)
|
|
34
35
|
- [Aspect ratio](#aspect-ratio)
|
|
35
36
|
- [Processable file](#processable-file)
|
|
36
37
|
- [Pages](#pages)
|
|
@@ -76,7 +77,7 @@ We recommend **libvips** (`ruby-vips` + `config.active_storage.variant_processor
|
|
|
76
77
|
|
|
77
78
|
### Using video and audio metadata validators
|
|
78
79
|
|
|
79
|
-
To use the video and audio metadata validators (`dimension`, `aspect_ratio`, `processable_file` and `
|
|
80
|
+
To use the video and audio metadata validators (`dimension`, `aspect_ratio`, `processable_file`, `duration` and `with_audio`), 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).
|
|
80
81
|
|
|
81
82
|
### Using pdf metadata validators
|
|
82
83
|
|
|
@@ -113,9 +114,22 @@ end
|
|
|
113
114
|
# event = ActiveSupport::Notifications::Event.new(*args)
|
|
114
115
|
# Rails.logger.warn("[ASV] command timeout: #{event.payload}")
|
|
115
116
|
# end
|
|
117
|
+
|
|
118
|
+
# Optional: monitor how long metadata / content type analysis takes
|
|
119
|
+
# ActiveSupport::Notifications.subscribe("analyze.active_storage_validations") do |*args|
|
|
120
|
+
# event = ActiveSupport::Notifications::Event.new(*args)
|
|
121
|
+
# Rails.logger.info("[ASV] analyzed: #{event.payload}")
|
|
122
|
+
# end
|
|
116
123
|
```
|
|
117
124
|
|
|
118
|
-
|
|
125
|
+
The gem emits two `ActiveSupport::Notifications` events:
|
|
126
|
+
|
|
127
|
+
| Event | Payload | Emitted |
|
|
128
|
+
|---|---|---|
|
|
129
|
+
| `analyze.active_storage_validations` | `analyzer`, `command`, `timeout`, `timed_out`, `duration` | Every time an analyzer command runs (i.e. on a cache miss) |
|
|
130
|
+
| `timeout.active_storage_validations` | `analyzer`, `command`, `timeout` | Only when a command exceeds its deadline |
|
|
131
|
+
|
|
132
|
+
`command_timeout` bounds metadata analysis used by `dimension`, `aspect_ratio`, `duration`, `with_audio`, `pages`, `processable_file`, and `content_type` (with `spoofing_protection`). When a command times out, analysis fails closed and the validator adds its usual error (`file_not_processable` / `media_metadata_missing` / `audio_missing` / content-type errors) — there is no separate timeout error message.
|
|
119
133
|
|
|
120
134
|
The 10s default is enough for typical uploads. Raise it (or set `nil`) if you analyze very large videos/PDFs, especially on slow or network storage — otherwise those files can start failing validation after upgrade. See [upgrade to 4.x](docs/upgrade_to_4.md#analyzer-command-timeout).
|
|
121
135
|
|
|
@@ -140,6 +154,7 @@ Notes:
|
|
|
140
154
|
- [Total size](#total-size): validates total file size for several files
|
|
141
155
|
- [Dimension](#dimension): validates image / video dimensions
|
|
142
156
|
- [Duration](#duration): validates video / audio duration
|
|
157
|
+
- [With audio](#with-audio): validates whether a video / audio file contains an audio track
|
|
143
158
|
- [Aspect ratio](#aspect-ratio): validates image / video aspect ratio
|
|
144
159
|
- [Processable file](#processable-file): validates if a file can be processed
|
|
145
160
|
- [Pages](#pages): validates pdf number of pages
|
|
@@ -161,6 +176,8 @@ Some validators rely on an expensive operation (metadata analysis and content ty
|
|
|
161
176
|
|
|
162
177
|
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.
|
|
163
178
|
|
|
179
|
+
This also covers metadata the analyzer cannot extract from a given file (e.g. a `duration` validation on an image): the gem remembers that the value is unavailable, so the validation keeps failing with `media_metadata_missing` but the file is only analyzed once. Files that yield no metadata at all — missing command-line tool, [timed out](#configuration) command, unreadable file — are not remembered and get analyzed again on the next validation.
|
|
180
|
+
|
|
164
181
|
---
|
|
165
182
|
|
|
166
183
|
### Attached
|
|
@@ -317,7 +334,8 @@ ActiveStorageValidations.configure { |config| config.infer_file_field_accept = f
|
|
|
317
334
|
|
|
318
335
|
Notes:
|
|
319
336
|
- Only broad MIME-type regexes of the form `/\Aimage\/.*\z/` (or `video` / `audio` / etc.) are inferred, as `image/*`
|
|
320
|
-
- Other regexes (e.g. `/\Aimage\/(png|gif)\z/`)
|
|
337
|
+
- Other regexes (e.g. `/\Aimage\/(png|gif)\z/`) are skipped, since they cannot be reliably represented in `accept`
|
|
338
|
+
- Proc / dynamic `content_type` options are skipped: they are not called while the form renders, because their result can depend on record state that is only set on submit, and a raising proc would break the whole view for what is merely a frontend hint. Backend validation is unchanged; pass a custom `accept:` on those fields if you want the picker filtered
|
|
321
339
|
- Conditional validators (`if:` / `unless:`) are not evaluated: their content types are always included in `accept`, even when the condition would skip the validator for that record. Backend validation is unchanged; use `infer_accept: false` (or a custom `accept:`) if the picker must match the active conditions
|
|
322
340
|
|
|
323
341
|
#### Content type shorthands
|
|
@@ -362,7 +380,7 @@ Spoofing protection compares the declared Active Storage content type to a type
|
|
|
362
380
|
|
|
363
381
|
Neither backend fully parses the file. They do **not** load the whole file into RAM. For already-persisted blobs (e.g. remote storage), the analyzer still downloads the blob to a local tempfile before sniffing. That download is streamed in chunks to disk, but it can still be costly for very large files. Local path uploads are analyzed in place.
|
|
364
382
|
|
|
365
|
-
Detected types are cached on the blob as `asv_content_type` + `asv_content_type_backend`. Switching backend re-analyzes. Legacy blobs that only have `asv_content_type` (no backend key) are treated as `:file` and keep using the cache — they are not re-analyzed.
|
|
383
|
+
Detected types are cached on the blob as `asv_content_type` + `asv_content_type_backend`. Switching backend re-analyzes. Legacy blobs that only have `asv_content_type` (no backend key) are treated as `:file` and keep using the cache — they are not re-analyzed. New (unsaved) blobs keep `asv_*` in memory until the record is saved; already-persisted blobs write the cache immediately so it survives `reload`.
|
|
366
384
|
|
|
367
385
|
Sniffers will not always return the exact same MIME as Active Storage (AS uses first ~4kb + filename + extension). Close parent types are accepted via `Marcel::TYPE_PARENTS` (e.g. `video/x-ms-wmv` vs `video/x-ms-asf`).
|
|
368
386
|
|
|
@@ -644,6 +662,49 @@ The `duration` validator error messages expose 4 values that you can use:
|
|
|
644
662
|
|
|
645
663
|
---
|
|
646
664
|
|
|
665
|
+
### With audio
|
|
666
|
+
|
|
667
|
+
Validates whether attached video / audio files contain an audio track.
|
|
668
|
+
(be sure to have the right dependencies installed as mentioned in [Using video and audio metadata validators](#using-video-and-audio-metadata-validators))
|
|
669
|
+
|
|
670
|
+
#### Options
|
|
671
|
+
|
|
672
|
+
The `with_audio` validator supports:
|
|
673
|
+
- `with`: `true` requires an audio track and `false` rejects one
|
|
674
|
+
- `timeout`: overrides the global analyzer [command timeout](#configuration) for this validation
|
|
675
|
+
|
|
676
|
+
#### Examples
|
|
677
|
+
|
|
678
|
+
Use it like this:
|
|
679
|
+
```ruby
|
|
680
|
+
class User < ApplicationRecord
|
|
681
|
+
has_one_attached :video
|
|
682
|
+
has_one_attached :silent_video
|
|
683
|
+
|
|
684
|
+
validates :video, with_audio: true
|
|
685
|
+
validates :silent_video, with_audio: { with: false }
|
|
686
|
+
validates :video, with_audio: { timeout: 5.seconds }
|
|
687
|
+
end
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
Rails treats a bare `with_audio: false` as a disabled validator. Use the hash form `with_audio: { with: false }` to reject files that contain audio.
|
|
691
|
+
|
|
692
|
+
Files that cannot contain an audio stream at all (images, pdfs) are treated as having no audio track: they fail `with_audio: true` and pass `with_audio: { with: false }`.
|
|
693
|
+
|
|
694
|
+
#### Error messages (I18n)
|
|
695
|
+
|
|
696
|
+
```yml
|
|
697
|
+
en:
|
|
698
|
+
errors:
|
|
699
|
+
messages:
|
|
700
|
+
audio_missing: "must have an audio track"
|
|
701
|
+
audio_present: "must not have an audio track"
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
The `with_audio` validator error message exposes the `filename` value containing the current file name.
|
|
705
|
+
|
|
706
|
+
---
|
|
707
|
+
|
|
647
708
|
### Aspect ratio
|
|
648
709
|
|
|
649
710
|
Validates the aspect ratio of the attached image / video files.
|
|
@@ -895,6 +956,10 @@ describe User do
|
|
|
895
956
|
it { is_expected.to validate_duration_of(:introduction).between(100..500.seconds) }
|
|
896
957
|
it { is_expected.to validate_duration_of(:avatar).equal_to(5.minutes) }
|
|
897
958
|
|
|
959
|
+
# with_audio
|
|
960
|
+
it { is_expected.to validate_with_audio_of(:video) }
|
|
961
|
+
it { is_expected.to validate_with_audio_of(:silent_video).without_audio }
|
|
962
|
+
|
|
898
963
|
# pages:
|
|
899
964
|
# #less_than, #less_than_or_equal_to, #greater_than, #greater_than_or_equal_to, #between, #equal_to
|
|
900
965
|
it { is_expected.to validate_pages_of(:contract).less_than(50) }
|
|
@@ -911,9 +976,6 @@ All matchers can currently be customized with these options:
|
|
|
911
976
|
|
|
912
977
|
```ruby
|
|
913
978
|
describe User do
|
|
914
|
-
# :allow_blank
|
|
915
|
-
it { is_expected.to validate_attached_of(:avatar).allow_blank }
|
|
916
|
-
|
|
917
979
|
# :on
|
|
918
980
|
it { is_expected.to validate_attached_of(:avatar).on(:update) }
|
|
919
981
|
it { is_expected.to validate_attached_of(:avatar).on(%i[update custom]) }
|
|
@@ -927,6 +989,7 @@ describe User do
|
|
|
927
989
|
|
|
928
990
|
# :timeout (analyzer command timeout — metadata validators + content_type with spoofing)
|
|
929
991
|
it { is_expected.to validate_duration_of(:video).less_than(5.minutes).timeout(30.seconds) }
|
|
992
|
+
it { is_expected.to validate_with_audio_of(:video).timeout(5.seconds) }
|
|
930
993
|
it { is_expected.to validate_processable_file_of(:avatar).timeout(5.seconds) }
|
|
931
994
|
end
|
|
932
995
|
```
|
|
@@ -934,7 +997,7 @@ end
|
|
|
934
997
|
### Minitest
|
|
935
998
|
|
|
936
999
|
#### Setup
|
|
937
|
-
To use the matchers, make sure you have the [shoulda-context](https://github.com/thoughtbot/shoulda-context) gem up and running.
|
|
1000
|
+
To use the matchers, make sure you have the [shoulda-context](https://github.com/thoughtbot/shoulda-context) gem up and running. No extra mock library is required (`minitest-mock` is not needed on Minitest 6).
|
|
938
1001
|
|
|
939
1002
|
You need to require the matchers:
|
|
940
1003
|
|
data/config/locales/da.yml
CHANGED
|
@@ -55,6 +55,8 @@ da:
|
|
|
55
55
|
aspect_ratio_not_x_y: "skal være %{authorized_aspect_ratios} (nuværende fil er %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "har et ugyldigt aspektforhold (gyldige aspektforhold er %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "identificeres ikke som en gyldig mediefil"
|
|
58
|
+
audio_missing: "skal have et lydspor"
|
|
59
|
+
audio_present: "må ikke have et lydspor"
|
|
58
60
|
pages_not_less_than: "sideantal skal være mindre end %{max} (aktuelt sideantal er %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "sideantal skal være mindre end eller lig med %{max} (aktuelt sideantal er %{pages})"
|
|
60
62
|
pages_not_greater_than: "sideantal skal være større end %{min} (aktuelt sideantal er %{pages})"
|
data/config/locales/de.yml
CHANGED
|
@@ -55,6 +55,8 @@ de:
|
|
|
55
55
|
aspect_ratio_not_x_y: "muss %{authorized_aspect_ratios} sein (aktuelle Datei ist %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "hat ein ungültiges Seitenverhältnis (gültige Seitenverhältnisse sind %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "wird nicht als gültige Mediendatei identifiziert"
|
|
58
|
+
audio_missing: "muss eine Audiospur enthalten"
|
|
59
|
+
audio_present: "darf keine Audiospur enthalten"
|
|
58
60
|
pages_not_less_than: "Die Seitenanzahl muss kleiner als %{max} sein (die aktuelle Seitenanzahl beträgt %{pages})."
|
|
59
61
|
pages_not_less_than_or_equal_to: "Die Seitenanzahl muss kleiner oder gleich %{max} sein (die aktuelle Seitenanzahl beträgt %{pages})."
|
|
60
62
|
pages_not_greater_than: "Die Seitenanzahl muss größer als %{min} sein (die aktuelle Seitenanzahl beträgt %{pages})."
|
data/config/locales/en-GB.yml
CHANGED
|
@@ -55,6 +55,8 @@ en-GB:
|
|
|
55
55
|
aspect_ratio_not_x_y: "must be %{authorized_aspect_ratios} (current file is %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "has an invalid aspect ratio (valid aspect ratios are %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "is not identified as a valid media file"
|
|
58
|
+
audio_missing: "must have an audio track"
|
|
59
|
+
audio_present: "must not have an audio track"
|
|
58
60
|
pages_not_less_than: "page count must be less than %{max} (current page count is %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "page count must be less than or equal to %{max} (current page count is %{pages})"
|
|
60
62
|
pages_not_greater_than: "page count must be greater than %{min} (current page count is %{pages})"
|
data/config/locales/en.yml
CHANGED
|
@@ -55,6 +55,8 @@ en:
|
|
|
55
55
|
aspect_ratio_not_x_y: "must be %{authorized_aspect_ratios} (current file is %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "has an invalid aspect ratio (valid aspect ratios are %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "is not identified as a valid media file"
|
|
58
|
+
audio_missing: "must have an audio track"
|
|
59
|
+
audio_present: "must not have an audio track"
|
|
58
60
|
pages_not_less_than: "page count must be less than %{max} (current page count is %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "page count must be less than or equal to %{max} (current page count is %{pages})"
|
|
60
62
|
pages_not_greater_than: "page count must be greater than %{min} (current page count is %{pages})"
|
data/config/locales/es.yml
CHANGED
|
@@ -55,6 +55,8 @@ es:
|
|
|
55
55
|
aspect_ratio_not_x_y: "debe ser %{authorized_aspect_ratios} (el archivo actual es %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "tiene una relación de aspecto inválida (las relaciones de aspecto válidas son %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "no se identifica como un archivo multimedia válido"
|
|
58
|
+
audio_missing: "debe tener una pista de audio"
|
|
59
|
+
audio_present: "no debe tener una pista de audio"
|
|
58
60
|
pages_not_less_than: "el número de páginas debe ser menor que %{max} (el número actual de páginas es %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "el número de páginas debe ser menor o igual que %{max} (el número actual de páginas es %{pages})"
|
|
60
62
|
pages_not_greater_than: "el número de páginas debe ser mayor que %{min} (el número actual de páginas es %{pages})"
|
data/config/locales/fr.yml
CHANGED
|
@@ -55,6 +55,8 @@ fr:
|
|
|
55
55
|
aspect_ratio_not_x_y: "doit être %{authorized_aspect_ratios} (le fichier actuel est %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "a un rapport d'aspect invalide (les rapports d'aspect valides sont %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "n'est pas identifié comme un fichier média valide"
|
|
58
|
+
audio_missing: "doit contenir une piste audio"
|
|
59
|
+
audio_present: "ne doit pas contenir de piste audio"
|
|
58
60
|
pages_not_less_than: "le nombre de pages doit être inférieur à %{max} (le nombre actuel est de %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "le nombre de pages doit être inférieur ou égal à %{max} (le nombre actuel est de %{pages})"
|
|
60
62
|
pages_not_greater_than: "le nombre de pages doit être supérieur à %{min} (le nombre actuel est de %{pages})"
|
data/config/locales/it.yml
CHANGED
|
@@ -55,6 +55,8 @@ it:
|
|
|
55
55
|
aspect_ratio_not_x_y: "deve essere %{authorized_aspect_ratios} (il file corrente è %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "ha un formato non valido (i rapporti di aspetto validi sono %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "non è identificato come file multimediale valido"
|
|
58
|
+
audio_missing: "deve contenere una traccia audio"
|
|
59
|
+
audio_present: "non deve contenere una traccia audio"
|
|
58
60
|
pages_not_less_than: "il numero di pagine deve essere inferiore a %{max} (il numero di pagine corrente è %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "il numero di pagine deve essere inferiore o uguale a %{max} (il numero di pagine corrente è %{pages})"
|
|
60
62
|
pages_not_greater_than: "il numero di pagine deve essere maggiore di %{min} (il numero di pagine corrente è %{pages})"
|
data/config/locales/ja.yml
CHANGED
|
@@ -55,6 +55,8 @@ ja:
|
|
|
55
55
|
aspect_ratio_not_x_y: "のアスペクト比は%{authorized_aspect_ratios}にしてください (添付されたメディアの解像度は%{width}x%{height})"
|
|
56
56
|
aspect_ratio_invalid: "のアスペクト比を検出できません (許可されたアスペクト比は%{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "のメディアを処理できません"
|
|
58
|
+
audio_missing: "には音声トラックが必要です"
|
|
59
|
+
audio_present: "に音声トラックを含めることはできません"
|
|
58
60
|
pages_not_less_than: "のページ数は%{max}未満にしてください (添付されたファイルは%{pages}ページ)"
|
|
59
61
|
pages_not_less_than_or_equal_to: "のページ数は%{max}以下にしてください (添付されたファイルは%{pages}ページ)"
|
|
60
62
|
pages_not_greater_than: "のページ数は%{min}より大きくしてください (添付されたファイルは%{pages}ページ)"
|
data/config/locales/nl.yml
CHANGED
|
@@ -55,6 +55,8 @@ nl:
|
|
|
55
55
|
aspect_ratio_not_x_y: "moet %{authorized_aspect_ratios} zijn (huidig bestand is %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "heeft een ongeldige beeldverhouding (geldige beeldverhoudingen zijn %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "wordt niet geïdentificeerd als een geldig mediabestand"
|
|
58
|
+
audio_missing: "moet een audiotrack bevatten"
|
|
59
|
+
audio_present: "mag geen audiotrack bevatten"
|
|
58
60
|
pages_not_less_than: "het aantal pagina's moet kleiner zijn dan %{max} (het huidige aantal pagina's is %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "het aantal pagina's moet kleiner zijn dan of gelijk zijn aan %{max} (het huidige aantal pagina's is %{pages})"
|
|
60
62
|
pages_not_greater_than: "het aantal pagina's moet groter zijn dan %{min} (het huidige aantal pagina's is %{pages})"
|
data/config/locales/pl.yml
CHANGED
|
@@ -55,6 +55,8 @@ pl:
|
|
|
55
55
|
aspect_ratio_not_x_y: "musi być %{authorized_aspect_ratios} (bieżący plik to %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "ma nieprawidłowy współczynnik kształtu (ważne współczynniki kształtu wynoszą %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "nie jest identyfikowany jako prawidłowy plik multimedialny"
|
|
58
|
+
audio_missing: "musi zawierać ścieżkę dźwiękową"
|
|
59
|
+
audio_present: "nie może zawierać ścieżki dźwiękowej"
|
|
58
60
|
pages_not_less_than: "liczba stron musi być mniejsza niż %{max} (bieżąca liczba stron to %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "liczba stron musi być mniejsza lub równa %{max} (bieżąca liczba stron to %{pages})"
|
|
60
62
|
pages_not_greater_than: "liczba stron musi być większa niż %{min} (bieżąca liczba stron to %{pages})"
|
data/config/locales/pt-BR.yml
CHANGED
|
@@ -55,6 +55,8 @@ pt-BR:
|
|
|
55
55
|
aspect_ratio_not_x_y: "deve ser %{authorized_aspect_ratios} (o arquivo atual é %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "tem uma proporção inválida (as proporções válidas são %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "não é identificado como um arquivo de mídia válido"
|
|
58
|
+
audio_missing: "deve ter uma faixa de áudio"
|
|
59
|
+
audio_present: "não deve ter uma faixa de áudio"
|
|
58
60
|
pages_not_less_than: "a contagem de páginas deve ser menor que %{max} (a contagem atual de páginas é %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "a contagem de páginas deve ser menor ou igual a %{max} (a contagem atual de páginas é %{pages})"
|
|
60
62
|
pages_not_greater_than: "a contagem de páginas deve ser maior que %{min} (a contagem atual de páginas é %{pages})"
|
data/config/locales/ru.yml
CHANGED
|
@@ -55,6 +55,8 @@ ru:
|
|
|
55
55
|
aspect_ratio_not_x_y: "должно быть %{authorized_aspect_ratios} (текущий файл %{width}x%{height} пикселей)"
|
|
56
56
|
aspect_ratio_invalid: "имеет недействительное соотношение сторон (действительные соотношения сторон %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "не идентифицируется как действительный медиафайл"
|
|
58
|
+
audio_missing: "должен содержать аудиодорожку"
|
|
59
|
+
audio_present: "не должен содержать аудиодорожку"
|
|
58
60
|
pages_not_less_than: "количество страниц должно быть меньше %{max} (текущее количество страниц %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "количество страниц должно быть меньше или равно %{max} (текущее количество страниц %{pages})"
|
|
60
62
|
pages_not_greater_than: "количество страниц должно быть больше %{min} (текущее количество страниц %{pages})"
|
data/config/locales/sv.yml
CHANGED
|
@@ -52,9 +52,11 @@ sv:
|
|
|
52
52
|
aspect_ratio_not_square: "måste vara kvadratisk (nuvarande fil är %{width}x%{height} pixlar)"
|
|
53
53
|
aspect_ratio_not_portrait: "måste vara porträtt (nuvarande fil är %{width}x%{height} pixlar)"
|
|
54
54
|
aspect_ratio_not_landscape: "måste vara landskap (nuvarande fil är %{width}x%{height} pixlar)"
|
|
55
|
-
aspect_ratio_not_x_y: "måste vara%
|
|
56
|
-
aspect_ratio_invalid: "har ett ogiltigt bildförhållande (giltiga bildförhållanden är %{
|
|
55
|
+
aspect_ratio_not_x_y: "måste vara %{authorized_aspect_ratios} (nuvarande fil är %{width}x%{height} pixlar)"
|
|
56
|
+
aspect_ratio_invalid: "har ett ogiltigt bildförhållande (giltiga bildförhållanden är %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "identifieras inte som en giltig mediefil"
|
|
58
|
+
audio_missing: "måste innehålla ett ljudspår"
|
|
59
|
+
audio_present: "får inte innehålla ett ljudspår"
|
|
58
60
|
pages_not_less_than: "sidantal måste vara mindre än %{max} (nuvarande sidantal är %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "sidantal måste vara mindre än eller lika med %{max} (nuvarande antal sidor är %{pages})"
|
|
60
62
|
pages_not_greater_than: "sidantal måste vara större än %{min} (nuvarande sidantal är %{pages})"
|
data/config/locales/tr.yml
CHANGED
|
@@ -55,6 +55,8 @@ tr:
|
|
|
55
55
|
aspect_ratio_not_x_y: "%{authorized_aspect_ratios} olmalıdır (geçerli dosya %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "geçersiz bir en boy oranına sahiptir (geçerli en boy oranları %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "geçerli bir medya dosyası olarak tanımlanmadı"
|
|
58
|
+
audio_missing: "bir ses parçası içermelidir"
|
|
59
|
+
audio_present: "bir ses parçası içermemelidir"
|
|
58
60
|
pages_not_less_than: "sayfa sayısı %{max} değerinden az olmalıdır (geçerli sayfa sayısı %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "sayfa sayısı %{max} değerinden az veya eşit olmalıdır (geçerli sayfa sayısı %{pages})"
|
|
60
62
|
pages_not_greater_than: "sayfa sayısı %{min} değerinden fazla olmalıdır (geçerli sayfa sayısı %{pages})"
|
data/config/locales/uk.yml
CHANGED
|
@@ -55,6 +55,8 @@ uk:
|
|
|
55
55
|
aspect_ratio_not_x_y: "повинен бути %{authorized_aspect_ratios} (поточний файл становить %{width}x%{height} пікселям)"
|
|
56
56
|
aspect_ratio_invalid: "має недійсне співвідношення сторін (дійсні співвідношення сторін %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "не ідентифікується як дійсний медіа-файл"
|
|
58
|
+
audio_missing: "має містити аудіодоріжку"
|
|
59
|
+
audio_present: "не має містити аудіодоріжку"
|
|
58
60
|
pages_not_less_than: "кількість сторінок має бути менше %{max} (поточна кількість сторінок %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "кількість сторінок має бути меншою або дорівнювати %{max} (поточна кількість сторінок становить %{pages})"
|
|
60
62
|
pages_not_greater_than: "кількість сторінок має бути більшою за %{min} (поточна кількість сторінок становить %{pages})"
|
data/config/locales/vi.yml
CHANGED
|
@@ -55,6 +55,8 @@ vi:
|
|
|
55
55
|
aspect_ratio_not_x_y: "phải là %{authorized_aspect_ratios} (tệp hiện tại là %{width}x%{height}px)"
|
|
56
56
|
aspect_ratio_invalid: "có tỷ lệ khung hình không hợp lệ (tỷ lệ khung hình hợp lệ là %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "không được xác định là tệp phương tiện hợp lệ"
|
|
58
|
+
audio_missing: "phải có một bản âm thanh"
|
|
59
|
+
audio_present: "không được chứa bản âm thanh"
|
|
58
60
|
pages_not_less_than: "số trang phải nhỏ hơn %{max} (số trang hiện tại là %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "số trang phải nhỏ hơn hoặc bằng %{max} (số trang hiện tại là %{pages})"
|
|
60
62
|
pages_not_greater_than: "số trang phải lớn hơn %{min} (số trang hiện tại là %{pages})"
|
data/config/locales/zh-CN.yml
CHANGED
|
@@ -55,6 +55,8 @@ zh-CN:
|
|
|
55
55
|
aspect_ratio_not_x_y: "必须为 %{authorized_aspect_ratios}(当前文件为 %{width} x%{height} 像素)"
|
|
56
56
|
aspect_ratio_invalid: "具有无效的长宽比(有效长宽比为 %{authorized_aspect_ratios})"
|
|
57
57
|
file_not_processable: "未标识为有效的媒体文件"
|
|
58
|
+
audio_missing: "必须包含音轨"
|
|
59
|
+
audio_present: "不得包含音轨"
|
|
58
60
|
pages_not_less_than: "页数必须小于 %{max}(当前页数为 %{pages})"
|
|
59
61
|
pages_not_less_than_or_equal_to: "页数必须小于或等于 %{max}(当前页数为 %{pages})"
|
|
60
62
|
pages_not_greater_than: "页数必须大于 %{min}(当前页数为 %{pages})"
|
|
@@ -10,12 +10,13 @@ module ActiveStorageValidations
|
|
|
10
10
|
# * Duration (seconds)
|
|
11
11
|
# * Bit rate (bits/s)
|
|
12
12
|
# * Sample rate (hertz)
|
|
13
|
+
# * Audio (true if file has an audio channel, false if not)
|
|
13
14
|
# * Tags (internal metadata)
|
|
14
15
|
#
|
|
15
16
|
# Example:
|
|
16
17
|
#
|
|
17
18
|
# ActiveStorageValidations::Analyzer::AudioAnalyzer.new(attachable).metadata
|
|
18
|
-
# # => { duration: 5.0, bit_rate: 320340, sample_rate: 44100, tags: { encoder: "Lavc57.64", ... } }
|
|
19
|
+
# # => { duration: 5.0, bit_rate: 320340, sample_rate: 44100, audio: true, tags: { encoder: "Lavc57.64", ... } }
|
|
19
20
|
#
|
|
20
21
|
# This analyzer requires the {FFmpeg}[https://www.ffmpeg.org] system library, which is not provided by \Rails.
|
|
21
22
|
class Analyzer::AudioAnalyzer < Analyzer
|
|
@@ -27,6 +28,7 @@ module ActiveStorageValidations
|
|
|
27
28
|
duration: duration,
|
|
28
29
|
bit_rate: bit_rate,
|
|
29
30
|
sample_rate: sample_rate,
|
|
31
|
+
audio: audio?,
|
|
30
32
|
tags: tags
|
|
31
33
|
}.compact
|
|
32
34
|
end
|
|
@@ -34,6 +36,10 @@ module ActiveStorageValidations
|
|
|
34
36
|
|
|
35
37
|
private
|
|
36
38
|
|
|
39
|
+
def audio?
|
|
40
|
+
audio_stream.present?
|
|
41
|
+
end
|
|
42
|
+
|
|
37
43
|
def duration
|
|
38
44
|
duration = audio_stream["duration"]
|
|
39
45
|
Float(duration).round(1) if duration
|
|
@@ -5,8 +5,6 @@ module ActiveStorageValidations
|
|
|
5
5
|
#
|
|
6
6
|
# Override the binary path with +ActiveStorage.paths[:file]+.
|
|
7
7
|
class Analyzer::ContentTypeAnalyzer::File < Analyzer::ContentTypeAnalyzer
|
|
8
|
-
class CommandLineToolNotInstalledError < StandardError; end
|
|
9
|
-
|
|
10
8
|
private
|
|
11
9
|
|
|
12
10
|
def backend
|
|
@@ -7,8 +7,6 @@ module ActiveStorageValidations
|
|
|
7
7
|
#
|
|
8
8
|
# Override the binary path with +ActiveStorage.paths[:magika]+.
|
|
9
9
|
class Analyzer::ContentTypeAnalyzer::Magika < Analyzer::ContentTypeAnalyzer
|
|
10
|
-
class CommandLineToolNotInstalledError < StandardError; end
|
|
11
|
-
|
|
12
10
|
private
|
|
13
11
|
|
|
14
12
|
def backend
|
|
@@ -12,6 +12,9 @@ module ActiveStorageValidations
|
|
|
12
12
|
# ActiveStorageValidations::Analyzer::ContentTypeAnalyzer::Magika.new(attachable).content_type
|
|
13
13
|
# # => { content_type: "image/png", content_type_backend: "magika" }
|
|
14
14
|
class Analyzer::ContentTypeAnalyzer < Analyzer
|
|
15
|
+
# Shared by File and Magika backends so consumers can rescue either missing CLI.
|
|
16
|
+
class CommandLineToolNotInstalledError < StandardError; end
|
|
17
|
+
|
|
15
18
|
def content_type
|
|
16
19
|
read_media do |media|
|
|
17
20
|
{
|
|
@@ -12,7 +12,15 @@ module ActiveStorageValidations
|
|
|
12
12
|
# ActiveStorageValidations::Analyzer::ImageAnalyzer::ImageMagick.new(attachable).metadata
|
|
13
13
|
# # => { width: 4104, height: 2736 }
|
|
14
14
|
class Analyzer::ImageAnalyzer < Analyzer
|
|
15
|
-
|
|
15
|
+
class << self
|
|
16
|
+
def reset_supported_analyzers_cache!
|
|
17
|
+
@supported_analyzers = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def supported_analyzers
|
|
21
|
+
@supported_analyzers ||= {}
|
|
22
|
+
end
|
|
23
|
+
end
|
|
16
24
|
|
|
17
25
|
def metadata
|
|
18
26
|
return {} unless analyzer_supported?
|
|
@@ -28,12 +36,14 @@ module ActiveStorageValidations
|
|
|
28
36
|
|
|
29
37
|
private
|
|
30
38
|
|
|
39
|
+
# Cache by class, not instance: a new analyzer is built per attachable, so
|
|
40
|
+
# an instance-keyed hash never hits and retains every attachable for the
|
|
41
|
+
# process lifetime.
|
|
31
42
|
def analyzer_supported?
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
end
|
|
43
|
+
cache = Analyzer::ImageAnalyzer.supported_analyzers
|
|
44
|
+
return cache[self.class] if cache.key?(self.class)
|
|
45
|
+
|
|
46
|
+
cache[self.class] = supported?
|
|
37
47
|
end
|
|
38
48
|
|
|
39
49
|
# Override this method in a concrete subclass. Have it return true if the image is rotated.
|
|
@@ -16,6 +16,11 @@ module ActiveStorageValidations
|
|
|
16
16
|
#
|
|
17
17
|
# This analyzer requires the {poppler}[https://pdf2image.readthedocs.io/en/latest/installation.html] system library, which is not provided by \Rails.
|
|
18
18
|
class Analyzer::PdfAnalyzer < Analyzer
|
|
19
|
+
# pdfinfo reports e.g. "595.276 x 841.89 pts (A4)". Capture the full
|
|
20
|
+
# integer or decimal so extra fractional digits are not split into a
|
|
21
|
+
# second number (which would make height 8 for A4).
|
|
22
|
+
PAGE_SIZE_NUMBER = /\d+(?:\.\d+)?/
|
|
23
|
+
|
|
19
24
|
def metadata
|
|
20
25
|
read_media do |media|
|
|
21
26
|
{
|
|
@@ -69,11 +74,15 @@ module ActiveStorageValidations
|
|
|
69
74
|
end
|
|
70
75
|
|
|
71
76
|
def width
|
|
72
|
-
|
|
77
|
+
page_size_in_points[0]
|
|
73
78
|
end
|
|
74
79
|
|
|
75
80
|
def height
|
|
76
|
-
|
|
81
|
+
page_size_in_points[1]
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def page_size_in_points
|
|
85
|
+
@media["page_size"].to_s.scan(PAGE_SIZE_NUMBER).map(&:to_i)
|
|
77
86
|
end
|
|
78
87
|
|
|
79
88
|
def pages
|
|
@@ -40,45 +40,12 @@ module ActiveStorageValidations
|
|
|
40
40
|
raise NotImplementedError
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
# rubocop:disable Metrics/MethodLength
|
|
44
43
|
def media(tempfile)
|
|
45
|
-
@media ||=
|
|
46
|
-
|
|
47
|
-
blob = @attachable.is_a?(String) ? ActiveStorage::Blob.find_signed!(@attachable) : @attachable
|
|
48
|
-
media_from_tempfile_path(tempfile, blob)
|
|
49
|
-
when Hash
|
|
50
|
-
io = @attachable[:io]
|
|
51
|
-
if io.is_a?(StringIO)
|
|
52
|
-
media_from_tempfile_path(tempfile, io)
|
|
53
|
-
else
|
|
54
|
-
File.open(io) do |file|
|
|
55
|
-
media_from_path(file.path)
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
|
|
59
|
-
media_from_path(@attachable.path)
|
|
60
|
-
when File
|
|
61
|
-
supports_file_attachment? ? media_from_path(@attachable.path) : raise_rails_like_error(@attachable)
|
|
62
|
-
when Pathname
|
|
63
|
-
supports_pathname_attachment? ? media_from_path(@attachable.to_s) : raise_rails_like_error(@attachable)
|
|
64
|
-
else
|
|
65
|
-
raise_rails_like_error(@attachable)
|
|
44
|
+
@media ||= wrap_attachable(@attachable).with_media_path(tempfile) do |path|
|
|
45
|
+
media_from_path(path)
|
|
66
46
|
end
|
|
67
47
|
end
|
|
68
48
|
|
|
69
|
-
def media_from_tempfile_path(tempfile, file_representation)
|
|
70
|
-
if file_representation.is_a?(ActiveStorage::Blob)
|
|
71
|
-
file_representation.download { |chunk| tempfile.write(chunk) }
|
|
72
|
-
else
|
|
73
|
-
IO.copy_stream(file_representation, tempfile)
|
|
74
|
-
file_representation.rewind
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
tempfile.flush
|
|
78
|
-
tempfile.rewind
|
|
79
|
-
media_from_path(tempfile.path)
|
|
80
|
-
end
|
|
81
|
-
|
|
82
49
|
# Override this method in a concrete subclass. Have it return a media object.
|
|
83
50
|
def media_from_path(path)
|
|
84
51
|
raise NotImplementedError
|
|
@@ -18,7 +18,7 @@ module ActiveStorageValidations
|
|
|
18
18
|
|
|
19
19
|
AVAILABLE_CHECKS = %i[with in].freeze
|
|
20
20
|
NAMED_ASPECT_RATIOS = %i[square portrait landscape].freeze
|
|
21
|
-
ASPECT_RATIO_REGEX = /is_([1-9]\d*)_([1-9]\d*)
|
|
21
|
+
ASPECT_RATIO_REGEX = /is_([1-9]\d*)_([1-9]\d*)/
|
|
22
22
|
ERROR_TYPES = %i[
|
|
23
23
|
aspect_ratio_not_square
|
|
24
24
|
aspect_ratio_not_portrait
|
|
@@ -27,7 +27,7 @@ module ActiveStorageValidations
|
|
|
27
27
|
aspect_ratio_invalid
|
|
28
28
|
media_metadata_missing
|
|
29
29
|
].freeze
|
|
30
|
-
PRECISION = 3
|
|
30
|
+
PRECISION = 3
|
|
31
31
|
METADATA_KEYS = %i[width height].freeze
|
|
32
32
|
|
|
33
33
|
def check_validity!
|
|
@@ -38,22 +38,28 @@ module ActiveStorageValidations
|
|
|
38
38
|
def validate_each(record, attribute, _value)
|
|
39
39
|
return if no_attachments?(record, attribute)
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
@authorized_aspect_ratios = authorized_aspect_ratios_from_options(flat_options).compact
|
|
43
|
-
return if @authorized_aspect_ratios.empty?
|
|
41
|
+
return if authorized_aspect_ratios(record).empty?
|
|
44
42
|
|
|
45
43
|
validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
|
|
46
44
|
end
|
|
47
45
|
|
|
48
46
|
private
|
|
49
47
|
|
|
48
|
+
# Recomputed for every attachable rather than memoized on the validator:
|
|
49
|
+
# Active Model reuses one validator instance across records and threads, so
|
|
50
|
+
# per-validation state cannot live in an instance variable.
|
|
51
|
+
def authorized_aspect_ratios(record)
|
|
52
|
+
authorized_aspect_ratios_from_options(set_flat_options(record)).compact
|
|
53
|
+
end
|
|
54
|
+
|
|
50
55
|
def is_valid?(record, attribute, attachable, metadata)
|
|
51
56
|
media_metadata_present?(record, attribute, attachable, metadata) &&
|
|
52
57
|
authorized_aspect_ratio?(record, attribute, attachable, metadata)
|
|
53
58
|
end
|
|
54
59
|
|
|
55
60
|
def authorized_aspect_ratio?(record, attribute, attachable, metadata)
|
|
56
|
-
|
|
61
|
+
aspect_ratios = authorized_aspect_ratios(record)
|
|
62
|
+
attachable_aspect_ratio_is_authorized = aspect_ratios.any? do |authorized_aspect_ratio|
|
|
57
63
|
case authorized_aspect_ratio
|
|
58
64
|
when :square then valid_square_aspect_ratio?(metadata)
|
|
59
65
|
when :portrait then valid_portrait_aspect_ratio?(metadata)
|
|
@@ -64,18 +70,18 @@ module ActiveStorageValidations
|
|
|
64
70
|
|
|
65
71
|
return true if attachable_aspect_ratio_is_authorized
|
|
66
72
|
|
|
67
|
-
errors_options = initialize_and_populate_error_options(options, attachable)
|
|
73
|
+
errors_options = initialize_and_populate_error_options(options, attachable, aspect_ratios)
|
|
68
74
|
errors_options[:width] = metadata[:width]
|
|
69
75
|
errors_options[:height] = metadata[:height]
|
|
70
|
-
error_type = aspect_ratio_error_mapping
|
|
76
|
+
error_type = aspect_ratio_error_mapping(aspect_ratios)
|
|
71
77
|
add_error(record, attribute, error_type, **errors_options)
|
|
72
78
|
false
|
|
73
79
|
end
|
|
74
80
|
|
|
75
|
-
def aspect_ratio_error_mapping
|
|
76
|
-
return :aspect_ratio_invalid if
|
|
81
|
+
def aspect_ratio_error_mapping(aspect_ratios)
|
|
82
|
+
return :aspect_ratio_invalid if aspect_ratios.many?
|
|
77
83
|
|
|
78
|
-
aspect_ratio =
|
|
84
|
+
aspect_ratio = aspect_ratios.first
|
|
79
85
|
NAMED_ASPECT_RATIOS.include?(aspect_ratio) ? :"aspect_ratio_not_#{aspect_ratio}" : :aspect_ratio_not_x_y
|
|
80
86
|
end
|
|
81
87
|
|
|
@@ -86,9 +92,9 @@ module ActiveStorageValidations
|
|
|
86
92
|
false
|
|
87
93
|
end
|
|
88
94
|
|
|
89
|
-
def initialize_and_populate_error_options(options, attachable)
|
|
95
|
+
def initialize_and_populate_error_options(options, attachable, aspect_ratios)
|
|
90
96
|
errors_options = initialize_error_options(options, attachable)
|
|
91
|
-
errors_options[:authorized_aspect_ratios] = string_aspect_ratios
|
|
97
|
+
errors_options[:authorized_aspect_ratios] = string_aspect_ratios(aspect_ratios)
|
|
92
98
|
errors_options
|
|
93
99
|
end
|
|
94
100
|
|
|
@@ -140,8 +146,8 @@ module ActiveStorageValidations
|
|
|
140
146
|
(Array.wrap(flat_options[:with]) + Array.wrap(flat_options[:in]))
|
|
141
147
|
end
|
|
142
148
|
|
|
143
|
-
def string_aspect_ratios
|
|
144
|
-
|
|
149
|
+
def string_aspect_ratios(aspect_ratios)
|
|
150
|
+
aspect_ratios.map do |aspect_ratio|
|
|
145
151
|
if NAMED_ASPECT_RATIOS.include?(aspect_ratio)
|
|
146
152
|
aspect_ratio
|
|
147
153
|
else
|