active_storage_validations 1.0.4 → 3.0.2
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 +785 -245
- data/config/locales/da.yml +63 -0
- data/config/locales/de.yml +60 -19
- data/config/locales/en-GB.yml +63 -0
- data/config/locales/en.yml +60 -20
- data/config/locales/es.yml +60 -19
- data/config/locales/fr.yml +60 -19
- data/config/locales/it.yml +60 -19
- data/config/locales/ja.yml +60 -19
- data/config/locales/nl.yml +60 -19
- data/config/locales/pl.yml +60 -19
- data/config/locales/pt-BR.yml +60 -19
- data/config/locales/ru.yml +60 -19
- data/config/locales/sv.yml +63 -0
- data/config/locales/tr.yml +60 -19
- data/config/locales/uk.yml +60 -19
- data/config/locales/vi.yml +60 -19
- data/config/locales/zh-CN.yml +60 -19
- data/lib/active_storage_validations/analyzer/audio_analyzer.rb +58 -0
- data/lib/active_storage_validations/analyzer/content_type_analyzer.rb +60 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/image_magick.rb +46 -0
- data/lib/active_storage_validations/analyzer/image_analyzer/vips.rb +56 -0
- data/lib/active_storage_validations/analyzer/image_analyzer.rb +49 -0
- data/lib/active_storage_validations/analyzer/null_analyzer.rb +18 -0
- data/lib/active_storage_validations/analyzer/pdf_analyzer.rb +89 -0
- data/lib/active_storage_validations/analyzer/shared/asv_ff_probable.rb +61 -0
- data/lib/active_storage_validations/analyzer/video_analyzer.rb +130 -0
- data/lib/active_storage_validations/analyzer.rb +88 -0
- data/lib/active_storage_validations/aspect_ratio_validator.rb +157 -97
- data/lib/active_storage_validations/attached_validator.rb +22 -5
- data/lib/active_storage_validations/base_comparison_validator.rb +83 -0
- data/lib/active_storage_validations/content_type_validator.rb +219 -31
- data/lib/active_storage_validations/dimension_validator.rb +187 -97
- data/lib/active_storage_validations/duration_validator.rb +70 -0
- data/lib/active_storage_validations/extensors/asv_blob_metadatable.rb +56 -0
- data/lib/active_storage_validations/extensors/asv_marcelable.rb +12 -0
- data/lib/active_storage_validations/limit_validator.rb +76 -9
- data/lib/active_storage_validations/matchers/aspect_ratio_validator_matcher.rb +119 -0
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +48 -25
- data/lib/active_storage_validations/matchers/base_comparison_validator_matcher.rb +150 -0
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +98 -39
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +93 -55
- data/lib/active_storage_validations/matchers/duration_validator_matcher.rb +39 -0
- data/lib/active_storage_validations/matchers/limit_validator_matcher.rb +127 -0
- data/lib/active_storage_validations/matchers/pages_validator_matcher.rb +39 -0
- data/lib/active_storage_validations/matchers/processable_file_validator_matcher.rb +78 -0
- data/lib/active_storage_validations/matchers/shared/asv_active_storageable.rb +19 -0
- data/lib/active_storage_validations/matchers/shared/asv_allow_blankable.rb +28 -0
- data/lib/active_storage_validations/matchers/shared/asv_attachable.rb +72 -0
- data/lib/active_storage_validations/matchers/shared/asv_contextable.rb +57 -0
- data/lib/active_storage_validations/matchers/shared/asv_messageable.rb +28 -0
- data/lib/active_storage_validations/matchers/shared/asv_rspecable.rb +27 -0
- data/lib/active_storage_validations/matchers/shared/asv_validatable.rb +56 -0
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +17 -71
- data/lib/active_storage_validations/matchers/total_size_validator_matcher.rb +47 -0
- data/lib/active_storage_validations/matchers.rb +17 -21
- data/lib/active_storage_validations/pages_validator.rb +61 -0
- data/lib/active_storage_validations/processable_file_validator.rb +37 -0
- data/lib/active_storage_validations/railtie.rb +14 -0
- data/lib/active_storage_validations/shared/asv_active_storageable.rb +30 -0
- data/lib/active_storage_validations/shared/asv_analyzable.rb +89 -0
- data/lib/active_storage_validations/shared/asv_attachable.rb +236 -0
- data/lib/active_storage_validations/shared/asv_errorable.rb +64 -0
- data/lib/active_storage_validations/shared/asv_loggable.rb +11 -0
- data/lib/active_storage_validations/shared/asv_optionable.rb +29 -0
- data/lib/active_storage_validations/shared/asv_symbolizable.rb +14 -0
- data/lib/active_storage_validations/size_validator.rb +24 -41
- data/lib/active_storage_validations/total_size_validator.rb +52 -0
- data/lib/active_storage_validations/version.rb +1 -1
- data/lib/active_storage_validations.rb +27 -13
- metadata +113 -31
- data/lib/active_storage_validations/metadata.rb +0 -151
- data/lib/active_storage_validations/option_proc_unfolding.rb +0 -16
- data/lib/active_storage_validations/processable_image_validator.rb +0 -43
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module ActiveStorageValidations
|
|
6
|
+
module Matchers
|
|
7
|
+
module ASVMessageable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
def initialize_messageable
|
|
11
|
+
@custom_message = nil
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def with_message(custom_message)
|
|
15
|
+
@custom_message = custom_message
|
|
16
|
+
self
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def has_an_error_message_which_is_custom_message?
|
|
22
|
+
validator_errors_for_attribute.one? do |error|
|
|
23
|
+
error[:custom_message] == @custom_message
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module ActiveStorageValidations
|
|
6
|
+
module Matchers
|
|
7
|
+
module ASVRspecable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
def initialize_rspecable
|
|
11
|
+
@failure_message_artefacts = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def description
|
|
15
|
+
raise NotImplementedError, "#{self.class} did not define #{__method__}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def failure_message
|
|
19
|
+
raise NotImplementedError, "#{self.class} did not define #{__method__}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def failure_message_when_negated
|
|
23
|
+
failure_message.sub(/is expected to validate/, "is expected not to validate")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_support/concern"
|
|
4
|
+
|
|
5
|
+
module ActiveStorageValidations
|
|
6
|
+
module Matchers
|
|
7
|
+
module ASVValidatable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def validate
|
|
13
|
+
@subject.validate(@context)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def validator_errors_for_attribute
|
|
17
|
+
@subject.errors.details[@attribute_name].select do |error|
|
|
18
|
+
error[:validator_type] == validator_class.to_sym
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def is_valid?
|
|
23
|
+
validator_errors_for_attribute.none? do |error|
|
|
24
|
+
error[:error].in?(available_errors)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def available_errors
|
|
29
|
+
[
|
|
30
|
+
*validator_class::ERROR_TYPES,
|
|
31
|
+
*errors_from_custom_messages
|
|
32
|
+
].compact
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validator_class
|
|
36
|
+
self.class.name.gsub(/::Matchers|Matcher/, "").constantize
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def attribute_validator
|
|
40
|
+
@subject.class.validators_on(@attribute_name).find do |validator|
|
|
41
|
+
validator.class == validator_class
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def attribute_validators
|
|
46
|
+
@subject.class.validators_on(@attribute_name).select do |validator|
|
|
47
|
+
validator.class == validator_class
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def errors_from_custom_messages
|
|
52
|
+
attribute_validators.map { |validator| validator.options[:message] }
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -1,91 +1,37 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
require_relative "base_comparison_validator_matcher"
|
|
4
|
+
|
|
5
5
|
module ActiveStorageValidations
|
|
6
6
|
module Matchers
|
|
7
|
-
def validate_size_of(
|
|
8
|
-
SizeValidatorMatcher.new(
|
|
7
|
+
def validate_size_of(attribute_name)
|
|
8
|
+
SizeValidatorMatcher.new(attribute_name)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
class SizeValidatorMatcher
|
|
12
|
-
def initialize(attribute_name)
|
|
13
|
-
@attribute_name = attribute_name
|
|
14
|
-
@low = @high = nil
|
|
15
|
-
end
|
|
16
|
-
|
|
11
|
+
class SizeValidatorMatcher < BaseComparisonValidatorMatcher
|
|
17
12
|
def description
|
|
18
|
-
"validate file size of
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
def less_than(size)
|
|
22
|
-
@high = size - 1.byte
|
|
23
|
-
self
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def less_than_or_equal_to(size)
|
|
27
|
-
@high = size
|
|
28
|
-
self
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def greater_than(size)
|
|
32
|
-
@low = size + 1.byte
|
|
33
|
-
self
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def greater_than_or_equal_to(size)
|
|
37
|
-
@low = size
|
|
38
|
-
self
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def between(range)
|
|
42
|
-
@low, @high = range.first, range.last
|
|
43
|
-
self
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def matches?(subject)
|
|
47
|
-
@subject = subject.is_a?(Class) ? subject.new : subject
|
|
48
|
-
responds_to_methods && lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
|
|
13
|
+
"validate file size of :#{@attribute_name}"
|
|
49
14
|
end
|
|
50
15
|
|
|
51
16
|
def failure_message
|
|
52
|
-
"is expected to validate file size of
|
|
17
|
+
message = [ "is expected to validate file size of :#{@attribute_name}" ]
|
|
18
|
+
build_failure_message(message)
|
|
19
|
+
message.join("\n")
|
|
53
20
|
end
|
|
54
21
|
|
|
55
|
-
|
|
56
|
-
"is expected to not validate file size of #{@attribute_name} to be between #{@low} and #{@high} bytes"
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
protected
|
|
60
|
-
|
|
61
|
-
def responds_to_methods
|
|
62
|
-
@subject.respond_to?(@attribute_name) &&
|
|
63
|
-
@subject.public_send(@attribute_name).respond_to?(:attach) &&
|
|
64
|
-
@subject.public_send(@attribute_name).respond_to?(:detach)
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def lower_than_low?
|
|
68
|
-
@low.nil? || !passes_validation_with_size(@low - 1)
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def higher_than_low?
|
|
72
|
-
@low.nil? || passes_validation_with_size(@low + 1)
|
|
73
|
-
end
|
|
22
|
+
private
|
|
74
23
|
|
|
75
|
-
def
|
|
76
|
-
|
|
24
|
+
def failure_message_unit
|
|
25
|
+
"bytes"
|
|
77
26
|
end
|
|
78
27
|
|
|
79
|
-
def
|
|
80
|
-
|
|
28
|
+
def smallest_measurement
|
|
29
|
+
1.byte
|
|
81
30
|
end
|
|
82
31
|
|
|
83
|
-
def
|
|
84
|
-
io
|
|
85
|
-
|
|
86
|
-
@subject.public_send(@attribute_name).attach(io: io, filename: 'test.png', content_type: 'image/pg')
|
|
87
|
-
@subject.validate
|
|
88
|
-
@subject.errors.details[@attribute_name].all? { |error| error[:error] != :file_size_out_of_range }
|
|
32
|
+
def mock_value_for(io, size)
|
|
33
|
+
Matchers.stub_method(io, :size, size) do
|
|
34
|
+
yield
|
|
89
35
|
end
|
|
90
36
|
end
|
|
91
37
|
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_comparison_validator_matcher"
|
|
4
|
+
|
|
5
|
+
module ActiveStorageValidations
|
|
6
|
+
module Matchers
|
|
7
|
+
def validate_total_size_of(attribute_name)
|
|
8
|
+
TotalSizeValidatorMatcher.new(attribute_name)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class TotalSizeValidatorMatcher < BaseComparisonValidatorMatcher
|
|
12
|
+
def description
|
|
13
|
+
"validate total file size of :#{@attribute_name}"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def failure_message
|
|
17
|
+
message = [ "is expected to validate total file size of :#{@attribute_name}" ]
|
|
18
|
+
build_failure_message(message)
|
|
19
|
+
message.join("\n")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
protected
|
|
23
|
+
|
|
24
|
+
def attach_file
|
|
25
|
+
# has_many_attached relation
|
|
26
|
+
@subject.public_send(@attribute_name).attach([ dummy_blob ])
|
|
27
|
+
@subject.public_send(@attribute_name)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def failure_message_unit
|
|
33
|
+
"bytes"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def smallest_measurement
|
|
37
|
+
1.byte
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def mock_value_for(io, size)
|
|
41
|
+
Matchers.stub_method(io, :size, size) do
|
|
42
|
+
yield
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
3
|
+
require "active_storage_validations/matchers/aspect_ratio_validator_matcher"
|
|
4
|
+
require "active_storage_validations/matchers/attached_validator_matcher"
|
|
5
|
+
require "active_storage_validations/matchers/processable_file_validator_matcher"
|
|
6
|
+
require "active_storage_validations/matchers/limit_validator_matcher"
|
|
7
|
+
require "active_storage_validations/matchers/content_type_validator_matcher"
|
|
8
|
+
require "active_storage_validations/matchers/dimension_validator_matcher"
|
|
9
|
+
require "active_storage_validations/matchers/duration_validator_matcher"
|
|
10
|
+
require "active_storage_validations/matchers/size_validator_matcher"
|
|
11
|
+
require "active_storage_validations/matchers/total_size_validator_matcher"
|
|
12
|
+
require "active_storage_validations/matchers/pages_validator_matcher"
|
|
7
13
|
|
|
8
14
|
module ActiveStorageValidations
|
|
9
15
|
module Matchers
|
|
@@ -17,26 +23,16 @@ module ActiveStorageValidations
|
|
|
17
23
|
RSpec::Mocks.allow_message(object, method) { result }
|
|
18
24
|
yield
|
|
19
25
|
else
|
|
20
|
-
raise
|
|
26
|
+
raise "Need either Minitest::Mock or RSpec::Mocks to run this validator matcher"
|
|
21
27
|
end
|
|
22
28
|
end
|
|
23
29
|
|
|
24
|
-
def self.mock_metadata(attachment,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
31
|
-
else
|
|
32
|
-
# Stub the metadata analysis for rails 5
|
|
33
|
-
stub_method(attachment, :analyze, true) do
|
|
34
|
-
stub_method(attachment, :analyzed?, true) do
|
|
35
|
-
stub_method(attachment, :metadata, { width: width, height: height }) do
|
|
36
|
-
yield
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
|
30
|
+
def self.mock_metadata(attachment, metadata = {})
|
|
31
|
+
asv_metadata_available_keys = { width: nil, height: nil, duration: nil, content_type: nil }
|
|
32
|
+
mock = Struct.new(:metadata).new(asv_metadata_available_keys.merge(metadata)) # ensure all keys are present, and it does not raise while trying to access them
|
|
33
|
+
|
|
34
|
+
stub_method(ActiveStorageValidations::Analyzer, :new, mock) do
|
|
35
|
+
yield
|
|
40
36
|
end
|
|
41
37
|
end
|
|
42
38
|
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base_comparison_validator"
|
|
4
|
+
require_relative "shared/asv_analyzable"
|
|
5
|
+
require_relative "shared/asv_attachable"
|
|
6
|
+
|
|
7
|
+
module ActiveStorageValidations
|
|
8
|
+
class PagesValidator < BaseComparisonValidator
|
|
9
|
+
include ASVAnalyzable
|
|
10
|
+
include ASVAttachable
|
|
11
|
+
|
|
12
|
+
ERROR_TYPES = %i[
|
|
13
|
+
pages_not_less_than
|
|
14
|
+
pages_not_less_than_or_equal_to
|
|
15
|
+
pages_not_greater_than
|
|
16
|
+
pages_not_greater_than_or_equal_to
|
|
17
|
+
pages_not_between
|
|
18
|
+
pages_not_equal_to
|
|
19
|
+
].freeze
|
|
20
|
+
METADATA_KEYS = %i[pages].freeze
|
|
21
|
+
|
|
22
|
+
delegate :number_to_delimited, to: ActiveSupport::NumberHelper
|
|
23
|
+
|
|
24
|
+
def validate_each(record, attribute, _value)
|
|
25
|
+
return if no_attachments?(record, attribute)
|
|
26
|
+
|
|
27
|
+
validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def is_valid?(record, attribute, file, metadata)
|
|
33
|
+
flat_options = set_flat_options(record)
|
|
34
|
+
errors_options = initialize_error_options(options, file)
|
|
35
|
+
|
|
36
|
+
unless valid_metadata?(metadata)
|
|
37
|
+
add_media_metadata_missing_error(record, attribute, file, errors_options)
|
|
38
|
+
return false
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
return true if super(metadata[:pages], flat_options)
|
|
42
|
+
|
|
43
|
+
populate_error_options(errors_options, flat_options)
|
|
44
|
+
errors_options[:pages] = format_bound_value(metadata[:pages])
|
|
45
|
+
|
|
46
|
+
keys = AVAILABLE_CHECKS & flat_options.keys
|
|
47
|
+
error_type = "pages_not_#{keys.first}".to_sym
|
|
48
|
+
|
|
49
|
+
add_error(record, attribute, error_type, **errors_options)
|
|
50
|
+
false
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def valid_metadata?(metadata)
|
|
54
|
+
metadata[:pages].to_i > 0
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def format_bound_value(value)
|
|
58
|
+
number_to_delimited(value)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
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_symbolizable"
|
|
8
|
+
|
|
9
|
+
module ActiveStorageValidations
|
|
10
|
+
class ProcessableFileValidator < ActiveModel::EachValidator # :nodoc
|
|
11
|
+
include ASVActiveStorageable
|
|
12
|
+
include ASVAnalyzable
|
|
13
|
+
include ASVAttachable
|
|
14
|
+
include ASVErrorable
|
|
15
|
+
include ASVSymbolizable
|
|
16
|
+
|
|
17
|
+
ERROR_TYPES = %i[
|
|
18
|
+
file_not_processable
|
|
19
|
+
].freeze
|
|
20
|
+
METADATA_KEYS = %i[].freeze
|
|
21
|
+
|
|
22
|
+
def validate_each(record, attribute, _value)
|
|
23
|
+
return if no_attachments?(record, attribute)
|
|
24
|
+
|
|
25
|
+
validate_changed_files_from_metadata(record, attribute, METADATA_KEYS)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def is_valid?(record, attribute, attachable, metadata)
|
|
31
|
+
return if !metadata.empty?
|
|
32
|
+
|
|
33
|
+
errors_options = initialize_error_options(options, attachable)
|
|
34
|
+
add_error(record, attribute, ERROR_TYPES.first, **errors_options)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -2,5 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module ActiveStorageValidations
|
|
4
4
|
class Railtie < ::Rails::Railtie
|
|
5
|
+
# Using after: :load_config_initializers would cause a stack level too deep error
|
|
6
|
+
# See: https://github.com/igorkasyanchuk/active_storage_validations/issues/364
|
|
7
|
+
|
|
8
|
+
initializer "active_storage_validations.configure" do
|
|
9
|
+
ActiveSupport.on_load(:active_record) do
|
|
10
|
+
include ActiveStorageValidations
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
initializer "active_storage_validations.extend_active_storage_blob" do
|
|
15
|
+
ActiveSupport.on_load(:active_storage_blob) do
|
|
16
|
+
include ActiveStorageValidations::ASVBlobMetadatable
|
|
17
|
+
end
|
|
18
|
+
end
|
|
5
19
|
end
|
|
6
20
|
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveStorageValidations
|
|
4
|
+
# ActiveStorageValidations::ASVActiveStorageable
|
|
5
|
+
#
|
|
6
|
+
# Validator helper methods to make our code more explicit.
|
|
7
|
+
module ASVActiveStorageable
|
|
8
|
+
extend ActiveSupport::Concern
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Retrieve either an `ActiveStorage::Attached::One` or an
|
|
13
|
+
# `ActiveStorage::Attached::Many` instance depending on attribute definition
|
|
14
|
+
def attached_files(record, attribute)
|
|
15
|
+
Array.wrap(record.send(attribute))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def attachments_present?(record, attribute)
|
|
19
|
+
record.send(attribute).attached?
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def no_attachments?(record, attribute)
|
|
23
|
+
!attachments_present?(record, attribute)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def will_have_attachments_after_save?(record, attribute)
|
|
27
|
+
!Array.wrap(record.send(attribute)).all?(&:marked_for_destruction?)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
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
|
+
# Retrieve the ASV metadata from the blob.
|
|
16
|
+
# If the blob has not been analyzed by our gem yet, the gem will analyze the
|
|
17
|
+
# attachable with the corresponding analyzer and set the metadata in the
|
|
18
|
+
# blob.
|
|
19
|
+
def metadata_for(blob, attachable, metadata_keys)
|
|
20
|
+
return blob.active_storage_validations_metadata if blob_has_asv_metadata?(blob, metadata_keys)
|
|
21
|
+
|
|
22
|
+
new_metadata = generate_metadata_for(attachable, metadata_keys)
|
|
23
|
+
blob.merge_into_active_storage_validations_metadata(new_metadata)
|
|
24
|
+
blob.save!
|
|
25
|
+
|
|
26
|
+
blob.active_storage_validations_metadata
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def blob_has_asv_metadata?(blob, metadata_keys)
|
|
30
|
+
return false unless blob.active_storage_validations_metadata.present?
|
|
31
|
+
|
|
32
|
+
metadata_keys.all? { |key| blob.active_storage_validations_metadata.key?(key) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def generate_metadata_for(attachable, metadata_keys)
|
|
36
|
+
if metadata_keys == ActiveStorageValidations::ContentTypeValidator::METADATA_KEYS
|
|
37
|
+
content_type_analyzer_for(attachable).content_type
|
|
38
|
+
else
|
|
39
|
+
metadata_analyzer_for(attachable).metadata
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def metadata_analyzer_for(attachable)
|
|
44
|
+
return pdf_analyzer_for(attachable) if attachable_content_type(attachable) == "application/pdf"
|
|
45
|
+
|
|
46
|
+
case attachable_media_type(attachable)
|
|
47
|
+
when "image" then image_analyzer_for(attachable)
|
|
48
|
+
when "video" then video_analyzer_for(attachable)
|
|
49
|
+
when "audio" then audio_analyzer_for(attachable)
|
|
50
|
+
else fallback_analyzer_for(attachable)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def pdf_analyzer_for(attachable)
|
|
55
|
+
ActiveStorageValidations::Analyzer::PdfAnalyzer.new(attachable)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def image_analyzer_for(attachable)
|
|
59
|
+
case image_processor
|
|
60
|
+
when :mini_magick
|
|
61
|
+
ActiveStorageValidations::Analyzer::ImageAnalyzer::ImageMagick.new(attachable)
|
|
62
|
+
when :vips
|
|
63
|
+
ActiveStorageValidations::Analyzer::ImageAnalyzer::Vips.new(attachable)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def image_processor
|
|
68
|
+
# Rails returns nil for default image processor, because it is set in an after initialize callback
|
|
69
|
+
# https://github.com/rails/rails/blob/main/activestorage/lib/active_storage/engine.rb
|
|
70
|
+
ActiveStorage.variant_processor || DEFAULT_IMAGE_PROCESSOR
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def video_analyzer_for(attachable)
|
|
74
|
+
ActiveStorageValidations::Analyzer::VideoAnalyzer.new(attachable)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def audio_analyzer_for(attachable)
|
|
78
|
+
ActiveStorageValidations::Analyzer::AudioAnalyzer.new(attachable)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def fallback_analyzer_for(attachable)
|
|
82
|
+
ActiveStorageValidations::Analyzer::NullAnalyzer.new(attachable)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def content_type_analyzer_for(attachable)
|
|
86
|
+
ActiveStorageValidations::Analyzer::ContentTypeAnalyzer.new(attachable)
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|