activestorage-validator 0.7.0 → 0.8.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: acc9fae7338e419736df276e25e94e198cdc8a0ca9a2b80210c3175d684f5f22
4
- data.tar.gz: 6b9be22b0d25c70bcf08cc51a05f9443982963d7e42e3817b90c60a596e5e071
3
+ metadata.gz: 7dd3596f7f7c5643a6bb22c897877b3d622acf3d525884108b047e02bfb36792
4
+ data.tar.gz: f705a000c6d22a36770bee09629c28c8fd3f5322c7e7ab48194aa8b5a976571d
5
5
  SHA512:
6
- metadata.gz: 57e1e9cd4353cfcb2aaeee8a2eef4bbedfe49b3382a3cacc7281fa1e5ec5ca1aa10158465a42e6eff8c2dbfd375b16f9ddb26c3027d1b337ca8fd0b41824c94b
7
- data.tar.gz: 597d5cd7f32091fcf00a196fa4bd9d7404e5f129db45d0235c70768a01167d47ae9ec361b635b2732add9ba8be45c2bb2fef6db5a4b67e676130dfe7cf9fd13a
6
+ metadata.gz: bb4c1f92c407d40b7bc8658f59bb94e5126db2f6418b9667ad45538c8a1cd91251b896db728a422c403a5c04292bb93f8759f6c9db3e606e71c767940f91a6fb
7
+ data.tar.gz: 50763f805cf34602d88bd07b805dcf0321e5f64ab2b50078209d6a201cfa95095cf7009cef688ebf85706eb8105f2669dee43656ba046a7ec86b542c7304f0a6
@@ -23,21 +23,15 @@ jobs:
23
23
  fail-fast: false
24
24
  matrix:
25
25
  include:
26
- - { ruby: "3.0", gemfile: "rails61" }
27
- - { ruby: "3.1", gemfile: "rails61" }
28
- - { ruby: "3.2", gemfile: "rails61" }
29
- - { ruby: "3.0", gemfile: "rails70" }
30
- - { ruby: "3.1", gemfile: "rails70" }
31
- - { ruby: "3.2", gemfile: "rails70" }
32
- - { ruby: "3.0", gemfile: "rails71" }
33
- - { ruby: "3.1", gemfile: "rails71" }
34
- - { ruby: "3.2", gemfile: "rails71" }
35
- - { ruby: "3.2", gemfile: "rails72" }
36
26
  - { ruby: "3.3", gemfile: "rails72" }
37
27
  - { ruby: "3.4", gemfile: "rails72" }
38
- - { ruby: "3.2", gemfile: "rails80" }
28
+ - { ruby: "4.0", gemfile: "rails72" }
39
29
  - { ruby: "3.3", gemfile: "rails80" }
40
30
  - { ruby: "3.4", gemfile: "rails80" }
31
+ - { ruby: "4.0", gemfile: "rails80" }
32
+ - { ruby: "3.3", gemfile: "rails81" }
33
+ - { ruby: "3.4", gemfile: "rails81" }
34
+ - { ruby: "4.0", gemfile: "rails81" }
41
35
  steps:
42
36
  - name: Install packages
43
37
  run: |
