active_storage_validations 1.1.1 → 1.2.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 +4 -4
- data/README.md +145 -73
- data/config/locales/da.yml +33 -0
- data/config/locales/de.yml +6 -0
- data/config/locales/en.yml +5 -0
- data/config/locales/es.yml +6 -0
- data/config/locales/fr.yml +6 -0
- data/config/locales/it.yml +6 -0
- data/config/locales/ja.yml +6 -0
- data/config/locales/nl.yml +6 -0
- data/config/locales/pl.yml +6 -0
- data/config/locales/pt-BR.yml +5 -0
- data/config/locales/ru.yml +7 -1
- data/config/locales/sv.yml +10 -1
- data/config/locales/tr.yml +6 -0
- data/config/locales/uk.yml +6 -0
- data/config/locales/vi.yml +6 -0
- data/config/locales/zh-CN.yml +6 -0
- data/lib/active_storage_validations/aspect_ratio_validator.rb +49 -22
- data/lib/active_storage_validations/attached_validator.rb +18 -4
- data/lib/active_storage_validations/base_size_validator.rb +66 -0
- data/lib/active_storage_validations/concerns/errorable.rb +38 -0
- data/lib/active_storage_validations/concerns/symbolizable.rb +12 -0
- data/lib/active_storage_validations/content_type_validator.rb +47 -8
- data/lib/active_storage_validations/dimension_validator.rb +30 -15
- data/lib/active_storage_validations/limit_validator.rb +48 -8
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +46 -33
- data/lib/active_storage_validations/matchers/base_size_validator_matcher.rb +134 -0
- 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/attachable.rb +48 -0
- data/lib/active_storage_validations/matchers/concerns/contextable.rb +47 -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 +54 -0
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +76 -52
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +93 -55
- data/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb +78 -0
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +9 -88
- data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +40 -0
- data/lib/active_storage_validations/matchers.rb +3 -0
- data/lib/active_storage_validations/metadata.rb +60 -28
- data/lib/active_storage_validations/processable_image_validator.rb +16 -5
- data/lib/active_storage_validations/size_validator.rb +18 -43
- data/lib/active_storage_validations/total_size_validator.rb +49 -0
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +3 -2
- metadata +42 -26
- data/lib/active_storage_validations/error_handler.rb +0 -18
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'concerns/active_storageable.rb'
|
4
|
+
require_relative 'concerns/allow_blankable.rb'
|
5
|
+
require_relative 'concerns/attachable.rb'
|
6
|
+
require_relative 'concerns/contextable.rb'
|
7
|
+
require_relative 'concerns/messageable.rb'
|
8
|
+
require_relative 'concerns/rspecable.rb'
|
9
|
+
require_relative 'concerns/validatable.rb'
|
10
|
+
|
11
|
+
module ActiveStorageValidations
|
12
|
+
module Matchers
|
13
|
+
def validate_aspect_ratio_of(attribute_name)
|
14
|
+
AspectRatioValidatorMatcher.new(attribute_name)
|
15
|
+
end
|
16
|
+
|
17
|
+
class AspectRatioValidatorMatcher
|
18
|
+
include ActiveStorageable
|
19
|
+
include AllowBlankable
|
20
|
+
include Attachable
|
21
|
+
include Contextable
|
22
|
+
include Messageable
|
23
|
+
include Rspecable
|
24
|
+
include Validatable
|
25
|
+
|
26
|
+
def initialize(attribute_name)
|
27
|
+
initialize_allow_blankable
|
28
|
+
initialize_contextable
|
29
|
+
initialize_messageable
|
30
|
+
initialize_rspecable
|
31
|
+
@attribute_name = attribute_name
|
32
|
+
@allowed_aspect_ratios = @rejected_aspect_ratios = []
|
33
|
+
end
|
34
|
+
|
35
|
+
def description
|
36
|
+
"validate the aspect ratios allowed on :#{@attribute_name}."
|
37
|
+
end
|
38
|
+
|
39
|
+
def failure_message
|
40
|
+
"is expected to validate aspect ratio of :#{@attribute_name}"
|
41
|
+
end
|
42
|
+
|
43
|
+
def allowing(*aspect_ratios)
|
44
|
+
@allowed_aspect_ratios = aspect_ratios.flatten
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def rejecting(*aspect_ratios)
|
49
|
+
@rejected_aspect_ratios = aspect_ratios.flatten
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
def matches?(subject)
|
54
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
55
|
+
|
56
|
+
is_a_valid_active_storage_attribute? &&
|
57
|
+
is_context_valid? &&
|
58
|
+
is_allowing_blank? &&
|
59
|
+
is_custom_message_valid? &&
|
60
|
+
all_allowed_aspect_ratios_allowed? &&
|
61
|
+
all_rejected_aspect_ratios_rejected?
|
62
|
+
end
|
63
|
+
|
64
|
+
protected
|
65
|
+
|
66
|
+
def all_allowed_aspect_ratios_allowed?
|
67
|
+
@allowed_aspect_ratios_not_allowed ||= @allowed_aspect_ratios.reject { |aspect_ratio| aspect_ratio_allowed?(aspect_ratio) }
|
68
|
+
@allowed_aspect_ratios_not_allowed.empty?
|
69
|
+
end
|
70
|
+
|
71
|
+
def all_rejected_aspect_ratios_rejected?
|
72
|
+
@rejected_aspect_ratios_not_rejected ||= @rejected_aspect_ratios.select { |aspect_ratio| aspect_ratio_allowed?(aspect_ratio) }
|
73
|
+
@rejected_aspect_ratios_not_rejected.empty?
|
74
|
+
end
|
75
|
+
|
76
|
+
def aspect_ratio_allowed?(aspect_ratio)
|
77
|
+
width, height = valid_width_and_height_for(aspect_ratio)
|
78
|
+
|
79
|
+
mock_dimensions_for(attach_file, width, height) do
|
80
|
+
validate
|
81
|
+
detach_file
|
82
|
+
is_valid?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def is_custom_message_valid?
|
87
|
+
return true unless @custom_message
|
88
|
+
|
89
|
+
mock_dimensions_for(attach_file, -1, -1) do
|
90
|
+
validate
|
91
|
+
detach_file
|
92
|
+
has_an_error_message_which_is_custom_message?
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def mock_dimensions_for(attachment, width, height)
|
97
|
+
Matchers.mock_metadata(attachment, width, height) do
|
98
|
+
yield
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def valid_width_and_height_for(aspect_ratio)
|
103
|
+
case aspect_ratio
|
104
|
+
when :square then [100, 100]
|
105
|
+
when :portrait then [100, 200]
|
106
|
+
when :landscape then [200, 100]
|
107
|
+
when validator_class::ASPECT_RATIO_REGEX
|
108
|
+
aspect_ratio =~ validator_class::ASPECT_RATIO_REGEX
|
109
|
+
x = Regexp.last_match(1).to_i
|
110
|
+
y = Regexp.last_match(2).to_i
|
111
|
+
|
112
|
+
[100 * x, 100 * y]
|
113
|
+
else
|
114
|
+
[-1, -1]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -1,68 +1,81 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'concerns/active_storageable.rb'
|
4
|
+
require_relative 'concerns/attachable.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
|
+
|
3
10
|
module ActiveStorageValidations
|
4
11
|
module Matchers
|
5
|
-
def validate_attached_of(
|
6
|
-
AttachedValidatorMatcher.new(
|
12
|
+
def validate_attached_of(attribute_name)
|
13
|
+
AttachedValidatorMatcher.new(attribute_name)
|
7
14
|
end
|
8
15
|
|
9
16
|
class AttachedValidatorMatcher
|
17
|
+
include ActiveStorageable
|
18
|
+
include Attachable
|
19
|
+
include Contextable
|
20
|
+
include Messageable
|
21
|
+
include Rspecable
|
22
|
+
include Validatable
|
23
|
+
|
10
24
|
def initialize(attribute_name)
|
25
|
+
initialize_contextable
|
26
|
+
initialize_messageable
|
27
|
+
initialize_rspecable
|
11
28
|
@attribute_name = attribute_name
|
12
|
-
@custom_message = nil
|
13
29
|
end
|
14
30
|
|
15
31
|
def description
|
16
|
-
"validate
|
32
|
+
"validate that :#{@attribute_name} must be attached"
|
17
33
|
end
|
18
34
|
|
19
|
-
def
|
20
|
-
|
21
|
-
self
|
35
|
+
def failure_message
|
36
|
+
"is expected to validate attachment of :#{@attribute_name}"
|
22
37
|
end
|
23
38
|
|
24
39
|
def matches?(subject)
|
25
40
|
@subject = subject.is_a?(Class) ? subject.new : subject
|
26
|
-
responds_to_methods && valid_when_attached && invalid_when_not_attached
|
27
|
-
end
|
28
|
-
|
29
|
-
def failure_message
|
30
|
-
"is expected to validate attached of #{@attribute_name}"
|
31
|
-
end
|
32
41
|
|
33
|
-
|
34
|
-
|
42
|
+
is_a_valid_active_storage_attribute? &&
|
43
|
+
is_context_valid? &&
|
44
|
+
is_custom_message_valid? &&
|
45
|
+
is_valid_when_file_attached? &&
|
46
|
+
is_invalid_when_file_not_attached?
|
35
47
|
end
|
36
48
|
|
37
49
|
private
|
38
50
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
|
51
|
+
def is_valid_when_file_attached?
|
52
|
+
attach_file unless file_attached?
|
53
|
+
validate
|
54
|
+
is_valid?
|
43
55
|
end
|
44
56
|
|
45
|
-
def
|
46
|
-
|
47
|
-
|
48
|
-
|
57
|
+
def is_invalid_when_file_not_attached?
|
58
|
+
detach_file if file_attached?
|
59
|
+
validate
|
60
|
+
!is_valid?
|
49
61
|
end
|
50
62
|
|
51
|
-
def
|
52
|
-
@
|
53
|
-
# Unset the direct relation since `detach` on an unpersisted record does not set `attached?` to false.
|
54
|
-
@subject.public_send("#{@attribute_name}=", nil)
|
63
|
+
def is_custom_message_valid?
|
64
|
+
return true unless @custom_message
|
55
65
|
|
56
|
-
|
57
|
-
|
66
|
+
detach_file if file_attached?
|
67
|
+
validate
|
68
|
+
has_an_error_message_which_is_custom_message?
|
58
69
|
end
|
59
70
|
|
60
|
-
def
|
61
|
-
@
|
71
|
+
def file_attached?
|
72
|
+
@subject.public_send(@attribute_name).attached?
|
62
73
|
end
|
63
74
|
|
64
|
-
def
|
65
|
-
|
75
|
+
def detach_file
|
76
|
+
@subject.public_send(@attribute_name).detach
|
77
|
+
# Unset the direct relation since `detach` on an unpersisted record does not set `attached?` to false.
|
78
|
+
@subject.public_send("#{@attribute_name}=", nil)
|
66
79
|
end
|
67
80
|
end
|
68
81
|
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Big thank you to the paperclip validation matchers:
|
4
|
+
# https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_size_matcher.rb
|
5
|
+
|
6
|
+
require_relative 'concerns/active_storageable.rb'
|
7
|
+
require_relative 'concerns/allow_blankable.rb'
|
8
|
+
require_relative 'concerns/attachable.rb'
|
9
|
+
require_relative 'concerns/contextable.rb'
|
10
|
+
require_relative 'concerns/messageable.rb'
|
11
|
+
require_relative 'concerns/rspecable.rb'
|
12
|
+
require_relative 'concerns/validatable.rb'
|
13
|
+
|
14
|
+
module ActiveStorageValidations
|
15
|
+
module Matchers
|
16
|
+
class BaseSizeValidatorMatcher
|
17
|
+
# BaseSizeValidatorMatcher is an abstract class and shouldn't be instantiated directly.
|
18
|
+
|
19
|
+
include ActiveStorageable
|
20
|
+
include AllowBlankable
|
21
|
+
include Attachable
|
22
|
+
include Contextable
|
23
|
+
include Messageable
|
24
|
+
include Rspecable
|
25
|
+
include Validatable
|
26
|
+
|
27
|
+
def initialize(attribute_name)
|
28
|
+
initialize_allow_blankable
|
29
|
+
initialize_contextable
|
30
|
+
initialize_messageable
|
31
|
+
initialize_rspecable
|
32
|
+
@attribute_name = attribute_name
|
33
|
+
@min = @max = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def less_than(size)
|
37
|
+
@max = size - 1.byte
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def less_than_or_equal_to(size)
|
42
|
+
@max = size
|
43
|
+
self
|
44
|
+
end
|
45
|
+
|
46
|
+
def greater_than(size)
|
47
|
+
@min = size + 1.byte
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def greater_than_or_equal_to(size)
|
52
|
+
@min = size
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
def between(range)
|
57
|
+
@min, @max = range.first, range.last
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
def matches?(subject)
|
62
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
63
|
+
|
64
|
+
is_a_valid_active_storage_attribute? &&
|
65
|
+
is_context_valid? &&
|
66
|
+
is_allowing_blank? &&
|
67
|
+
is_custom_message_valid? &&
|
68
|
+
not_lower_than_min? &&
|
69
|
+
higher_than_min? &&
|
70
|
+
lower_than_max? &&
|
71
|
+
not_higher_than_max?
|
72
|
+
end
|
73
|
+
|
74
|
+
protected
|
75
|
+
|
76
|
+
def build_failure_message(message)
|
77
|
+
return unless @failure_message_artefacts.present?
|
78
|
+
|
79
|
+
message << " but there seem to have issues with the matcher methods you used, since:"
|
80
|
+
@failure_message_artefacts.each do |error_case|
|
81
|
+
message << " validation failed when provided with a #{error_case[:size]} bytes test file"
|
82
|
+
end
|
83
|
+
message << " whereas it should have passed"
|
84
|
+
end
|
85
|
+
|
86
|
+
def not_lower_than_min?
|
87
|
+
@min.nil? || !passes_validation_with_size(@min - 1)
|
88
|
+
end
|
89
|
+
|
90
|
+
def higher_than_min?
|
91
|
+
@min.nil? || passes_validation_with_size(@min + 1)
|
92
|
+
end
|
93
|
+
|
94
|
+
def lower_than_max?
|
95
|
+
@max.nil? || @max == Float::INFINITY || passes_validation_with_size(@max - 1)
|
96
|
+
end
|
97
|
+
|
98
|
+
def not_higher_than_max?
|
99
|
+
@max.nil? || @max == Float::INFINITY || !passes_validation_with_size(@max + 1)
|
100
|
+
end
|
101
|
+
|
102
|
+
def passes_validation_with_size(size)
|
103
|
+
mock_size_for(io, size) do
|
104
|
+
attach_file
|
105
|
+
validate
|
106
|
+
detach_file
|
107
|
+
is_valid? || add_failure_message_artefact(size)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def add_failure_message_artefact(size)
|
112
|
+
@failure_message_artefacts << { size: size }
|
113
|
+
false
|
114
|
+
end
|
115
|
+
|
116
|
+
def is_custom_message_valid?
|
117
|
+
return true unless @custom_message
|
118
|
+
|
119
|
+
mock_size_for(io, -1.kilobytes) do
|
120
|
+
attach_file
|
121
|
+
validate
|
122
|
+
detach_file
|
123
|
+
has_an_error_message_which_is_custom_message?
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def mock_size_for(io, size)
|
128
|
+
Matchers.stub_method(io, :size, size) do
|
129
|
+
yield
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -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,48 @@
|
|
1
|
+
module ActiveStorageValidations
|
2
|
+
module Matchers
|
3
|
+
module Attachable
|
4
|
+
private
|
5
|
+
|
6
|
+
def attach_file(file = dummy_file)
|
7
|
+
@subject.public_send(@attribute_name).attach(file)
|
8
|
+
@subject.public_send(@attribute_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def dummy_file
|
12
|
+
{
|
13
|
+
io: io,
|
14
|
+
filename: 'test.png',
|
15
|
+
content_type: 'image/png'
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
def processable_image
|
20
|
+
{
|
21
|
+
io: File.open(Rails.root.join('public', 'image_1920x1080.png')),
|
22
|
+
filename: 'image_1920x1080_file.png',
|
23
|
+
content_type: 'image/png'
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def not_processable_image
|
28
|
+
{
|
29
|
+
io: Tempfile.new('.'),
|
30
|
+
filename: 'processable.txt',
|
31
|
+
content_type: 'text/plain'
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
def io
|
36
|
+
@io ||= Tempfile.new('Hello world!')
|
37
|
+
end
|
38
|
+
|
39
|
+
def detach_file
|
40
|
+
@subject.attachment_changes.delete(@attribute_name.to_s)
|
41
|
+
end
|
42
|
+
|
43
|
+
def file_attached?
|
44
|
+
@subject.public_send(@attribute_name).attached?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,47 @@
|
|
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_validators.none? { |validator| validator.options[:on] }
|
21
|
+
|
22
|
+
raise ArgumentError, "This validator matcher needs the #on option to work since its validator has one" if !@context && attribute_validators.all? { |validator| validator.options[:on] }
|
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)
|
26
|
+
(validator_contexts & @context.map(&:to_s)) == validator_contexts || raise_context_not_listed_error
|
27
|
+
elsif @context.is_a?(Symbol)
|
28
|
+
validator_contexts.include?(@context.to_s) || raise_context_not_listed_error
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def validator_contexts
|
33
|
+
attribute_validators.map do |validator|
|
34
|
+
case validator.options[:on]
|
35
|
+
when Array then validator.options[:on].map { |context| context.to_s }
|
36
|
+
when NilClass then nil
|
37
|
+
else validator.options[:on].to_s
|
38
|
+
end
|
39
|
+
end.flatten.compact
|
40
|
+
end
|
41
|
+
|
42
|
+
def raise_context_not_listed_error
|
43
|
+
raise ArgumentError, "One of the provided contexts to the #on method is not found in any of the listed contexts for this attribute"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
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
|
@@ -0,0 +1,54 @@
|
|
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
|
+
*errors_from_custom_messages
|
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 attribute_validators
|
44
|
+
@subject.class.validators_on(@attribute_name).select do |validator|
|
45
|
+
validator.class == validator_class
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def errors_from_custom_messages
|
50
|
+
attribute_validators.map { |validator| validator.options[:message] }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|