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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +145 -73
  3. data/config/locales/da.yml +33 -0
  4. data/config/locales/de.yml +6 -0
  5. data/config/locales/en.yml +5 -0
  6. data/config/locales/es.yml +6 -0
  7. data/config/locales/fr.yml +6 -0
  8. data/config/locales/it.yml +6 -0
  9. data/config/locales/ja.yml +6 -0
  10. data/config/locales/nl.yml +6 -0
  11. data/config/locales/pl.yml +6 -0
  12. data/config/locales/pt-BR.yml +5 -0
  13. data/config/locales/ru.yml +7 -1
  14. data/config/locales/sv.yml +10 -1
  15. data/config/locales/tr.yml +6 -0
  16. data/config/locales/uk.yml +6 -0
  17. data/config/locales/vi.yml +6 -0
  18. data/config/locales/zh-CN.yml +6 -0
  19. data/lib/active_storage_validations/aspect_ratio_validator.rb +49 -22
  20. data/lib/active_storage_validations/attached_validator.rb +18 -4
  21. data/lib/active_storage_validations/base_size_validator.rb +66 -0
  22. data/lib/active_storage_validations/concerns/errorable.rb +38 -0
  23. data/lib/active_storage_validations/concerns/symbolizable.rb +12 -0
  24. data/lib/active_storage_validations/content_type_validator.rb +47 -8
  25. data/lib/active_storage_validations/dimension_validator.rb +30 -15
  26. data/lib/active_storage_validations/limit_validator.rb +48 -8
  27. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
  28. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +46 -33
  29. data/lib/active_storage_validations/matchers/base_size_validator_matcher.rb +134 -0
  30. data/lib/active_storage_validations/matchers/concerns/active_storageable.rb +17 -0
  31. data/lib/active_storage_validations/matchers/concerns/allow_blankable.rb +26 -0
  32. data/lib/active_storage_validations/matchers/concerns/attachable.rb +48 -0
  33. data/lib/active_storage_validations/matchers/concerns/contextable.rb +47 -0
  34. data/lib/active_storage_validations/matchers/concerns/messageable.rb +26 -0
  35. data/lib/active_storage_validations/matchers/concerns/rspecable.rb +25 -0
  36. data/lib/active_storage_validations/matchers/concerns/validatable.rb +54 -0
  37. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +76 -52
  38. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +93 -55
  39. data/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb +78 -0
  40. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +9 -88
  41. data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +40 -0
  42. data/lib/active_storage_validations/matchers.rb +3 -0
  43. data/lib/active_storage_validations/metadata.rb +60 -28
  44. data/lib/active_storage_validations/processable_image_validator.rb +16 -5
  45. data/lib/active_storage_validations/size_validator.rb +18 -43
  46. data/lib/active_storage_validations/total_size_validator.rb +49 -0
  47. data/lib/active_storage_validations/version.rb +1 -1
  48. data/lib/active_storage_validations.rb +3 -2
  49. metadata +42 -26
  50. data/lib/active_storage_validations/error_handler.rb +0 -18
@@ -2,20 +2,47 @@
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/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
+
5
14
  module ActiveStorageValidations
6
15
  module Matchers
7
- def validate_content_type_of(name)
8
- ContentTypeValidatorMatcher.new(name)
16
+ def validate_content_type_of(attribute_name)
17
+ ContentTypeValidatorMatcher.new(attribute_name)
9
18
  end
10
19
 
11
20
  class ContentTypeValidatorMatcher
21
+ include ActiveStorageable
22
+ include AllowBlankable
23
+ include Attachable
24
+ include Contextable
25
+ include Messageable
26
+ include Rspecable
27
+ include Validatable
28
+
12
29
  def initialize(attribute_name)
30
+ initialize_allow_blankable
31
+ initialize_contextable
32
+ initialize_messageable
33
+ initialize_rspecable
13
34
  @attribute_name = attribute_name
14
- @custom_message = nil
35
+ @allowed_types = @rejected_types = []
15
36
  end
16
37
 
17
38
  def description