data/.gitignore CHANGED
@@ -14,3 +14,4 @@
14
14
  gemfiles/*.lock
15
15
  *.sqlite
16
16
  Gemfile.local
17
+ CLAUDE.local.md
data/README.md CHANGED
@@ -55,9 +55,9 @@ end
55
55
 
56
56
  | Option | Description |
57
57
  |--------------|---------------------------------------------------------------------------------------------|
58
- | content_type | Allowed MIME types. Accepts a symbol (`:web_image`, `:image`, `:audio`, `:video`, `:text`), an array of MIME types, a regular expression, or a string (single MIME type). |
59
- | size_range | Allowed file size range (e.g. `1..5.megabytes`) |
60
- | extension | Allowed file extensions. Accepts a String or an Array of Strings. Case-insensitive, leading dot optional. Files without an extension are rejected. |
58
+ | content_type | Allowed MIME types. Accepts a symbol (`:web_image`, `:image`, `:audio`, `:video`, `:text`), an array of MIME types, a regular expression, or a string (single MIME type). Or a Proc/lambda returning one of the above. |
59
+ | size_range | Allowed file size range (e.g. `1..5.megabytes`). Or a Proc/lambda returning a Range. |
60
+ | extension | Allowed file extensions. Accepts a String or an Array of Strings. Case-insensitive, leading dot optional. Files without an extension are rejected. Or a Proc/lambda returning one of the above. |
61
61
 
62
62
  ### content_type Examples
63
63
 
@@ -82,6 +82,29 @@ end
82
82
 
83
83
  The leading dot is optional (`"mp3"` and `".mp3"` behave the same), and matching is case-insensitive (`PHOTO.JPG` matches `extension: "jpg"`).
84
84
 
85
+ ## Dynamic Options (Proc/lambda)
86
+
87
+ Every option (`content_type`, `size_range`, `extension`) also accepts a Proc/lambda,
88
+ so you can decide the allowed values per record at validation time:
89
+
90
+ ```ruby
91
+ validates :avatar, blob: {
92
+ content_type: ->(record) { record.premium? ? %w[image/png image/jpeg] : :web_image },
93
+ size_range: ->(record) { 1..(record.premium? ? 20.megabytes : 5.megabytes) }
94
+ }
95
+ ```
96
+
97
+ The Proc is resolved with the same arity convention as Rails' built-in validators
98
+ (the same rule used by `if:` / `unless:`):
99
+
100
+ - **No argument** (`-> { ... }`) — called as-is.
101
+ - **One argument** (`->(record) { ... }`) — the record is passed in.
102
+
103
+ The Proc must return a value of one of the types the option already accepts
104
+ (a Symbol/Array/Regexp/String for `content_type`, a Range for `size_range`, a
105
+ String/Array for `extension`). For `has_many_attached`, the Proc is evaluated
106
+ once per record (not per attached file).
107
+
85
108
  ## I18n Error Message Options
86
109
 
87
110
  Validation error messages are I18n compatible. The following interpolation keys are available in your translation files, according to the validator's implementation:
@@ -23,10 +23,10 @@ Gem::Specification.new do |spec|
23
23
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
24
  spec.require_paths = ["lib"]
25
25
 
26
- spec.required_ruby_version = '>= 3.0.0'
26
+ spec.required_ruby_version = '>= 3.3.0'
27
27
 
28
- spec.add_dependency "rails", ">= 6.1.0"
29
- spec.add_development_dependency "bundler", "~> 2.0"
28
+ spec.add_dependency "rails", ">= 7.2.0"
29
+ spec.add_development_dependency "bundler", ">= 2.0"
30
30
  spec.add_development_dependency "rake", ">= 12.3.3"
31
31
  spec.add_development_dependency "rspec", "~> 3.0"
32
32
  spec.add_development_dependency "combustion"
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gem 'rails', '~> 7.2.0'
4
4
  gem 'sqlite3'
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
 
3
3
  gem 'rails', '~> 8.0.0'
4
4
  gem 'sqlite3'
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ gem 'rails', '~> 8.1.0'
4
+ gem 'sqlite3'
5
+
6
+ gemspec path: '../'
@@ -4,25 +4,23 @@ module ActiveRecord
4
4
  def validate_each(record, attribute, values) # rubocop:disable Metrics/AbcSize
5
5
  return unless values.attached?
6
6
 
7
+ size_range = resolve_option(record, options[:size_range])
8
+ content_type = resolve_option(record, options[:content_type])
9
+ extension = resolve_option(record, options[:extension])
10
+
7
11
  Array(values).each do |value|
8
- if options[:size_range].present?
9
- if options[:size_range].min > value.blob.byte_size
10
- record.errors.add(attribute, :min_size_error, min_size: ActiveSupport::NumberHelper.number_to_human_size(options[:size_range].min), filename: value.blob.filename.to_s)
11
- elsif options[:size_range].max < value.blob.byte_size
12
- record.errors.add(attribute, :max_size_error, max_size: ActiveSupport::NumberHelper.number_to_human_size(options[:size_range].max), filename: value.blob.filename.to_s)
13
- end
14
- end
12
+ validate_size(record, attribute, value, size_range) if size_range.present?
15
13
 
16
- unless valid_content_type?(value.blob)
14
+ unless valid_content_type?(value.blob, content_type)
17
15
  record.errors.add(attribute, :content_type, filename: value.blob.filename.to_s)
18
16
  end
19
17
 
20
- unless valid_extension?(value.blob)
18
+ unless valid_extension?(value.blob, extension)
21
19
  record.errors.add(
22
20
  attribute,
23
21
  :extension,
24
22
  filename: value.blob.filename.to_s,
25
- extension: Array(options[:extension]).map { |e| normalize_extension(e) }.join(', ')
23
+ extension: Array(extension).map { |e| normalize_extension(e) }.join(', ')
26
24
  )
27
25
  end
28
26
  end
@@ -30,27 +28,46 @@ module ActiveRecord
30
28
 
31
29
  private
32
30
 
33
- def valid_content_type?(blob)
34
- return true if options[:content_type].nil?
31
+ # Resolve only when the value is a Proc, using the same arity convention
32
+ # as Rails' built-in validators: arity 0 calls the proc as-is, otherwise
33
+ # the record is passed in. Anything else (Symbol/String/Array/Regexp/
34
+ # Range/nil) is returned untouched, preserving backward compatibility.
35
+ def resolve_option(record, value)
36
+ return value unless value.is_a?(Proc)
37
+
38
+ value.arity.zero? ? value.call : value.call(record)
39
+ end
40
+
41
+ def validate_size(record, attribute, value, size_range)
42
+ byte_size = value.blob.byte_size
43
+ if size_range.min > byte_size
44
+ record.errors.add(attribute, :min_size_error, min_size: ActiveSupport::NumberHelper.number_to_human_size(size_range.min), filename: value.blob.filename.to_s)
45
+ elsif size_range.max < byte_size
46
+ record.errors.add(attribute, :max_size_error, max_size: ActiveSupport::NumberHelper.number_to_human_size(size_range.max), filename: value.blob.filename.to_s)
47
+ end
48
+ end
49
+
50
+ def valid_content_type?(blob, content_type)
51
+ return true if content_type.nil?
35
52
 
36
- case options[:content_type]
53
+ case content_type
37
54
  when Regexp
38
- options[:content_type].match?(blob.content_type)
55
+ content_type.match?(blob.content_type)
39
56
  when Array
40
- options[:content_type].include?(blob.content_type)
57
+ content_type.include?(blob.content_type)
41
58
  when :web_image
42
59
  ActiveStorage.web_image_content_types.include?(blob.content_type)
43
60
  when Symbol
44
- blob.public_send("#{options[:content_type]}?")
61
+ blob.public_send("#{content_type}?")
45
62
  else
46
- options[:content_type] == blob.content_type
63
+ content_type == blob.content_type
47
64
  end
48
65
  end
49
66
 
50
- def valid_extension?(blob)
51
- return true if options[:extension].nil?
67
+ def valid_extension?(blob, extension)
68
+ return true if extension.nil?
52
69
 
53
- allowed = Array(options[:extension]).map { |e| normalize_extension(e) }
70
+ allowed = Array(extension).map { |e| normalize_extension(e) }
54
71
  actual = normalize_extension(blob.filename.extension)
55
72
  return false if actual.empty?
56
73
 
@@ -1,5 +1,5 @@
1
1
  module ActiveStorage
2
2
  module Validator
3
- VERSION = '0.7.0'
3
+ VERSION = '0.8.0'
4
4
  end
5
5
  end
@@ -54,9 +54,15 @@ end
54
54
 
55
55
  | Option | Accepts | Notes |
56
56
  | --- | --- | --- |
57
- | `content_type` | Symbol / Array / Regexp / String | Allowed MIME type(s). See matching below. |
58
- | `size_range` | Range | Allowed byte size, e.g. `1..(5.megabytes)`. |
59
- | `extension` | String / Array | Allowed filename extension(s). |
57
+ | `content_type` | Symbol / Array / Regexp / String / Proc | Allowed MIME type(s). See matching below. |
58
+ | `size_range` | Range / Proc | Allowed byte size, e.g. `1..(5.megabytes)`. |
59
+ | `extension` | String / Array / Proc | Allowed filename extension(s). |
60
+
61
+ Any option also accepts a **Proc/lambda** returning a value of the listed type,
62
+ resolved with the same arity convention as Rails' built-in validators (`if:` /
63
+ `unless:`): `-> { ... }` is called as-is, `->(record) { ... }` receives the
64
+ record. For `has_many_attached` the Proc is evaluated once per record. Example:
65
+ `content_type: ->(record) { record.premium? ? %w[image/png] : :web_image }`.
60
66
 
61
67
  ### `content_type` matching
62
68
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage-validator
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - aki
@@ -15,26 +15,26 @@ dependencies:
15
15
  requirements:
16
16
  - - ">="
17
17
  - !ruby/object:Gem::Version
18
- version: 6.1.0
18
+ version: 7.2.0
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
- version: 6.1.0
25
+ version: 7.2.0
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: bundler
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2.0'
33
33
  type: :development
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
- - - "~>"
37
+ - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '2.0'
40
40
  - !ruby/object:Gem::Dependency
@@ -107,11 +107,9 @@ files:
107
107
  - config/locales/ja.yml
108
108
  - config/locales/pl.yml
109
109
  - config/locales/pt-br.yml
110
- - gemfiles/rails61.gemfile
111
- - gemfiles/rails70.gemfile
112
- - gemfiles/rails71.gemfile
113
110
  - gemfiles/rails72.gemfile
114
111
  - gemfiles/rails80.gemfile
112
+ - gemfiles/rails81.gemfile
115
113
  - lib/activestorage/validator.rb
116
114
  - lib/activestorage/validator/blob.rb
117
115
  - lib/activestorage/validator/version.rb
@@ -127,7 +125,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
127
125
  requirements:
128
126
  - - ">="
129
127
  - !ruby/object:Gem::Version
130
- version: 3.0.0
128
+ version: 3.3.0
131
129
  required_rubygems_version: !ruby/object:Gem::Requirement
132
130
  requirements:
133
131
  - - ">="
@@ -1,7 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- gem 'rails', '6.1.4'
4
- gem 'sqlite3', '~> 1.4'
5
- gem 'concurrent-ruby', '< 1.3.5'
6
-
7
- gemspec path: '../'
@@ -1,7 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- gem 'rails', '~> 7.0.0'
4
- gem 'sqlite3', '~> 1.4'
5
- gem 'concurrent-ruby', '< 1.3.5'
6
-
7
- gemspec path: '../'
@@ -1,6 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- gem 'rails', '~> 7.1.0'
4
- gem 'sqlite3'
5
-
6
- gemspec path: '../'