active_storage_validations 1.1.2 → 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +86 -59
- data/lib/active_storage_validations/aspect_ratio_validator.rb +47 -22
- data/lib/active_storage_validations/attached_validator.rb +12 -3
- data/lib/active_storage_validations/concerns/errorable.rb +38 -0
- data/lib/active_storage_validations/concerns/symbolizable.rb +8 -6
- data/lib/active_storage_validations/content_type_validator.rb +41 -6
- data/lib/active_storage_validations/dimension_validator.rb +15 -15
- data/lib/active_storage_validations/limit_validator.rb +44 -7
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +128 -0
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +20 -23
- data/lib/active_storage_validations/matchers/concerns/active_storageable.rb +17 -0
- data/lib/active_storage_validations/matchers/concerns/allow_blankable.rb +26 -0
- data/lib/active_storage_validations/matchers/concerns/contextable.rb +35 -0
- data/lib/active_storage_validations/matchers/concerns/messageable.rb +26 -0
- data/lib/active_storage_validations/matchers/concerns/rspecable.rb +25 -0
- data/lib/active_storage_validations/matchers/concerns/validatable.rb +45 -43
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +39 -25
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +61 -44
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +41 -24
- data/lib/active_storage_validations/matchers.rb +1 -0
- data/lib/active_storage_validations/metadata.rb +42 -28
- data/lib/active_storage_validations/processable_image_validator.rb +14 -5
- data/lib/active_storage_validations/size_validator.rb +7 -6
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +1 -1
- metadata +9 -3
- data/lib/active_storage_validations/error_handler.rb +0 -21
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/errorable.rb'
|
3
4
|
require_relative 'concerns/symbolizable.rb'
|
4
5
|
require_relative 'metadata.rb'
|
5
6
|
|
6
7
|
module ActiveStorageValidations
|
7
8
|
class DimensionValidator < ActiveModel::EachValidator # :nodoc
|
8
9
|
include OptionProcUnfolding
|
9
|
-
include
|
10
|
+
include Errorable
|
10
11
|
include Symbolizable
|
11
12
|
|
12
13
|
AVAILABLE_CHECKS = %i[width height min max].freeze
|
@@ -46,7 +47,6 @@ module ActiveStorageValidations
|
|
46
47
|
flat_options
|
47
48
|
end
|
48
49
|
|
49
|
-
|
50
50
|
def check_validity!
|
51
51
|
unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
52
52
|
raise ArgumentError, 'You must pass either :width, :height, :min or :max to the validator'
|
@@ -64,7 +64,7 @@ module ActiveStorageValidations
|
|
64
64
|
files = Array.wrap(changes.is_a?(ActiveStorage::Attached::Changes::CreateMany) ? changes.attachables : changes.attachable)
|
65
65
|
files.each do |file|
|
66
66
|
metadata = Metadata.new(file).metadata
|
67
|
-
next if is_valid?(record, attribute, metadata)
|
67
|
+
next if is_valid?(record, attribute, file, metadata)
|
68
68
|
break
|
69
69
|
end
|
70
70
|
end
|
@@ -78,19 +78,19 @@ module ActiveStorageValidations
|
|
78
78
|
# Analyze file first if not analyzed to get all required metadata.
|
79
79
|
file.analyze; file.reload unless file.analyzed?
|
80
80
|
metadata = file.metadata rescue {}
|
81
|
-
next if is_valid?(record, attribute, metadata)
|
81
|
+
next if is_valid?(record, attribute, file, metadata)
|
82
82
|
break
|
83
83
|
end
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
87
|
|
88
|
-
def is_valid?(record, attribute,
|
88
|
+
def is_valid?(record, attribute, file, metadata)
|
89
89
|
flat_options = process_options(record)
|
90
|
-
errors_options = initialize_error_options(options)
|
90
|
+
errors_options = initialize_error_options(options, file)
|
91
91
|
|
92
92
|
# Validation fails unless file metadata contains valid width and height.
|
93
|
-
if
|
93
|
+
if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
|
94
94
|
add_error(record, attribute, :image_metadata_missing, **errors_options)
|
95
95
|
return false
|
96
96
|
end
|
@@ -98,8 +98,8 @@ module ActiveStorageValidations
|
|
98
98
|
# Validation based on checks :min and :max (:min, :max has higher priority to :width, :height).
|
99
99
|
if flat_options[:min] || flat_options[:max]
|
100
100
|
if flat_options[:min] && (
|
101
|
-
(flat_options[:width][:min] &&
|
102
|
-
(flat_options[:height][:min] &&
|
101
|
+
(flat_options[:width][:min] && metadata[:width] < flat_options[:width][:min]) ||
|
102
|
+
(flat_options[:height][:min] && metadata[:height] < flat_options[:height][:min])
|
103
103
|
)
|
104
104
|
errors_options[:width] = flat_options[:width][:min]
|
105
105
|
errors_options[:height] = flat_options[:height][:min]
|
@@ -108,8 +108,8 @@ module ActiveStorageValidations
|
|
108
108
|
return false
|
109
109
|
end
|
110
110
|
if flat_options[:max] && (
|
111
|
-
(flat_options[:width][:max] &&
|
112
|
-
(flat_options[:height][:max] &&
|
111
|
+
(flat_options[:width][:max] && metadata[:width] > flat_options[:width][:max]) ||
|
112
|
+
(flat_options[:height][:max] && metadata[:height] > flat_options[:height][:max])
|
113
113
|
)
|
114
114
|
errors_options[:width] = flat_options[:width][:max]
|
115
115
|
errors_options[:height] = flat_options[:height][:max]
|
@@ -125,7 +125,7 @@ module ActiveStorageValidations
|
|
125
125
|
[:width, :height].each do |length|
|
126
126
|
next unless flat_options[length]
|
127
127
|
if flat_options[length].is_a?(Hash)
|
128
|
-
if flat_options[length][:in] && (
|
128
|
+
if flat_options[length][:in] && (metadata[length] < flat_options[length][:min] || metadata[length] > flat_options[length][:max])
|
129
129
|
error_type = :"dimension_#{length}_inclusion"
|
130
130
|
errors_options[:min] = flat_options[length][:min]
|
131
131
|
errors_options[:max] = flat_options[length][:max]
|
@@ -133,13 +133,13 @@ module ActiveStorageValidations
|
|
133
133
|
add_error(record, attribute, error_type, **errors_options)
|
134
134
|
width_or_height_invalid = true
|
135
135
|
else
|
136
|
-
if flat_options[length][:min] &&
|
136
|
+
if flat_options[length][:min] && metadata[length] < flat_options[length][:min]
|
137
137
|
error_type = :"dimension_#{length}_greater_than_or_equal_to"
|
138
138
|
errors_options[:length] = flat_options[length][:min]
|
139
139
|
|
140
140
|
add_error(record, attribute, error_type, **errors_options)
|
141
141
|
width_or_height_invalid = true
|
142
|
-
elsif flat_options[length][:max] &&
|
142
|
+
elsif flat_options[length][:max] && metadata[length] > flat_options[length][:max]
|
143
143
|
error_type = :"dimension_#{length}_less_than_or_equal_to"
|
144
144
|
errors_options[:length] = flat_options[length][:max]
|
145
145
|
|
@@ -148,7 +148,7 @@ module ActiveStorageValidations
|
|
148
148
|
end
|
149
149
|
end
|
150
150
|
else
|
151
|
-
if
|
151
|
+
if metadata[length] != flat_options[length]
|
152
152
|
error_type = :"dimension_#{length}_equal_to"
|
153
153
|
errors_options[:length] = flat_options[length]
|
154
154
|
|
@@ -1,32 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/errorable.rb'
|
3
4
|
require_relative 'concerns/symbolizable.rb'
|
4
5
|
|
5
6
|
module ActiveStorageValidations
|
6
7
|
class LimitValidator < ActiveModel::EachValidator # :nodoc:
|
7
8
|
include OptionProcUnfolding
|
8
|
-
include
|
9
|
+
include Errorable
|
9
10
|
include Symbolizable
|
10
11
|
|
11
12
|
AVAILABLE_CHECKS = %i[max min].freeze
|
13
|
+
ERROR_TYPES = %i[
|
14
|
+
limit_out_of_range
|
15
|
+
].freeze
|
12
16
|
|
13
17
|
def check_validity!
|
14
|
-
|
15
|
-
|
16
|
-
end
|
18
|
+
ensure_at_least_one_validator_option
|
19
|
+
ensure_arguments_validity
|
17
20
|
end
|
18
21
|
|
19
22
|
def validate_each(record, attribute, _)
|
20
23
|
files = Array.wrap(record.send(attribute)).reject { |file| file.blank? }.compact.uniq
|
21
24
|
flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
|
25
|
+
|
26
|
+
return true if files_count_valid?(files.count, flat_options)
|
27
|
+
|
22
28
|
errors_options = initialize_error_options(options)
|
23
29
|
errors_options[:min] = flat_options[:min]
|
24
30
|
errors_options[:max] = flat_options[:max]
|
25
|
-
|
26
|
-
return true if files_count_valid?(files.count, flat_options)
|
27
|
-
add_error(record, attribute, :limit_out_of_range, **errors_options)
|
31
|
+
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
|
28
32
|
end
|
29
33
|
|
34
|
+
private
|
35
|
+
|
30
36
|
def files_count_valid?(count, flat_options)
|
31
37
|
if flat_options[:max].present? && flat_options[:min].present?
|
32
38
|
count >= flat_options[:min] && count <= flat_options[:max]
|
@@ -36,5 +42,36 @@ module ActiveStorageValidations
|
|
36
42
|
count >= flat_options[:min]
|
37
43
|
end
|
38
44
|
end
|
45
|
+
|
46
|
+
def ensure_at_least_one_validator_option
|
47
|
+
unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
48
|
+
raise ArgumentError, 'You must pass either :max or :min to the validator'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def ensure_arguments_validity
|
53
|
+
return true if min_max_are_proc? || min_or_max_is_proc_and_other_not_present?
|
54
|
+
|
55
|
+
raise ArgumentError, 'You must pass integers to :min and :max' if min_or_max_defined_and_not_integer?
|
56
|
+
raise ArgumentError, 'You must pass a higher value to :max than to :min' if min_higher_than_max?
|
57
|
+
end
|
58
|
+
|
59
|
+
def min_max_are_proc?
|
60
|
+
options[:min]&.is_a?(Proc) && options[:max]&.is_a?(Proc)
|
61
|
+
end
|
62
|
+
|
63
|
+
def min_or_max_is_proc_and_other_not_present?
|
64
|
+
(options[:min]&.is_a?(Proc) && options[:max].nil?) ||
|
65
|
+
(options[:min].nil? && options[:max]&.is_a?(Proc))
|
66
|
+
end
|
67
|
+
|
68
|
+
def min_or_max_defined_and_not_integer?
|
69
|
+
(options.key?(:min) && !options[:min].is_a?(Integer)) ||
|
70
|
+
(options.key?(:max) && !options[:max].is_a?(Integer))
|
71
|
+
end
|
72
|
+
|
73
|
+
def min_higher_than_max?
|
74
|
+
options[:min] > options[:max] if options[:min].is_a?(Integer) && options[:max].is_a?(Integer)
|
75
|
+
end
|
39
76
|
end
|
40
77
|
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'concerns/active_storageable.rb'
|
4
|
+
require_relative 'concerns/allow_blankable.rb'
|
5
|
+
require_relative 'concerns/contextable.rb'
|
6
|
+
require_relative 'concerns/messageable.rb'
|
7
|
+
require_relative 'concerns/rspecable.rb'
|
8
|
+
require_relative 'concerns/validatable.rb'
|
9
|
+
|
10
|
+
module ActiveStorageValidations
|
11
|
+
module Matchers
|
12
|
+
def validate_aspect_ratio_of(name, expected_aspect_ratio)
|
13
|
+
AspectRatioValidatorMatcher.new(name, expected_aspect_ratio)
|
14
|
+
end
|
15
|
+
|
16
|
+
class AspectRatioValidatorMatcher
|
17
|
+
include ActiveStorageable
|
18
|
+
include AllowBlankable
|
19
|
+
include Contextable
|
20
|
+
include Messageable
|
21
|
+
include Rspecable
|
22
|
+
include Validatable
|
23
|
+
|
24
|
+
def initialize(attribute_name)
|
25
|
+
initialize_allow_blankable
|
26
|
+
initialize_contextable
|
27
|
+
initialize_messageable
|
28
|
+
initialize_rspecable
|
29
|
+
@attribute_name = attribute_name
|
30
|
+
@allowed_aspect_ratios = @rejected_aspect_ratios = []
|
31
|
+
end
|
32
|
+
|
33
|
+
def description
|
34
|
+
"validate the aspect ratios allowed on :#{@attribute_name}."
|
35
|
+
end
|
36
|
+
|
37
|
+
def failure_message
|
38
|
+
"is expected to validate aspect ratio of :#{@attribute_name}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def allowing(*aspect_ratios)
|
42
|
+
@allowed_aspect_ratios = aspect_ratios.flatten
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def rejecting(*aspect_ratios)
|
47
|
+
@rejected_aspect_ratios = aspect_ratios.flatten
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def matches?(subject)
|
52
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
53
|
+
|
54
|
+
is_a_valid_active_storage_attribute? &&
|
55
|
+
is_context_valid? &&
|
56
|
+
is_allowing_blank? &&
|
57
|
+
is_custom_message_valid? &&
|
58
|
+
all_allowed_aspect_ratios_allowed? &&
|
59
|
+
all_rejected_aspect_ratios_rejected?
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
def all_allowed_aspect_ratios_allowed?
|
65
|
+
@allowed_aspect_ratios_not_allowed ||= @allowed_aspect_ratios.reject { |aspect_ratio| aspect_ratio_allowed?(aspect_ratio) }
|
66
|
+
@allowed_aspect_ratios_not_allowed.empty?
|
67
|
+
end
|
68
|
+
|
69
|
+
def all_rejected_aspect_ratios_rejected?
|
70
|
+
@rejected_aspect_ratios_not_rejected ||= @rejected_aspect_ratios.select { |aspect_ratio| aspect_ratio_allowed?(aspect_ratio) }
|
71
|
+
@rejected_aspect_ratios_not_rejected.empty?
|
72
|
+
end
|
73
|
+
|
74
|
+
def aspect_ratio_allowed?(aspect_ratio)
|
75
|
+
width, height = valid_width_and_height_for(aspect_ratio)
|
76
|
+
|
77
|
+
mock_dimensions_for(attach_file, width, height) do
|
78
|
+
validate
|
79
|
+
is_valid?
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def is_custom_message_valid?
|
84
|
+
return true unless @custom_message
|
85
|
+
|
86
|
+
mock_dimensions_for(attach_file, -1, -1) do
|
87
|
+
validate
|
88
|
+
has_an_error_message_which_is_custom_message?
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def attach_file
|
93
|
+
@subject.public_send(@attribute_name).attach(dummy_file)
|
94
|
+
@subject.public_send(@attribute_name)
|
95
|
+
end
|
96
|
+
|
97
|
+
def dummy_file
|
98
|
+
{
|
99
|
+
io: Tempfile.new('Hello world!'),
|
100
|
+
filename: 'test.png',
|
101
|
+
content_type: 'image/png'
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
def mock_dimensions_for(attachment, width, height)
|
106
|
+
Matchers.mock_metadata(attachment, width, height) do
|
107
|
+
yield
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def valid_width_and_height_for(aspect_ratio)
|
112
|
+
case aspect_ratio
|
113
|
+
when :square then [100, 100]
|
114
|
+
when :portrait then [100, 200]
|
115
|
+
when :landscape then [200, 100]
|
116
|
+
when validator_class::ASPECT_RATIO_REGEX
|
117
|
+
aspect_ratio =~ validator_class::ASPECT_RATIO_REGEX
|
118
|
+
x = Regexp.last_match(1).to_i
|
119
|
+
y = Regexp.last_match(2).to_i
|
120
|
+
|
121
|
+
[100 * x, 100 * y]
|
122
|
+
else
|
123
|
+
[-1, -1]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -1,5 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/active_storageable.rb'
|
4
|
+
require_relative 'concerns/contextable.rb'
|
5
|
+
require_relative 'concerns/messageable.rb'
|
6
|
+
require_relative 'concerns/rspecable.rb'
|
3
7
|
require_relative 'concerns/validatable.rb'
|
4
8
|
|
5
9
|
module ActiveStorageValidations
|
@@ -9,46 +13,39 @@ module ActiveStorageValidations
|
|
9
13
|
end
|
10
14
|
|
11
15
|
class AttachedValidatorMatcher
|
16
|
+
include ActiveStorageable
|
17
|
+
include Contextable
|
18
|
+
include Messageable
|
19
|
+
include Rspecable
|
12
20
|
include Validatable
|
13
21
|
|
14
22
|
def initialize(attribute_name)
|
23
|
+
initialize_contextable
|
24
|
+
initialize_messageable
|
25
|
+
initialize_rspecable
|
15
26
|
@attribute_name = attribute_name
|
16
|
-
@custom_message = nil
|
17
27
|
end
|
18
28
|
|
19
29
|
def description
|
20
|
-
"validate
|
30
|
+
"validate that :#{@attribute_name} must be attached"
|
21
31
|
end
|
22
32
|
|
23
|
-
def
|
24
|
-
|
25
|
-
self
|
33
|
+
def failure_message
|
34
|
+
"is expected to validate attachment of :#{@attribute_name}"
|
26
35
|
end
|
27
36
|
|
28
37
|
def matches?(subject)
|
29
38
|
@subject = subject.is_a?(Class) ? subject.new : subject
|
30
|
-
responds_to_methods &&
|
31
|
-
is_valid_when_file_attached? &&
|
32
|
-
is_invalid_when_file_not_attached? &&
|
33
|
-
validate_custom_message?
|
34
|
-
end
|
35
39
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
"is expected to not validate attached of #{@attribute_name}"
|
40
|
+
is_a_valid_active_storage_attribute? &&
|
41
|
+
is_context_valid? &&
|
42
|
+
is_custom_message_valid? &&
|
43
|
+
is_valid_when_file_attached? &&
|
44
|
+
is_invalid_when_file_not_attached?
|
42
45
|
end
|
43
46
|
|
44
47
|
private
|
45
48
|
|
46
|
-
def responds_to_methods
|
47
|
-
@subject.respond_to?(@attribute_name) &&
|
48
|
-
@subject.public_send(@attribute_name).respond_to?(:attach) &&
|
49
|
-
@subject.public_send(@attribute_name).respond_to?(:detach)
|
50
|
-
end
|
51
|
-
|
52
49
|
def is_valid_when_file_attached?
|
53
50
|
attach_dummy_file unless file_attached?
|
54
51
|
validate
|
@@ -61,7 +58,7 @@ module ActiveStorageValidations
|
|
61
58
|
!is_valid?
|
62
59
|
end
|
63
60
|
|
64
|
-
def
|
61
|
+
def is_custom_message_valid?
|
65
62
|
return true unless @custom_message
|
66
63
|
|
67
64
|
detach_file if file_attached?
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
module ActiveStorageable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def is_a_valid_active_storage_attribute?
|
11
|
+
@subject.respond_to?(@attribute_name) &&
|
12
|
+
@subject.public_send(@attribute_name).respond_to?(:attach) &&
|
13
|
+
@subject.public_send(@attribute_name).respond_to?(:detach)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
module AllowBlankable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def initialize_allow_blankable
|
9
|
+
@allow_blank = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def allow_blank
|
13
|
+
@allow_blank = true
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def is_allowing_blank?
|
20
|
+
return true unless @allow_blank
|
21
|
+
|
22
|
+
validate
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
module Contextable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def initialize_contextable
|
9
|
+
@context = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def on(context)
|
13
|
+
@context = context
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def is_context_valid?
|
20
|
+
return true if !@context && !(attribute_validator && attribute_validator.options[:on])
|
21
|
+
|
22
|
+
raise ArgumentError, "This validator matcher needs the #on option to work since its validator has one" if !@context
|
23
|
+
raise ArgumentError, "This validator matcher option only allows a symbol or an array" if !(@context.is_a?(Symbol) || @context.is_a?(Array))
|
24
|
+
|
25
|
+
if @context.is_a?(Array) && attribute_validator.options[:on].is_a?(Array)
|
26
|
+
@context.to_set == attribute_validator.options[:on].to_set
|
27
|
+
elsif @context.is_a?(Symbol) && attribute_validator.options[:on].is_a?(Symbol)
|
28
|
+
@context == attribute_validator.options[:on]
|
29
|
+
else
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
module Messageable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def initialize_messageable
|
9
|
+
@custom_message = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_message(custom_message)
|
13
|
+
@custom_message = custom_message
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def has_an_error_message_which_is_custom_message?
|
20
|
+
validator_errors_for_attribute.one? do |error|
|
21
|
+
error[:error] == @custom_message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
module Rspecable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
def initialize_rspecable
|
9
|
+
@failure_message_artefacts = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def description
|
13
|
+
raise NotImplementedError, "#{self.class} did not define #{__method__}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def failure_message
|
17
|
+
raise NotImplementedError, "#{self.class} did not define #{__method__}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure_message_when_negated
|
21
|
+
failure_message.sub(/is expected to validate/, 'is expected not to validate')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,46 +1,48 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
module Validatable
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def validate
|
11
|
+
@subject.validate(@context)
|
12
|
+
end
|
13
|
+
|
14
|
+
def validator_errors_for_attribute
|
15
|
+
@subject.errors.details[@attribute_name].select do |error|
|
16
|
+
error[:validator_type] == validator_class.to_sym
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def is_valid?
|
21
|
+
validator_errors_for_attribute.none? do |error|
|
22
|
+
error[:error].in?(available_errors)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def available_errors
|
27
|
+
[
|
28
|
+
*validator_class::ERROR_TYPES,
|
29
|
+
*error_from_custom_message
|
30
|
+
].compact
|
31
|
+
end
|
32
|
+
|
33
|
+
def validator_class
|
34
|
+
self.class.name.gsub(/::Matchers|Matcher/, '').constantize
|
35
|
+
end
|
36
|
+
|
37
|
+
def attribute_validator
|
38
|
+
@subject.class.validators_on(@attribute_name).find do |validator|
|
39
|
+
validator.class == validator_class
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def error_from_custom_message
|
44
|
+
attribute_validator.options[:message]
|
45
|
+
end
|
44
46
|
end
|
45
47
|
end
|
46
48
|
end
|