active_storage_validations 4.0.0 → 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21b87628a4e532d945c76ceca7d37444c7524f15ed11b587a4bab0a5eae7eaef
4
- data.tar.gz: 90fd47c4a439b00503e8997de2e344ace1771d15206a1e147fb785b94280abd4
3
+ metadata.gz: 24d8643d1f970252124d4429ca9bc15b222ef203cdcae93427a0860c7190c370
4
+ data.tar.gz: 62a74e517b40b32dba39c0529f0d4e4529d615a11decdebf8ec338505c082435
5
5
  SHA512:
6
- metadata.gz: b851e77dcdd882cb2bcae49dc3871c5d5d5920409cb94bba357a77ecd55ff34efd0266fe52bb0866bf31db52eb324bc0ef80749d2805dd27b97d7c89a2adcb51
7
- data.tar.gz: 422c62e2832c2adf34686832ec76ecc570f093133110838234c854c2ede5485324ebf9865aeb911f590a9c003485fd2d6535cced5e74d6ec686a562157ce983a
6
+ metadata.gz: c8fa701638b13eea05351e5a76847d144e93ec83b4e14a8d5b5e23ae71944eabd092704c723fcb81e9c44a3fb909ab6431be680390ee414f13002af671792ca1
7
+ data.tar.gz: 82af6984104d830cd7eda7ffdb557f3bb997d8bb59fbef6edc85e0830c3a4397209d4ab31f6427f5945fe392935fefe7c49dc6cabd87c840a34592e8b827f300
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 `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).
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
 
@@ -115,7 +116,7 @@ end
115
116
  # end
116
117
  ```
117
118
 
118
- `command_timeout` bounds metadata analysis used by `dimension`, `aspect_ratio`, `duration`, `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` / content-type errors) — there is no separate timeout error message.
119
+ `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
120
 
120
121
  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
122
 
@@ -140,6 +141,7 @@ Notes:
140
141
  - [Total size](#total-size): validates total file size for several files
141
142
  - [Dimension](#dimension): validates image / video dimensions
142
143
  - [Duration](#duration): validates video / audio duration
144
+ - [With audio](#with-audio): validates whether a video contains an audio track
143
145
  - [Aspect ratio](#aspect-ratio): validates image / video aspect ratio
144
146
  - [Processable file](#processable-file): validates if a file can be processed
145
147
  - [Pages](#pages): validates pdf number of pages
@@ -644,6 +646,47 @@ The `duration` validator error messages expose 4 values that you can use:
644
646
 
645
647
  ---
646
648
 
649
+ ### With audio
650
+
651
+ Validates whether attached video files contain an audio track.
652
+ (be sure to have the right dependencies installed as mentioned in [Using video and audio metadata validators](#using-video-and-audio-metadata-validators))
653
+
654
+ #### Options
655
+
656
+ The `with_audio` validator supports:
657
+ - `with`: `true` requires an audio track and `false` rejects one
658
+ - `timeout`: overrides the global analyzer [command timeout](#configuration) for this validation
659
+
660
+ #### Examples
661
+
662
+ Use it like this:
663
+ ```ruby
664
+ class User < ApplicationRecord
665
+ has_one_attached :video
666
+ has_one_attached :silent_video
667
+
668
+ validates :video, with_audio: true
669
+ validates :silent_video, with_audio: { with: false }
670
+ validates :video, with_audio: { timeout: 5.seconds }
671
+ end
672
+ ```
673
+
674
+ Rails treats a bare `with_audio: false` as a disabled validator. Use the hash form `with_audio: { with: false }` to reject videos that contain audio.
675
+
676
+ #### Error messages (I18n)
677
+
678
+ ```yml
679
+ en:
680
+ errors:
681
+ messages:
682
+ audio_missing: "must have an audio track"
683
+ audio_present: "must not have an audio track"
684
+ ```
685
+
686
+ The `with_audio` validator error message exposes the `filename` value containing the current file name.
687
+
688
+ ---
689
+
647
690
  ### Aspect ratio
648
691
 
649
692
  Validates the aspect ratio of the attached image / video files.
@@ -895,6 +938,10 @@ describe User do
895
938
  it { is_expected.to validate_duration_of(:introduction).between(100..500.seconds) }
896
939
  it { is_expected.to validate_duration_of(:avatar).equal_to(5.minutes) }
897
940
 
941
+ # with_audio
942
+ it { is_expected.to validate_with_audio_of(:video) }
943
+ it { is_expected.to validate_with_audio_of(:silent_video).without_audio }
944
+
898
945
  # pages:
899
946
  # #less_than, #less_than_or_equal_to, #greater_than, #greater_than_or_equal_to, #between, #equal_to
900
947
  it { is_expected.to validate_pages_of(:contract).less_than(50) }
@@ -927,6 +974,7 @@ describe User do
927
974
 
928
975
  # :timeout (analyzer command timeout — metadata validators + content_type with spoofing)
929
976
  it { is_expected.to validate_duration_of(:video).less_than(5.minutes).timeout(30.seconds) }
977
+ it { is_expected.to validate_with_audio_of(:video).timeout(5.seconds) }
930
978
  it { is_expected.to validate_processable_file_of(:avatar).timeout(5.seconds) }
931
979
  end
932
980
  ```
