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.
- checksums.yaml +4 -4
- data/README.md +7 -0
- data/config/locales/de.yml +1 -0
- data/config/locales/es.yml +1 -0
- data/config/locales/fr.yml +1 -0
- data/config/locales/it.yml +1 -0
- data/config/locales/ja.yml +1 -0
- data/config/locales/nl.yml +1 -0
- data/config/locales/pl.yml +1 -0
- data/config/locales/ru.yml +2 -1
- data/config/locales/tr.yml +1 -0
- data/config/locales/uk.yml +1 -0
- data/config/locales/vi.yml +1 -0
- data/config/locales/zh-CN.yml +1 -0
- data/lib/active_storage_validations/aspect_ratio_validator.rb +2 -0
- data/lib/active_storage_validations/attached_validator.rb +6 -1
- data/lib/active_storage_validations/concerns/symbolizable.rb +10 -0
- data/lib/active_storage_validations/content_type_validator.rb +6 -2
- data/lib/active_storage_validations/dimension_validator.rb +15 -0
- data/lib/active_storage_validations/error_handler.rb +9 -6
- data/lib/active_storage_validations/limit_validator.rb +4 -1
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +39 -15
- data/lib/active_storage_validations/matchers/concerns/validatable.rb +46 -0
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +39 -32
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +43 -13
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +49 -10
- data/lib/active_storage_validations/processable_image_validator.rb +2 -0
- data/lib/active_storage_validations/size_validator.rb +21 -2
- data/lib/active_storage_validations/version.rb +1 -1
- metadata +20 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 288f21f798861bd7e44ad225052620bfcfd2d58efe73f0aeda8f4964ace98f56
|
4
|
+
data.tar.gz: b19cce430aae6c1eed3c2d29cbafa6d07b36423f122157603d4391a892267d64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
data/config/locales/de.yml
CHANGED
data/config/locales/es.yml
CHANGED
data/config/locales/fr.yml
CHANGED
@@ -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"
|
data/config/locales/it.yml
CHANGED
@@ -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"
|
data/config/locales/ja.yml
CHANGED
data/config/locales/nl.yml
CHANGED
data/config/locales/pl.yml
CHANGED
data/config/locales/ru.yml
CHANGED
@@ -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: "не является допустимым изображением"
|
data/config/locales/tr.yml
CHANGED
data/config/locales/uk.yml
CHANGED
data/config/locales/vi.yml
CHANGED
data/config/locales/zh-CN.yml
CHANGED
@@ -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,
|
17
|
+
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
|
13
18
|
end
|
14
19
|
end
|
15
20
|
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,
|
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
|
-
|
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,
|
11
|
-
|
12
|
-
return if record.errors.added?(attribute,
|
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
|
-
|
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 &&
|
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
|
46
|
-
|
47
|
-
|
48
|
-
|
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
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
57
|
-
|
67
|
+
detach_file if file_attached?
|
68
|
+
validate
|
69
|
+
has_an_error_message_which_is_custom_message?
|
58
70
|
end
|
59
71
|
|
60
|
-
def
|
61
|
-
|
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
|
65
|
-
|
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
|
-
|
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 @
|
45
|
-
message << "Accept content types: #{allowed_types.join(", ")}"
|
46
|
-
message << "#{@
|
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 @
|
50
|
-
message << "Reject content types: #{rejected_types.join(", ")}"
|
51
|
-
message << "#{@
|
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
|
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
|
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
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
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
|
-
|
95
|
-
|
96
|
-
|
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
|
102
|
-
@
|
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
|
-
|
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? &&
|
69
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
152
|
-
|
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
|
-
|
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(
|
90
|
-
io
|
91
|
-
|
92
|
-
|
93
|
-
|
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[
|
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
|
-
|
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?
|
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.
|
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-
|
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:
|