active_storage_validations 0.8.2 → 0.9.4
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 +131 -19
- data/config/locales/de.yml +13 -8
- data/config/locales/en.yml +4 -4
- data/config/locales/es.yml +22 -0
- data/config/locales/fr.yml +22 -0
- data/config/locales/it.yml +22 -0
- data/config/locales/ja.yml +22 -0
- data/config/locales/nl.yml +22 -0
- data/config/locales/pl.yml +22 -0
- data/config/locales/pt-BR.yml +22 -0
- data/config/locales/ru.yml +22 -0
- data/config/locales/tr.yml +22 -0
- data/config/locales/uk.yml +22 -0
- data/config/locales/vi.yml +22 -0
- data/lib/active_storage_validations/attached_validator.rb +4 -1
- data/lib/active_storage_validations/content_type_validator.rb +15 -7
- data/lib/active_storage_validations/dimension_validator.rb +5 -5
- data/lib/active_storage_validations/limit_validator.rb +4 -8
- data/lib/active_storage_validations/matchers.rb +43 -0
- data/lib/active_storage_validations/matchers/attached_validator_matcher.rb +59 -0
- data/lib/active_storage_validations/matchers/content_type_validator_matcher.rb +101 -0
- data/lib/active_storage_validations/matchers/dimension_validator_matcher.rb +152 -0
- data/lib/active_storage_validations/matchers/size_validator_matcher.rb +93 -0
- data/lib/active_storage_validations/metadata.rb +14 -3
- data/lib/active_storage_validations/size_validator.rb +1 -1
- data/lib/active_storage_validations/version.rb +1 -1
- metadata +30 -14
@@ -0,0 +1,22 @@
|
|
1
|
+
tr:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
content_type_invalid: "geçersiz dosya tipine sahip"
|
5
|
+
file_size_out_of_range: "dosya boyutu %{file_size} gerekli aralık dışında"
|
6
|
+
limit_out_of_range: "toplam miktar aralık dışında"
|
7
|
+
image_metadata_missing: "geçerli bir imaj değil"
|
8
|
+
dimension_min_inclusion: "%{width} x %{height} piksele eşit ya da büyük olmalı"
|
9
|
+
dimension_max_inclusion: "%{width} x %{height} piksele eşit ya da küçük olmalı"
|
10
|
+
dimension_width_inclusion: "en %{min} ve %{max} piksel aralığı dışında"
|
11
|
+
dimension_height_inclusion: "boy %{min} ve %{max} piksel aralığı dışında"
|
12
|
+
dimension_width_greater_than_or_equal_to: "en %{length} piksele eşit ya da büyük olmalı"
|
13
|
+
dimension_height_greater_than_or_equal_to: "boy %{length} piksele eşit ya da büyük olmalı"
|
14
|
+
dimension_width_less_than_or_equal_to: "en %{length} piksele eşit ya da küçük olmalı"
|
15
|
+
dimension_height_less_than_or_equal_to: "boy %{length} piksele eşit ya da küçük olmalı"
|
16
|
+
dimension_width_equal_to: "en %{length} piksele eşit olmalı"
|
17
|
+
dimension_height_equal_to: "boy %{length} piksele eşit olmalı"
|
18
|
+
aspect_ratio_not_square: "kare bir imaj olmalı"
|
19
|
+
aspect_ratio_not_portrait: "dikey bir imaj olmalı"
|
20
|
+
aspect_ratio_not_landscape: "yatay bir imaj olmalı"
|
21
|
+
aspect_ratio_is_not: "%{aspect_ratio} en boy oranına sahip olmalı"
|
22
|
+
aspect_ratio_unknown: "bilinmeyen en boy oranı"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
uk:
|
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: "має невідоме співвідношення сторін"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
vi:
|
2
|
+
errors:
|
3
|
+
messages:
|
4
|
+
content_type_invalid: "tệp không hợp lệ"
|
5
|
+
file_size_out_of_range: "kích thước %{file_size} vượt giới hạn"
|
6
|
+
limit_out_of_range: "tổng số tệp vượt giới hạn"
|
7
|
+
image_metadata_missing: "không phải là ảnh"
|
8
|
+
dimension_min_inclusion: "phải lớn hơn hoặc bằng %{width} x %{height} pixel"
|
9
|
+
dimension_max_inclusion: "phải nhỏ hơn hoặc bằng %{width} x %{height} pixel"
|
10
|
+
dimension_width_inclusion: "chiều rộng không nằm trong %{min} và %{max} pixel"
|
11
|
+
dimension_height_inclusion: "chiều cao không nằm trong %{min} và %{max} pixel"
|
12
|
+
dimension_width_greater_than_or_equal_to: "chiều rộng phải lớn hơn hoặc bằng %{length} pixel"
|
13
|
+
dimension_height_greater_than_or_equal_to: "chiều cao phải lớn hơn hoặc bằng %{length} pixel"
|
14
|
+
dimension_width_less_than_or_equal_to: "chiều rộng phải nhỏ hơn hoặc bằng %{length} pixel"
|
15
|
+
dimension_height_less_than_or_equal_to: "chiều cao phải nhỏ hơn hoặc bằng %{length} pixel"
|
16
|
+
dimension_width_equal_to: "chiều rộng phải bằng %{length} pixel"
|
17
|
+
dimension_height_equal_to: "chiều cao phải bằng %{length} pixel"
|
18
|
+
aspect_ratio_not_square: "phải là ảnh hình vuông"
|
19
|
+
aspect_ratio_not_portrait: "phải là ảnh đứng"
|
20
|
+
aspect_ratio_not_landscape: "phải là ảnh ngang"
|
21
|
+
aspect_ratio_is_not: "phải có tỉ lệ ảnh %{aspect_ratio}"
|
22
|
+
aspect_ratio_unknown: "tỉ lệ ảnh không xác định"
|
@@ -5,7 +5,10 @@ module ActiveStorageValidations
|
|
5
5
|
def validate_each(record, attribute, _value)
|
6
6
|
return if record.send(attribute).attached?
|
7
7
|
|
8
|
-
|
8
|
+
errors_options = {}
|
9
|
+
errors_options[:message] = options[:message] if options[:message].present?
|
10
|
+
|
11
|
+
record.errors.add(attribute, :blank, **errors_options)
|
9
12
|
end
|
10
13
|
end
|
11
14
|
end
|
@@ -14,19 +14,28 @@ module ActiveStorageValidations
|
|
14
14
|
next if is_valid?(file)
|
15
15
|
|
16
16
|
errors_options[:content_type] = content_type(file)
|
17
|
-
record.errors.add(attribute, :content_type_invalid, errors_options)
|
17
|
+
record.errors.add(attribute, :content_type_invalid, **errors_options)
|
18
18
|
break
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
22
22
|
def types
|
23
23
|
(Array.wrap(options[:with]) + Array.wrap(options[:in])).compact.map do |type|
|
24
|
-
|
24
|
+
if type.is_a?(Regexp)
|
25
|
+
type
|
26
|
+
elsif type.is_a?(String) && type =~ %r{\A\w+/[-+.\w]+\z} # mime-type-ish string
|
27
|
+
type
|
28
|
+
else
|
29
|
+
Mime[type] || raise(ArgumentError, "content_type must be one of Regxep,"\
|
30
|
+
" supported mime types (e.g. :png, 'jpg'), or mime type String ('image/jpeg')")
|
31
|
+
end
|
25
32
|
end
|
26
33
|
end
|
27
34
|
|
28
35
|
def types_to_human_format
|
29
|
-
types
|
36
|
+
types
|
37
|
+
.map { |type| type.to_s.split('/').last.upcase }
|
38
|
+
.join(', ')
|
30
39
|
end
|
31
40
|
|
32
41
|
def content_type(file)
|
@@ -34,10 +43,9 @@ module ActiveStorageValidations
|
|
34
43
|
end
|
35
44
|
|
36
45
|
def is_valid?(file)
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
content_type(file).in?(types)
|
46
|
+
file_type = content_type(file)
|
47
|
+
types.any? do |type|
|
48
|
+
type == file_type || (type.is_a?(Regexp) && type.match?(file_type.to_s))
|
41
49
|
end
|
42
50
|
end
|
43
51
|
end
|
@@ -52,7 +52,7 @@ module ActiveStorageValidations
|
|
52
52
|
# Rails 5
|
53
53
|
def validate_each(record, attribute, _value)
|
54
54
|
return true unless record.send(attribute).attached?
|
55
|
-
|
55
|
+
|
56
56
|
files = Array.wrap(record.send(attribute))
|
57
57
|
files.each do |file|
|
58
58
|
# Analyze file first if not analyzed to get all required metadata.
|
@@ -110,7 +110,7 @@ module ActiveStorageValidations
|
|
110
110
|
else
|
111
111
|
if file_metadata[length] != options[length]
|
112
112
|
add_error(record, attribute, options[:message].presence || :"dimension_#{length}_equal_to", length: options[length])
|
113
|
-
return false
|
113
|
+
return false
|
114
114
|
end
|
115
115
|
end
|
116
116
|
end
|
@@ -119,11 +119,11 @@ module ActiveStorageValidations
|
|
119
119
|
true # valid file
|
120
120
|
end
|
121
121
|
|
122
|
-
def add_error(record, attribute, type,
|
122
|
+
def add_error(record, attribute, type, **attrs)
|
123
123
|
key = options[:message].presence || type
|
124
124
|
return if record.errors.added?(attribute, key)
|
125
|
-
record.errors.add(attribute, key,
|
126
|
-
end
|
125
|
+
record.errors.add(attribute, key, **attrs)
|
126
|
+
end
|
127
127
|
|
128
128
|
end
|
129
129
|
end
|
@@ -10,18 +10,14 @@ module ActiveStorageValidations
|
|
10
10
|
raise ArgumentError, 'You must pass either :max or :min to the validator'
|
11
11
|
end
|
12
12
|
|
13
|
-
def validate_each(record, attribute,
|
13
|
+
def validate_each(record, attribute, _)
|
14
14
|
return true unless record.send(attribute).attached?
|
15
15
|
|
16
|
-
files = Array.wrap(record.send(attribute))
|
17
|
-
|
18
|
-
errors_options = {}
|
19
|
-
errors_options[:min] = options[:min]
|
20
|
-
errors_options[:max] = options[:max]
|
16
|
+
files = Array.wrap(record.send(attribute)).compact.uniq
|
17
|
+
errors_options = { min: options[:min], max: options[:max] }
|
21
18
|
|
22
19
|
return true if files_count_valid?(files.count)
|
23
|
-
|
24
|
-
record.errors.add(attribute, options[:message].presence || :limit_out_of_range, errors_options)
|
20
|
+
record.errors.add(attribute, options[:message].presence || :limit_out_of_range, **errors_options)
|
25
21
|
end
|
26
22
|
|
27
23
|
def files_count_valid?(count)
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_storage_validations/matchers/attached_validator_matcher'
|
4
|
+
require 'active_storage_validations/matchers/content_type_validator_matcher'
|
5
|
+
require 'active_storage_validations/matchers/dimension_validator_matcher'
|
6
|
+
require 'active_storage_validations/matchers/size_validator_matcher'
|
7
|
+
|
8
|
+
module ActiveStorageValidations
|
9
|
+
module Matchers
|
10
|
+
# Helper to stub a method with either RSpec or Minitest (whatever is available)
|
11
|
+
def self.stub_method(object, method, result)
|
12
|
+
if defined?(Minitest::Mock)
|
13
|
+
object.stub(method, result) do
|
14
|
+
yield
|
15
|
+
end
|
16
|
+
elsif defined?(RSpec::Mocks)
|
17
|
+
RSpec::Mocks.allow_message(object, method) { result }
|
18
|
+
yield
|
19
|
+
else
|
20
|
+
raise 'Need either Minitest::Mock or RSpec::Mocks to run this validator matcher'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.mock_metadata(attachment, width, height)
|
25
|
+
if Rails::VERSION::MAJOR >= 6
|
26
|
+
# Mock the Metadata class for rails 6
|
27
|
+
mock = OpenStruct.new(metadata: { width: width, height: height })
|
28
|
+
stub_method(ActiveStorageValidations::Metadata, :new, mock) do
|
29
|
+
yield
|
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
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
def validate_attached_of(name)
|
6
|
+
AttachedValidatorMatcher.new(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
class AttachedValidatorMatcher
|
10
|
+
def initialize(attribute_name)
|
11
|
+
@attribute_name = attribute_name
|
12
|
+
end
|
13
|
+
|
14
|
+
def description
|
15
|
+
"validate #{@attribute_name} must be attached"
|
16
|
+
end
|
17
|
+
|
18
|
+
def matches?(subject)
|
19
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
20
|
+
responds_to_methods && valid_when_attached && invalid_when_not_attached
|
21
|
+
end
|
22
|
+
|
23
|
+
def failure_message
|
24
|
+
"is expected to validate attached of #{@attribute_name}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def failure_message_when_negated
|
28
|
+
"is expected to not validate attached of #{@attribute_name}"
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def responds_to_methods
|
34
|
+
@subject.respond_to?(@attribute_name) &&
|
35
|
+
@subject.public_send(@attribute_name).respond_to?(:attach) &&
|
36
|
+
@subject.public_send(@attribute_name).respond_to?(:detach)
|
37
|
+
end
|
38
|
+
|
39
|
+
def valid_when_attached
|
40
|
+
@subject.public_send(@attribute_name).attach(attachable) unless @subject.public_send(@attribute_name).attached?
|
41
|
+
@subject.validate
|
42
|
+
@subject.errors.details[@attribute_name].exclude?(error: :blank)
|
43
|
+
end
|
44
|
+
|
45
|
+
def invalid_when_not_attached
|
46
|
+
@subject.public_send(@attribute_name).detach
|
47
|
+
# Unset the direct relation since `detach` on an unpersisted record does not set `attached?` to false.
|
48
|
+
@subject.public_send("#{@attribute_name}=", nil)
|
49
|
+
|
50
|
+
@subject.validate
|
51
|
+
@subject.errors.details[@attribute_name].include?(error: :blank)
|
52
|
+
end
|
53
|
+
|
54
|
+
def attachable
|
55
|
+
{ io: Tempfile.new('.'), filename: 'dummy.txt', content_type: 'text/plain' }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Big thank you to the paperclip validation matchers:
|
4
|
+
# https://github.com/thoughtbot/paperclip/blob/v6.1.0/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb
|
5
|
+
module ActiveStorageValidations
|
6
|
+
module Matchers
|
7
|
+
def validate_content_type_of(name)
|
8
|
+
ContentTypeValidatorMatcher.new(name)
|
9
|
+
end
|
10
|
+
|
11
|
+
class ContentTypeValidatorMatcher
|
12
|
+
def initialize(attribute_name)
|
13
|
+
@attribute_name = attribute_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
"validate the content types allowed on attachment #{@attribute_name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def allowing(*types)
|
21
|
+
@allowed_types = types.flatten
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def rejecting(*types)
|
26
|
+
@rejected_types = types.flatten
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def matches?(subject)
|
31
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
32
|
+
responds_to_methods && allowed_types_allowed? && rejected_types_rejected?
|
33
|
+
end
|
34
|
+
|
35
|
+
def failure_message
|
36
|
+
<<~MESSAGE
|
37
|
+
Expected #{@attribute_name}
|
38
|
+
|
39
|
+
Accept content types: #{allowed_types.join(", ")}
|
40
|
+
#{accepted_types_and_failures}
|
41
|
+
|
42
|
+
Reject content types: #{rejected_types.join(", ")}
|
43
|
+
#{rejected_types_and_failures}
|
44
|
+
MESSAGE
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
|
49
|
+
def responds_to_methods
|
50
|
+
@subject.respond_to?(@attribute_name) &&
|
51
|
+
@subject.public_send(@attribute_name).respond_to?(:attach) &&
|
52
|
+
@subject.public_send(@attribute_name).respond_to?(:detach)
|
53
|
+
end
|
54
|
+
|
55
|
+
def allowed_types
|
56
|
+
@allowed_types || []
|
57
|
+
end
|
58
|
+
|
59
|
+
def rejected_types
|
60
|
+
@rejected_types || (Mime::LOOKUP.keys - allowed_types)
|
61
|
+
end
|
62
|
+
|
63
|
+
def allowed_types_allowed?
|
64
|
+
@missing_allowed_types ||= allowed_types.reject { |type| type_allowed?(type) }
|
65
|
+
@missing_allowed_types.none?
|
66
|
+
end
|
67
|
+
|
68
|
+
def rejected_types_rejected?
|
69
|
+
@missing_rejected_types ||= rejected_types.select { |type| type_allowed?(type) }
|
70
|
+
@missing_rejected_types.none?
|
71
|
+
end
|
72
|
+
|
73
|
+
def accepted_types_and_failures
|
74
|
+
if @missing_allowed_types.present?
|
75
|
+
"#{@missing_allowed_types.join(", ")} were rejected."
|
76
|
+
else
|
77
|
+
"All were accepted successfully."
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def rejected_types_and_failures
|
82
|
+
if @missing_rejected_types.present?
|
83
|
+
"#{@missing_rejected_types.join(", ")} were accepted."
|
84
|
+
else
|
85
|
+
"All were rejected successfully."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def type_allowed?(type)
|
90
|
+
@subject.public_send(@attribute_name).attach(attachment_for(type))
|
91
|
+
@subject.validate
|
92
|
+
@subject.errors.details[@attribute_name].all? { |error| error[:error] != :content_type_invalid }
|
93
|
+
end
|
94
|
+
|
95
|
+
def attachment_for(type)
|
96
|
+
suffix = type.to_s.split('/').last
|
97
|
+
{ io: Tempfile.new('.'), filename: "test.#{suffix}", content_type: type }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveStorageValidations
|
4
|
+
module Matchers
|
5
|
+
def validate_dimensions_of(name)
|
6
|
+
DimensionValidatorMatcher.new(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
class DimensionValidatorMatcher
|
10
|
+
def initialize(attribute_name)
|
11
|
+
@attribute_name = attribute_name
|
12
|
+
@width_min = @width_max = @height_min = @height_max = nil
|
13
|
+
@custom_message = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
"validate image dimensions of #{@attribute_name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def width_min(width)
|
21
|
+
@width_min = width
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def width_max(width)
|
26
|
+
@width_max = width
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def with_message(message)
|
31
|
+
@custom_message = message
|
32
|
+
self
|
33
|
+
end
|
34
|
+
|
35
|
+
def width(width)
|
36
|
+
@width_min = @width_max = width
|
37
|
+
self
|
38
|
+
end
|
39
|
+
|
40
|
+
def height_min(height)
|
41
|
+
@height_min = height
|
42
|
+
self
|
43
|
+
end
|
44
|
+
|
45
|
+
def height_max(height)
|
46
|
+
@height_max = height
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def width_between(range)
|
51
|
+
@width_min, @width_max = range.first, range.last
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def height_between(range)
|
56
|
+
@height_min, @height_max = range.first, range.last
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def height(height)
|
61
|
+
@height_min = @height_max = height
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
def matches?(subject)
|
66
|
+
@subject = subject.is_a?(Class) ? subject.new : subject
|
67
|
+
responds_to_methods &&
|
68
|
+
width_smaller_than_min? && width_larger_than_min? && width_smaller_than_max? && width_larger_than_max? && width_equals? &&
|
69
|
+
height_smaller_than_min? && height_larger_than_min? && height_smaller_than_max? && height_larger_than_max? && height_equals?
|
70
|
+
end
|
71
|
+
|
72
|
+
def failure_message
|
73
|
+
<<~MESSAGE
|
74
|
+
is expected to validate dimensions of #{@attribute_name}
|
75
|
+
width between #{@width_min} and #{@width_max}
|
76
|
+
height between #{@height_min} and #{@height_max}
|
77
|
+
MESSAGE
|
78
|
+
end
|
79
|
+
|
80
|
+
protected
|
81
|
+
|
82
|
+
def responds_to_methods
|
83
|
+
@subject.respond_to?(@attribute_name) &&
|
84
|
+
@subject.public_send(@attribute_name).respond_to?(:attach) &&
|
85
|
+
@subject.public_send(@attribute_name).respond_to?(:detach)
|
86
|
+
end
|
87
|
+
|
88
|
+
def valid_width
|
89
|
+
((@width_min || 0) + (@width_max || 2000)) / 2
|
90
|
+
end
|
91
|
+
|
92
|
+
def valid_height
|
93
|
+
((@height_min || 0) + (@height_max || 2000)) / 2
|
94
|
+
end
|
95
|
+
|
96
|
+
def width_smaller_than_min?
|
97
|
+
@width_min.nil? || !passes_validation_with_dimensions(@width_min - 1, valid_height, 'width')
|
98
|
+
end
|
99
|
+
|
100
|
+
def width_larger_than_min?
|
101
|
+
@width_min.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_min + 1, valid_height, 'width')
|
102
|
+
end
|
103
|
+
|
104
|
+
def width_smaller_than_max?
|
105
|
+
@width_max.nil? || @width_min == @width_max || passes_validation_with_dimensions(@width_max - 1, valid_height, 'width')
|
106
|
+
end
|
107
|
+
|
108
|
+
def width_larger_than_max?
|
109
|
+
@width_max.nil? || !passes_validation_with_dimensions(@width_max + 1, valid_height, 'width')
|
110
|
+
end
|
111
|
+
|
112
|
+
def width_equals?
|
113
|
+
@width_min.nil? || @width_min != @width_max || passes_validation_with_dimensions(@width_min, valid_height, 'width')
|
114
|
+
end
|
115
|
+
|
116
|
+
def height_smaller_than_min?
|
117
|
+
@height_min.nil? || !passes_validation_with_dimensions(valid_width, @height_min - 1, 'height')
|
118
|
+
end
|
119
|
+
|
120
|
+
def height_larger_than_min?
|
121
|
+
@height_min.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_min + 1, 'height')
|
122
|
+
end
|
123
|
+
|
124
|
+
def height_smaller_than_max?
|
125
|
+
@height_max.nil? || @height_min == @height_max || passes_validation_with_dimensions(valid_width, @height_max - 1, 'height')
|
126
|
+
end
|
127
|
+
|
128
|
+
def height_larger_than_max?
|
129
|
+
@height_max.nil? || !passes_validation_with_dimensions(valid_width, @height_max + 1, 'height')
|
130
|
+
end
|
131
|
+
|
132
|
+
def height_equals?
|
133
|
+
@height_min.nil? || @height_min != @height_max || passes_validation_with_dimensions(valid_width, @height_min, 'height')
|
134
|
+
end
|
135
|
+
|
136
|
+
def passes_validation_with_dimensions(width, height, check)
|
137
|
+
@subject.public_send(@attribute_name).attach attachment_for(width, height)
|
138
|
+
|
139
|
+
attachment = @subject.public_send(@attribute_name)
|
140
|
+
Matchers.mock_metadata(attachment, width, height) do
|
141
|
+
@subject.validate
|
142
|
+
exclude_error_message = @custom_message || "dimension_#{check}"
|
143
|
+
@subject.errors.details[@attribute_name].all? { |error| error[:error].to_s.exclude?(exclude_error_message) }
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def attachment_for(width, height)
|
148
|
+
{ io: Tempfile.new('Hello world!'), filename: 'test.png', content_type: 'image/png' }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|