active_storage_validations 2.0.1 → 2.0.3

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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -60
  3. data/config/locales/da.yml +1 -0
  4. data/config/locales/de.yml +1 -0
  5. data/config/locales/en-GB.yml +54 -0
  6. data/config/locales/en.yml +1 -0
  7. data/config/locales/es.yml +1 -0
  8. data/config/locales/fr.yml +1 -0
  9. data/config/locales/it.yml +2 -1
  10. data/config/locales/ja.yml +1 -0
  11. data/config/locales/nl.yml +1 -0
  12. data/config/locales/pl.yml +1 -0
  13. data/config/locales/pt-BR.yml +1 -0
  14. data/config/locales/ru.yml +1 -0
  15. data/config/locales/sv.yml +1 -0
  16. data/config/locales/tr.yml +1 -0
  17. data/config/locales/uk.yml +1 -0
  18. data/config/locales/vi.yml +1 -0
  19. data/config/locales/zh-CN.yml +1 -0
  20. data/lib/active_storage_validations/analyzer/audio_analyzer.rb +2 -2
  21. data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +5 -5
  22. data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +0 -1
  23. data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +0 -1
  24. data/lib/active_storage_validations/analyzer/video_analyzer.rb +4 -4
  25. data/lib/active_storage_validations/analyzer.rb +24 -23
  26. data/lib/active_storage_validations/aspect_ratio_validator.rb +19 -16
  27. data/lib/active_storage_validations/attached_validator.rb +3 -3
  28. data/lib/active_storage_validations/base_comparison_validator.rb +6 -6
  29. data/lib/active_storage_validations/content_type_validator.rb +39 -24
  30. data/lib/active_storage_validations/dimension_validator.rb +158 -82
  31. data/lib/active_storage_validations/duration_validator.rb +28 -14
  32. data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +15 -8
  33. data/lib/active_storage_validations/extensors/asv_marcelable.rb +1 -1
  34. data/lib/active_storage_validations/limit_validator.rb +27 -19
  35. data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +12 -12
  36. data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +6 -6
  37. data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +7 -7
  38. data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +15 -13
  39. data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +8 -8
  40. data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +2 -2
  41. data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +8 -8
  42. data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +7 -7
  43. data/lib/active_storage_validations/matchers/shared/asv_active_storageable.rb +1 -1
  44. data/lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb +1 -1
  45. data/lib/active_storage_validations/matchers/shared/asv_attachable.rb +9 -9
  46. data/lib/active_storage_validations/matchers/shared/asv_contextable.rb +11 -3
  47. data/lib/active_storage_validations/matchers/shared/asv_messageable.rb +1 -1
  48. data/lib/active_storage_validations/matchers/shared/asv_rspecable.rb +2 -2
  49. data/lib/active_storage_validations/matchers/shared/asv_validatable.rb +2 -2
  50. data/lib/active_storage_validations/matchers/size_validator_matcher.rb +2 -2
  51. data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +3 -3
  52. data/lib/active_storage_validations/matchers.rb +10 -10
  53. data/lib/active_storage_validations/processable_file_validator.rb +37 -37
  54. data/lib/active_storage_validations/railtie.rb +7 -1
  55. data/lib/active_storage_validations/shared/asv_attachable.rb +60 -28
  56. data/lib/active_storage_validations/shared/asv_errorable.rb +1 -1
  57. data/lib/active_storage_validations/shared/asv_symbolizable.rb +1 -1
  58. data/lib/active_storage_validations/size_validator.rb +1 -1
  59. data/lib/active_storage_validations/total_size_validator.rb +2 -2
  60. data/lib/active_storage_validations/version.rb +1 -1
  61. data/lib/active_storage_validations.rb +22 -25
  62. metadata +3 -2
