active_storage_validations 0.9.7 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +737 -229
  3. data/config/locales/da.yml +53 -0
  4. data/config/locales/de.yml +50 -19
  5. data/config/locales/en.yml +50 -19
  6. data/config/locales/es.yml +50 -19
  7. data/config/locales/fr.yml +50 -19
  8. data/config/locales/it.yml +50 -19
  9. data/config/locales/ja.yml +50 -19
  10. data/config/locales/nl.yml +50 -19
  11. data/config/locales/pl.yml +50 -19
  12. data/config/locales/pt-BR.yml +50 -19
  13. data/config/locales/ru.yml +50 -19
  14. data/config/locales/sv.yml +53 -0
  15. data/config/locales/tr.yml +50 -19
  16. data/config/locales/uk.yml +50 -19
  17. data/config/locales/vi.yml +50 -19
  18. data/config/locales/zh-CN.yml +53 -0
  19. data/lib/active_storage_validations/analyzer/audio_analyzer.rb +58 -0
  20. data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +60 -0
  21. data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +47 -0
  22. data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +57 -0
  23. data/lib/active_storage_validations/analyzer/image_analyzer.rb +49 -0
  24. data/lib/active_storage_validations/analyzer/null_analyzer.rb +18 -0
  25. data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +61 -0
  26. data/lib/active_storage_validations/analyzer/video_analyzer.rb +130 -0
  27. data/lib/active_storage_validations/analyzer.rb +87 -0
  28. data/lib/active_storage_validations/aspect_ratio_validator.rb +154 -99
  29. data/lib/active_storage_validations/attached_validator.rb +22 -5
  30. data/lib/active_storage_validations/base_comparison_validator.rb +71 -0
  31. data/lib/active_storage_validations/content_type_validator.rb +206 -25
  32. data/lib/active_storage_validations/dimension_validator.rb +105 -82
  33. data/lib/active_storage_validations/duration_validator.rb +55 -0
  34. data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +49 -0
  35. data/lib/active_storage_validations/extensors/asv_marcelable.rb +12 -0
  36. data/lib/active_storage_validations/limit_validator.rb +75 -16
  37. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
  38. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +48 -25
  39. data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +140 -0
  40. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +94 -59
  41. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +97 -55
  42. data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +39 -0
  43. data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +127 -0
  44. data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +78 -0
  45. data/lib/active_storage_validations/matchers/shared/asv_active_storageable.rb +19 -0
  46. data/lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb +28 -0
  47. data/lib/active_storage_validations/matchers/shared/asv_attachable.rb +72 -0
  48. data/lib/active_storage_validations/matchers/shared/asv_contextable.rb +49 -0
  49. data/lib/active_storage_validations/matchers/shared/asv_messageable.rb +28 -0
  50. data/lib/active_storage_validations/matchers/shared/asv_rspecable.rb +27 -0
  51. data/lib/active_storage_validations/matchers/shared/asv_validatable.rb +56 -0
  52. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +17 -71
  53. data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +47 -0
  54. data/lib/active_storage_validations/matchers.rb +11 -16
  55. data/lib/active_storage_validations/processable_file_validator.rb +37 -0
  56. data/lib/active_storage_validations/railtie.rb +11 -0
  57. data/lib/active_storage_validations/shared/asv_active_storageable.rb +30 -0
  58. data/lib/active_storage_validations/shared/asv_analyzable.rb +80 -0
  59. data/lib/active_storage_validations/shared/asv_attachable.rb +204 -0
  60. data/lib/active_storage_validations/shared/asv_errorable.rb +40 -0
  61. data/lib/active_storage_validations/shared/asv_loggable.rb +11 -0
  62. data/lib/active_storage_validations/shared/asv_optionable.rb +29 -0
  63. data/lib/active_storage_validations/shared/asv_symbolizable.rb +14 -0
  64. data/lib/active_storage_validations/size_validator.rb +24 -40
  65. data/lib/active_storage_validations/total_size_validator.rb +51 -0
  66. data/lib/active_storage_validations/version.rb +1 -1
  67. data/lib/active_storage_validations.rb +20 -6
  68. metadata +127 -21
  69. data/lib/active_storage_validations/metadata.rb +0 -123
