active_storage_validations 1.4.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +619 -213
  3. data/config/locales/da.yml +50 -30
  4. data/config/locales/de.yml +50 -30
  5. data/config/locales/en.yml +50 -30
  6. data/config/locales/es.yml +50 -30
  7. data/config/locales/fr.yml +50 -30
  8. data/config/locales/it.yml +50 -30
  9. data/config/locales/ja.yml +50 -30
  10. data/config/locales/nl.yml +50 -30
  11. data/config/locales/pl.yml +50 -30
  12. data/config/locales/pt-BR.yml +50 -30
  13. data/config/locales/ru.yml +50 -30
  14. data/config/locales/sv.yml +50 -30
  15. data/config/locales/tr.yml +50 -30
  16. data/config/locales/uk.yml +50 -30
  17. data/config/locales/vi.yml +50 -30
  18. data/config/locales/zh-CN.yml +50 -30
  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 +4 -4
  22. data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +11 -12
  23. data/lib/active_storage_validations/analyzer/image_analyzer.rb +9 -53
  24. data/lib/active_storage_validations/analyzer/null_analyzer.rb +2 -2
  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 +54 -1
  28. data/lib/active_storage_validations/aspect_ratio_validator.rb +15 -11
  29. data/lib/active_storage_validations/{base_size_validator.rb → base_comparison_validator.rb} +18 -16
  30. data/lib/active_storage_validations/content_type_validator.rb +56 -23
  31. data/lib/active_storage_validations/dimension_validator.rb +20 -19
  32. data/lib/active_storage_validations/duration_validator.rb +55 -0
  33. data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +49 -0
  34. data/lib/active_storage_validations/{marcel_extensor.rb → extensors/asv_marcelable.rb} +3 -0
  35. data/lib/active_storage_validations/limit_validator.rb +14 -2
  36. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +1 -1
  37. data/lib/active_storage_validations/matchers/{base_size_validator_matcher.rb → base_comparison_validator_matcher.rb} +31 -25
  38. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +7 -3
  39. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +1 -1
  40. data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +39 -0
  41. data/lib/active_storage_validations/matchers/{processable_image_validator_matcher.rb → processable_file_validator_matcher.rb} +5 -5
  42. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +18 -2
  43. data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +18 -2
  44. data/lib/active_storage_validations/matchers.rb +5 -3
  45. data/lib/active_storage_validations/{processable_image_validator.rb → processable_file_validator.rb} +4 -3
  46. data/lib/active_storage_validations/railtie.rb +5 -0
  47. data/lib/active_storage_validations/shared/asv_analyzable.rb +38 -3
  48. data/lib/active_storage_validations/shared/asv_attachable.rb +36 -15
  49. data/lib/active_storage_validations/size_validator.rb +11 -3
  50. data/lib/active_storage_validations/total_size_validator.rb +9 -3
  51. data/lib/active_storage_validations/version.rb +1 -1
  52. data/lib/active_storage_validations.rb +7 -3
  53. metadata +14 -8
  54. data/lib/active_storage_validations/content_type_spoof_detector.rb +0 -96
@@ -18,18 +18,19 @@ module ActiveStorageValidations
18
18
 
19
19
  AVAILABLE_CHECKS = %i[width height min max].freeze
20
20
  ERROR_TYPES = %i[
21
- image_metadata_missing
22
- dimension_min_inclusion
23
- dimension_max_inclusion
24
- dimension_width_inclusion
25
- dimension_height_inclusion
26
- dimension_width_greater_than_or_equal_to
27
- dimension_height_greater_than_or_equal_to
28
- dimension_width_less_than_or_equal_to
29
- dimension_height_less_than_or_equal_to
30
- dimension_width_equal_to
31
- dimension_height_equal_to
21
+ dimension_min_not_included_in
22
+ dimension_max_not_included_in
23
+ dimension_width_not_included_in
24
+ dimension_height_not_included_in
25
+ dimension_width_not_greater_than_or_equal_to
26
+ dimension_height_not_greater_than_or_equal_to
27
+ dimension_width_not_less_than_or_equal_to
28
+ dimension_height_not_less_than_or_equal_to
29
+ dimension_width_not_equal_to
30
+ dimension_height_not_equal_to
31
+ media_metadata_missing
32
32
  ].freeze