@@ -1,12 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'shared/asv_active_storageable'
4
- require_relative 'shared/asv_analyzable'
5
- require_relative 'shared/asv_attachable'
6
- require_relative 'shared/asv_errorable'
7
- require_relative 'shared/asv_optionable'
8
- require_relative 'shared/asv_symbolizable'
9
- require_relative 'analyzer/content_type_analyzer'
3
+ require_relative "shared/asv_active_storageable"
4
+ require_relative "shared/asv_analyzable"
5
+ require_relative "shared/asv_attachable"
6
+ require_relative "shared/asv_errorable"
7
+ require_relative "shared/asv_optionable"
8
+ require_relative "shared/asv_symbolizable"
9
+ require_relative "analyzer/content_type_analyzer"
10
10
 
11
11
  module ActiveStorageValidations
12
12
  class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
@@ -96,9 +96,7 @@ module ActiveStorageValidations
96
96
 
97
97
  return true if attachable_content_type_is_authorized
98
98
 
99
- errors_options = initialize_and_populate_error_options(options, attachable)
100
- add_error(record, attribute, ERROR_TYPES.first, **errors_options)
101
- false
99
+ add_content_type_invalid_error(record, attribute, attachable)
102
100
  end
103
101
 
104
102
  def marcel_attachable_content_type(attachable)
@@ -108,14 +106,15 @@ module ActiveStorageValidations
108
106
  def not_spoofing_content_type?(record, attribute, attachable, blob)
109
107
  return true unless enable_spoofing_protection?
110
108
 
111
- @detected_content_type = metadata_for(blob, attachable, METADATA_KEYS)&.fetch(:content_type, nil)
109
+ @detected_content_type = begin
110
+ metadata_for(blob, attachable, METADATA_KEYS)&.fetch(:content_type, nil)
111
+ rescue ActiveStorage::FileNotFoundError
112
+ add_attachment_missing_error(record, attribute, attachable)
113
+ return false
114
+ end
112
115
 
113
116
  if attachable_content_type_vs_detected_content_type_mismatch?
114
- errors_options = initialize_and_populate_error_options(options, attachable)
115
- errors_options[:detected_content_type] = @detected_content_type
116
- errors_options[:detected_human_content_type] = content_type_to_human_format(@detected_content_type)
117
- add_error(record, attribute, ERROR_TYPES.second, **errors_options)
118
- false
117
+ add_content_type_spoofed_error(record, attribute, attachable, @detected_content_type)
119
118
  else
120
119
  true
121
120
  end
@@ -142,13 +141,27 @@ module ActiveStorageValidations
142
141
  end
143
142
 
144
143
  def enlarged_content_type(content_type)
145
- [content_type, *parent_content_types(content_type)].compact.uniq
144
+ [ content_type, *parent_content_types(content_type) ].compact.uniq
146
145
  end
147
146
 
148
147
  def parent_content_types(content_type)
149
148
  Marcel::TYPE_PARENTS[content_type] || []
150
149
  end
151
150
 
151
+ def add_content_type_invalid_error(record, attribute, attachable)
152
+ errors_options = initialize_and_populate_error_options(options, attachable)
153
+ add_error(record, attribute, ERROR_TYPES.first, **errors_options)
154
+ false
155
+ end
156
+
157
+ def add_content_type_spoofed_error(record, attribute, attachable, detected_content_type)
158
+ errors_options = initialize_and_populate_error_options(options, attachable)
159
+ errors_options[:detected_content_type] = @detected_content_type
160
+ errors_options[:detected_human_content_type] = content_type_to_human_format(@detected_content_type)
161
+ add_error(record, attribute, ERROR_TYPES.second, **errors_options)
162
+ false
163
+ end
164
+
152
165
  def initialize_and_populate_error_options(options, attachable)
153
166
  errors_options = initialize_error_options(options, attachable)
154
167
  errors_options[:content_type] = @attachable_content_type
@@ -170,12 +183,12 @@ module ActiveStorageValidations
170
183
  end
171
184
  .flatten
172
185
  .compact
173
- .join(', ')
186
+ .join(", ")
174
187
  end
175
188
 
176
189
  def ensure_exactly_one_validator_option