@@ -1,20 +1,50 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'shared/asv_active_storageable'
4
+ require_relative 'shared/asv_allow_blankable'
5
+ require_relative 'shared/asv_attachable'
6
+ require_relative 'shared/asv_contextable'
7
+ require_relative 'shared/asv_messageable'
8
+ require_relative 'shared/asv_rspecable'
9
+ require_relative 'shared/asv_validatable'
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 ASVActiveStorageable
19
+ include ASVAllowBlankable
20
+ include ASVAttachable
21
+ include ASVContextable
22
+ include ASVMessageable
23
+ include ASVRspecable
24
+ include ASVValidatable
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_smaller_than_min? && width_larger_than_min? && width_smaller_than_max? && width_larger_than_max? && width_equals? &&
69
- height_smaller_than_min? && height_larger_than_min? && height_smaller_than_max? && height_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
@@ -93,59 +121,73 @@ module ActiveStorageValidations
93
121
  ((@height_min || 0) + (@height_max || 2000)) / 2
94
122
  end
95
123
 
96
- def width_smaller_than_min?
97
- @width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height, 'width')
124
+ def width_not_smaller_than_min?
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
- def width_larger_than_max?
109
- @width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height, 'width')
136
+ def width_not_larger_than_max?
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
- def height_smaller_than_min?
117
- @height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1, 'height')
144
+ def height_not_smaller_than_min?
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
- def height_larger_than_max?
129
- @height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1, 'height')
156
+ def height_not_larger_than_max?
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].all? { |error| error[:error].to_s.exclude?(exclude_error_message) }
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?
144
184
  end
145
185
  end
146
186
 
147
- def attachment_for(width, height)
148
- { 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: width, height: height }) do
189
+ yield
190
+ end
149
191
  end
150
192
  end