33
+ METADATA_KEYS = %i[width height].freeze
33
34
 
34
35
  def check_validity!
35
36
  unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
@@ -40,7 +41,7 @@ module ActiveStorageValidations
40
41
  def validate_each(record, attribute, _value)
41
42
  return if no_attachments?(record, attribute)
42
43
 
43
- validate_changed_files_from_metadata(record, attribute)
44
+ validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
44
45
  end
45
46
 
46
47
  private
@@ -51,7 +52,7 @@ module ActiveStorageValidations
51
52
 
52
53
  # Validation fails unless file metadata contains valid width and height.
53
54
  if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
54
- add_error(record, attribute, :image_metadata_missing, **errors_options)
55
+ add_error(record, attribute, :media_metadata_missing, **errors_options)
55
56
  return false
56
57
  end
57
58
 
@@ -64,7 +65,7 @@ module ActiveStorageValidations
64
65
  errors_options[:width] = flat_options[:width][:min]
65
66
  errors_options[:height] = flat_options[:height][:min]
66
67
 
67
- add_error(record, attribute, :dimension_min_inclusion, **errors_options)
68
+ add_error(record, attribute, :dimension_min_not_included_in, **errors_options)
68
69
  return false
69
70
  end
70
71
  if flat_options[:max] && (
@@ -74,7 +75,7 @@ module ActiveStorageValidations
74
75
  errors_options[:width] = flat_options[:width][:max]
75
76
  errors_options[:height] = flat_options[:height][:max]
76
77
 
77
- add_error(record, attribute, :dimension_max_inclusion, **errors_options)
78
+ add_error(record, attribute, :dimension_max_not_included_in, **errors_options)
78
79
  return false
79
80
  end
80
81
 
@@ -86,7 +87,7 @@ module ActiveStorageValidations
86
87
  next unless flat_options[length]
87
88
  if flat_options[length].is_a?(Hash)
88
89
  if flat_options[length][:in] && (metadata[length] < flat_options[length][:min] || metadata[length] > flat_options[length][:max])
89
- error_type = :"dimension_#{length}_inclusion"
90
+ error_type = :"dimension_#{length}_not_included_in"
90
91
  errors_options[:min] = flat_options[length][:min]
91
92
  errors_options[:max] = flat_options[length][:max]
92
93
 
@@ -94,13 +95,13 @@ module ActiveStorageValidations
94
95
  width_or_height_invalid = true
95
96
  else
96
97
  if flat_options[length][:min] && metadata[length] < flat_options[length][:min]
97
- error_type = :"dimension_#{length}_greater_than_or_equal_to"
98
+ error_type = :"dimension_#{length}_not_greater_than_or_equal_to"
98
99
  errors_options[:length] = flat_options[length][:min]
99
100
 
100
101
  add_error(record, attribute, error_type, **errors_options)
101
102
  width_or_height_invalid = true
102
103
  elsif flat_options[length][:max] && metadata[length] > flat_options[length][:max]
103
- error_type = :"dimension_#{length}_less_than_or_equal_to"
104
+ error_type = :"dimension_#{length}_not_less_than_or_equal_to"
104
105
  errors_options[:length] = flat_options[length][:max]
105
106
 
106
107
  add_error(record, attribute, error_type, **errors_options)
@@ -109,7 +110,7 @@ module ActiveStorageValidations
109
110
  end
110
111
  else
111
112
  if metadata[length] != flat_options[length]
112
- error_type = :"dimension_#{length}_equal_to"
113
+ error_type = :"dimension_#{length}_not_equal_to"
113
114
  errors_options[:length] = flat_options[length]
114
115
 
115
116
  add_error(record, attribute, error_type, **errors_options)
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_comparison_validator'
4
+
5
+ module ActiveStorageValidations
6
+ class DurationValidator < BaseComparisonValidator
7
+ include ASVAnalyzable
8
+ include ASVAttachable
9
+
10
+ ERROR_TYPES = %i[
11
+ duration_not_less_than
12
+ duration_not_less_than_or_equal_to
13
+ duration_not_greater_than
14
+ duration_not_greater_than_or_equal_to
15
+ duration_not_between
16
+ ].freeze
17
+ METADATA_KEYS = %i[duration].freeze
18
+
19
+ def validate_each(record, attribute, _value)
20
+ return if no_attachments?(record, attribute)
21
+
22
+ flat_options = set_flat_options(record)
23
+
24
+ attachables_and_blobs(record, attribute).each do |attachable, blob|
25
+ duration = metadata_for(blob, attachable, METADATA_KEYS)&.fetch(:duration, nil)
26
+
27
+ if duration.to_i <= 0
28
+ errors_options = initialize_error_options(options, attachable)
29
+ add_error(record, attribute, :media_metadata_missing, **errors_options)
30
+ next
31
+ end
32
+
33
+ next if is_valid?(duration, flat_options)
34
+
35
+ errors_options = initialize_error_options(options, attachable)
36
+ populate_error_options(errors_options, flat_options)
37
+ errors_options[:duration] = format_bound_value(duration)
38
+
39
+ keys = AVAILABLE_CHECKS & flat_options.keys
40
+ error_type = "duration_not_#{keys.first}".to_sym
41
+
42
+ add_error(record, attribute, error_type, **errors_options)
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def format_bound_value(value)
49
+ return nil unless value
50
+
51
+ custom_value = value == value.to_i ? value.to_i : value
52
+ ActiveSupport::Duration.build(custom_value).inspect
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveStorageValidations
4
+ module ASVBlobMetadatable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ # This method returns the metadata that has been set by our gem.
9
+ # The metadata is stored in the blob's custom metadata. All keys are prefixed with 'asv_'
10
+ # to avoid conflicts with other metadata.
11
+ # It is not to set a active_storage_validation key equal to a a hash of our gem's metadata,
12
+ # because this would result in errors down the road with services such as S3.
13
+ #
14
+ # Because of how the metadata is stored, we need to convert the values from String
15
+ # to Integer or Boolean.
16
+ def active_storage_validations_metadata
17
+ metadata.dig('custom')
18
+ &.select { |key, _| key.to_s.start_with?('asv_') }
19
+ &.transform_keys { |key| key.to_s.delete_prefix('asv_') }
20
+ &.transform_values do |value|
21
+ case value
22
+ when /\A\d+\z/ then value.to_i
23
+ when /\A\d+\.\d+\z/ then value.to_f
24
+ when 'true' then true
25
+ when 'false' then false
26
+ else value
27
+ end
28
+ end || {}
29
+ end
30
+
31
+ # This method sets the metadata that has been detected by our gem.
32
+ # The metadata is stored in the blob's custom metadata. All keys are prefixed with 'asv_'.
33
+ # We need to store values as String, because services such as S3 will not accept other types.
34
+ def merge_into_active_storage_validations_metadata(hash)
35
+ aws_compatible_metadata = normalize_active_storage_validations_metadata_for_aws(hash)
36
+
37
+ metadata['custom'] ||= {}
38
+ metadata['custom'].merge!(aws_compatible_metadata)
39
+
40
+ active_storage_validations_metadata
41
+ end
42
+
43
+ def normalize_active_storage_validations_metadata_for_aws(hash)
44
+ hash.transform_keys { |key, _| key.to_s.start_with?('asv_') ? key : "asv_#{key}" }
45
+ .transform_values(&:to_s)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -7,3 +7,6 @@ Marcel::MimeType.extend "audio/x-hx-aac-adts", parents: %(audio/x-aac)
7
7
  Marcel::MimeType.extend "audio/x-m4a", parents: %(audio/mp4)
8
8
  Marcel::MimeType.extend "text/xml", parents: %(application/xml) # alias
9
9
  Marcel::MimeType.extend "video/theora", parents: %(video/ogg)
10
+
11
+ # Add empty content type
12
+ Marcel::MimeType.extend "inode/x-empty", extensions: %w(empty)
@@ -15,6 +15,8 @@ module ActiveStorageValidations
15
15
  AVAILABLE_CHECKS = %i[max min].freeze
16
16
  ERROR_TYPES = %i[
17
17
  limit_out_of_range
18
+ limit_min_not_reached
19
+ limit_max_exceeded
18
20
  ].freeze
19
21
 
20
22
  def check_validity!
@@ -25,13 +27,23 @@ module ActiveStorageValidations
25
27
  def validate_each(record, attribute, _value)
26
28
  files = attached_files(record, attribute).reject(&:blank?)
27
29
  flat_options = set_flat_options(record)
30
+ count = files.count
28
31
 
29
- return if files_count_valid?(files.count, flat_options)
32
+ return if files_count_valid?(count, flat_options)
30
33
 
31
34
  errors_options = initialize_error_options(options)
32
35
  errors_options[:min] = flat_options[:min]
33
36
  errors_options[:max] = flat_options[:max]
34
- add_error(record, attribute, ERROR_TYPES.first, **errors_options)
37
+ errors_options[:count] = count
38
+ error_type = if flat_options[:min] && flat_options[:max]
39
+ :limit_out_of_range
40
+ elsif flat_options[:min] && count < flat_options[:min]
41
+ :limit_min_not_reached
42
+ else
43
+ :limit_max_exceeded
44
+ end
45
+
46
+ add_error(record, attribute, error_type, **errors_options)
35
47
  end
36
48
 
37
49
  private
@@ -94,7 +94,7 @@ module ActiveStorageValidations
94
94
  end
95
95
 
96
96
  def mock_dimensions_for(attachment, width, height)
97
- Matchers.mock_metadata(attachment, width, height) do
97
+ Matchers.mock_metadata(attachment, { width: width, height: height }) do
98
98
  yield
99
99
  end
100
100
  end
@@ -13,8 +13,8 @@ require_relative 'shared/asv_validatable'
13
13
 
14
14
  module ActiveStorageValidations
15
15
  module Matchers
16
- class BaseSizeValidatorMatcher
17
- # BaseSizeValidatorMatcher is an abstract class and shouldn't be instantiated directly.
16
+ class BaseComparisonValidatorMatcher
17
+ # BaseComparisonValidatorMatcher is an abstract class and shouldn't be instantiated directly.
18
18
 
19
19
  include ASVActiveStorageable
20
20
  include ASVAllowBlankable
@@ -33,23 +33,23 @@ module ActiveStorageValidations
33
33
  @min = @max = nil
34
34
  end
35
35
 
36
- def less_than(size)
37
- @max = size - 1.byte
36
+ def less_than(value)
37
+ @max = value - smallest_measurement
38
38
  self
39
39
  end
40
40
 
41
- def less_than_or_equal_to(size)
42
- @max = size
41
+ def less_than_or_equal_to(value)
42
+ @max = value
43
43
  self
44
44
  end
45
45
 
46
- def greater_than(size)
47
- @min = size + 1.byte
46
+ def greater_than(value)
47
+ @min = value + smallest_measurement
48
48
  self
49
49
  end
50
50
 
51
- def greater_than_or_equal_to(size)
52
- @min = size
51
+ def greater_than_or_equal_to(value)
52
+ @min = value
53
53
  self
54
54
  end
55
55
 
@@ -78,45 +78,53 @@ module ActiveStorageValidations
78
78
 
79
79
  message << " but there seem to have issues with the matcher methods you used, since:"
80
80
  @failure_message_artefacts.each do |error_case|
81
- message << " validation failed when provided with a #{error_case[:size]} bytes test file"
81
+ message << " validation failed when provided with a #{error_case[:value]} #{failure_message_unit} test file"
82
82
  end
83
83
  message << " whereas it should have passed"
84
84
  end
85
85
 
86
+ def failure_message_unit
87
+ raise NotImplementedError
88
+ end
89
+
86
90
  def not_lower_than_min?
87
- @min.nil? || !passes_validation_with_size(@min - 1)
91
+ @min.nil? || !passes_validation_with_value(@min - 1)
88
92
  end
89
93
 
90
94
  def higher_than_min?
91
- @min.nil? || passes_validation_with_size(@min + 1)
95
+ @min.nil? || passes_validation_with_value(@min + 1)
92
96
  end
93
97
 
94
98
  def lower_than_max?
95
- @max.nil? || @max == Float::INFINITY || passes_validation_with_size(@max - 1)
99
+ @max.nil? || @max == Float::INFINITY || passes_validation_with_value(@max - 1)
96
100
  end
97
101
 
98
102
  def not_higher_than_max?
99
- @max.nil? || @max == Float::INFINITY || !passes_validation_with_size(@max + 1)
103
+ @max.nil? || @max == Float::INFINITY || !passes_validation_with_value(@max + 1)
100
104
  end
101
105
 
102
- def passes_validation_with_size(size)
103
- mock_size_for(io, size) do
106
+ def smallest_measurement
107
+ raise NotImplementedError
108
+ end
109
+
110
+ def passes_validation_with_value(value)
111
+ mock_value_for(io, value) do
104
112
  attach_file
105
113
  validate
106
114
  detach_file
107
- is_valid? || add_failure_message_artefact(size)
115
+ is_valid? || add_failure_message_artefact(value)
108
116
  end
109
117
  end
110
118
 
111
- def add_failure_message_artefact(size)
112
- @failure_message_artefacts << { size: size }
119
+ def add_failure_message_artefact(value)
120
+ @failure_message_artefacts << { value: value }
113
121
  false
114
122
  end
115
123
 
116
124
  def is_custom_message_valid?
117
125
  return true unless @custom_message
118
126
 
119
- mock_size_for(io, -1.kilobytes) do
127
+ mock_value_for(io, -smallest_measurement) do
120
128
  attach_file
121
129
  validate
122
130
  detach_file
@@ -124,10 +132,8 @@ module ActiveStorageValidations
124
132
  end
125
133
  end
126
134
 
127
- def mock_size_for(io, size)
128
- Matchers.stub_method(io, :size, size) do
129
- yield
130
- end
135
+ def mock_value_for(io, size)
136
+ raise NotImplementedError
131
137
  end
132
138
  end
133
139
  end
@@ -46,12 +46,12 @@ module ActiveStorageValidations
46
46
  end
47
47
 
48
48
  def allowing(*content_types)
49
- @allowed_content_types = content_types.flatten
49
+ @allowed_content_types = content_types.map { |content_type| normalize_content_type(content_type) }.flatten
50
50
  self
51
51
  end
52
52
 
53
53
  def rejecting(*content_types)
54
- @rejected_content_types = content_types.flatten
54
+ @rejected_content_types = content_types.map { |content_type| normalize_content_type(content_type) }.flatten
55
55
  self
56
56
  end
57
57
 
@@ -88,6 +88,10 @@ module ActiveStorageValidations
88
88
  end
89
89
  end
90
90
 
91
+ def normalize_content_type(content_type)
92
+ Marcel::MimeType.for(declared_type: content_type.to_s, extension: content_type.to_s)
93
+ end
94
+
91
95
  def all_allowed_content_types_allowed?
92
96
  @allowed_content_types_not_allowed ||= @allowed_content_types.reject { |type| type_allowed?(type) }
93
97
  @allowed_content_types_not_allowed.empty?
@@ -135,7 +139,7 @@ module ActiveStorageValidations
135
139
  # (ie spoofed file basically), we need to ignore the error related to
136
140
  # content type spoofing in our matcher to pass the tests
137
141
  def validator_errors_for_attribute
138
- super.reject { |hash| hash[:error] == :spoofed_content_type }
142
+ super.reject { |hash| hash[:error] == :content_type_spoofed }
139
143
  end
140
144
  end
141
145
  end
@@ -185,7 +185,7 @@ module ActiveStorageValidations
185
185
  end
186
186
 
187
187
  def mock_dimensions_for(attachment, width, height)
188
- Matchers.mock_metadata(attachment, width, height) do
188
+ Matchers.mock_metadata(attachment, { width: width, height: height }) do
189
189
  yield
190
190
  end
191
191
  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
@@ -10,11 +10,11 @@ require_relative 'shared/asv_validatable'
10
10
 
11
11
  module ActiveStorageValidations
12
12
  module Matchers
13
- def validate_processable_image_of(name)
14
- ProcessableImageValidatorMatcher.new(name)
13
+ def validate_processable_file_of(name)
14
+ ProcessableFileValidatorMatcher.new(name)
15
15
  end
16
16
 
17
- class ProcessableImageValidatorMatcher
17
+ class ProcessableFileValidatorMatcher
18
18
  include ASVActiveStorageable
19
19
  include ASVAllowBlankable
20
20
  include ASVAttachable
@@ -46,7 +46,7 @@ module ActiveStorageValidations
46
46
  is_context_valid? &&
47
47
  is_custom_message_valid? &&
48
48
  is_valid_when_image_processable? &&
49
- is_invalid_when_image_not_processable?
49
+ is_invalid_when_file_not_processable?
50
50
  end
51
51
 
52
52
  private
@@ -58,7 +58,7 @@ module ActiveStorageValidations
58
58
  is_valid?
59
59
  end
60
60
 
61
- def is_invalid_when_image_not_processable?
61
+ def is_invalid_when_file_not_processable?
62
62
  attach_file(not_processable_image)
63
63
  validate
64
64
  detach_file
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base_size_validator_matcher'
3
+ require_relative 'base_comparison_validator_matcher'
4
4
 
5
5
  module ActiveStorageValidations
6
6
  module Matchers
@@ -8,7 +8,7 @@ module ActiveStorageValidations
8
8
  SizeValidatorMatcher.new(attribute_name)
9
9
  end
10
10
 
11
- class SizeValidatorMatcher < BaseSizeValidatorMatcher
11
+ class SizeValidatorMatcher < BaseComparisonValidatorMatcher
12
12
  def description
13
13
  "validate file size of :#{@attribute_name}"
14
14
  end
@@ -18,6 +18,22 @@ module ActiveStorageValidations
18
18
  build_failure_message(message)
19
19
  message.join("\n")
20
20
  end
21
+
22
+ private
23
+
24
+ def failure_message_unit
25
+ "bytes"
26
+ end
27
+
28
+ def smallest_measurement
29
+ 1.byte
30
+ end
31
+
32
+ def mock_value_for(io, size)
33
+ Matchers.stub_method(io, :size, size) do
34
+ yield
35
+ end
36
+ end
21
37
  end
22
38
  end
23
39
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base_size_validator_matcher'
3
+ require_relative 'base_comparison_validator_matcher'
4
4
 
5
5
  module ActiveStorageValidations
6
6
  module Matchers
@@ -8,7 +8,7 @@ module ActiveStorageValidations
8
8
  TotalSizeValidatorMatcher.new(attribute_name)
9
9
  end
10
10
 
11
- class TotalSizeValidatorMatcher < BaseSizeValidatorMatcher
11
+ class TotalSizeValidatorMatcher < BaseComparisonValidatorMatcher
12
12
  def description
13
13
  "validate total file size of :#{@attribute_name}"
14
14
  end
@@ -26,6 +26,22 @@ module ActiveStorageValidations
26
26
  @subject.public_send(@attribute_name).attach([dummy_blob])
27
27
  @subject.public_send(@attribute_name)
28
28
  end
29
+
30
+ private
31
+
32
+ def failure_message_unit
33
+ "bytes"
34
+ end
35
+
36
+ def smallest_measurement
37
+ 1.byte
38
+ end
39
+
40
+ def mock_value_for(io, size)
41
+ Matchers.stub_method(io, :size, size) do
42
+ yield
43
+ end
44
+ end
29
45
  end
30
46
  end
31
47
  end
@@ -2,10 +2,11 @@
2
2
 
3
3
  require 'active_storage_validations/matchers/aspect_ratio_validator_matcher'
4
4
  require 'active_storage_validations/matchers/attached_validator_matcher'
5
- require 'active_storage_validations/matchers/processable_image_validator_matcher'
5
+ require 'active_storage_validations/matchers/processable_file_validator_matcher'
6
6
  require 'active_storage_validations/matchers/limit_validator_matcher'
7
7
  require 'active_storage_validations/matchers/content_type_validator_matcher'
8
8
  require 'active_storage_validations/matchers/dimension_validator_matcher'
9
+ require 'active_storage_validations/matchers/duration_validator_matcher'
9
10
  require 'active_storage_validations/matchers/size_validator_matcher'
10
11
  require 'active_storage_validations/matchers/total_size_validator_matcher'
11
12
 
@@ -25,8 +26,9 @@ module ActiveStorageValidations
25
26
  end
26
27
  end
27
28
 
28
- def self.mock_metadata(attachment, width, height)
29
- mock = Struct.new(:metadata).new({ width: width, height: height })
29
+ def self.mock_metadata(attachment, metadata = {})
30
+ asv_metadata_available_keys = { width: nil, height: nil, duration: nil, content_type: nil }
31
+ mock = Struct.new(:metadata).new(asv_metadata_available_keys.merge(metadata)) # ensure all keys are present, and it does not raise while trying to access them
30
32
 
31
33
  stub_method(ActiveStorageValidations::Analyzer, :new, mock) do
32
34
  yield
@@ -7,7 +7,7 @@ require_relative 'shared/asv_errorable'
7
7
  require_relative 'shared/asv_symbolizable'
8
8
 
9
9
  module ActiveStorageValidations
10
- class ProcessableImageValidator < ActiveModel::EachValidator # :nodoc
10
+ class ProcessableFileValidator < ActiveModel::EachValidator # :nodoc
11
11
  include ASVActiveStorageable
12
12
  include ASVAnalyzable
13
13
  include ASVAttachable
@@ -15,13 +15,14 @@ module ActiveStorageValidations
15
15
  include ASVSymbolizable
16
16
 
17
17
  ERROR_TYPES = %i[
18
- image_not_processable
18
+ file_not_processable
19
19
  ].freeze
20
+ METADATA_KEYS = %i[].freeze
20
21
 
21
22
  def validate_each(record, attribute, _value)
22
23
  return if no_attachments?(record, attribute)
23
24
 
24
- validate_changed_files_from_metadata(record, attribute)
25
+ validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
25
26
  end
26
27
 
27
28
  private
@@ -2,5 +2,10 @@
2
2
 
3
3
  module ActiveStorageValidations
4
4
  class Railtie < ::Rails::Railtie
5
+ initializer 'active_storage_validations.extend_active_storage_blob' do
6
+ ActiveSupport.on_load(:active_storage_blob) do
7
+ include(ActiveStorageValidations::ASVBlobMetadatable)
8
+ end
9
+ end
5
10
  end
6
11
  end