177
190
  unless AVAILABLE_CHECKS.one? { |argument| options.key?(argument) }
178
- raise ArgumentError, 'You must pass either :with or :in to the validator'
191
+ raise ArgumentError, "You must pass either :with or :in to the validator"
179
192
  end
180
193
  end
181
194
 
@@ -211,20 +224,22 @@ module ActiveStorageValidations
211
224
  end
212
225
 
213
226
  def invalid_content_type?(content_type)
214
- if content_type == 'image/jpg'
227
+ if content_type == "image/jpg"
215
228
  raise ArgumentError, "'image/jpg' is not a valid content type, you should use 'image/jpeg' instead"
216
229
  end
217
230
 
218
- all_available_marcel_content_types.keys.exclude?(content_type.to_s)
231
+ all_available_marcel_content_types.exclude?(content_type.to_s)
219
232
  end
220
233
 
221
234
  def all_available_marcel_content_types
222
- @all_available_marcel_content_types ||= Marcel::MAGIC.map {|dd| dd.first }
223
- .each_with_object(Marcel::TYPE_EXTS) { |(k,v), h| h[k] = v unless h.key?(k) }
235
+ @all_available_marcel_content_types ||= Marcel::TYPE_EXTS
236
+ .keys
237
+ .push(*Marcel::MAGIC.map(&:first))
238
+ .tap(&:uniq!)
224
239
  end
225
240
 
226
241
  def invalid_extension?(content_type)
227
- Marcel::MimeType.for(extension: content_type.to_s) == 'application/octet-stream'
242
+ Marcel::MimeType.for(extension: content_type.to_s) == "application/octet-stream"
228
243
  end
229
244
  end
230
245
  end
@@ -1,11 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'shared/asv_active_storageable'
4
- require_relative 'shared/asv_analyzable'
5
- require_relative 'shared/asv_attachable'
6
- require_relative 'shared/asv_errorable'
7
- require_relative 'shared/asv_optionable'
8
- require_relative 'shared/asv_symbolizable'
3
+ require_relative "shared/asv_active_storageable"
4
+ require_relative "shared/asv_analyzable"
5
+ require_relative "shared/asv_attachable"
6
+ require_relative "shared/asv_errorable"
7
+ require_relative "shared/asv_optionable"
8
+ require_relative "shared/asv_symbolizable"
9
9
 
10
10
  module ActiveStorageValidations
11
11
  class DimensionValidator < ActiveModel::EachValidator # :nodoc
@@ -33,9 +33,9 @@ module ActiveStorageValidations
33
33
  METADATA_KEYS = %i[width height].freeze
34
34
 
35
35
  def check_validity!
36
- unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
37
- raise ArgumentError, 'You must pass either :width, :height, :min or :max to the validator'
38
- end
36
+ ensure_at_least_one_validator_option
37
+ ensure_dimension_in_option_validity
38
+ ensure_min_max_option_validity
39
39
  end
40
40
 
41
41
  def validate_each(record, attribute, _value)
@@ -46,101 +46,177 @@ module ActiveStorageValidations
46
46
 
47
47
  private
48
48
 
49
+ def ensure_at_least_one_validator_option
50
+ unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
51
+ raise ArgumentError, "You must pass either :width, :height, :min or :max to the validator"
52
+ end
53
+ end
54
+
55
+ def ensure_dimension_in_option_validity
56
+ %i[width height].each do |dimension|
57
+ if options[dimension]&.is_a?(Hash) && options[dimension][:in].present?
58
+ raise ArgumentError, "{ #{dimension}: { in: value } } value must be a Range (min..max)" if !options[dimension][:in].is_a?(Range) && !options[dimension][:in].is_a?(Proc)
59
+ end
60
+ end
61
+ end
62
+
63
+ def ensure_min_max_option_validity
64
+ %i[min max].each do |bound|
65
+ if options[bound].present?
66
+ raise ArgumentError, "{ #{bound}: value } value must be a Range (#{bound}_width..#{bound}_height)" if !options[bound]&.is_a?(Range) && !options[bound]&.is_a?(Proc)
67
+ end
68
+ end
69
+ end
70
+
49
71
  def is_valid?(record, attribute, file, metadata)