151
193
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_comparison_validator_matcher'
4
+
5
+ module ActiveStorageValidations
6
+ module Matchers
7
+ def validate_duration_of(attribute_name)
8
+ DurationValidatorMatcher.new(attribute_name)
9
+ end
10
+
11
+ class DurationValidatorMatcher < BaseComparisonValidatorMatcher
12
+ def description
13
+ "validate file duration of :#{@attribute_name}"
14
+ end
15
+
16
+ def failure_message
17
+ message = ["is expected to validate file duration of :#{@attribute_name}"]
18
+ build_failure_message(message)
19
+ message.join("\n")
20
+ end
21
+
22
+ private
23
+
24
+ def failure_message_unit
25
+ "seconds"
26
+ end
27
+
28
+ def smallest_measurement
29
+ 1.second
30
+ end
31
+
32
+ def mock_value_for(io, duration)
33
+ Matchers.mock_metadata(io, { duration: duration }) do
34
+ yield
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'shared/asv_active_storageable'
4
+ require_relative 'shared/asv_allow_blankable'
5
+ require_relative 'shared/asv_attachable'
6
+ require_relative 'shared/asv_contextable'
7
+ require_relative 'shared/asv_messageable'
8
+ require_relative 'shared/asv_rspecable'
9
+ require_relative 'shared/asv_validatable'
10
+
11
+ module ActiveStorageValidations
12
+ module Matchers
13
+ def validate_limits_of(name)
14
+ LimitValidatorMatcher.new(name)
15
+ end
16
+
17
+ class LimitValidatorMatcher
18
+ include ASVActiveStorageable
19
+ include ASVAllowBlankable
20
+ include ASVAttachable
21
+ include ASVContextable
22
+ include ASVMessageable
23
+ include ASVRspecable
24
+ include ASVValidatable
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
+ @min = @max = nil
33
+ end
34
+
35
+ def description
36
+ "validate the limit files of :#{@attribute_name}"
37
+ end
38
+
39
+ def failure_message
40
+ message = ["is expected to validate limit file of :#{@attribute_name}"]
41
+ build_failure_message(message)
42
+ message.join("\n")
43
+ end
44
+
45
+ def min(count)
46
+ @min = count
47
+ self
48
+ end
49
+
50
+ def max(count)
51
+ @max = count
52
+ self
53
+ end
54
+
55
+ def matches?(subject)
56
+ @subject = subject.is_a?(Class) ? subject.new : subject
57
+
58
+ is_a_valid_active_storage_attribute? &&
59
+ is_context_valid? &&
60
+ is_custom_message_valid? &&
61
+ file_count_not_smaller_than_min? &&
62
+ file_count_equal_min? &&
63
+ file_count_larger_than_min? &&
64
+ file_count_smaller_than_max? &&
65
+ file_count_equal_max? &&
66
+ file_count_not_larger_than_max?
67
+ end
68
+
69
+ private
70
+
71
+ def build_failure_message(message)
72
+ return unless @failure_message_artefacts.present?
73
+
74
+ message << " but there seem to have issues with the matcher methods you used, since:"
75
+ @failure_message_artefacts.each do |error_case|
76
+ message << " validation failed when provided with #{error_case[:count]} file(s)"
77
+ end
78
+ message << " whereas it should have passed"
79
+ end
80
+
81
+ def file_count_not_smaller_than_min?
82
+ @min.nil? || @min.zero? || !passes_validation_with_limits(@min - 1)
83
+ end
84
+
85
+ def file_count_equal_min?
86
+ @min.nil? || @min.zero? || passes_validation_with_limits(@min)
87
+ end
88
+
89
+ def file_count_larger_than_min?
90
+ @min.nil? || @min.zero? || @min == @max || passes_validation_with_limits(@min + 1)
91
+ end
92
+
93
+ def file_count_smaller_than_max?
94
+ @max.nil? || @min == @max || passes_validation_with_limits(@max - 1)
95
+ end
96
+
97
+ def file_count_equal_max?
98
+ @max.nil? || passes_validation_with_limits(@max)
99
+ end
100
+
101
+ def file_count_not_larger_than_max?
102
+ @max.nil? || !passes_validation_with_limits(@max + 1)
103
+ end
104
+
105
+ def passes_validation_with_limits(count)
106
+ attach_files(count)
107
+ validate
108
+ detach_files
109
+ is_valid? || add_failure_message_artefact(count)
110
+ end
111
+
112
+ def is_custom_message_valid?
113
+ return true if !@custom_message || (@min&.zero? && @max.nil?)
114
+
115
+ @min.nil? ? attach_files(@max + 1) : attach_files(@min - 1)
116
+ validate
117
+ detach_files
118
+ has_an_error_message_which_is_custom_message?
119
+ end
120
+
121
+ def add_failure_message_artefact(count)
122
+ @failure_message_artefacts << { count: count }
123
+ false
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'shared/asv_active_storageable'
4
+ require_relative 'shared/asv_allow_blankable'
5
+ require_relative 'shared/asv_attachable'
6
+ require_relative 'shared/asv_contextable'
7
+ require_relative 'shared/asv_messageable'
8
+ require_relative 'shared/asv_rspecable'
9
+ require_relative 'shared/asv_validatable'
10
+
11
+ module ActiveStorageValidations
12
+ module Matchers
13
+ def validate_processable_file_of(name)
14
+ ProcessableFileValidatorMatcher.new(name)
15
+ end
16
+
17
+ class ProcessableFileValidatorMatcher
18
+ include ASVActiveStorageable
19
+ include ASVAllowBlankable
20
+ include ASVAttachable
21
+ include ASVContextable
22
+ include ASVMessageable
23
+ include ASVRspecable
24
+ include ASVValidatable
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_file_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_file_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
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module ActiveStorageValidations
6
+ module Matchers
7
+ module ASVActiveStorageable
8
+ extend ActiveSupport::Concern
9
+
10
+ private
11
+
12
+ def is_a_valid_active_storage_attribute?
13
+ @subject.respond_to?(@attribute_name) &&
14
+ @subject.public_send(@attribute_name).respond_to?(:attach) &&
15
+ @subject.public_send(@attribute_name).respond_to?(:detach)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module ActiveStorageValidations
6
+ module Matchers
7
+ module ASVAllowBlankable
8
+ extend ActiveSupport::Concern
9
+
10
+ def initialize_allow_blankable
11
+ @allow_blank = nil
12
+ end
13
+
14
+ def allow_blank
15
+ @allow_blank = true
16
+ self
17
+ end
18
+
19
+ private
20
+
21
+ def is_allowing_blank?
22
+ return true unless @allow_blank
23
+
24
+ validate
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module ActiveStorageValidations
6
+ module Matchers
7
+ module ASVAttachable
8
+ extend ActiveSupport::Concern
9
+
10
+ private
11
+
12
+ def attach_file(file = dummy_file)
13
+ @subject.public_send(@attribute_name).attach(file)
14
+ @subject.public_send(@attribute_name)
15
+ end
16
+
17
+ def attach_files(count)
18
+ return unless count.positive?
19
+
20
+ file_array = Array.new(count, dummy_file)
21
+
22
+ @subject.public_send(@attribute_name).attach(file_array)
23
+ end
24
+
25
+ def detach_file
26
+ @subject.attachment_changes.delete(@attribute_name.to_s)
27
+ end
28
+ alias :detach_files :detach_file
29
+
30
+ def file_attached?
31
+ @subject.public_send(@attribute_name).attached?
32
+ end
33
+
34
+ def dummy_blob
35
+ ActiveStorage::Blob.create_and_upload!(**dummy_file)
36
+ end
37
+
38
+ def dummy_file
39
+ {
40
+ io: io,
41
+ filename: 'test.png',
42
+ content_type: 'image/png'
43
+ }
44
+ end
45
+
46
+ def processable_image
47
+ {
48
+ io: StringIO.new(image_data),
49
+ filename: 'processable_image.png',
50
+ content_type: 'image/png'
51
+ }
52
+ end
53
+
54
+ def not_processable_image
55
+ {
56
+ io: Tempfile.new('.'),
57
+ filename: 'not_processable_image.txt',
58
+ content_type: 'text/plain'
59
+ }
60
+ end
61
+
62
+ def io
63
+ @io ||= Tempfile.new('Hello world!')
64
+ end
65
+
66
+ def image_data
67
+ # Binary data for a 1x1 transparent PNG image
68
+ "\x89PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00\nIDATx\x9Cc\x00\x01\x00\x00\x05\x00\x01\r\n\x2D\xB4\x00\x00\x00\x00IEND\xAE\x42\x60\x82"
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module ActiveStorageValidations
6
+ module Matchers
7
+ module ASVContextable
8
+ extend ActiveSupport::Concern
9
+
10
+ def initialize_contextable
11
+ @context = nil
12
+ end
13
+
14
+ def on(context)
15
+ @context = context
16
+ self
17
+ end
18
+
19
+ private
20
+
21
+ def is_context_valid?
22
+ return true if !@context && attribute_validators.none? { |validator| validator.options[:on] }
23
+
24
+ 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] }
25
+ raise ArgumentError, "This validator matcher option only allows a symbol or an array" if !(@context.is_a?(Symbol) || @context.is_a?(Array))
26
+
27
+ if @context.is_a?(Array)
28
+ (validator_contexts & @context.map(&:to_s)) == validator_contexts || raise_context_not_listed_error
29
+ elsif @context.is_a?(Symbol)
30
+ validator_contexts.include?(@context.to_s) || raise_context_not_listed_error
31
+ end
32
+ end
33
+
34
+ def validator_contexts
35
+ attribute_validators.map do |validator|
36
+ case validator.options[:on]
37
+ when Array then validator.options[:on].map { |context| context.to_s }
38
+ when NilClass then nil
39
+ else validator.options[:on].to_s
40
+ end
41
+ end.flatten.compact
42
+ end
43
+
44
+ def raise_context_not_listed_error
45
+ raise ArgumentError, "One of the provided contexts to the #on method is not found in any of the listed contexts for this attribute"
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/concern'
4
+
5
+ module ActiveStorageValidations
6
+ module Matchers
7
+ module ASVMessageable
8
+ extend ActiveSupport::Concern
9
+
10
+ def initialize_messageable
11
+ @custom_message = nil
12
+ end
13
+
14
+ def with_message(custom_message)
15
+ @custom_message = custom_message
16
+ self
17
+ end
18
+
19
+ private
20
+
21
+ def has_an_error_message_which_is_custom_message?
22
+ validator_errors_for_attribute.one? do |error|
23
+ error[:custom_message] == @custom_message
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end