active_storage_validations 1.1.1 → 1.1.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.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -0
  3. data/config/locales/de.yml +1 -0
  4. data/config/locales/es.yml +1 -0
  5. data/config/locales/fr.yml +1 -0
  6. data/config/locales/it.yml +1 -0
  7. data/config/locales/ja.yml +1 -0
  8. data/config/locales/nl.yml +1 -0
  9. data/config/locales/pl.yml +1 -0
  10. data/config/locales/ru.yml +2 -1
  11. data/config/locales/tr.yml +1 -0
  12. data/config/locales/uk.yml +1 -0
  13. data/config/locales/vi.yml +1 -0
  14. data/config/locales/zh-CN.yml +1 -0
  15. data/lib/active_storage_validations/aspect_ratio_validator.rb +2 -0
  16. data/lib/active_storage_validations/attached_validator.rb +6 -1
  17. data/lib/active_storage_validations/concerns/symbolizable.rb +10 -0
  18. data/lib/active_storage_validations/content_type_validator.rb +6 -2
  19. data/lib/active_storage_validations/dimension_validator.rb +15 -0
  20. data/lib/active_storage_validations/error_handler.rb +9 -6
  21. data/lib/active_storage_validations/limit_validator.rb +4 -1
  22. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +39 -15
  23. data/lib/active_storage_validations/matchers/concerns/validatable.rb +46 -0
  24. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +39 -32
  25. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +43 -13
  26. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +49 -10
  27. data/lib/active_storage_validations/processable_image_validator.rb +2 -0
  28. data/lib/active_storage_validations/size_validator.rb +21 -2
  29. data/lib/active_storage_validations/version.rb +1 -1
  30. metadata +20 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b512ecda0ff1fa0f06649f78b7b4f58433ca7f108abd729ce08cd0eea2b1baf
4
- data.tar.gz: 1ef72d050d31ed0d4052672370849c5d66538df5b06e5a477ebc3575fc110e40
3
+ metadata.gz: 288f21f798861bd7e44ad225052620bfcfd2d58efe73f0aeda8f4964ace98f56
4
+ data.tar.gz: b19cce430aae6c1eed3c2d29cbafa6d07b36423f122157603d4391a892267d64
5
5
  SHA512:
6
- metadata.gz: dc0392b8725f2a567df3f99b61c5053ab0870e877ba16f3085871842a70ca7f5db328b63b3275912017831d4ed29697a66106cdad4e7cfbb779a1985b7d20488
7
- data.tar.gz: 8a6883ac4f1cbf0e703c41d1e8ddf6136397fb3973997cc37c83aef3f0456ab5278b5475518ce3ddb8a776a41c8e8f7774160850ddcb49b5ece6e9c846cab8db
6
+ metadata.gz: e7d6e8ac601a1bd25c2c4f4a199782e7b8dd5821ce99a2bcf14a60c0d8dd2f5e55c46d1eeae758438f8e9da34ae2f9366bf4690345dde242af897cf405dc1624
7
+ data.tar.gz: a4335b501b1298517053f45ec6f1192d053cd73fd682bc19127f5891aa7704e2116659991e29c3d2c4bd6ff9acf0847b91389b6bbfafb52d2c45a941dbb9dd35
data/README.md CHANGED
@@ -368,6 +368,11 @@ BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test
368
368
  BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle exec rake test