50
72
  flat_options = process_options(record)
51
73
  errors_options = initialize_error_options(options, file)
52
74
 
53
- # Validation fails unless file metadata contains valid width and height.
54
- if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
55
- add_error(record, attribute, :media_metadata_missing, **errors_options)
56
- return false
75
+ return add_media_metadata_missing_error(record, attribute, file, errors_options) unless valid_metadata?(metadata)
76
+
77
+ if min_max_validation?(flat_options)
78
+ validate_min_max(record, attribute, metadata, flat_options, errors_options)
79
+ else
80
+ validate_width_height(record, attribute, metadata, flat_options, errors_options)
57
81
  end
82
+ end
83
+
84
+ def valid_metadata?(metadata)
85
+ metadata[:width].to_i > 0 && metadata[:height].to_i > 0
86
+ end
58
87
 
59
- # Validation based on checks :min and :max (:min, :max has higher priority to :width, :height).
60
- if flat_options[:min] || flat_options[:max]
61
- if flat_options[:min] && (
62
- (flat_options[:width][:min] && metadata[:width] < flat_options[:width][:min]) ||
63
- (flat_options[:height][:min] && metadata[:height] < flat_options[:height][:min])
64
- )
65
- errors_options[:width] = flat_options[:width][:min]
66
- errors_options[:height] = flat_options[:height][:min]
88
+ def min_max_validation?(flat_options)
89
+ flat_options[:min] || flat_options[:max]
90
+ end
91
+
92
+ def validate_min_max(record, attribute, metadata, flat_options, errors_options)
93
+ return false unless validate_min(record, attribute, metadata, flat_options, errors_options)
94
+ return false unless validate_max(record, attribute, metadata, flat_options, errors_options)
95
+
96
+ true
97
+ end
67
98
 
68
- add_error(record, attribute, :dimension_min_not_included_in, **errors_options)
69
- return false
99
+ def validate_width_height(record, attribute, metadata, flat_options, errors_options)
100
+ %i[width height].each do |dimension|
101
+ next unless flat_options[dimension]
102
+
103
+ if flat_options[dimension].is_a?(Hash)
104
+ validate_range(record, attribute, dimension, metadata, flat_options, errors_options)
105
+ else
106
+ validate_exact(record, attribute, dimension, metadata, flat_options, errors_options)
70
107
  end
71
- if flat_options[:max] && (
72
- (flat_options[:width][:max] && metadata[:width] > flat_options[:width][:max]) ||
73
- (flat_options[:height][:max] && metadata[:height] > flat_options[:height][:max])
74
- )
75
- errors_options[:width] = flat_options[:width][:max]
76
- errors_options[:height] = flat_options[:height][:max]
77
-
78
- add_error(record, attribute, :dimension_max_not_included_in, **errors_options)
79
- return false
108
+ end
109
+ end
110
+
111
+ # rubocop:disable Metrics/BlockLength
112
+ %i[min max].each do |bound|
113
+ define_method("validate_#{bound}") do |record, attribute, metadata, flat_options, errors_options|
114
+ if send(:"invalid_#{bound}?", flat_options, metadata)
115
+ send(:"add_#{bound}_error", record, attribute, flat_options, errors_options)
116
+ false
117
+ else
118
+ true
80
119
  end
120
+ end
81
121
 