@@ -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})"
@@ -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})."
@@ -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})"
@@ -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})"
@@ -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})"
@@ -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})"
@@ -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})"
@@ -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}ページ)"
@@ -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})"
@@ -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})"
@@ -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})"
@@ -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})"
@@ -55,6 +55,8 @@ sv:
55
55
  aspect_ratio_not_x_y: "måste vara% { authorized_aspect_ratios } (nuvarande fil är %{width}x%{height} pixlar)"
56
56
  aspect_ratio_invalid: "har ett ogiltigt bildförhållande (giltiga bildförhållanden är %{förväntat_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})"
@@ -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})"
@@ -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})"
@@ -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})"
@@ -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})"
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "shared/asv_active_storageable"
4
+ require_relative "shared/asv_allow_blankable"
5
+ require_relative "shared/asv_attachable"
6
+ require_relative "shared/asv_contextable"
7
+ require_relative "shared/asv_except_onable"
8
+ require_relative "shared/asv_messageable"
9
+ require_relative "shared/asv_rspecable"
10
+ require_relative "shared/asv_timeoutable"
11
+ require_relative "shared/asv_validatable"
12
+
13
+ module ActiveStorageValidations
14
+ module Matchers
15
+ def validate_with_audio_of(attribute_name)
16
+ WithAudioValidatorMatcher.new(attribute_name)
17
+ end
18
+
19
+ class WithAudioValidatorMatcher
20
+ include ASVActiveStorageable
21
+ include ASVAllowBlankable
22
+ include ASVAttachable
23
+ include ASVContextable
24
+ include ASVExceptOnable
25
+ include ASVMessageable
26
+ include ASVRspecable
27
+ include ASVTimeoutable
28
+ include ASVValidatable
29
+
30
+ def initialize(attribute_name)
31
+ initialize_allow_blankable
32
+ initialize_contextable
33
+ initialize_except_onable
34
+ initialize_messageable
35
+ initialize_rspecable
36
+ initialize_timeoutable
37
+ @attribute_name = attribute_name
38
+ @expected_audio = true
39
+ end
40
+
41
+ def description
42
+ "validate that :#{@attribute_name} #{audio_expectation}"
43
+ end
44
+
45
+ def failure_message
46
+ "is expected to validate that :#{@attribute_name} #{audio_expectation}"
47
+ end
48
+
49
+ def without_audio
50
+ @expected_audio = false
51
+ self
52
+ end
53
+
54
+ def matches?(subject)
55
+ @subject = subject.is_a?(Class) ? subject.new : subject
56
+
57
+ is_a_valid_active_storage_attribute? &&
58
+ is_context_valid? &&
59
+ is_except_on_valid? &&
60
+ is_allowing_blank? &&
61
+ is_timeout_valid? &&
62
+ is_custom_message_valid? &&
63
+ is_valid_with_expected_audio? &&
64
+ is_invalid_with_unexpected_audio?
65
+ end
66
+
67
+ private
68
+
69
+ def audio_expectation
70
+ @expected_audio ? "has an audio track" : "has no audio track"
71
+ end
72
+
73
+ def is_valid_with_expected_audio?
74
+ validation_passes_with_audio?(@expected_audio)
75
+ end
76
+
77
+ def is_invalid_with_unexpected_audio?
78
+ !validation_passes_with_audio?(!@expected_audio)
79
+ end
80
+
81
+ def is_custom_message_valid?
82
+ return true unless @custom_message
83
+
84
+ with_audio_metadata(false) do
85
+ attach_file(video_file)
86
+ validate
87
+ detach_file
88
+ has_an_error_message_which_is_custom_message?
89
+ end
90
+ end
91
+
92
+ def validation_passes_with_audio?(audio)
93
+ with_audio_metadata(audio) do
94
+ attach_file(video_file)
95
+ validate
96
+ detach_file
97
+ is_valid?
98
+ end
99
+ end
100
+
101
+ def with_audio_metadata(audio, &block)
102
+ Matchers.mock_metadata(io, { audio: audio }, &block)
103
+ end
104
+
105
+ def video_file
106
+ {
107
+ io: io,
108
+ filename: "test.mp4",
109
+ content_type: "video/mp4"
110
+ }
111
+ end
112
+ end
113
+ end
114
+ end
@@ -7,6 +7,7 @@ require "active_storage_validations/matchers/limit_validator_matcher"
7
7
  require "active_storage_validations/matchers/content_type_validator_matcher"
