active_storage_validations 1.3.4 → 1.4.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 +4 -4
- data/README.md +5 -67
- data/config/locales/da.yml +1 -0
- data/config/locales/de.yml +1 -0
- data/config/locales/en.yml +1 -0
- data/config/locales/es.yml +1 -0
- data/config/locales/fr.yml +1 -0
- data/config/locales/it.yml +1 -0
- data/config/locales/ja.yml +1 -0
- data/config/locales/nl.yml +1 -0
- data/config/locales/pl.yml +1 -0
- data/config/locales/pt-BR.yml +1 -0
- data/config/locales/ru.yml +1 -0
- data/config/locales/sv.yml +1 -0
- data/config/locales/tr.yml +1 -0
- data/config/locales/uk.yml +1 -0
- data/config/locales/vi.yml +1 -0
- data/config/locales/zh-CN.yml +1 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +47 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +58 -0
- data/lib/active_storage_validations/analyzer/image_analyzer.rb +93 -0
- data/lib/active_storage_validations/analyzer/null_analyzer.rb +18 -0
- data/lib/active_storage_validations/analyzer.rb +34 -0
- data/lib/active_storage_validations/aspect_ratio_validator.rb +150 -118
- data/lib/active_storage_validations/content_type_spoof_detector.rb +3 -1
- data/lib/active_storage_validations/content_type_validator.rb +13 -5
- data/lib/active_storage_validations/dimension_validator.rb +2 -0
- data/lib/active_storage_validations/marcel_extensor.rb +2 -0
- data/lib/active_storage_validations/matchers/processable_image_validator_matcher.rb +4 -4
- data/lib/active_storage_validations/matchers.rb +2 -1
- data/lib/active_storage_validations/processable_image_validator.rb +2 -0
- data/lib/active_storage_validations/shared/asv_active_storageable.rb +2 -2
- data/lib/active_storage_validations/shared/asv_analyzable.rb +45 -0
- data/lib/active_storage_validations/shared/asv_attachable.rb +63 -16
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +6 -0
- metadata +8 -3
- data/lib/active_storage_validations/metadata.rb +0 -179
@@ -1,118 +1,150 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative 'shared/asv_active_storageable'
|
4
|
-
require_relative 'shared/
|
5
|
-
require_relative 'shared/
|
6
|
-
require_relative 'shared/
|
7
|
-
require_relative 'shared/
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
include
|
13
|
-
include
|
14
|
-
include
|
15
|
-
include
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
errors_options =
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
def
|
73
|
-
return
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
errors_options =
|
84
|
-
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
def
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
end
|
99
|
-
|
100
|
-
def
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
1
|
+
# frozen_string_literal: true
|
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
|
+
|
10
|
+
module ActiveStorageValidations
|
11
|
+
class AspectRatioValidator < ActiveModel::EachValidator # :nodoc
|
12
|
+
include ASVActiveStorageable
|
13
|
+
include ASVAnalyzable
|
14
|
+
include ASVAttachable
|
15
|
+
include ASVErrorable
|
16
|
+
include ASVOptionable
|
17
|
+
include ASVSymbolizable
|
18
|
+
|
19
|
+
AVAILABLE_CHECKS = %i[with in].freeze
|
20
|
+
NAMED_ASPECT_RATIOS = %i[square portrait landscape].freeze
|
21
|
+
ASPECT_RATIO_REGEX = /is_([1-9]\d*)_([1-9]\d*)/.freeze
|
22
|
+
ERROR_TYPES = %i[
|
23
|
+
aspect_ratio_not_square
|
24
|
+
aspect_ratio_not_portrait
|
25
|
+
aspect_ratio_not_landscape
|
26
|
+
aspect_ratio_is_not
|
27
|
+
aspect_ratio_invalid
|
28
|
+
image_metadata_missing
|
29
|
+
].freeze
|
30
|
+
PRECISION = 3.freeze
|
31
|
+
|
32
|
+
def check_validity!
|
33
|
+
ensure_at_least_one_validator_option
|
34
|
+
ensure_aspect_ratio_validity
|
35
|
+
end
|
36
|
+
|
37
|
+
def validate_each(record, attribute, _value)
|
38
|
+
return if no_attachments?(record, attribute)
|
39
|
+
|
40
|
+
flat_options = set_flat_options(record)
|
41
|
+
@authorized_aspect_ratios = authorized_aspect_ratios_from_options(flat_options).compact
|
42
|
+
return if @authorized_aspect_ratios.empty?
|
43
|
+
|
44
|
+
validate_changed_files_from_metadata(record, attribute)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def is_valid?(record, attribute, attachable, metadata)
|
50
|
+
!image_metadata_missing?(record, attribute, attachable, metadata) &&
|
51
|
+
authorized_aspect_ratio?(record, attribute, attachable, metadata)
|
52
|
+
end
|
53
|
+
|
54
|
+
def authorized_aspect_ratio?(record, attribute, attachable, metadata)
|
55
|
+
attachable_aspect_ratio_is_authorized = @authorized_aspect_ratios.any? do |authorized_aspect_ratio|
|
56
|
+
case authorized_aspect_ratio
|
57
|
+
when :square then valid_square_aspect_ratio?(metadata)
|
58
|
+
when :portrait then valid_portrait_aspect_ratio?(metadata)
|
59
|
+
when :landscape then valid_landscape_aspect_ratio?(metadata)
|
60
|
+
when ASPECT_RATIO_REGEX then valid_regex_aspect_ratio?(authorized_aspect_ratio, metadata)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
return true if attachable_aspect_ratio_is_authorized
|
65
|
+
|
66
|
+
errors_options = initialize_error_options(options, attachable)
|
67
|
+
errors_options[:aspect_ratio] = string_aspect_ratios
|
68
|
+
add_error(record, attribute, aspect_ratio_error_mapping, **errors_options)
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
def aspect_ratio_error_mapping
|
73
|
+
return :aspect_ratio_invalid unless @authorized_aspect_ratios.one?
|
74
|
+
|
75
|
+
aspect_ratio = @authorized_aspect_ratios.first
|
76
|
+
NAMED_ASPECT_RATIOS.include?(aspect_ratio) ? :"aspect_ratio_not_#{aspect_ratio}" : :aspect_ratio_is_not
|
77
|
+
end
|
78
|
+
|
79
|
+
def image_metadata_missing?(record, attribute, attachable, metadata)
|
80
|
+
return false if metadata[:width].to_i > 0 && metadata[:height].to_i > 0
|
81
|
+
|
82
|
+
errors_options = initialize_error_options(options, attachable)
|
83
|
+
errors_options[:aspect_ratio] = string_aspect_ratios
|
84
|
+
add_error(record, attribute, :image_metadata_missing, **errors_options)
|
85
|
+
true
|
86
|
+
end
|
87
|
+
|
88
|
+
def valid_square_aspect_ratio?(metadata)
|
89
|
+
metadata[:width] == metadata[:height]
|
90
|
+
end
|
91
|
+
|
92
|
+
def valid_portrait_aspect_ratio?(metadata)
|
93
|
+
metadata[:width] < metadata[:height]
|
94
|
+
end
|
95
|
+
|
96
|
+
def valid_landscape_aspect_ratio?(metadata)
|
97
|
+
metadata[:width] > metadata[:height]
|
98
|
+
end
|
99
|
+
|
100
|
+
def valid_regex_aspect_ratio?(aspect_ratio, metadata)
|
101
|
+
aspect_ratio =~ ASPECT_RATIO_REGEX
|
102
|
+
x = ::Regexp.last_match(1).to_i
|
103
|
+
y = ::Regexp.last_match(2).to_i
|
104
|
+
|
105
|
+
x > 0 && y > 0 && (x.to_f / y).round(PRECISION) == (metadata[:width].to_f / metadata[:height]).round(PRECISION)
|
106
|
+
end
|
107
|
+
|
108
|
+
def ensure_at_least_one_validator_option
|
109
|
+
return if AVAILABLE_CHECKS.any? { |argument| options.key?(argument) }
|
110
|
+
|
111
|
+
raise ArgumentError, 'You must pass either :with or :in to the validator'
|
112
|
+
end
|
113
|
+
|
114
|
+
def ensure_aspect_ratio_validity
|
115
|
+
return true if options[:with]&.is_a?(Proc) || options[:in]&.is_a?(Proc)
|
116
|
+
|
117
|
+
authorized_aspect_ratios_from_options(options).each do |aspect_ratio|
|
118
|
+
unless NAMED_ASPECT_RATIOS.include?(aspect_ratio) || aspect_ratio =~ ASPECT_RATIO_REGEX
|
119
|
+
raise ArgumentError, invalid_aspect_ratio_message
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def invalid_aspect_ratio_message
|
125
|
+
<<~ERROR_MESSAGE
|
126
|
+
You must pass a valid aspect ratio to the validator
|
127
|
+
It should either be a named aspect ratio (#{NAMED_ASPECT_RATIOS.join(', ')})
|
128
|
+
Or an aspect ratio like 'is_16_9' (matching /#{ASPECT_RATIO_REGEX.source}/)
|
129
|
+
ERROR_MESSAGE
|
130
|
+
end
|
131
|
+
|
132
|
+
def authorized_aspect_ratios_from_options(flat_options)
|
133
|
+
(Array.wrap(flat_options[:with]) + Array.wrap(flat_options[:in]))
|
134
|
+
end
|
135
|
+
|
136
|
+
def string_aspect_ratios
|
137
|
+
@authorized_aspect_ratios.map do |aspect_ratio|
|
138
|
+
if NAMED_ASPECT_RATIOS.include?(aspect_ratio)
|
139
|
+
aspect_ratio
|
140
|
+
else
|
141
|
+
aspect_ratio =~ ASPECT_RATIO_REGEX
|
142
|
+
x = ::Regexp.last_match(1).to_i
|
143
|
+
y = ::Regexp.last_match(2).to_i
|
144
|
+
|
145
|
+
"#{x}:#{y}"
|
146
|
+
end
|
147
|
+
end.join(', ')
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'shared/asv_analyzable'
|
3
4
|
require_relative 'shared/asv_attachable'
|
4
5
|
require_relative 'shared/asv_loggable'
|
5
6
|
require 'open3'
|
@@ -8,6 +9,7 @@ module ActiveStorageValidations
|
|
8
9
|
class ContentTypeSpoofDetector
|
9
10
|
class FileCommandLineToolNotInstalledError < StandardError; end
|
10
11
|
|
12
|
+
include ASVAnalyzable
|
11
13
|
include ASVAttachable
|
12
14
|
include ASVLoggable
|
13
15
|
|
@@ -49,7 +51,7 @@ module ActiveStorageValidations
|
|
49
51
|
end
|
50
52
|
|
51
53
|
def open3_mime_type_for_io
|
52
|
-
return nil if io.
|
54
|
+
return nil if io.bytesize == 0
|
53
55
|
|
54
56
|
Tempfile.create do |tempfile|
|
55
57
|
tempfile.binmode
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'shared/asv_active_storageable'
|
4
|
+
require_relative 'shared/asv_analyzable'
|
4
5
|
require_relative 'shared/asv_attachable'
|
5
6
|
require_relative 'shared/asv_errorable'
|
6
7
|
require_relative 'shared/asv_optionable'
|
@@ -10,6 +11,7 @@ require_relative 'content_type_spoof_detector'
|
|
10
11
|
module ActiveStorageValidations
|
11
12
|
class ContentTypeValidator < ActiveModel::EachValidator # :nodoc:
|
12
13
|
include ASVActiveStorageable
|
14
|
+
include ASVAnalyzable
|
13
15
|
include ASVAttachable
|
14
16
|
include ASVErrorable
|
15
17
|
include ASVOptionable
|
@@ -32,9 +34,11 @@ module ActiveStorageValidations
|
|
32
34
|
@authorized_content_types = authorized_content_types_from_options(record)
|
33
35
|
return if @authorized_content_types.empty?
|
34
36
|
|
35
|
-
|
36
|
-
|
37
|
-
|
37
|
+
checked_files = disable_spoofing_protection? ? attached_files(record, attribute) : attachables_from_changes(record, attribute)
|
38
|
+
|
39
|
+
checked_files.each do |file|
|
40
|
+
set_attachable_cached_values(file)
|
41
|
+
is_valid?(record, attribute, file)
|
38
42
|
end
|
39
43
|
end
|
40
44
|
|
@@ -52,8 +56,8 @@ module ActiveStorageValidations
|
|
52
56
|
end
|
53
57
|
|
54
58
|
def set_attachable_cached_values(attachable)
|
55
|
-
@attachable_content_type =
|
56
|
-
@attachable_filename = attachable_filename(attachable).to_s
|
59
|
+
@attachable_content_type = disable_spoofing_protection? ? attachable.blob.content_type : attachable_content_type_rails_like(attachable)
|
60
|
+
@attachable_filename = disable_spoofing_protection? ? attachable.blob.filename.to_s : attachable_filename(attachable).to_s
|
57
61
|
end
|
58
62
|
|
59
63
|
# Check if the provided content_type is authorized and not spoofed against
|
@@ -114,6 +118,10 @@ module ActiveStorageValidations
|
|
114
118
|
Marcel::MimeType.for(declared_type: @attachable_content_type, name: @attachable_filename)
|
115
119
|
end
|
116
120
|
|
121
|
+
def disable_spoofing_protection?
|
122
|
+
!enable_spoofing_protection?
|
123
|
+
end
|
124
|
+
|
117
125
|
def enable_spoofing_protection?
|
118
126
|
options[:spoofing_protection] == true
|
119
127
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'shared/asv_active_storageable'
|
4
|
+
require_relative 'shared/asv_analyzable'
|
4
5
|
require_relative 'shared/asv_attachable'
|
5
6
|
require_relative 'shared/asv_errorable'
|
6
7
|
require_relative 'shared/asv_optionable'
|
@@ -9,6 +10,7 @@ require_relative 'shared/asv_symbolizable'
|
|
9
10
|
module ActiveStorageValidations
|
10
11
|
class DimensionValidator < ActiveModel::EachValidator # :nodoc
|
11
12
|
include ASVActiveStorageable
|
13
|
+
include ASVAnalyzable
|
12
14
|
include ASVAttachable
|
13
15
|
include ASVErrorable
|
14
16
|
include ASVOptionable
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "marcel"
|
4
|
+
|
3
5
|
Marcel::MimeType.extend "application/x-rar-compressed", parents: %(application/x-rar)
|
4
6
|
Marcel::MimeType.extend "audio/x-hx-aac-adts", parents: %(audio/x-aac)
|
5
7
|
Marcel::MimeType.extend "audio/x-m4a", parents: %(audio/mp4)
|
@@ -43,10 +43,10 @@ module ActiveStorageValidations
|
|
43
43
|
@subject = subject.is_a?(Class) ? subject.new : subject
|
44
44
|
|
45
45
|
is_a_valid_active_storage_attribute? &&
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
46
|
+
is_context_valid? &&
|
47
|
+
is_custom_message_valid? &&
|
48
|
+
is_valid_when_image_processable? &&
|
49
|
+
is_invalid_when_image_not_processable?
|
50
50
|
end
|
51
51
|
|
52
52
|
private
|
@@ -27,7 +27,8 @@ module ActiveStorageValidations
|
|
27
27
|
|
28
28
|
def self.mock_metadata(attachment, width, height)
|
29
29
|
mock = Struct.new(:metadata).new({ width: width, height: height })
|
30
|
-
|
30
|
+
|
31
|
+
stub_method(ActiveStorageValidations::Analyzer, :new, mock) do
|
31
32
|
yield
|
32
33
|
end
|
33
34
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative 'shared/asv_active_storageable'
|
4
|
+
require_relative 'shared/asv_analyzable'
|
4
5
|
require_relative 'shared/asv_attachable'
|
5
6
|
require_relative 'shared/asv_errorable'
|
6
7
|
require_relative 'shared/asv_symbolizable'
|
@@ -8,6 +9,7 @@ require_relative 'shared/asv_symbolizable'
|
|
8
9
|
module ActiveStorageValidations
|
9
10
|
class ProcessableImageValidator < ActiveModel::EachValidator # :nodoc
|
10
11
|
include ASVActiveStorageable
|
12
|
+
include ASVAnalyzable
|
11
13
|
include ASVAttachable
|
12
14
|
include ASVErrorable
|
13
15
|
include ASVSymbolizable
|
@@ -9,8 +9,8 @@ module ActiveStorageValidations
|
|
9
9
|
|
10
10
|
private
|
11
11
|
|
12
|
-
# Retrieve either an ActiveStorage::Attached::One or an
|
13
|
-
# ActiveStorage::Attached::Many instance depending on attribute definition
|
12
|
+
# Retrieve either an `ActiveStorage::Attached::One` or an
|
13
|
+
# `ActiveStorage::Attached::Many` instance depending on attribute definition
|
14
14
|
def attached_files(record, attribute)
|
15
15
|
Array.wrap(record.send(attribute))
|
16
16
|
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
# ActiveStorageValidations::ASVAnalyzable
|
5
|
+
#
|
6
|
+
# Validator methods for choosing the right analyzer depending on the file
|
7
|
+
# media type and available third-party analyzers.
|
8
|
+
module ASVAnalyzable
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
DEFAULT_IMAGE_PROCESSOR = :mini_magick.freeze
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def metadata_for(attachable)
|
16
|
+
analyzer_for(attachable).metadata
|
17
|
+
end
|
18
|
+
|
19
|
+
def analyzer_for(attachable)
|
20
|
+
case attachable_media_type(attachable)
|
21
|
+
when "image" then image_analyzer_for(attachable)
|
22
|
+
else fallback_analyzer_for(attachable)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def image_analyzer_for(attachable)
|
27
|
+
case image_processor
|
28
|
+
when :mini_magick
|
29
|
+
ActiveStorageValidations::Analyzer::ImageAnalyzer::ImageMagick.new(attachable)
|
30
|
+
when :vips
|
31
|
+
ActiveStorageValidations::Analyzer::ImageAnalyzer::Vips.new(attachable)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def image_processor
|
36
|
+
# Rails returns nil for default image processor, because it is set in an after initialize callback
|
37
|
+
# https://github.com/rails/rails/blob/main/activestorage/lib/active_storage/engine.rb
|
38
|
+
ActiveStorage.variant_processor || DEFAULT_IMAGE_PROCESSOR
|
39
|
+
end
|
40
|
+
|
41
|
+
def fallback_analyzer_for(attachable)
|
42
|
+
ActiveStorageValidations::Analyzer::NullAnalyzer.new(attachable)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "../metadata"
|
4
|
-
|
5
3
|
module ActiveStorageValidations
|
6
4
|
# ActiveStorageValidations::ASVAttachable
|
7
5
|
#
|
@@ -20,7 +18,7 @@ module ActiveStorageValidations
|
|
20
18
|
# to perform file analyses.
|
21
19
|
def validate_changed_files_from_metadata(record, attribute)
|
22
20
|
attachables_from_changes(record, attribute).each do |attachable|
|
23
|
-
is_valid?(record, attribute, attachable,
|
21
|
+
is_valid?(record, attribute, attachable, metadata_for(attachable))
|
24
22
|
end
|
25
23
|
end
|
26
24
|
|
@@ -64,25 +62,67 @@ module ActiveStorageValidations
|
|
64
62
|
def attachable_content_type(attachable)
|
65
63
|
full_attachable_content_type(attachable) && full_attachable_content_type(attachable).downcase.split(/[;,\s]/, 2).first
|
66
64
|
end
|
65
|
+
|
66
|
+
# Retrieve the content_type from attachable using the same logic as Rails
|
67
|
+
# ActiveStorage::Blob::Identifiable#identify_content_type
|
68
|
+
def attachable_content_type_rails_like(attachable)
|
69
|
+
Marcel::MimeType.for(
|
70
|
+
attachable_io(attachable, max_byte_size: 4.kilobytes),
|
71
|
+
name: attachable_filename(attachable).to_s,
|
72
|
+
declared_type: full_attachable_content_type(attachable)
|
73
|
+
)
|
74
|
+
end
|
67
75
|
|
76
|
+
# Retrieve the media type of the attachable, which is the first part of the
|
77
|
+
# content type (or mime type).
|
78
|
+
# Possible values are: application/audio/example/font/image/model/text/video
|
79
|
+
def attachable_media_type(attachable)
|
80
|
+
(full_attachable_content_type(attachable) || marcel_content_type_from_filename(attachable)).split("/").first
|
81
|
+
end
|
82
|
+
|
68
83
|
# Retrieve the io from attachable.
|
69
|
-
def attachable_io(attachable)
|
84
|
+
def attachable_io(attachable, max_byte_size: nil)
|
85
|
+
io = case attachable
|
86
|
+
when ActiveStorage::Blob
|
87
|
+
(max_byte_size && supports_blob_download_chunk?) ? attachable.download_chunk(0...max_byte_size) : attachable.download
|
88
|
+
when ActionDispatch::Http::UploadedFile
|
89
|
+
max_byte_size ? attachable.read(max_byte_size) : attachable.read
|
90
|
+
when Rack::Test::UploadedFile
|
91
|
+
max_byte_size ? attachable.read(max_byte_size) : attachable.read
|
92
|
+
when String
|
93
|
+
blob = ActiveStorage::Blob.find_signed!(attachable)
|
94
|
+
(max_byte_size && supports_blob_download_chunk?) ? blob.download_chunk(0...max_byte_size) : blob.download
|
95
|
+
when Hash
|
96
|
+
max_byte_size ? attachable[:io].read(max_byte_size) : attachable[:io].read
|
97
|
+
when File
|
98
|
+
raise_rails_like_error(attachable) unless supports_file_attachment?
|
99
|
+
max_byte_size ? attachable.read(max_byte_size) : attachable.read
|
100
|
+
when Pathname
|
101
|
+
raise_rails_like_error(attachable) unless supports_pathname_attachment?
|
102
|
+
max_byte_size ? attachable.read(max_byte_size) : attachable.read
|
103
|
+
else
|
104
|
+
raise_rails_like_error(attachable)
|
105
|
+
end
|
106
|
+
|
107
|
+
rewind_attachable_io(attachable)
|
108
|
+
io
|
109
|
+
end
|
110
|
+
|
111
|
+
# Rewind the io attachable.
|
112
|
+
def rewind_attachable_io(attachable)
|
70
113
|
case attachable
|
71
|
-
when ActiveStorage::Blob
|
72
|
-
|
73
|
-
when ActionDispatch::Http::UploadedFile
|
74
|
-
attachable.
|
75
|
-
when Rack::Test::UploadedFile
|
76
|
-
attachable.read
|
77
|
-
when String
|
78
|
-
blob = ActiveStorage::Blob.find_signed!(attachable)
|
79
|
-
blob.download
|
114
|
+
when ActiveStorage::Blob, String
|
115
|
+
# nothing to do
|
116
|
+
when ActionDispatch::Http::UploadedFile, Rack::Test::UploadedFile
|
117
|
+
attachable.rewind
|
80
118
|
when Hash
|
81
|
-
attachable[:io].
|
119
|
+
attachable[:io].rewind
|
82
120
|
when File
|
83
|
-
|
121
|
+
raise_rails_like_error(attachable) unless supports_file_attachment?
|
122
|
+
attachable.rewind
|
84
123
|
when Pathname
|
85
|
-
|
124
|
+
raise_rails_like_error(attachable) unless supports_pathname_attachment?
|
125
|
+
File.open(attachable) { |f| f.rewind }
|
86
126
|
else
|
87
127
|
raise_rails_like_error(attachable)
|
88
128
|
end
|
@@ -128,6 +168,13 @@ module ActiveStorageValidations
|
|
128
168
|
end
|
129
169
|
alias :supports_pathname_attachment? :supports_file_attachment?
|
130
170
|
|
171
|
+
# Check if the current Rails version supports ActiveStorage::Blob#download_chunk
|
172
|
+
#
|
173
|
+
# https://github.com/rails/rails/blob/7-0-stable/activestorage/CHANGELOG.md#rails-700alpha1-september-15-2021
|
174
|
+
def supports_blob_download_chunk?
|
175
|
+
Rails.gem_version >= Gem::Version.new('7.0.0.alpha1')
|
176
|
+
end
|
177
|
+
|
131
178
|
# Retrieve the content_type from the file name only
|
132
179
|
def marcel_content_type_from_filename(attachable)
|
133
180
|
Marcel::MimeType.for(name: attachable_filename(attachable).to_s)
|
@@ -3,6 +3,12 @@
|
|
3
3
|
require 'active_model'
|
4
4
|
require 'active_support/concern'
|
5
5
|
|
6
|
+
require 'active_storage_validations/analyzer'
|
7
|
+
require 'active_storage_validations/analyzer/image_analyzer'
|
8
|
+
require 'active_storage_validations/analyzer/image_analyzer/image_magick'
|
9
|
+
require 'active_storage_validations/analyzer/image_analyzer/vips'
|
10
|
+
require 'active_storage_validations/analyzer/null_analyzer'
|
11
|
+
|
6
12
|
require 'active_storage_validations/railtie'
|
7
13
|
require 'active_storage_validations/engine'
|
8
14
|
require 'active_storage_validations/attached_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: 1.
|
4
|
+
version: 1.4.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: 2024-
|
11
|
+
date: 2024-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activejob
|
@@ -259,6 +259,11 @@ files:
|
|
259
259
|
- config/locales/vi.yml
|
260
260
|
- config/locales/zh-CN.yml
|
261
261
|
- lib/active_storage_validations.rb
|
262
|
+
- lib/active_storage_validations/analyzer.rb
|
263
|
+
- lib/active_storage_validations/analyzer/image_analyzer.rb
|
264
|
+
- lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb
|
265
|
+
- lib/active_storage_validations/analyzer/image_analyzer/vips.rb
|
266
|
+
- lib/active_storage_validations/analyzer/null_analyzer.rb
|
262
267
|
- lib/active_storage_validations/aspect_ratio_validator.rb
|
263
268
|
- lib/active_storage_validations/attached_validator.rb
|
264
269
|
- lib/active_storage_validations/base_size_validator.rb
|
@@ -285,10 +290,10 @@ files:
|
|
285
290
|
- lib/active_storage_validations/matchers/shared/asv_validatable.rb
|
286
291
|
- lib/active_storage_validations/matchers/size_validator_matcher.rb
|
287
292
|
- lib/active_storage_validations/matchers/total_size_validator_matcher.rb
|
288
|
-
- lib/active_storage_validations/metadata.rb
|
289
293
|
- lib/active_storage_validations/processable_image_validator.rb
|
290
294
|
- lib/active_storage_validations/railtie.rb
|
291
295
|
- lib/active_storage_validations/shared/asv_active_storageable.rb
|
296
|
+
- lib/active_storage_validations/shared/asv_analyzable.rb
|
292
297
|
- lib/active_storage_validations/shared/asv_attachable.rb
|
293
298
|
- lib/active_storage_validations/shared/asv_errorable.rb
|
294
299
|
- lib/active_storage_validations/shared/asv_loggable.rb
|