82
- # Validation based on checks :width and :height.
83
- else
84
- width_or_height_invalid = false
85
-
86
- [:width, :height].each do |length|
87
- next unless flat_options[length]
88
- if flat_options[length].is_a?(Hash)
89
- if flat_options[length][:in] && (metadata[length] < flat_options[length][:min] || metadata[length] > flat_options[length][:max])
90
- error_type = :"dimension_#{length}_not_included_in"
91
- errors_options[:min] = flat_options[length][:min]
92
- errors_options[:max] = flat_options[length][:max]
93
-
94
- add_error(record, attribute, error_type, **errors_options)
95
- width_or_height_invalid = true
96
- else
97
- if flat_options[length][:min] && metadata[length] < flat_options[length][:min]
98
- error_type = :"dimension_#{length}_not_greater_than_or_equal_to"
99
- errors_options[:length] = flat_options[length][:min]
100
-
101
- add_error(record, attribute, error_type, **errors_options)
102
- width_or_height_invalid = true
103
- elsif flat_options[length][:max] && metadata[length] > flat_options[length][:max]
104
- error_type = :"dimension_#{length}_not_less_than_or_equal_to"
105
- errors_options[:length] = flat_options[length][:max]
106
-
107
- add_error(record, attribute, error_type, **errors_options)
108
- width_or_height_invalid = true
109
- end
110
- end
111
- else
112
- if metadata[length] != flat_options[length]
113
- error_type = :"dimension_#{length}_not_equal_to"
114
- errors_options[:length] = flat_options[length]
115
-
116
- add_error(record, attribute, error_type, **errors_options)
117
- width_or_height_invalid = true
118
- end
119
- end
122
+ define_method("validate_dimension_#{bound}") do |record, attribute, dimension, metadata, flat_options, errors_options|
123
+ if send(:"invalid_dimension_#{bound}?", flat_options, dimension, metadata)
124
+ send(:"add_dimension_#{bound}_error", record, attribute, dimension, flat_options, errors_options)
125
+ false
126
+ else
127
+ true
120
128
  end
129
+ end
130
+
131
+ define_method("invalid_#{bound}?") do |flat_options, metadata|
132
+ flat_options[bound] && (
133
+ send(:"invalid_dimension_#{bound}?", flat_options, :width, metadata) ||
134
+ send(:"invalid_dimension_#{bound}?", flat_options, :height, metadata)
135
+ )
136
+ end
137
+
138
+ define_method("invalid_dimension_#{bound}?") do |flat_options, dimension, metadata|
139
+ flat_options[dimension][bound] && metadata[dimension].public_send(bound == :min ? :< : :>, flat_options[dimension][bound])
140
+ end
141
+
142
+ define_method("add_#{bound}_error") do |record, attribute, flat_options, errors_options|
143
+ errors_options[:width] = flat_options[:width][bound]
144
+ errors_options[:height] = flat_options[:height][bound]
145
+ add_error(record, attribute, :"dimension_#{bound}_not_included_in", **errors_options)
146
+ end
121
147
 
122
- return false if width_or_height_invalid
148
+ define_method("add_dimension_#{bound}_error") do |record, attribute, dimension, flat_options, errors_options|
149
+ error_type = bound == :min ? :not_greater_than_or_equal_to : :not_less_than_or_equal_to
150
+ errors_options[:length] = flat_options[dimension][bound]
151
+ add_error(record, attribute, :"dimension_#{dimension}_#{error_type}", **errors_options)
123
152
  end
153
+ end
154
+ # rubocop:enable Metrics/BlockLength
124
155
 
