active_storage_validations 0.9.7 → 2.0.2

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 (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