active_storage_validations 1.1.2 → 1.1.4

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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +86 -59
  3. data/lib/active_storage_validations/aspect_ratio_validator.rb +47 -22
  4. data/lib/active_storage_validations/attached_validator.rb +12 -3
  5. data/lib/active_storage_validations/concerns/errorable.rb +38 -0
  6. data/lib/active_storage_validations/concerns/symbolizable.rb +8 -6
  7. data/lib/active_storage_validations/content_type_validator.rb +41 -6
  8. data/lib/active_storage_validations/dimension_validator.rb +15 -15
  9. data/lib/active_storage_validations/limit_validator.rb +44 -7
  10. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +128 -0
  11. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +20 -23
  12. data/lib/active_storage_validations/matchers/concerns/active_storageable.rb +17 -0
  13. data/lib/active_storage_validations/matchers/concerns/allow_blankable.rb +26 -0
  14. data/lib/active_storage_validations/matchers/concerns/contextable.rb +35 -0
  15. data/lib/active_storage_validations/matchers/concerns/messageable.rb +26 -0
  16. data/lib/active_storage_validations/matchers/concerns/rspecable.rb +25 -0
  17. data/lib/active_storage_validations/matchers/concerns/validatable.rb +45 -43
  18. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +39 -25
  19. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +61 -44
  20. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +41 -24
  21. data/lib/active_storage_validations/matchers.rb +1 -0
  22. data/lib/active_storage_validations/metadata.rb +42 -28
  23. data/lib/active_storage_validations/processable_image_validator.rb +14 -5
  24. data/lib/active_storage_validations/size_validator.rb +7 -6
  25. data/lib/active_storage_validations/version.rb +1 -1
  26. data/lib/active_storage_validations.rb +1 -1
  27. metadata +9 -3
  28. 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 ErrorHandler
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, file_metadata)
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 file_metadata[:width].to_i <= 0 || file_metadata[:height].to_i <= 0
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] && file_metadata[:width] < flat_options[:width][:min]) ||
102
- (flat_options[:height][:min] && file_metadata[:height] < 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] && file_metadata[:width] > flat_options[:width][:max]) ||
112
- (flat_options[:height][:max] && file_metadata[:height] > 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] && (file_metadata[length] < flat_options[length][:min] || file_metadata[length] > flat_options[length][:max])
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] && file_metadata[length] < 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] && file_metadata[length] > 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 file_metadata[length] != flat_options[length]
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 ErrorHandler
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
- unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
15
- raise ArgumentError, 'You must pass either :max or :min to the validator'
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 #{@attribute_name} must be attached"
30
+ "validate that :#{@attribute_name} must be attached"
21
31
  end
22
32
 
23
- def with_message(message)
24
- @custom_message = message
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
- def failure_message
37
- "is expected to validate attached of #{@attribute_name}"
38
- end
39
-
40
- def failure_message_when_negated
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 validate_custom_message?
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
- 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
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