125
- true # valid file
156
+ def validate_range(record, attribute, dimension, metadata, flat_options, errors_options)
157
+ if in_option_used?(flat_options, dimension)
158
+ return false unless validate_in(record, attribute, dimension, metadata, flat_options, errors_options)
159
+ else
160
+ return false unless validate_dimension_min_max(record, attribute, dimension, metadata, flat_options, errors_options)
161
+ end
162
+
163
+ true
164
+ end
165
+
166
+ def validate_in(record, attribute, dimension, metadata, flat_options, errors_options)
167
+ if outside_range?(metadata[dimension], flat_options[dimension])
168
+ add_range_error(record, attribute, dimension, flat_options, errors_options)
169
+ false
170
+ else
171
+ true
172
+ end
173
+ end
174
+
175
+ def in_option_used?(flat_options, dimension)
176
+ flat_options[dimension][:in]
177
+ end
178
+
179
+ def outside_range?(value, options)
180
+ value < options[:min] || value > options[:max]
181
+ end
182
+
183
+ def add_range_error(record, attribute, dimension, flat_options, errors_options)
184
+ errors_options[:min] = flat_options[dimension][:min]
185
+ errors_options[:max] = flat_options[dimension][:max]
186
+ add_error(record, attribute, :"dimension_#{dimension}_not_included_in", **errors_options)
187
+ end
188
+
189
+ def validate_dimension_min_max(record, attribute, dimension, metadata, flat_options, errors_options)
190
+ %i[min max].each do |bound|
191
+ send(:"validate_dimension_#{bound}", record, attribute, dimension, metadata, flat_options, errors_options)
192
+ end
193
+ end
194
+
195
+ def validate_exact(record, attribute, dimension, metadata, flat_options, errors_options)
196
+ if metadata[dimension] != flat_options[dimension]
197
+ errors_options[:length] = flat_options[dimension]
198
+ add_error(record, attribute, :"dimension_#{dimension}_not_equal_to", **errors_options)
199
+ false
200
+ else
201
+ true
202
+ end
126
203
  end
127
204
 
128
205
  def process_options(record)
129
206
  flat_options = set_flat_options(record)
130
207
 
131
- [:width, :height].each do |length|
132
- if flat_options[length] and flat_options[length].is_a?(Hash)
133
- if (range = flat_options[length][:in])
134
- raise ArgumentError, ":in must be a Range" unless range.is_a?(Range)
135
- flat_options[length][:min], flat_options[length][:max] = range.min, range.max
208
+ %i[width height].each do |dimension|
209
+ if flat_options[dimension] and flat_options[dimension].is_a?(Hash)
210
+ if (range = flat_options[dimension][:in])
211
+ flat_options[dimension][:min], flat_options[dimension][:max] = range.min, range.max
136
212
  end
137
213
  end
138
214
  end
139
- [:min, :max].each do |dim|
140
- if (range = flat_options[dim])
141
- raise ArgumentError, ":#{dim} must be a Range (width..height)" unless range.is_a?(Range)
142
- flat_options[:width] = { dim => range.first }
143
- flat_options[:height] = { dim => range.last }
215
+
216
+ %i[min max].each do |bound|
217
+ if (range = flat_options[bound])
218
+ flat_options[:width] = { bound => range.first }
219
+ flat_options[:height] = { bound => range.last }
144
220
  end
145
221
  end
146
222
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'base_comparison_validator'
3
+ require_relative "base_comparison_validator"
4
4
 
5
5
  module ActiveStorageValidations
6
6
  class DurationValidator < BaseComparisonValidator
@@ -22,34 +22,48 @@ module ActiveStorageValidations
22
22
  flat_options = set_flat_options(record)
23
23
 
24
24
  attachables_and_blobs(record, attribute).each do |attachable, blob|
25
- duration = metadata_for(blob, attachable, METADATA_KEYS)&.fetch(:duration, nil)
25
+ duration = begin
26
+ metadata_for(blob, attachable, METADATA_KEYS)&.fetch(:duration, nil)
27
+ rescue ActiveStorage::FileNotFoundError
28
+ add_attachment_missing_error(record, attribute, attachable)
29
+ next
30
+ end
26
31
 
27
32
  if duration.to_i <= 0
28
- errors_options = initialize_error_options(options, attachable)
29
- add_error(record, attribute, :media_metadata_missing, **errors_options)
33
+ add_media_metadata_missing_error(record, attribute, attachable)
30
34
  next
31
35
  end
32
36
 
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)
37
+ is_valid?(duration, flat_options) || populate_error_options_and_add_error(record, attribute, attachable, flat_options, duration)
43
38
  end
44
39
  end
45
40
 
46
41
  private
47
42
 
43
+ def populate_error_options_and_add_error(record, attribute, attachable, flat_options, duration)
44
+ errors_options = initialize_error_options(options, attachable)
45
+ populate_error_options(errors_options, flat_options, duration)
46
+
47
+ error_type = set_error_type(flat_options)
48
+
49
+ add_error(record, attribute, error_type, **errors_options)
50
+ end
51
+
48
52
  def format_bound_value(value)
49
53
  return nil unless value
50
54
 
51
55
  custom_value = value == value.to_i ? value.to_i : value
52
56
  ActiveSupport::Duration.build(custom_value).inspect
53
57
  end
58
+
59
+ def populate_error_options(errors_options, flat_options, duration)
60
+ super(errors_options, flat_options)
61
+ errors_options[:duration] = format_bound_value(duration)
62
+ end
63
+
64
+ def set_error_type(flat_options)
65
+ keys = AVAILABLE_CHECKS & flat_options.keys
66
+ "duration_not_#{keys.first}".to_sym
67
+ end
54
68
  end
55
69
  end
@@ -4,6 +4,7 @@ module ActiveStorageValidations
4
4
  module ASVBlobMetadatable
5
5
  extend ActiveSupport::Concern
6
6
 
7
+ # rubocop:disable Metrics/BlockLength
7
8
  included do
8
9
  # This method returns the metadata that has been set by our gem.
9
10
  # The metadata is stored in the blob's custom metadata. All keys are prefixed with 'asv_'
@@ -14,15 +15,15 @@ module ActiveStorageValidations
14
15
  # Because of how the metadata is stored, we need to convert the values from String
15
16
  # to Integer or Boolean.
16
17
  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_') }
18
+ metadata.dig("custom")
19
+ &.select { |key, _| key.to_s.start_with?("asv_") }
20
+ &.transform_keys { |key| key.to_s.delete_prefix("asv_") }
20
21
  &.transform_values do |value|
21
22
  case value
22
23
  when /\A\d+\z/ then value.to_i
23
24
  when /\A\d+\.\d+\z/ then value.to_f
24
- when 'true' then true
25
- when 'false' then false
25
+ when "true" then true
26
+ when "false" then false
26
27
  else value
27
28
  end
28
29
  end || {}
@@ -34,16 +35,22 @@ module ActiveStorageValidations
34
35
  def merge_into_active_storage_validations_metadata(hash)
35
36
  aws_compatible_metadata = normalize_active_storage_validations_metadata_for_aws(hash)
36
37
 
37
- metadata['custom'] ||= {}
38
- metadata['custom'].merge!(aws_compatible_metadata)
38
+ metadata["custom"] ||= {}
39
+ metadata["custom"].merge!(aws_compatible_metadata)
39
40
 
40
41
  active_storage_validations_metadata
41
42
  end
42
43
 
43
44
  def normalize_active_storage_validations_metadata_for_aws(hash)
44
- hash.transform_keys { |key, _| key.to_s.start_with?('asv_') ? key : "asv_#{key}" }
45
+ hash.transform_keys { |key, _| key.to_s.start_with?("asv_") ? key : "asv_#{key}" }
45
46
  .transform_values(&:to_s)
46
47
  end
48
+
49
+ def remove_active_storage_validations_metadata!
50
+ metadata["custom"] ||= {}
51
+ metadata["custom"].delete_if { |key, _| key.to_s.start_with?("asv_") }
52
+ end
47
53
  end
54
+ # rubocop:enable Metrics/BlockLength
48
55
  end
49
56
  end
@@ -9,4 +9,4 @@ Marcel::MimeType.extend "text/xml", parents: %(application/xml) # alias
9
9
  Marcel::MimeType.extend "video/theora", parents: %(video/ogg)
10
10
 
11
11
  # Add empty content type
12
- Marcel::MimeType.extend "inode/x-empty", extensions: %w(empty)
12
+ Marcel::MimeType.extend "inode/x-empty", extensions: %w[empty]
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'shared/asv_active_storageable'
4
- require_relative 'shared/asv_errorable'
5
- require_relative 'shared/asv_optionable'
6
- require_relative 'shared/asv_symbolizable'
3
+ require_relative "shared/asv_active_storageable"
4
+ require_relative "shared/asv_errorable"
5
+ require_relative "shared/asv_optionable"
6
+ require_relative "shared/asv_symbolizable"
7
7
 