18
- "validate the content types allowed on attachment #{@attribute_name}"
39
+ "validate the content types allowed on :#{@attribute_name}"
40
+ end
41
+
42
+ def failure_message
43
+ message = ["is expected to validate the content types of :#{@attribute_name}"]
44
+ build_failure_message(message)
45
+ message.join("\n")
19
46
  end
20
47
 
21
48
  def allowing(*types)
@@ -28,83 +55,80 @@ module ActiveStorageValidations
28
55
  self
29
56
  end
30
57
 
31
- def with_message(message)
32
- @custom_message = message
33
- self
34
- end
35
-
36
58
  def matches?(subject)
37
59
  @subject = subject.is_a?(Class) ? subject.new : subject
38
- responds_to_methods && allowed_types_allowed? && rejected_types_rejected? && validate_custom_message?
60
+
61
+ is_a_valid_active_storage_attribute? &&
62
+ is_context_valid? &&
63
+ is_allowing_blank? &&
64
+ is_custom_message_valid? &&
65
+ all_allowed_types_allowed? &&
66
+ all_rejected_types_rejected?
39
67
  end
40
68
 
41
- def failure_message
42
- message = ["Expected #{@attribute_name}"]
69
+ protected
43
70
 
44
- if @allowed_types
45
- message << "Accept content types: #{allowed_types.join(", ")}"
46
- message << "#{@missing_allowed_types.join(", ")} were rejected"
71
+ def build_failure_message(message)
72
+ if @allowed_types_not_allowed.present?
73
+ message << " the following content type#{'s' if @allowed_types.count > 1} should be allowed: :#{@allowed_types.join(", :")}"
74
+ message << " but #{pluralize(@allowed_types_not_allowed)} rejected"
47
75
  end
48
76
 
49
- if @rejected_types
50
- message << "Reject content types: #{rejected_types.join(", ")}"
51
- message << "#{@missing_rejected_types.join(", ")} were accepted"
77
+ if @rejected_types_not_rejected.present?
78
+ message << " the following content type#{'s' if @rejected_types.count > 1} should be rejected: :#{@rejected_types.join(", :")}"
79
+ message << " but #{pluralize(@rejected_types_not_rejected)} accepted"
52
80
  end
53
-
54
- message.join("\n")
55
81
  end
56
82
 
57
- protected
58
-
59
- def responds_to_methods
60
- @subject.respond_to?(@attribute_name) &&
61
- @subject.public_send(@attribute_name).respond_to?(:attach) &&
62
- @subject.public_send(@attribute_name).respond_to?(:detach)
63
- end
64
-
65
- def allowed_types
66
- @allowed_types || []
83
+ def pluralize(types)
84
+ if types.count == 1
85
+ ":#{types[0]} was"
86
+ else
87
+ ":#{types.join(", :")} were"
88
+ end
67
89
  end
68
90
 
69
- def rejected_types
70
- @rejected_types || []
91
+ def all_allowed_types_allowed?
92
+ @allowed_types_not_allowed ||= @allowed_types.reject { |type| type_allowed?(type) }
93
+ @allowed_types_not_allowed.empty?
71
94
  end
72
95
 
73
- def allowed_types_allowed?
74
- @missing_allowed_types ||= allowed_types.reject { |type| type_allowed?(type) }
75
- @missing_allowed_types.none?
96
+ def all_rejected_types_rejected?
97
+ @rejected_types_not_rejected ||= @rejected_types.select { |type| type_allowed?(type) }
98
+ @rejected_types_not_rejected.empty?
76
99
  end
77
100
 
78
- def rejected_types_rejected?
79
- @missing_rejected_types ||= rejected_types.select { |type| type_allowed?(type) }
80
- @missing_rejected_types.none?
101
+ def type_allowed?(type)
102
+ attach_file_of_type(type)
103
+ validate
104
+ detach_file
105
+ is_valid?
81
106
  end
82
107
 
83
- def type_allowed?(type)
108
+ def attach_file_of_type(type)
84
109
  @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
110
  end
90
111
 
