active_storage_validations 0.9.7 → 1.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0f77bdb678731cdb6bb3aca1bddf3a3b1d1d4f34355a778c504931d067f6d0e2
4
- data.tar.gz: cb723cb17a4cbfcfec2e7a9b8432ac0d2641afbef327a92e38baa376d15ba2fd
3
+ metadata.gz: c3fa76d4dc0cf3d5bffc62dcc604d6f38edf165931c94d9314288606b6205a56
4
+ data.tar.gz: 9adb8defb7d44c65a4ea67cf632087d423a1c2c44b918822653638132f8beef4
5
5
  SHA512:
6
- metadata.gz: d44aed46b1f790d3df1913009fc1c84ea5831be3c8809063c94a0c50de3d8b9f40613eab920c49fb8f81dd0808d9c489aebb1705e022defe1e745d1205a48203
7
- data.tar.gz: 3d770860efb253f13ea1f18086c5b8a51253caf69875ba1d9d7921c17b9c5ce43f8001f4d8eeb1e095f8f37d1f0924dbddcdb35ca24b721d6fd5cf2a002d9a90
6
+ metadata.gz: 9a2d4ce5113ed8a018d71b4d40b01c11946892eea438d2b7481b7007511022695998271d06215e6ddf07929d4e057fc8411a2b0babcb508f70af6140fb4d65c8
7
+ data.tar.gz: 3d31b7848f3174a8df330e5410b5c1aec018a243bfdefa2abdfc8eee9d9f652e34645853da38dee2b1627ef42efa679fabdac11e896a6a37ce9cd0ed1c5569f7
data/README.md CHANGED
@@ -1,3 +1,6 @@
1
+ [<img src="https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/more_gems.png?raw=true"
2
+ />](https://www.railsjazz.com/?utm_source=github&utm_medium=top&utm_campaign=active_storage_validations)
3
+
1
4
  # Active Storage Validations
2
5
 
3
6
  [![MiniTest](https://github.com/igorkasyanchuk/active_storage_validations/workflows/MiniTest/badge.svg)](https://github.com/igorkasyanchuk/active_storage_validations/actions)
@@ -17,6 +20,7 @@ This gems doing it for you. Just use `attached: true` or `content_type: 'image/p
17
20
  * validates number of uploaded files (min/max required)
18
21
  * validates aspect ratio (if square, portrait, landscape, is_16_9, ...)
19
22
  * custom error messages
23
+ * allow procs for dynamic determination of values
20
24
 
21
25
  ## Usage
22
26
 
@@ -121,6 +125,18 @@ class User < ApplicationRecord
121
125
  end
122
126
  ```
123
127
 
128
+ - Proc Usage:
129
+
130
+ Procs can be used instead of values in all the above examples. They will be called on every validation.
131
+ ```ruby
132
+ class User < ApplicationRecord
133
+ has_many_attached :proc_files
134
+
135
+ validates :proc_files, limit: { max: -> (record) { record.admin? ? 100 : 10 } }
136
+ end
137
+
138
+ ```
139
+
124
140
  ## Internationalization (I18n)
125
141
 
126
142
  Active Storage Validations uses I18n for error messages. For this, add these keys in your translation file:
@@ -303,12 +319,10 @@ To run tests in root folder of gem:
303
319
  Snippet to run in console:
304
320
 
305
321
  ```
306
- BUNDLE_GEMFILE=gemfiles/rails_5_2.gemfile bundle
307
322
  BUNDLE_GEMFILE=gemfiles/rails_6_0.gemfile bundle
308
323
  BUNDLE_GEMFILE=gemfiles/rails_6_1.gemfile bundle
309
324
  BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle
310
325
  BUNDLE_GEMFILE=gemfiles/rails_next.gemfile bundle
311
- BUNDLE_GEMFILE=gemfiles/rails_5_2.gemfile bundle exec rake test
312
326
  BUNDLE_GEMFILE=gemfiles/rails_6_0.gemfile bundle exec rake test
313
327
  BUNDLE_GEMFILE=gemfiles/rails_6_1.gemfile bundle exec rake test
314
328
  BUNDLE_GEMFILE=gemfiles/rails_7_0.gemfile bundle exec rake test
@@ -375,10 +389,16 @@ You are welcome to contribute.
375
389
  - https://github.com/luiseugenio
376
390
  - https://github.com/equivalent
377
391
  - https://github.com/NARKOZ
392
+ - https://github.com/stephensolis
393
+ - https://github.com/kwent
394
+ = https://github.com/Animesh-Ghosh
395
+ = https://github.com/gr8bit
396
+ = https://github.com/codegeek319
397
+ = https://github.com/clwy-cn
378
398
 
379
399
  ## License
380
400
 
381
401
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
382
402
 
383
403
  [<img src="https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/more_gems.png?raw=true"
384
- />](https://www.railsjazz.com/)
404
+ />](https://www.railsjazz.com/?utm_source=github&utm_medium=bottom&utm_campaign=active_storage_validations)
@@ -0,0 +1,22 @@
1
+ zh-CN:
2
+ errors:
3
+ messages:
4
+ content_type_invalid: "文件类型错误"
5
+ file_size_out_of_range: "文件大小 %{file_size} 超出限定范围"
6
+ limit_out_of_range: "文件数超出限定范围"
7
+ image_metadata_missing: "不是有效的图像"
8
+ dimension_min_inclusion: "必须大于或等于 %{width} x %{height} 像素"
9
+ dimension_max_inclusion: "必须小于或等于 %{width} x %{height} 像素"
10
+ dimension_width_inclusion: "宽度不在 %{min} 和 %{max} 像素之间"
11
+ dimension_height_inclusion: "高度不在 %{min} 和 %{max} 像素之间"
12
+ dimension_width_greater_than_or_equal_to: "宽度必须大于或等于 %{length} 像素"
13
+ dimension_height_greater_than_or_equal_to: "高度必须大于或等于 %{length} 像素"
14
+ dimension_width_less_than_or_equal_to: "宽度必须小于或等于 %{length} 像素"
15
+ dimension_height_less_than_or_equal_to: "高度必须小于或等于 %{length} 像素"
16
+ dimension_width_equal_to: "宽度必须等于 %{length} 像素"
17
+ dimension_height_equal_to: "高度必须等于 %{length} 像素"
18
+ aspect_ratio_not_square: "必须是方形图片"
19
+ aspect_ratio_not_portrait: "必须是竖屏图片"
20
+ aspect_ratio_not_landscape: "必须是横屏图片"
21
+ aspect_ratio_is_not: "纵横比必须是 %{aspect_ratio}"
22
+ aspect_ratio_unknown: "未知的纵横比"
@@ -4,17 +4,14 @@ require_relative 'metadata.rb'
4
4
 
5
5
  module ActiveStorageValidations
6
6
  class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
7
+ include OptionProcUnfolding
8
+
7
9
  AVAILABLE_CHECKS = %i[with].freeze
8
10
  PRECISION = 3
9
11
 
10
- def initialize(options)
11
- super(options)
12
- end
13
-
14
-
15
12
  def check_validity!
16
13
  return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
17
- raise ArgumentError, 'You must pass "aspect_ratio: :OPTION" option to the validator'
14
+ raise ArgumentError, 'You must pass :with to the validator'
18
15
  end
19
16
 
20
17
  if Rails.gem_version >= Gem::Version.new('6.0.0')
@@ -55,26 +52,27 @@ module ActiveStorageValidations
55
52
 
56
53
 
57
54
  def is_valid?(record, attribute, metadata)
55
+ flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
58
56
  if metadata[:width].to_i <= 0 || metadata[:height].to_i <= 0
59
- add_error(record, attribute, options[:message].presence || :image_metadata_missing)
57
+ add_error(record, attribute, :image_metadata_missing, flat_options[:with])
60
58
  return false
61
59
  end
62
60
 
63
- case options[:with]
61
+ case flat_options[:with]
64
62
  when :square
65
63
  return true if metadata[:width] == metadata[:height]
66
- add_error(record, attribute, :aspect_ratio_not_square)
64
+ add_error(record, attribute, :aspect_ratio_not_square, flat_options[:with])
67
65
 
68
66
  when :portrait
69
67
  return true if metadata[:height] > metadata[:width]
70
- add_error(record, attribute, :aspect_ratio_not_portrait)
68
+ add_error(record, attribute, :aspect_ratio_not_portrait, flat_options[:with])
71
69
 
72
70
  when :landscape
73
71
  return true if metadata[:width] > metadata[:height]
74
- add_error(record, attribute, :aspect_ratio_not_landscape)
72
+ add_error(record, attribute, :aspect_ratio_not_landscape, flat_options[:with])
75
73
 
76
74
  else
77
- if options[:with] =~ /is\_(\d*)\_(\d*)/
75
+ if flat_options[:with] =~ /is_(\d*)_(\d*)/
78
76
  x = $1.to_i
79
77
  y = $2.to_i
80
78
 
@@ -82,17 +80,17 @@ module ActiveStorageValidations
82
80
 
83
81
  add_error(record, attribute, :aspect_ratio_is_not, "#{x}x#{y}")
84
82
  else
85
- add_error(record, attribute, :aspect_ratio_unknown)
83
+ add_error(record, attribute, :aspect_ratio_unknown, flat_options[:with])
86
84
  end
87
85
  end
88
86
  false
89
87
  end
90
88
 
91
89
 
92
- def add_error(record, attribute, type, interpolate = options[:with])
93
- key = options[:message].presence || type
94
- return if record.errors.added?(attribute, key)
95
- record.errors.add(attribute, key, aspect_ratio: interpolate)
90
+ def add_error(record, attribute, default_message, interpolate)
91
+ message = options[:message].presence || default_message
92
+ return if record.errors.added?(attribute, message)
93
+ record.errors.add(attribute, message, aspect_ratio: interpolate)
96
94
  end
97
95
 
98
96
  end
@@ -2,16 +2,23 @@
2
2
 
3
3
  module ActiveStorageValidations
4
4
  class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
5
+ include OptionProcUnfolding
6
+
7
+ AVAILABLE_CHECKS = %i[with in].freeze
8
+
5
9
  def validate_each(record, attribute, _value)
6
- return true if !record.send(attribute).attached? || types.empty?
10
+ return true unless record.send(attribute).attached?
7
11
 
12
+ types = authorized_types(record)
13
+ return true if types.empty?
14
+
8
15
  files = Array.wrap(record.send(attribute))
9
16
 
10
- errors_options = { authorized_types: types_to_human_format }
17
+ errors_options = { authorized_types: types_to_human_format(types) }
11
18
  errors_options[:message] = options[:message] if options[:message].present?
12
19
 
13
20
  files.each do |file|
14
- next if is_valid?(file)
21
+ next if is_valid?(file, types)
15
22
 
16
23
  errors_options[:content_type] = content_type(file)
17
24
  record.errors.add(attribute, :content_type_invalid, **errors_options)
@@ -19,10 +26,9 @@ module ActiveStorageValidations
19
26
  end
20
27
  end
21
28
 
22
- def types
23
- return @types if defined? @types
24
-
25
- @types = (Array.wrap(options[:with]) + Array.wrap(options[:in])).compact.map do |type|
29
+ def authorized_types(record)
30
+ flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
31
+ (Array.wrap(flat_options[:with]) + Array.wrap(flat_options[:in])).compact.map do |type|
26
32
  if type.is_a?(Regexp)
27
33
  type
28
34
  else
@@ -31,7 +37,7 @@ module ActiveStorageValidations
31
37
  end
32
38
  end
33
39
 
34
- def types_to_human_format
40
+ def types_to_human_format(types)
35
41
  types
36
42
  .map { |type| type.to_s.split('/').last.upcase }
37
43
  .join(', ')
@@ -41,7 +47,7 @@ module ActiveStorageValidations
41
47
  file.blob.present? && file.blob.content_type
42
48
  end
43
49
 
44
- def is_valid?(file)
50
+ def is_valid?(file, types)
45
51
  file_type = content_type(file)
46
52
  types.any? do |type|
47
53
  type == file_type || (type.is_a?(Regexp) && type.match?(file_type.to_s))
@@ -4,25 +4,30 @@ require_relative 'metadata.rb'
4
4
 
5
5
  module ActiveStorageValidations
6
6
  class DimensionValidator < ActiveModel::EachValidator # :nodoc
7
+ include OptionProcUnfolding
8
+
7
9
  AVAILABLE_CHECKS = %i[width height min max].freeze
8
10
 
9
- def initialize(options)
11
+ def process_options(record)
12
+ flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
13
+
10
14
  [:width, :height].each do |length|
11
- if options[length] and options[length].is_a?(Hash)
12
- if range = options[length][:in]
15
+ if flat_options[length] and flat_options[length].is_a?(Hash)
16
+ if (range = flat_options[length][:in])
13
17
  raise ArgumentError, ":in must be a Range" unless range.is_a?(Range)
14
- options[length][:min], options[length][:max] = range.min, range.max
18
+ flat_options[length][:min], flat_options[length][:max] = range.min, range.max
15
19
  end
16
20
  end
17
21
  end
18
22
  [:min, :max].each do |dim|
19
- if range = options[dim]
23
+ if (range = flat_options[dim])
20
24
  raise ArgumentError, ":#{dim} must be a Range (width..height)" unless range.is_a?(Range)
21
- options[:width] = { dim => range.first }
22
- options[:height] = { dim => range.last }
25
+ flat_options[:width] = { dim => range.first }
26
+ flat_options[:height] = { dim => range.last }
23
27
  end
24
28
  end
25
- super
29
+
30
+ flat_options
26
31
  end
27
32
 
28
33
 
@@ -64,63 +69,67 @@ module ActiveStorageValidations
64
69
 
65
70
 
66
71
  def is_valid?(record, attribute, file_metadata)
72
+ flat_options = process_options(record)
67
73
  # Validation fails unless file metadata contains valid width and height.
68
74
  if file_metadata[:width].to_i <= 0 || file_metadata[:height].to_i <= 0
69
- add_error(record, attribute, options[:message].presence || :image_metadata_missing)
75
+ add_error(record, attribute, :image_metadata_missing)
70
76
  return false
71
77
  end
72
78
 
73
79
  # Validation based on checks :min and :max (:min, :max has higher priority to :width, :height).
74
- if options[:min] || options[:max]
75
- if options[:min] && (
76
- (options[:width][:min] && file_metadata[:width] < options[:width][:min]) ||
77
- (options[:height][:min] && file_metadata[:height] < options[:height][:min])
80
+ if flat_options[:min] || flat_options[:max]
81
+ if flat_options[:min] && (
82
+ (flat_options[:width][:min] && file_metadata[:width] < flat_options[:width][:min]) ||
83
+ (flat_options[:height][:min] && file_metadata[:height] < flat_options[:height][:min])
78
84
  )
79
- add_error(record, attribute, options[:message].presence || :"dimension_min_inclusion", width: options[:width][:min], height: options[:height][:min])
85
+ add_error(record, attribute, :dimension_min_inclusion, width: flat_options[:width][:min], height: flat_options[:height][:min])
80
86
  return false
81
87
  end
82
- if options[:max] && (
83
- (options[:width][:max] && file_metadata[:width] > options[:width][:max]) ||
84
- (options[:height][:max] && file_metadata[:height] > options[:height][:max])
88
+ if flat_options[:max] && (
89
+ (flat_options[:width][:max] && file_metadata[:width] > flat_options[:width][:max]) ||
90
+ (flat_options[:height][:max] && file_metadata[:height] > flat_options[:height][:max])
85
91
  )
86
- add_error(record, attribute, options[:message].presence || :"dimension_max_inclusion", width: options[:width][:max], height: options[:height][:max])
92
+ add_error(record, attribute, :dimension_max_inclusion, width: flat_options[:width][:max], height: flat_options[:height][:max])
87
93
  return false
88
94
  end
89
95
 
90
96
  # Validation based on checks :width and :height.
91
97
  else
98
+ width_or_height_invalid = false
92
99
  [:width, :height].each do |length|
93
- next unless options[length]
94
- if options[length].is_a?(Hash)
95
- if options[length][:in] && (file_metadata[length] < options[length][:min] || file_metadata[length] > options[length][:max])
96
- add_error(record, attribute, options[:message].presence || :"dimension_#{length}_inclusion", min: options[length][:min], max: options[length][:max])
97
- return false
100
+ next unless flat_options[length]
101
+ if flat_options[length].is_a?(Hash)
102
+ if flat_options[length][:in] && (file_metadata[length] < flat_options[length][:min] || file_metadata[length] > flat_options[length][:max])
103
+ add_error(record, attribute, :"dimension_#{length}_inclusion", min: flat_options[length][:min], max: flat_options[length][:max])
104
+ width_or_height_invalid = true
98
105
  else
99
- if options[length][:min] && file_metadata[length] < options[length][:min]
100
- add_error(record, attribute, options[:message].presence || :"dimension_#{length}_greater_than_or_equal_to", length: options[length][:min])
101
- return false
106
+ if flat_options[length][:min] && file_metadata[length] < flat_options[length][:min]
107
+ add_error(record, attribute, :"dimension_#{length}_greater_than_or_equal_to", length: flat_options[length][:min])
108
+ width_or_height_invalid = true
102
109
  end
103
- if options[length][:max] && file_metadata[length] > options[length][:max]
104
- add_error(record, attribute, options[:message].presence || :"dimension_#{length}_less_than_or_equal_to", length: options[length][:max])
105
- return false
110
+ if flat_options[length][:max] && file_metadata[length] > flat_options[length][:max]
111
+ add_error(record, attribute, :"dimension_#{length}_less_than_or_equal_to", length: flat_options[length][:max])
112
+ width_or_height_invalid = true
106
113
  end
107
114
  end
108
115
  else
109
- if file_metadata[length] != options[length]
110
- add_error(record, attribute, options[:message].presence || :"dimension_#{length}_equal_to", length: options[length])
111
- return false
116
+ if file_metadata[length] != flat_options[length]
117
+ add_error(record, attribute, :"dimension_#{length}_equal_to", length: flat_options[length])
118
+ width_or_height_invalid = true
112
119
  end
113
120
  end
114
121
  end
122
+
123
+ return false if width_or_height_invalid
115
124
  end
116
125
 
117
126
  true # valid file
118
127
  end
119
128
 
120
- def add_error(record, attribute, type, **attrs)
121
- key = options[:message].presence || type
122
- return if record.errors.added?(attribute, key)
123
- record.errors.add(attribute, key, **attrs)
129
+ def add_error(record, attribute, default_message, **attrs)
130
+ message = options[:message].presence || default_message
131
+ return if record.errors.added?(attribute, message)
132
+ record.errors.add(attribute, message, **attrs)
124
133
  end
125
134
 
126
135
  end
@@ -2,11 +2,12 @@
2
2
 
3
3
  module ActiveStorageValidations
4
4
  class LimitValidator < ActiveModel::EachValidator # :nodoc:
5
+ include OptionProcUnfolding
6
+
5
7
  AVAILABLE_CHECKS = %i[max min].freeze
6
8
 
7
9
  def check_validity!
8
10
  return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
9
-
10
11
  raise ArgumentError, 'You must pass either :max or :min to the validator'
11
12
  end
12
13
 
@@ -14,19 +15,20 @@ module ActiveStorageValidations
14
15
  return true unless record.send(attribute).attached?
15
16
 
16
17
  files = Array.wrap(record.send(attribute)).compact.uniq
17
- errors_options = { min: options[:min], max: options[:max] }
18
+ flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
19
+ errors_options = { min: flat_options[:min], max: flat_options[:max] }
18
20
 
19
- return true if files_count_valid?(files.count)
21
+ return true if files_count_valid?(files.count, flat_options)
20
22
  record.errors.add(attribute, options[:message].presence || :limit_out_of_range, **errors_options)
21
23
  end
22
24
 
23
- def files_count_valid?(count)
24
- if options[:max].present? && options[:min].present?
25
- count >= options[:min] && count <= options[:max]
26
- elsif options[:max].present?
27
- count <= options[:max]
28
- elsif options[:min].present?
29
- count >= options[:min]
25
+ def files_count_valid?(count, flat_options)
26
+ if flat_options[:max].present? && flat_options[:min].present?
27
+ count >= flat_options[:min] && count <= flat_options[:max]
28
+ elsif flat_options[:max].present?
29
+ count <= flat_options[:max]
30
+ elsif flat_options[:min].present?
31
+ count >= flat_options[:min]
30
32
  end
31
33
  end
32
34
  end
@@ -10,6 +10,14 @@ module ActiveStorageValidations
10
10
  def image_processor
11
11
  Rails.application.config.active_storage.variant_processor
12
12
  end
13
+
14
+ def exception_class
15
+ if image_processor == :vips && defined?(Vips)
16
+ Vips::Error
17
+ elsif defined?(MiniMagick)
18
+ MiniMagick::Error
19
+ end
20
+ end
13
21
 
14
22
  def require_image_processor
15
23
  if image_processor == :vips
@@ -55,15 +63,15 @@ module ActiveStorageValidations
55
63
  tempfile.flush
56
64
  tempfile.rewind
57
65
 
58
- image = if image_processor == :vips && Vips::get_suffixes.include?(File.extname(tempfile.path))
66
+ image = if image_processor == :vips && defined?(Vips) && Vips::get_suffixes.include?(File.extname(tempfile.path).downcase)
59
67
  Vips::Image.new_from_file(tempfile.path)
60
- else
68
+ elsif defined?(MiniMagick)
61
69
  MiniMagick::Image.new(tempfile.path)
62
70
  end
63
71
  else
64
- image = if image_processor == :vips && Vips::get_suffixes.include?(File.extname(read_file_path))
72
+ image = if image_processor == :vips && defined?(Vips) && Vips::get_suffixes.include?(File.extname(read_file_path).downcase)
65
73
  Vips::Image.new_from_file(read_file_path)
66
- else
74
+ elsif defined?(MiniMagick)
67
75
  MiniMagick::Image.new(read_file_path)
68
76
  end
69
77
  end
@@ -77,30 +85,27 @@ module ActiveStorageValidations
77
85
  rescue LoadError, NameError
78
86
  logger.info "Skipping image analysis because the mini_magick or ruby-vips gem isn't installed"
79
87
  {}
80
- rescue MiniMagick::Error => error
81
- logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
82
- {}
83
- rescue Vips::Error => error
84
- logger.error "Skipping image analysis due to a Vips error: #{error.message}"
88
+ rescue exception_class => error
89
+ logger.error "Skipping image analysis due to an #{exception_class.name.split('::').map(&:downcase).join(' ').capitalize} error: #{error.message}"
85
90
  {}
86
91
  ensure
87
92
  image = nil
88
93
  end
89
94
 
90
95
  def valid_image?(image)
91
- image_processor == :vips ? image.avg : image.valid?
92
- rescue Vips::Error
96
+ image_processor == :vips && image.is_a?(Vips::Image) ? image.avg : image.valid?
97
+ rescue exception_class
93
98
  false
94
99
  end
95
100
 
96
101
  def rotated_image?(image)
97
- if image_processor == :vips
102
+ if image_processor == :vips && image.is_a?(Vips::Image)
98
103
  image.get('exif-ifd0-Orientation').include?('Right-top') ||
99
104
  image.get('exif-ifd0-Orientation').include?('Left-bottom')
100
105
  else
101
106
  %w[ RightTop LeftBottom ].include?(image["%[orientation]"])
102
107
  end
103
- rescue Vips::Error # field "exif-ifd0-Orientation" not found
108
+ rescue exception_class # field "exif-ifd0-Orientation" not found
104
109
  false
105
110
  end
106
111
 
@@ -0,0 +1,16 @@
1
+ module ActiveStorageValidations
2
+ module OptionProcUnfolding
3
+
4
+ def unfold_procs(record, object, only_keys = nil)
5
+ case object
6
+ when Hash
7
+ object.merge(object) { |key, value| only_keys&.exclude?(key) ? unfold_procs(record, value, []) : unfold_procs(record, value) }
8
+ when Array
9
+ object.map { |o| unfold_procs(record, o, only_keys) }
10
+ else
11
+ object.is_a?(Proc) ? object.call(record) : object
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -2,14 +2,15 @@
2
2
 
3
3
  module ActiveStorageValidations
4
4
  class SizeValidator < ActiveModel::EachValidator # :nodoc:
5
+ include OptionProcUnfolding
6
+
5
7
  delegate :number_to_human_size, to: ActiveSupport::NumberHelper
6
8
 
7
9
  AVAILABLE_CHECKS = %i[less_than less_than_or_equal_to greater_than greater_than_or_equal_to between].freeze
8
10
 
9
11
  def check_validity!
10
12
  return true if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
11
-
12
- raise ArgumentError, 'You must pass either :less_than, :greater_than, or :between to the validator'
13
+ raise ArgumentError, 'You must pass either :less_than(_or_equal_to), :greater_than(_or_equal_to), or :between to the validator'
13
14
  end
14
15
 
15
16
  def validate_each(record, attribute, _value)
@@ -20,39 +21,40 @@ module ActiveStorageValidations
20
21
 
21
22
  errors_options = {}
22
23
  errors_options[:message] = options[:message] if options[:message].present?
24
+ flat_options = unfold_procs(record, self.options, AVAILABLE_CHECKS)
23
25
 
24
26
  files.each do |file|
25
- next if content_size_valid?(file.blob.byte_size)
27
+ next if content_size_valid?(file.blob.byte_size, flat_options)
26
28
 
27
29
  errors_options[:file_size] = number_to_human_size(file.blob.byte_size)
28
- errors_options[:min_size] = number_to_human_size(min_size)
29
- errors_options[:max_size] = number_to_human_size(max_size)
30
+ errors_options[:min_size] = number_to_human_size(min_size(flat_options))
31
+ errors_options[:max_size] = number_to_human_size(max_size(flat_options))
30
32
 
31
33
  record.errors.add(attribute, :file_size_out_of_range, **errors_options)
32
34
  break
33
35
  end
34
36
  end
35
37
 
36
- def content_size_valid?(file_size)
37
- if options[:between].present?
38
- options[:between].include?(file_size)
39
- elsif options[:less_than].present?
40
- file_size < options[:less_than]
41
- elsif options[:less_than_or_equal_to].present?
42
- file_size <= options[:less_than_or_equal_to]
43
- elsif options[:greater_than].present?
44
- file_size > options[:greater_than]
45
- elsif options[:greater_than_or_equal_to].present?
46
- file_size >= options[:greater_than_or_equal_to]
38
+ def content_size_valid?(file_size, flat_options)
39
+ if flat_options[:between].present?
40
+ flat_options[:between].include?(file_size)
41
+ elsif flat_options[:less_than].present?
42
+ file_size < flat_options[:less_than]
43
+ elsif flat_options[:less_than_or_equal_to].present?
44
+ file_size <= flat_options[:less_than_or_equal_to]
45
+ elsif flat_options[:greater_than].present?
46
+ file_size > flat_options[:greater_than]
47
+ elsif flat_options[:greater_than_or_equal_to].present?
48
+ file_size >= flat_options[:greater_than_or_equal_to]
47
49
  end
48
50
  end
49
51
 
50
- def min_size
51
- options[:between]&.min || options[:greater_than] || options[:greater_than_or_equal_to]
52
+ def min_size(flat_options)
53
+ flat_options[:between]&.min || flat_options[:greater_than] || flat_options[:greater_than_or_equal_to]
52
54
  end
53
55
 
54
- def max_size
55
- options[:between]&.max || options[:less_than] || options[:less_than_or_equal_to]
56
+ def max_size(flat_options)
57
+ flat_options[:between]&.max || flat_options[:less_than] || flat_options[:less_than_or_equal_to]
56
58
  end
57
59
  end
58
60
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveStorageValidations
4
- VERSION = '0.9.7'
4
+ VERSION = '1.0.0'
5
5
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'active_storage_validations/railtie'
4
4
  require 'active_storage_validations/engine'
5
+ require 'active_storage_validations/option_proc_unfolding'
5
6
  require 'active_storage_validations/attached_validator'
6
7
  require 'active_storage_validations/content_type_validator'
7
8
  require 'active_storage_validations/size_validator'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_storage_validations
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Igor Kasyanchuk
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-05 00:00:00.000000000 Z
11
+ date: 2022-10-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activejob
@@ -164,6 +164,20 @@ dependencies:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: globalid
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
167
181
  description: Validations for Active Storage (presence)
168
182
  email:
169
183
  - igorkasyanchuk@gmail.com
@@ -187,6 +201,7 @@ files:
187
201
  - config/locales/tr.yml
188
202
  - config/locales/uk.yml
189
203
  - config/locales/vi.yml
204
+ - config/locales/zh-CN.yml
190
205
  - lib/active_storage_validations.rb
191
206
  - lib/active_storage_validations/aspect_ratio_validator.rb
192
207
  - lib/active_storage_validations/attached_validator.rb
@@ -200,6 +215,7 @@ files:
200
215
  - lib/active_storage_validations/matchers/dimension_validator_matcher.rb
201
216
  - lib/active_storage_validations/matchers/size_validator_matcher.rb
202
217
  - lib/active_storage_validations/metadata.rb
218
+ - lib/active_storage_validations/option_proc_unfolding.rb
203
219
  - lib/active_storage_validations/railtie.rb
204
220
  - lib/active_storage_validations/size_validator.rb
205
221
  - lib/active_storage_validations/version.rb
@@ -223,7 +239,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
223
239
  - !ruby/object:Gem::Version
224
240
  version: '0'
225
241
  requirements: []
226
- rubygems_version: 3.2.3
242
+ rubygems_version: 3.3.7
227
243
  signing_key:
228
244
  specification_version: 4
229
245
  summary: Validations for Active Storage