8
8
  module ActiveStorageValidations
9
9
  class LimitValidator < ActiveModel::EachValidator # :nodoc:
@@ -31,18 +31,8 @@ module ActiveStorageValidations
31
31
 
32
32
  return if files_count_valid?(count, flat_options)
33
33
 
34
- errors_options = initialize_error_options(options)
35
- errors_options[:min] = flat_options[:min]
36
- errors_options[:max] = flat_options[:max]
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
-
34
+ errors_options = initialize_and_populate_error_options(options, flat_options, count)
35
+ error_type = set_error_type(flat_options, count)
46
36
  add_error(record, attribute, error_type, **errors_options)
47
37
  end
48
38
 
@@ -60,15 +50,15 @@ module ActiveStorageValidations
60
50
 
61
51
  def ensure_at_least_one_validator_option
62
52
  unless AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
63
- raise ArgumentError, 'You must pass either :max or :min to the validator'
53
+ raise ArgumentError, "You must pass either :max or :min to the validator"
64
54
  end
65
55
  end
66
56
 
67
57
  def ensure_arguments_validity
68
58
  return true if min_max_are_proc? || min_or_max_is_proc_and_other_not_present?
69
59
 
70
- raise ArgumentError, 'You must pass integers to :min and :max' if min_or_max_defined_and_not_integer?
71
- raise ArgumentError, 'You must pass a higher value to :max than to :min' if min_higher_than_max?
60
+ raise ArgumentError, "You must pass integers to :min and :max" if min_or_max_defined_and_not_integer?
61
+ raise ArgumentError, "You must pass a higher value to :max than to :min" if min_higher_than_max?
72
62
  end
73
63
 
74
64
  def min_max_are_proc?
@@ -88,5 +78,23 @@ module ActiveStorageValidations
88
78
  def min_higher_than_max?
89
79
  options[:min] > options[:max] if options[:min].is_a?(Integer) && options[:max].is_a?(Integer)
90
80
  end
81
+
82
+ def initialize_and_populate_error_options(options, flat_options, count)
83
+ errors_options = initialize_error_options(options)
84
+ errors_options[:min] = flat_options[:min]
85
+ errors_options[:max] = flat_options[:max]
86
+ errors_options[:count] = count
87
+ errors_options
88
+ end
89
+
90
+ def set_error_type(flat_options, count)
91
+ if flat_options[:min] && flat_options[:max]
92
+ :limit_out_of_range
93
+ elsif flat_options[:min] && count < flat_options[:min]
94
+ :limit_min_not_reached
95
+ else
96
+ :limit_max_exceeded
97
+ end
98
+ end
91
99
  end
92
100
  end
@@ -1,12 +1,12 @@
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'
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
10
 
11
11
  module ActiveStorageValidations
12
12
  module Matchers
@@ -101,17 +101,17 @@ module ActiveStorageValidations
101
101
 
102
102
  def valid_width_and_height_for(aspect_ratio)
103
103
  case aspect_ratio
104
- when :square then [100, 100]
105
- when :portrait then [100, 200]
106
- when :landscape then [200, 100]
104
+ when :square then [ 100, 100 ]
105
+ when :portrait then [ 100, 200 ]
106
+ when :landscape then [ 200, 100 ]
107
107
  when validator_class::ASPECT_RATIO_REGEX
108
108
  aspect_ratio =~ validator_class::ASPECT_RATIO_REGEX
109
109
  x = Regexp.last_match(1).to_i
110
110
  y = Regexp.last_match(2).to_i
111
111
 
112
- [100 * x, 100 * y]
112
+ [ 100 * x, 100 * y ]
113
113
  else
114
- [-1, -1]
114
+ [ -1, -1 ]
115
115
  end
116
116
  end
117
117
  end