91
- def validate_custom_message?
112
+ def is_custom_message_valid?
92
113
  return true unless @custom_message
93
114
 
94
- @subject.public_send(@attribute_name).attach(attachment_for('fake/fake'))
95
- @subject.validate
96
- @subject.errors.details[@attribute_name].select{|error| error[:content_type]}.all? do |error|
97
- error[:error].to_s.include?(error_message)
98
- end
115
+ attach_invalid_content_type_file
116
+ validate
117
+ has_an_error_message_which_is_custom_message?
99
118
  end
100
119
 
101
- def error_message
102
- @custom_message || :content_type_invalid.to_s
120
+ def attach_invalid_content_type_file
121
+ @subject.public_send(@attribute_name).attach(attachment_for('fake/fake'))
103
122
  end
104
123
 
105
124
  def attachment_for(type)
106
125
  suffix = type.to_s.split('/').last
107
- { io: Tempfile.new('.'), filename: "test.#{suffix}", content_type: type }
126
+
127
+ {
128
+ io: Tempfile.new('.'),
129
+ filename: "test.#{suffix}",
130
+ content_type: type
131
+ }
108
132
  end
109
133
  end
110
134
  end
@@ -1,20 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'concerns/active_storageable.rb'
4
+ require_relative 'concerns/allow_blankable.rb'
5
+ require_relative 'concerns/attachable'
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
+
3
11
  module ActiveStorageValidations
4
12
  module Matchers
5
- def validate_dimensions_of(name)
6
- DimensionValidatorMatcher.new(name)
13
+ def validate_dimensions_of(attribute_name)
14
+ DimensionValidatorMatcher.new(attribute_name)
7
15
  end
8
16
 
9
17
  class DimensionValidatorMatcher
18
+ include ActiveStorageable
19
+ include AllowBlankable
20
+ include Attachable
21
+ include Contextable
22
+ include Messageable
23
+ include Rspecable
24
+ include Validatable
25
+
10
26
  def initialize(attribute_name)
27
+ initialize_allow_blankable
28
+ initialize_contextable
29
+ initialize_messageable
30
+ initialize_rspecable
11
31
  @attribute_name = attribute_name
12
32
  @width_min = @width_max = @height_min = @height_max = nil
13
- @custom_message = nil
14
33
  end
15
34
 
16
35
  def description
17
- "validate image dimensions of #{@attribute_name}"
36
+ "validate the image dimensions of :#{@attribute_name}"
37
+ end
38
+
39
+ def failure_message
40
+ message = ["is expected to validate dimensions of :#{@attribute_name}"]
41
+ build_failure_message(message)
42
+ message.join("\n")
43
+ end
44
+
45
+ def width(width)
46
+ @width_min = @width_max = width
47
+ self
18
48
  end
19
49
 
20
50
  def width_min(width)
@@ -27,13 +57,13 @@ module ActiveStorageValidations
27
57
  self
28
58
  end
29
59
 
30
- def with_message(message)
31
- @custom_message = message
60
+ def width_between(range)
61
+ @width_min, @width_max = range.first, range.last
32
62
  self
33
63
  end
34
64
 
35
- def width(width)
36
- @width_min = @width_max = width
65
+ def height(height)
66
+ @height_min = @height_max = height
37
67
  self
38
68
  end
39
69
 
@@ -47,42 +77,40 @@ module ActiveStorageValidations
47
77
  self
48
78
  end
49
79
 
50
- def width_between(range)
51
- @width_min, @width_max = range.first, range.last
52
- self
53
- end
54
-
55
80
  def height_between(range)
56
81
  @height_min, @height_max = range.first, range.last
57
82
  self
58
83
  end
59
84
 
60
- def height(height)
61
- @height_min = @height_max = height
62
- self
63
- end
64
-
65
85
  def matches?(subject)
66
86
  @subject = subject.is_a?(Class) ? subject.new : subject
67
- responds_to_methods &&
68
- width_not_smaller_than_min? && width_larger_than_min? && width_smaller_than_max? && width_not_larger_than_max? && width_equals? &&
69
- height_not_smaller_than_min? && height_larger_than_min? && height_smaller_than_max? && height_not_larger_than_max? && height_equals?
70
- end
71
87
 