369
369
  ```
370
370
 
371
+ Tips:
372
+ - To focus a specific test, use the `focus` class method provided by [minitest-focus](https://github.com/minitest/minitest-focus)
373
+ - To focus a specific file, use the TEST option provided by minitest, e.g. to only run size_validator_test.rb file you will execute the following command: `bundle exec rake test TEST=test/validators/size_validator_test.rb`
374
+
375
+
371
376
  ## Known issues
372
377
 
373
378
  - 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:
@@ -446,6 +451,8 @@ You are welcome to contribute.
446
451
  - https://github.com/technicalpickles
447
452
  - https://github.com/ricsdeol
448
453
  - https://github.com/Fonsan
454
+ - https://github.com/tagliala
455
+ - https://github.com/ocarreterom
449
456
 
450
457
 
451
458
  ## License
@@ -24,3 +24,4 @@ de:
24
24
  aspect_ratio_not_landscape: "muss Querformat sein"
25
25
  aspect_ratio_is_not: "muss ein Bildseitenverhältnis von %{aspect_ratio} haben"
26
26
  aspect_ratio_unknown: "hat ein unbekanntes Bildseitenverhältnis"
27
+ image_not_processable: "ist kein gültiges Bild"
@@ -24,3 +24,4 @@ es:
24
24
  aspect_ratio_not_landscape: "debe ser una imagen apaisada"
25
25
  aspect_ratio_is_not: "debe tener una relación de aspecto de %{aspect_ratio}"
26
26
  aspect_ratio_unknown: "tiene una relación de aspecto desconocida"
27
+ image_not_processable: "no es una imagen válida"
@@ -24,3 +24,4 @@ fr:
24
24
  aspect_ratio_not_landscape: "doit être une image en format paysage"
25
25
  aspect_ratio_is_not: "doit avoir un rapport hauteur / largeur de %{aspect_ratio}"
26
26
  aspect_ratio_unknown: "a un rapport d'aspect inconnu"
27
+ image_not_processable: "n'est pas une image valide"
@@ -24,3 +24,4 @@ it:
24
24
  aspect_ratio_not_landscape: "l’orientamento dell’immagine deve essere orizzontale"
25
25
  aspect_ratio_is_not: "deve avere un rapporto altezza / larghezza di %{aspect_ratio}"
26
26
  aspect_ratio_unknown: "ha un rapporto altezza / larghezza sconosciuto"
27
+ image_not_processable: "non è un'immagine valida"
@@ -24,3 +24,4 @@ ja:
24
24
  aspect_ratio_not_landscape: "は横長にしてください"
25
25
  aspect_ratio_is_not: "のアスペクト比は %{aspect_ratio} にしてください"
26
26
  aspect_ratio_unknown: "のアスペクト比を取得できませんでした"
27
+ image_not_processable: "は不正な画像です"
@@ -24,3 +24,4 @@ nl:
24
24
  aspect_ratio_not_landscape: "moet een liggende afbeelding zijn"
25
25
  aspect_ratio_is_not: "moet een beeldverhouding hebben van %{aspect_ratio}"
26
26
  aspect_ratio_unknown: "heeft een onbekende beeldverhouding"
27
+ image_not_processable: "is geen geldige afbeelding"
@@ -24,3 +24,4 @@ pl:
24
24
  aspect_ratio_not_landscape: "musi mieć proporcje pejzażu"
25
25
  aspect_ratio_is_not: "musi mieć proporcje %{aspect_ratio}"
26
26
  aspect_ratio_unknown: "ma nieokreślone proporcje"
27
+ image_not_processable: "nie jest prawidłowym obrazem"
@@ -23,4 +23,5 @@ ru:
23
23
  aspect_ratio_not_portrait: "должно быть портретное изображение"
24
24
  aspect_ratio_not_landscape: "должно быть пейзажное изображение"
25
25
  aspect_ratio_is_not: "должен иметь соотношение сторон %{aspect_ratio}"
26
- aspect_ratio_unknown: "имеет неизвестное соотношение сторон"
26
+ aspect_ratio_unknown: "имеет неизвестное соотношение сторон"
27
+ image_not_processable: "не является допустимым изображением"
@@ -24,3 +24,4 @@ tr:
24
24
  aspect_ratio_not_landscape: "yatay bir imaj olmalı"
25
25
  aspect_ratio_is_not: "%{aspect_ratio} en boy oranına sahip olmalı"
26
26
  aspect_ratio_unknown: "bilinmeyen en boy oranı"
27
+ image_not_processable: "geçerli bir imaj değil"
@@ -24,3 +24,4 @@ uk:
24
24
  aspect_ratio_not_landscape: "мусить бути пейзажне зображення"
25
25
  aspect_ratio_is_not: "мусить мати співвідношення сторін %{aspect_ratio}"
26
26
  aspect_ratio_unknown: "має невідоме співвідношення сторін"
27
+ image_not_processable: "не є допустимим зображенням"
@@ -24,3 +24,4 @@ vi:
24
24
  aspect_ratio_not_landscape: "phải là ảnh ngang"
25
25
  aspect_ratio_is_not: "phải có tỉ lệ ảnh %{aspect_ratio}"
26
26
  aspect_ratio_unknown: "tỉ lệ ảnh không xác định"
27
+ image_not_processable: "không phải là ảnh"
@@ -24,3 +24,4 @@ zh-CN:
24
24
  aspect_ratio_not_landscape: "必须是横屏图片"
25
25
  aspect_ratio_is_not: "纵横比必须是 %{aspect_ratio}"
26
26
  aspect_ratio_unknown: "未知的纵横比"
27
+ image_not_processable: "不是有效的图像"
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/symbolizable.rb'
3
4
  require_relative 'metadata.rb'
4
5
 
5
6
  module ActiveStorageValidations
6
7
  class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
7
8
  include OptionProcUnfolding
8
9
  include ErrorHandler
10
+ include Symbolizable
9
11
 
10
12
  AVAILABLE_CHECKS = %i[with].freeze
11
13
  PRECISION = 3
@@ -1,15 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/symbolizable.rb'
4
+
3
5
  module ActiveStorageValidations
4
6
  class AttachedValidator < ActiveModel::EachValidator # :nodoc:
5
7
  include ErrorHandler
8
+ include Symbolizable
9
+
10
+ ERROR_TYPES = %i[blank].freeze
6
11
 
7
12
  def validate_each(record, attribute, _value)
8
13
  return if record.send(attribute).attached?
9
14
 
10
15
  errors_options = initialize_error_options(options)
11
16
 
12
- add_error(record, attribute, :blank, **errors_options)
17
+ add_error(record, attribute, ERROR_TYPES.first, **errors_options)
13
18
  end
14
19
  end
15
20
  end
@@ -0,0 +1,10 @@
1
+ module Symbolizable
2
+ extend ActiveSupport::Concern
3
+
4
+ class_methods do
5
+ def to_sym
6
+ validator_class = self.name.split("::").last
7
+ validator_class.sub(/Validator/, '').underscore.to_sym
8
+ end
9
+ end
10
+ end
@@ -1,12 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/symbolizable.rb'
4
+
3
5
  module ActiveStorageValidations
4
6
  class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
5
7
  include OptionProcUnfolding
6
8
  include ErrorHandler
9
+ include Symbolizable
7
10
 
8
11
  AVAILABLE_CHECKS = %i[with in].freeze
9
-
12
+ ERROR_TYPES = %i[content_type_invalid].freeze
13
+
10
14
  def validate_each(record, attribute, _value)
11
15
  return true unless record.send(attribute).attached?
12
16
 
@@ -22,7 +26,7 @@ module ActiveStorageValidations
22
26
  next if is_valid?(file, types)
23
27
 
24
28
  errors_options[:content_type] = content_type(file)
25
- add_error(record, attribute, :content_type_invalid, **errors_options)
29
+ add_error(record, attribute, ERROR_TYPES.first, **errors_options)
26
30
  break
27
31
  end
28
32
  end
@@ -1,13 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/symbolizable.rb'
3
4
  require_relative 'metadata.rb'
4
5
 
5
6
  module ActiveStorageValidations
6
7
  class DimensionValidator < ActiveModel::EachValidator # :nodoc
7
8
  include OptionProcUnfolding
8
9
  include ErrorHandler
10
+ include Symbolizable
9
11
 
10
12
  AVAILABLE_CHECKS = %i[width height min max].freeze
13
+ ERROR_TYPES = %i[
14
+ image_metadata_missing
15
+ dimension_min_inclusion
16
+ dimension_max_inclusion
17
+ dimension_width_inclusion
18
+ dimension_height_inclusion
19
+ dimension_width_greater_than_or_equal_to
20
+ dimension_height_greater_than_or_equal_to
21
+ dimension_width_less_than_or_equal_to
22
+ dimension_height_less_than_or_equal_to
23
+ dimension_width_equal_to
24
+ dimension_height_equal_to
25
+ ].freeze
11
26
 
12
27
  def process_options(record)
13
28
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
@@ -3,15 +3,18 @@ module ActiveStorageValidations
3
3
 
4
4
  def initialize_error_options(options)
5
5
  {
6
- message: (options[:message] if options[:message].present?)
7
- }
6
+ validator_type: self.class.to_sym,
7
+ custom_message: (options[:message] if options[:message].present?)
8
+ }.compact
8
9
  end
9
10
 
10
- def add_error(record, attribute, default_message, **errors_options)
11
- message = errors_options[:message].presence || default_message
12
- return if record.errors.added?(attribute, message)
11
+ def add_error(record, attribute, error_type, **errors_options)
12
+ type = errors_options[:custom_message].presence || error_type
13
+ return if record.errors.added?(attribute, type)
13
14
 
14
- record.errors.add(attribute, message, **errors_options)
15
+ # You can read https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-add
16
+ # to better understand how Rails model errors work
17
+ record.errors.add(attribute, type, **errors_options)
15
18
  end
16
19
 
17
20
  end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/symbolizable.rb'
4
+
3
5
  module ActiveStorageValidations
4
6
  class LimitValidator < ActiveModel::EachValidator # :nodoc:
5
7
  include OptionProcUnfolding
6
8
  include ErrorHandler
9
+ include Symbolizable
7
10
 
8
11
  AVAILABLE_CHECKS = %i[max min].freeze
9
12
 
@@ -14,7 +17,7 @@ module ActiveStorageValidations
14
17
  end
15
18
 
16
19
  def validate_each(record, attribute, _)
17
- files = Array.wrap(record.send(attribute)).compact.uniq
20
+ files = Array.wrap(record.send(attribute)).reject { |file| file.blank? }.compact.uniq
18
21
  flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
19
22
  errors_options = initialize_error_options(options)
20
23
  errors_options[:min] = flat_options[:min]
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/validatable.rb'
4
+
3
5
  module ActiveStorageValidations
4
6
  module Matchers
5
7
  def validate_attached_of(name)
@@ -7,6 +9,8 @@ module ActiveStorageValidations
7
9
  end
8
10
 
9
11
  class AttachedValidatorMatcher
12
+ include Validatable
13
+
10
14
  def initialize(attribute_name)
11
15
  @attribute_name = attribute_name
12
16
  @custom_message = nil
@@ -23,7 +27,10 @@ module ActiveStorageValidations
23
27
 
24
28
  def matches?(subject)
25
29
  @subject = subject.is_a?(Class) ? subject.new : subject
26
- responds_to_methods && valid_when_attached && invalid_when_not_attached
30
+ responds_to_methods &&
31
+ is_valid_when_file_attached? &&
32
+ is_invalid_when_file_not_attached? &&
33
+ validate_custom_message?
27
34
  end
28
35
 
29
36
  def failure_message
@@ -42,27 +49,44 @@ module ActiveStorageValidations
42
49
  @subject.public_send(@attribute_name).respond_to?(:detach)
43
50
  end
44
51
 
45
- def valid_when_attached
46
- @subject.public_send(@attribute_name).attach(attachable) unless @subject.public_send(@attribute_name).attached?
47
- @subject.validate
48
- @subject.errors.details[@attribute_name].exclude?(error: error_message)
52
+ def is_valid_when_file_attached?
53
+ attach_dummy_file unless file_attached?
54
+ validate
55
+ is_valid?
49
56
  end
50
57
 
51
- def invalid_when_not_attached
52
- @subject.public_send(@attribute_name).detach
53
- # Unset the direct relation since `detach` on an unpersisted record does not set `attached?` to false.
54
- @subject.public_send("#{@attribute_name}=", nil)
58
+ def is_invalid_when_file_not_attached?
59
+ detach_file if file_attached?
60
+ validate
61
+ !is_valid?
62
+ end
63
+
64
+ def validate_custom_message?
65
+ return true unless @custom_message
55
66
 
56
- @subject.validate
57
- @subject.errors.details[@attribute_name].include?(error: error_message)
67
+ detach_file if file_attached?
68
+ validate
69
+ has_an_error_message_which_is_custom_message?
58
70
  end
59
71
 
60
- def error_message
61
- @custom_message || :blank
72
+ def attach_dummy_file
73
+ dummy_file = {
74
+ io: Tempfile.new('.'),
75
+ filename: 'dummy.txt',
76
+ content_type: 'text/plain'
77
+ }
78
+
79
+ @subject.public_send(@attribute_name).attach(dummy_file)
80
+ end
81
+
82
+ def file_attached?
83
+ @subject.public_send(@attribute_name).attached?
62
84
  end
63
85
 
64
- def attachable
65
- { io: Tempfile.new('.'), filename: 'dummy.txt', content_type: 'text/plain' }
86
+ def detach_file
87
+ @subject.public_send(@attribute_name).detach
88
+ # Unset the direct relation since `detach` on an unpersisted record does not set `attached?` to false.
89
+ @subject.public_send("#{@attribute_name}=", nil)
66
90
  end
67
91
  end
68
92
  end
@@ -0,0 +1,46 @@
1
+ module Validatable
2
+ extend ActiveSupport::Concern
3
+
4
+ private
5
+
6
+ def validate
7
+ @subject.validate
8
+ end
9
+
10
+ def validator_errors_for_attribute
11
+ @subject.errors.details[@attribute_name].select do |error|
12
+ error[:validator_type] == validator_class.to_sym
13
+ end
14
+ end
15
+
16
+ def is_valid?
17
+ validator_errors_for_attribute.none? do |error|
18
+ error[:error].in?(available_errors)
19
+ end
20
+ end
21
+
22
+ def available_errors
23
+ [
24
+ *validator_class::ERROR_TYPES,
25
+ *error_from_custom_message
26
+ ].compact
27
+ end
28
+
29
+ def validator_class
30
+ self.class.name.gsub(/::Matchers|Matcher/, '').constantize
31
+ end
32
+
33
+ def error_from_custom_message
34
+ associated_validation = @subject.class.validators_on(@attribute_name).find do |validator|
35
+ validator.class == validator_class
36
+ end
37
+
38
+ associated_validation.options[:message]
39
+ end
40
+
41
+ def has_an_error_message_which_is_custom_message?
42
+ validator_errors_for_attribute.one? do |error|
43
+ error[:error] == @custom_message
44
+ end
45
+ end
46
+ end
@@ -2,6 +2,9 @@
2
2
 
3
3
  # Big thank you to the paperclip validation matchers:
4
4
  # https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb
5
+
6
+ require_relative 'concerns/validatable.rb'
7
+
5
8
  module ActiveStorageValidations
6
9
  module Matchers
7
10
  def validate_content_type_of(name)
@@ -9,8 +12,11 @@ module ActiveStorageValidations
9
12
  end
10
13
 
11
14
  class ContentTypeValidatorMatcher
15
+ include Validatable
16
+
12
17
  def initialize(attribute_name)
13
18
  @attribute_name = attribute_name
19
+ @allowed_types = @rejected_types = []
14
20
  @custom_message = nil
15
21
  end
16
22
 
@@ -35,20 +41,24 @@ module ActiveStorageValidations
35
41
 
36
42
  def matches?(subject)
37
43
  @subject = subject.is_a?(Class) ? subject.new : subject
38
- responds_to_methods && allowed_types_allowed? && rejected_types_rejected? && validate_custom_message?
44
+
45
+ responds_to_methods &&
46
+ all_allowed_types_allowed? &&
47
+ all_rejected_types_rejected? &&
48
+ validate_custom_message?
39
49
  end
40
50
 
41
51
  def failure_message
42
52
  message = ["Expected #{@attribute_name}"]
43
53
 
44
- if @allowed_types
45
- message << "Accept content types: #{allowed_types.join(", ")}"
46
- message << "#{@missing_allowed_types.join(", ")} were rejected"
54
+ if @allowed_types_not_allowed.present?
55
+ message << "Accept content types: #{@allowed_types.join(", ")}"
56
+ message << "#{@allowed_types_not_allowed.join(", ")} were rejected"
47
57
  end
48
58
 
49
- if @rejected_types
50
- message << "Reject content types: #{rejected_types.join(", ")}"
51
- message << "#{@missing_rejected_types.join(", ")} were accepted"
59
+ if @rejected_types_not_rejected.present?
60
+ message << "Reject content types: #{@rejected_types.join(", ")}"
61
+ message << "#{@rejected_types_not_rejected.join(", ")} were accepted"
52
62
  end
53
63
 
54
64
  message.join("\n")
@@ -62,49 +72,46 @@ module ActiveStorageValidations
62
72
  @subject.public_send(@attribute_name).respond_to?(:detach)
63
73
  end
64
74
 
65
- def allowed_types
66
- @allowed_types || []
75
+ def all_allowed_types_allowed?
76
+ @allowed_types_not_allowed ||= @allowed_types.reject { |type| type_allowed?(type) }
77
+ @allowed_types_not_allowed.empty?
67
78
  end
68
79
 
69
- def rejected_types
70
- @rejected_types || []
80
+ def all_rejected_types_rejected?
81
+ @rejected_types_not_rejected ||= @rejected_types.select { |type| type_allowed?(type) }
82
+ @rejected_types_not_rejected.empty?
71
83
  end
72
84
 
73
- def allowed_types_allowed?
74
- @missing_allowed_types ||= allowed_types.reject { |type| type_allowed?(type) }
75
- @missing_allowed_types.none?
76
- end
77
-
78
- def rejected_types_rejected?
79
- @missing_rejected_types ||= rejected_types.select { |type| type_allowed?(type) }
80
- @missing_rejected_types.none?
85
+ def type_allowed?(type)
86
+ attach_file_of_type(type)
87
+ validate
88
+ is_valid?
81
89
  end
82
90
 
83
- def type_allowed?(type)
91
+ def attach_file_of_type(type)
84
92
  @subject.public_send(@attribute_name).attach(attachment_for(type))
85
- @subject.validate
86
- @subject.errors.details[@attribute_name].none? do |error|
87
- error[:error].to_s.include?(error_message)
88
- end
89
93
  end
90
94
 
91
95
  def validate_custom_message?
92
96
  return true unless @custom_message
93
97
 
94
- @subject.public_send(@attribute_name).attach(attachment_for('fake/fake'))
95
- @subject.validate
96
- @subject.errors.details[@attribute_name].select{|error| error[:content_type]}.all? do |error|
97
- error[:error].to_s.include?(error_message)
98
- end
98
+ attach_invalid_content_type_file
99
+ validate
100
+ has_an_error_message_which_is_custom_message?
99
101
  end
100
102
 
101
- def error_message
102
- @custom_message || :content_type_invalid.to_s
103
+ def attach_invalid_content_type_file
104
+ @subject.public_send(@attribute_name).attach(attachment_for('fake/fake'))
103
105
  end
104
106
 
105
107
  def attachment_for(type)
106
108
  suffix = type.to_s.split('/').last
107
- { io: Tempfile.new('.'), filename: "test.#{suffix}", content_type: type }
109
+
110
+ {
111
+ io: Tempfile.new('.'),
112
+ filename: "test.#{suffix}",
113
+ content_type: type
114
+ }
108
115
  end
109
116
  end
110
117
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/validatable.rb'
4
+
3
5
  module ActiveStorageValidations
4
6
  module Matchers
5
7
  def validate_dimensions_of(name)
@@ -7,6 +9,8 @@ module ActiveStorageValidations
7
9
  end
8
10
 
9
11
  class DimensionValidatorMatcher
12
+ include Validatable
13
+
10
14
  def initialize(attribute_name)
11
15
  @attribute_name = attribute_name
12
16
  @width_min = @width_max = @height_min = @height_max = nil
@@ -64,9 +68,19 @@ module ActiveStorageValidations
64
68
 
65
69
  def matches?(subject)
66
70
  @subject = subject.is_a?(Class) ? subject.new : subject
71
+
67
72
  responds_to_methods &&
68
- width_not_smaller_than_min? && width_larger_than_min? && width_smaller_than_max? && width_not_larger_than_max? && width_equals? &&
69
- height_not_smaller_than_min? && height_larger_than_min? && height_smaller_than_max? && height_not_larger_than_max? && height_equals?
73
+ width_not_smaller_than_min? &&
74
+ width_larger_than_min? &&
75
+ width_smaller_than_max? &&
76
+ width_not_larger_than_max? &&
77
+ width_equals? &&
78
+ height_not_smaller_than_min? &&
79
+ height_larger_than_min? &&
80
+ height_smaller_than_max? &&
81
+ height_not_larger_than_max? &&
82
+ height_equals? &&
83
+ validate_custom_message?
70
84
  end
71
85
 
72
86
  def failure_message
@@ -134,22 +148,38 @@ module ActiveStorageValidations
134
148
  end
135
149
 
136
150
  def passes_validation_with_dimensions(width, height, check)
137
- @subject.public_send(@attribute_name).attach attachment_for(width, height)
151
+ mock_dimensions_for(attach_file, width, height) do
152
+ validate
153
+ is_valid?
154
+ end
155
+ end
156
+
157
+ def validate_custom_message?
158
+ return true unless @custom_message
138
159
 
139
- attachment = @subject.public_send(@attribute_name)
160
+ mock_dimensions_for(attach_file, -1, -1) do
161
+ validate
162
+ has_an_error_message_which_is_custom_message?
163
+ end
164
+ end
165
+
166
+ def mock_dimensions_for(attachment, width, height)
140
167
  Matchers.mock_metadata(attachment, width, height) do
141
- @subject.validate
142
- exclude_error_message = @custom_message || "dimension_#{check}"
143
- @subject.errors.details[@attribute_name].none? do |error|
144
- error[:error].to_s.include?(exclude_error_message) ||
145
- error[:error].to_s.include?("dimension_min") ||
146
- error[:error].to_s.include?("dimension_max")
147
- end
168
+ yield
148
169
  end
149
170
  end
150
171
 
151
- def attachment_for(width, height)
152
- { io: Tempfile.new('Hello world!'), filename: 'test.png', content_type: 'image/png' }
172
+ def attach_file
173
+ @subject.public_send(@attribute_name).attach(dummy_file)
174
+ @subject.public_send(@attribute_name)
175
+ end
176
+
177
+ def dummy_file
178
+ {
179
+ io: Tempfile.new('Hello world!'),
180
+ filename: 'test.png',
181
+ content_type: 'image/png'
182
+ }
153
183
  end
154
184
  end
155
185
  end
@@ -2,6 +2,9 @@
2
2
 
3
3
  # Big thank you to the paperclip validation matchers:
4
4
  # https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_size_matcher.rb
5
+
6
+ require_relative 'concerns/validatable.rb'
7
+
5
8
  module ActiveStorageValidations
6
9
  module Matchers
7
10
  def validate_size_of(name)
@@ -9,6 +12,8 @@ module ActiveStorageValidations
9
12
  end
10
13
 
11
14
  class SizeValidatorMatcher
15
+ include Validatable
16
+
12
17
  def initialize(attribute_name)
13
18
  @attribute_name = attribute_name
14
19
  @min = @max = nil
@@ -51,7 +56,13 @@ module ActiveStorageValidations
51
56
 
52
57
  def matches?(subject)
53
58
  @subject = subject.is_a?(Class) ? subject.new : subject
54
- responds_to_methods && not_lower_than_min? && higher_than_min? && lower_than_max? && not_higher_than_max?
59
+
60
+ responds_to_methods &&
61
+ not_lower_than_min? &&
62
+ higher_than_min? &&
63
+ lower_than_max? &&
64
+ not_higher_than_max? &&
65
+ validate_custom_message?
55
66
  end
56
67
 
57
68
  def failure_message
@@ -86,17 +97,45 @@ module ActiveStorageValidations
86
97
  @max.nil? || @max == Float::INFINITY || !passes_validation_with_size(@max + 1)
87
98
  end
88
99
 
89
- def passes_validation_with_size(new_size)
90
- io = Tempfile.new('Hello world!')
91
- Matchers.stub_method(io, :size, new_size) do
92
- @subject.public_send(@attribute_name).attach(io: io, filename: 'test.png', content_type: 'image/pg')
93
- @subject.validate
94
- exclude_error_message = @custom_message || "file_size_not_"
95
- @subject.errors.details[@attribute_name].none? do |error|
96
- error[:error].to_s.include?(exclude_error_message)
97
- end
100
+ def passes_validation_with_size(size)
101
+ mock_size_for(io, size) do
102
+ attach_file
103
+ validate
104
+ is_valid?
98
105
  end
99
106
  end
107
+
108
+ def validate_custom_message?
109
+ return true unless @custom_message
110
+
111
+ mock_size_for(io, -1.kilobytes) do
112
+ attach_file
113
+ validate
114
+ has_an_error_message_which_is_custom_message?
115
+ end
116
+ end
117
+
118
+ def mock_size_for(io, size)
119
+ Matchers.stub_method(io, :size, size) do
120
+ yield
121
+ end
122
+ end
123
+
124
+ def attach_file
125
+ @subject.public_send(@attribute_name).attach(dummy_file)
126
+ end
127
+
128
+ def dummy_file
129
+ {
130
+ io: io,
131
+ filename: 'test.png',
132
+ content_type: 'image/png'
133
+ }
134
+ end
135
+
136
+ def io
137
+ @io ||= Tempfile.new('Hello world!')
138
+ end
100
139
  end
101
140
  end
102
141
  end
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/symbolizable.rb'
3
4
  require_relative 'metadata.rb'
4
5
 
5
6
  module ActiveStorageValidations
6
7
  class ProcessableImageValidator < ActiveModel::EachValidator # :nodoc
7
8
  include OptionProcUnfolding
8
9
  include ErrorHandler
10
+ include Symbolizable
9
11
 
10
12
  if Rails.gem_version >= Gem::Version.new('6.0.0')
11
13
  def validate_each(record, attribute, _value)
@@ -1,13 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/symbolizable.rb'
4
+
3
5
  module ActiveStorageValidations
4
6
  class SizeValidator < ActiveModel::EachValidator # :nodoc:
5
7
  include OptionProcUnfolding
6
8
  include ErrorHandler
9
+ include Symbolizable
7
10
 
8
11
  delegate :number_to_human_size, to: ActiveSupport::NumberHelper
9
12
 
10
- AVAILABLE_CHECKS = %i[less_than less_than_or_equal_to greater_than greater_than_or_equal_to between].freeze
13
+ AVAILABLE_CHECKS = %i[
14
+ less_than
15
+ less_than_or_equal_to
16
+ greater_than
17
+ greater_than_or_equal_to
18
+ between
19
+ ].freeze
20
+ ERROR_TYPES = %i[
21
+ file_size_not_less_than
22
+ file_size_not_less_than_or_equal_to
23
+ file_size_not_greater_than
24
+ file_size_not_greater_than_or_equal_to
25
+ file_size_not_between
26
+ ].freeze
11
27
 
12
28
  def check_validity!
13
29
  unless AVAILABLE_CHECKS.one? { |argument| options.key?(argument) }
@@ -31,7 +47,8 @@ module ActiveStorageValidations
31
47
  errors_options[:file_size] = number_to_human_size(file.blob.byte_size)
32
48
  errors_options[:min_size] = number_to_human_size(min_size(flat_options))
33
49
  errors_options[:max_size] = number_to_human_size(max_size(flat_options))
34
- error_type = "file_size_not_#{flat_options.keys.first}".to_sym
50
+ keys = AVAILABLE_CHECKS & flat_options.keys
51
+ error_type = "file_size_not_#{keys.first}".to_sym
35
52
 
36
53
  add_error(record, attribute, error_type, **errors_options)
37
54
  break
@@ -39,6 +56,8 @@ module ActiveStorageValidations
39
56
  end
40
57
 
41
58
  def content_size_valid?(file_size, flat_options)
59
+ return false if file_size < 0
60
+
42
61
  if flat_options[:between].present?
43
62
  flat_options[:between].include?(file_size)
44
63
  elsif flat_options[:less_than].present?
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageValidations
4
- VERSION = '1.1.1'
4
+ VERSION = '1.1.2'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_storage_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-10-26 00:00:00.000000000 Z
11
+ date: 2023-11-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: 5.2.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest-focus
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.4'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: combustion
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -220,6 +234,7 @@ files:
220
234
  - lib/active_storage_validations.rb
221
235
  - lib/active_storage_validations/aspect_ratio_validator.rb
222
236
  - lib/active_storage_validations/attached_validator.rb
237
+ - lib/active_storage_validations/concerns/symbolizable.rb
223
238
  - lib/active_storage_validations/content_type_validator.rb
224
239
  - lib/active_storage_validations/dimension_validator.rb
225
240
  - lib/active_storage_validations/engine.rb
@@ -227,6 +242,7 @@ files:
227
242
  - lib/active_storage_validations/limit_validator.rb
228
243
  - lib/active_storage_validations/matchers.rb
229
244
  - lib/active_storage_validations/matchers/attached_validator_matcher.rb
245
+ - lib/active_storage_validations/matchers/concerns/validatable.rb
230
246
  - lib/active_storage_validations/matchers/content_type_validator_matcher.rb
231
247
  - lib/active_storage_validations/matchers/dimension_validator_matcher.rb
232
248
  - lib/active_storage_validations/matchers/size_validator_matcher.rb
@@ -240,7 +256,8 @@ files:
240
256
  homepage: https://github.com/igorkasyanchuk/active_storage_validations
241
257
  licenses:
242
258
  - MIT
243
- metadata: {}
259
+ metadata:
260
+ rubygems_mfa_required: 'true'
244
261
  post_install_message:
245
262
  rdoc_options: []
246
263
  require_paths: