active_storage_validations 1.4.0 → 2.0.1

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