72
- def failure_message
73
- <<~MESSAGE
74
- is expected to validate dimensions of #{@attribute_name}
75
- width between #{@width_min} and #{@width_max}
76
- height between #{@height_min} and #{@height_max}
77
- MESSAGE
88
+ is_a_valid_active_storage_attribute? &&
89
+ is_context_valid? &&
90
+ is_allowing_blank? &&
91
+ is_custom_message_valid? &&
92
+ width_not_smaller_than_min? &&
93
+ width_larger_than_min? &&
94
+ width_smaller_than_max? &&
95
+ width_not_larger_than_max? &&
96
+ width_equals? &&
97
+ height_not_smaller_than_min? &&
98
+ height_larger_than_min? &&
99
+ height_smaller_than_max? &&
100
+ height_not_larger_than_max? &&
101
+ height_equals?
78
102
  end
79
103
 
80
104
  protected
81
105
 
82
- def responds_to_methods
83
- @subject.respond_to?(@attribute_name) &&
84
- @subject.public_send(@attribute_name).respond_to?(:attach) &&
85
- @subject.public_send(@attribute_name).respond_to?(:detach)
106
+ def build_failure_message(message)
107
+ return unless @failure_message_artefacts.present?
108
+
109
+ message << " but there seem to have issues with the matcher methods you used, since:"
110
+ @failure_message_artefacts.each do |error_case|
111
+ message << " validation failed when provided with a #{error_case[:width]}x#{error_case[:height]}px test image"
112
+ end
113
+ message << " whereas it should have passed"
86
114
  end
87
115
 
88
116
  def valid_width
@@ -94,62 +122,72 @@ module ActiveStorageValidations
94
122
  end
95
123
 
96
124
  def width_not_smaller_than_min?
97
- @width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height, 'width')
125
+ @width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height)
98
126
  end
99
127
 
100
128
  def width_larger_than_min?
101
- @width_min.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_min + 1, valid_height, 'width')
129
+ @width_min.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_min + 1, valid_height)
102
130
  end
103
131
 
104
132
  def width_smaller_than_max?
105
- @width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height, 'width')
133
+ @width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height)
106
134
  end
107
135
 
108
136
  def width_not_larger_than_max?
109
- @width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height, 'width')
137
+ @width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height)
110
138
  end
111
139
 
112
140
  def width_equals?
113
- @width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height, 'width')
141
+ @width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height)
114
142
  end
115
143
 
116
144
  def height_not_smaller_than_min?
117
- @height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1, 'height')
145
+ @height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1)
118
146
  end
119
147
 
120
148
  def height_larger_than_min?
121
- @height_min.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_min + 1, 'height')
149
+ @height_min.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_min + 1)
122
150
  end
123
151
 
124
152
  def height_smaller_than_max?
125
- @height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1, 'height')
153
+ @height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1)
126
154
  end
127
155
 
128
156
  def height_not_larger_than_max?
129
- @height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1, 'height')
157
+ @height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1)
130
158
  end
131
159
 
132
160
  def height_equals?
133
- @height_min.nil? || @height_min != @height_max || passes_validation_with_dimensions(valid_width, @height_min, 'height')
161
+ @height_min.nil? || @height_min != @height_max || passes_validation_with_dimensions(valid_width, @height_min)
134
162
  end
135
163
 
136
- def passes_validation_with_dimensions(width, height, check)
137
- @subject.public_send(@attribute_name).attach attachment_for(width, height)
164
+ def passes_validation_with_dimensions(width, height)
165
+ mock_dimensions_for(attach_file, width, height) do
166
+ validate
167
+ detach_file
168
+ is_valid? || add_failure_message_artefact(width, height)
169
+ end
170
+ end
138
171
 