8
8
  require "active_storage_validations/matchers/dimension_validator_matcher"
9
9
  require "active_storage_validations/matchers/duration_validator_matcher"
10
+ require "active_storage_validations/matchers/with_audio_validator_matcher"
10
11
  require "active_storage_validations/matchers/size_validator_matcher"
11
12
  require "active_storage_validations/matchers/total_size_validator_matcher"
12
13
  require "active_storage_validations/matchers/pages_validator_matcher"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageValidations
4
- VERSION = "4.0.0"
4
+ VERSION = "4.1.0"
5
5
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "shared/asv_active_storageable"
4
+ require_relative "shared/asv_analyzable"
5
+ require_relative "shared/asv_attachable"
6
+ require_relative "shared/asv_errorable"
7
+ require_relative "shared/asv_symbolizable"
8
+
9
+ module ActiveStorageValidations
10
+ class WithAudioValidator < ActiveModel::EachValidator # :nodoc
11
+ include ASVActiveStorageable
12
+ include ASVAnalyzable
13
+ include ASVAttachable
14
+ include ASVErrorable
15
+ include ASVSymbolizable
16
+
17
+ ERROR_TYPES = %i[audio_missing audio_present].freeze
18
+ METADATA_KEYS = %i[audio].freeze
19
+
20
+ def validate_each(record, attribute, _value)
21
+ return if no_attachments?(record, attribute)
22
+
23
+ validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
24
+ end
25
+
26
+ private
27
+
28
+ def is_valid?(record, attribute, attachable, metadata)
29
+ expected_audio = options.fetch(:with, true)
30
+ return if metadata&.fetch(:audio, false) == expected_audio
31
+
32
+ errors_options = initialize_error_options(options, attachable)
33
+ error_type = expected_audio ? :audio_missing : :audio_present
34
+ add_error(record, attribute, error_type, **errors_options)
35
+ end
36
+ end
37
+ end
@@ -41,6 +41,7 @@ require "active_storage_validations/content_type_validator"
41
41
  require "active_storage_validations/limit_validator"
42
42
  require "active_storage_validations/dimension_validator"
43
43
  require "active_storage_validations/duration_validator"
44
+ require "active_storage_validations/with_audio_validator"
44
45
  require "active_storage_validations/aspect_ratio_validator"
45
46
  require "active_storage_validations/processable_file_validator"
46
47
  require "active_storage_validations/size_validator"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_storage_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-02 00:00:00.000000000 Z
11
+ date: 2026-08-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -265,6 +265,7 @@ files:
265
265
  - lib/active_storage_validations/matchers/shared/asv_validatable.rb
266
266
  - lib/active_storage_validations/matchers/size_validator_matcher.rb
267
267
  - lib/active_storage_validations/matchers/total_size_validator_matcher.rb
268
+ - lib/active_storage_validations/matchers/with_audio_validator_matcher.rb
268
269
  - lib/active_storage_validations/pages_validator.rb
269
270
  - lib/active_storage_validations/processable_file_validator.rb
270
271
  - lib/active_storage_validations/railtie.rb
@@ -279,6 +280,7 @@ files:
279
280
  - lib/active_storage_validations/size_validator.rb
280
281
  - lib/active_storage_validations/total_size_validator.rb
281
282
  - lib/active_storage_validations/version.rb
283
+ - lib/active_storage_validations/with_audio_validator.rb
282
284
  homepage: https://github.com/igorkasyanchuk/active_storage_validations
283
285
  licenses:
284
286
  - MIT