139
- attachment = @subject.public_send(@attribute_name)
140
- Matchers.mock_metadata(attachment, width, height) do
141
- @subject.validate
142
- exclude_error_message = @custom_message || "dimension_#{check}"
143
- @subject.errors.details[@attribute_name].none? do |error|
144
- error[:error].to_s.include?(exclude_error_message) ||
145
- error[:error].to_s.include?("dimension_min") ||
146
- error[:error].to_s.include?("dimension_max")
147
- end
172
+ def add_failure_message_artefact(width, height)
173
+ @failure_message_artefacts << { width: width, height: height }
174
+ false
175
+ end
176
+
177
+ def is_custom_message_valid?
178
+ return true unless @custom_message
179
+
180
+ mock_dimensions_for(attach_file, -1, -1) do
181
+ validate
182
+ detach_file
183
+ has_an_error_message_which_is_custom_message?
148
184
  end
149
185
  end
150
186
 
151
- def attachment_for(width, height)
152
- { io: Tempfile.new('Hello world!'), filename: 'test.png', content_type: 'image/png' }
187
+ def mock_dimensions_for(attachment, width, height)
188
+ Matchers.mock_metadata(attachment, width, height) do
189
+ yield
190
+ end
153
191
  end
154
192
  end
155
193
  end
@@ -0,0 +1,78 @@
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_processable_image_of(name)
14
+ ProcessableImageValidatorMatcher.new(name)
15
+ end
16
+
17
+ class ProcessableImageValidatorMatcher
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
+ end
33
+
34
+ def description
35
+ "validate that :#{@attribute_name} is a processable image"
36
+ end
37
+
38
+ def failure_message
39
+ "is expected to validate the processable image of :#{@attribute_name}"
40
+ end
41
+
42
+ def matches?(subject)
43
+ @subject = subject.is_a?(Class) ? subject.new : subject
44
+
45
+ is_a_valid_active_storage_attribute? &&
46
+ is_context_valid? &&
47
+ is_custom_message_valid? &&
48
+ is_valid_when_image_processable? &&
49
+ is_invalid_when_image_not_processable?
50
+ end
51
+
52
+ private
53
+
54
+ def is_valid_when_image_processable?
55
+ attach_file(processable_image)
56
+ validate
57
+ detach_file
58
+ is_valid?
59
+ end
60
+
61
+ def is_invalid_when_image_not_processable?
62
+ attach_file(not_processable_image)
63
+ validate
64
+ detach_file
65
+ !is_valid?
66
+ end
67
+
68
+ def is_custom_message_valid?
69
+ return true unless @custom_message
70
+
71
+ attach_file(not_processable_image)
72
+ validate
73
+ detach_file
74
+ has_an_error_message_which_is_custom_message?
75
+ end
76
+ end
77
+ end
78
+ end
@@ -1,101 +1,22 @@
1
1
  # frozen_string_literal: true
2
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
3
+ require_relative 'base_size_validator_matcher'
4
+
5
5
  module ActiveStorageValidations
6
6
  module Matchers
7
- def validate_size_of(name)
8
- SizeValidatorMatcher.new(name)
7
+ def validate_size_of(attribute_name)
8
+ SizeValidatorMatcher.new(attribute_name)
9
9
  end
10
10
 
11
- class SizeValidatorMatcher
12
- def initialize(attribute_name)
13
- @attribute_name = attribute_name
14
- @min = @max = nil
15
- @custom_message = nil
16
- end
17
-
11
+ class SizeValidatorMatcher < BaseSizeValidatorMatcher
18
12
  def description
19
- "validate file size of #{@attribute_name}"
20
- end
21
-
22
- def less_than(size)
23
- @max = size - 1.byte
24
- self
25
- end
26
-
27
- def less_than_or_equal_to(size)
28
- @max = size
29
- self
30
- end
31
-
32
- def greater_than(size)
33
- @min = size + 1.byte
34
- self
35
- end
36
-
37
- def greater_than_or_equal_to(size)
38
- @min = size
39
- self
40
- end
41
-
42
- def between(range)
43
- @min, @max = range.first, range.last
44
- self
45
- end
46
-
47
- def with_message(message)
48
- @custom_message = message
49
- self
50
- end
51
-
52
- def matches?(subject)
53
- @subject = subject.is_a?(Class) ? subject.new : subject
54
- responds_to_methods && not_lower_than_min? && higher_than_min? && lower_than_max? && not_higher_than_max?
13
+ "validate file size of :#{@attribute_name}"
55
14
  end
56
15
 
57
16
  def failure_message
58
- "is expected to validate file size of #{@attribute_name} to be between #{@min} and #{@max} bytes"
59
- end
60
-
61
- def failure_message_when_negated
62
- "is expected to not validate file size of #{@attribute_name} to be between #{@min} and #{@max} bytes"
63
- end
64
-
65
- protected
66
-
67
- def responds_to_methods
68
- @subject.respond_to?(@attribute_name) &&
69
- @subject.public_send(@attribute_name).respond_to?(:attach) &&
70
- @subject.public_send(@attribute_name).respond_to?(:detach)
71
- end
72
-
73
- def not_lower_than_min?
74
- @min.nil? || !passes_validation_with_size(@min - 1)
75
- end
76
-
77
- def higher_than_min?
78
- @min.nil? || passes_validation_with_size(@min + 1)
79
- end
80
-
81
- def lower_than_max?
82
- @max.nil? || @max == Float::INFINITY || passes_validation_with_size(@max - 1)
83
- end
84
-
85
- def not_higher_than_max?
86
- @max.nil? || @max == Float::INFINITY || !passes_validation_with_size(@max + 1)
87
- end
88
-
89
- def passes_validation_with_size(new_size)
90
- io = Tempfile.new('Hello world!')
91
- Matchers.stub_method(io, :size, new_size) do
92
- @subject.public_send(@attribute_name).attach(io: io, filename: 'test.png', content_type: 'image/pg')
93
- @subject.validate
94
- exclude_error_message = @custom_message || "file_size_not_"
95
- @subject.errors.details[@attribute_name].none? do |error|
96
- error[:error].to_s.include?(exclude_error_message)
97
- end
98
- end
17
+ message = ["is expected to validate file size of :#{@attribute_name}"]
18
+ build_failure_message(message)
19
+ message.join("\n")
99
20
  end
100
21
  end
101
22
  end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_size_validator_matcher'
4
+
5
+ module ActiveStorageValidations
6
+ module Matchers
7
+ def validate_total_size_of(attribute_name)
8
+ TotalSizeValidatorMatcher.new(attribute_name)
9
+ end
10
+
11
+ class TotalSizeValidatorMatcher < BaseSizeValidatorMatcher
12
+ def description
13
+ "validate total file size of :#{@attribute_name}"
14
+ end
15
+
16
+ def failure_message
17
+ message = ["is expected to validate total file size of :#{@attribute_name}"]
18
+ build_failure_message(message)
19
+ message.join("\n")
20
+ end
21
+
22
+ protected
23
+
24
+ def attach_file
25
+ # We attach blobs instead of io for has_many_attached relation
26
+ @subject.public_send(@attribute_name).attach([dummy_blob])
27
+ @subject.public_send(@attribute_name)
28
+ end
29
+
30
+ def dummy_blob
31
+ ActiveStorage::Blob.create_and_upload!(
32
+ io: io,
33
+ filename: 'test.png',
34
+ content_type: 'image/png',
35
+ service_name: 'test'
36
+ )
37
+ end
38
+ end
39
+ end
40
+ end
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'active_storage_validations/matchers/aspect_ratio_validator_matcher'
3
4
  require 'active_storage_validations/matchers/attached_validator_matcher'
5
+ require 'active_storage_validations/matchers/processable_image_validator_matcher'
4
6
  require 'active_storage_validations/matchers/content_type_validator_matcher'
5
7
  require 'active_storage_validations/matchers/dimension_validator_matcher'
6
8
  require 'active_storage_validations/matchers/size_validator_matcher'
9
+ require 'active_storage_validations/matchers/total_size_validator_matcher'
7
10
 
8
11
  module ActiveStorageValidations
9
12
  module Matchers