file_validators 2.3.0 → 3.0.0.beta1
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/.rubocop.yml +32 -0
- data/Appraisals +2 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +2 -0
- data/README.md +10 -9
- data/Rakefile +3 -1
- data/file_validators.gemspec +9 -6
- data/gemfiles/activemodel_3.2.gemfile +7 -5
- data/gemfiles/activemodel_4.0.gemfile +7 -5
- data/gemfiles/activemodel_4.1.gemfile +7 -5
- data/gemfiles/activemodel_4.2.gemfile +7 -5
- data/gemfiles/activemodel_5.0.gemfile +6 -4
- data/lib/file_validators.rb +3 -1
- data/lib/file_validators/utils/content_type_detector.rb +9 -10
- data/lib/file_validators/utils/media_type_spoof_detector.rb +4 -4
- data/lib/file_validators/validators/file_content_type_validator.rb +15 -15
- data/lib/file_validators/validators/file_size_validator.rb +36 -18
- data/lib/file_validators/version.rb +3 -1
- data/spec/integration/combined_validators_integration_spec.rb +3 -1
- data/spec/integration/file_content_type_validation_integration_spec.rb +43 -17
- data/spec/integration/file_size_validator_integration_spec.rb +37 -15
- data/spec/lib/file_validators/utils/content_type_detector_spec.rb +2 -0
- data/spec/lib/file_validators/utils/media_type_spoof_detector_spec.rb +2 -0
- data/spec/lib/file_validators/validators/file_content_type_validator_spec.rb +90 -32
- data/spec/lib/file_validators/validators/file_size_validator_spec.rb +78 -30
- data/spec/spec_helper.rb +2 -0
- data/spec/support/matchers/allow_content_type.rb +2 -0
- data/spec/support/matchers/allow_file_size.rb +2 -0
- metadata +34 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6738e978bc3966d2acd83590b2e77b240e005bed
|
4
|
+
data.tar.gz: 9fccf7c9b07a08f867582ed3c65aee9606018bd7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59033aade86b442a9bd3eb3f8cbc61cf38c66452af71ba58471998e3c015a8b0ead2eba3275fd23a988c61f5cc8719b0dee7af0b099c0c25f7322d2be7775616
|
7
|
+
data.tar.gz: 90e211e12d9f5b3d9dc6aebecb4b3b84b853538d61eeb6ae8c014c9618d06fae927d0af7509aa5e616598034a54995e45a6e10fee8c03ad88c401e4a231c01bb
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
Bundler/OrderedGems:
|
2
|
+
Enabled: false
|
3
|
+
|
4
|
+
Style/Documentation:
|
5
|
+
Enabled: false
|
6
|
+
|
7
|
+
Style/MissingRespondToMissing:
|
8
|
+
Enabled: false
|
9
|
+
|
10
|
+
Style/CaseEquality:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
Style/GuardClause:
|
14
|
+
Enabled: false
|
15
|
+
|
16
|
+
Style/RegexpLiteral:
|
17
|
+
Enabled: false
|
18
|
+
|
19
|
+
Style/Next:
|
20
|
+
Enabled: false
|
21
|
+
|
22
|
+
Metrics/LineLength:
|
23
|
+
Max: 110
|
24
|
+
|
25
|
+
Metrics/ModuleLength:
|
26
|
+
Enabled: false
|
27
|
+
|
28
|
+
Metrics/BlockLength:
|
29
|
+
Enabled: false
|
30
|
+
|
31
|
+
Metrics/MethodLength:
|
32
|
+
Enabled: false
|
data/Appraisals
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,13 @@
|
|
1
|
+
# 3.0.0.beta1
|
2
|
+
|
3
|
+
* [#29](https://github.com/musaffa/file_validators/pull/29) Upgrade cocaine to terrapin
|
4
|
+
* Rubocop style guide
|
5
|
+
|
6
|
+
# 2.3.0
|
7
|
+
|
8
|
+
* [#19](https://github.com/musaffa/file_validators/pull/19) Return false with blank size
|
9
|
+
* [#27](https://github.com/musaffa/file_validators/pull/27) Fix file size validator for ActiveStorage
|
10
|
+
|
1
11
|
# 2.2.0-beta.1
|
2
12
|
|
3
13
|
* [#17](https://github.com/musaffa/file_validators/pull/17) Now Supports multiple file uploads
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -39,7 +39,7 @@ class Profile
|
|
39
39
|
|
40
40
|
attr_accessor :avatar
|
41
41
|
validates :avatar, file_size: { less_than_or_equal_to: 100.kilobytes },
|
42
|
-
file_content_type: { allow: ['image/jpeg', 'image/png'] }
|
42
|
+
file_content_type: { allow: ['image/jpeg', 'image/png'] }
|
43
43
|
end
|
44
44
|
```
|
45
45
|
ActiveRecord example:
|
@@ -67,23 +67,23 @@ validates :avatar, file_size: { less_than: 2.gigabytes }
|
|
67
67
|
```
|
68
68
|
* `less_than_or_equal_to`: Less than or equal to a number in bytes or a proc that returns a number
|
69
69
|
```ruby
|
70
|
-
validates :avatar, file_size: { less_than_or_equal_to: 50.bytes }
|
70
|
+
validates :avatar, file_size: { less_than_or_equal_to: 50.bytes }
|
71
71
|
```
|
72
72
|
* `greater_than`: greater than a number in bytes or a proc that returns a number
|
73
73
|
```ruby
|
74
|
-
validates :avatar, file_size: { greater_than: 1.byte }
|
74
|
+
validates :avatar, file_size: { greater_than: 1.byte }
|
75
75
|
```
|
76
76
|
* `greater_than_or_equal_to`: Greater than or equal to a number in bytes or a proc that returns a number
|
77
77
|
```ruby
|
78
|
-
validates :avatar, file_size: { greater_than_or_equal_to: 50.bytes }
|
78
|
+
validates :avatar, file_size: { greater_than_or_equal_to: 50.bytes }
|
79
79
|
```
|
80
|
-
* `message`: Error message to display. With all the options above except `:in`, you will get `count` as a replacement.
|
81
|
-
With `:in` you will get `min` and `max` as replacements.
|
80
|
+
* `message`: Error message to display. With all the options above except `:in`, you will get `count` as a replacement.
|
81
|
+
With `:in` you will get `min` and `max` as replacements.
|
82
82
|
`count`, `min` and `max` each will have its value and unit together.
|
83
83
|
You can write error messages without using any replacement.
|
84
84
|
```ruby
|
85
85
|
validates :avatar, file_size: { less_than: 100.kilobytes,
|
86
|
-
message: 'avatar should be less than %{count}' }
|
86
|
+
message: 'avatar should be less than %{count}' }
|
87
87
|
```
|
88
88
|
```ruby
|
89
89
|
validates :document, file_size: { in: 1.kilobyte..1.megabyte,
|
@@ -172,7 +172,7 @@ validates :avatar, file_content_type: { allow: /^image\/.*/, exclude: ['image/pn
|
|
172
172
|
This gem can use Unix file command to get the content type based on the content of the file rather
|
173
173
|
than the extension. This prevents fake content types inserted in the request header.
|
174
174
|
|
175
|
-
It also prevents file media type spoofing. For example, user may upload a .html document as
|
175
|
+
It also prevents file media type spoofing. For example, user may upload a .html document as
|
176
176
|
a part of the EXIF header of a valid JPEG file. Content type validator will identify its content type
|
177
177
|
as `image/jpeg` and, without spoof detection, it may pass the validation and be saved as .html document
|
178
178
|
thus exposing your application to a security vulnerability. Media type spoof detector wont let that happen.
|
@@ -180,7 +180,7 @@ It will not allow a file having `image/jpeg` content type to be saved as `text/p
|
|
180
180
|
type mismatch, for example `text` of `text/plain` and `image` of `image/jpeg`. So it will not prevent
|
181
181
|
`image/jpeg` from saving as `image/png` as both have the same `image` media type.
|
182
182
|
|
183
|
-
**note**: This security feature is disabled by default. To enable it, first add `
|
183
|
+
**note**: This security feature is disabled by default. To enable it, first add `terrapin` gem in
|
184
184
|
your Gemfile and then add `mode: :strict` option in [content type validations](#file-content-type-validator).
|
185
185
|
`:strict` mode may not work in direct file uploading systems as the file is not passed along with the form.
|
186
186
|
|
@@ -254,6 +254,7 @@ uploaders start processing a file immediately after its assignment (even before
|
|
254
254
|
$ rake
|
255
255
|
$ rake test:unit
|
256
256
|
$ rake test:integration
|
257
|
+
$ rubocop
|
257
258
|
|
258
259
|
# test different active model versions
|
259
260
|
$ bundle exec appraisal install
|
data/Rakefile
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
begin
|
2
4
|
require 'bundler/setup'
|
3
5
|
rescue LoadError
|
@@ -16,7 +18,7 @@ namespace :test do
|
|
16
18
|
end
|
17
19
|
end
|
18
20
|
|
19
|
-
task :
|
21
|
+
task default: ['test:unit', 'test:integration']
|
20
22
|
|
21
23
|
# require 'rdoc/task'
|
22
24
|
|
data/file_validators.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
2
4
|
|
3
5
|
require 'file_validators/version'
|
4
6
|
|
@@ -12,17 +14,18 @@ Gem::Specification.new do |s|
|
|
12
14
|
s.homepage = 'https://github.com/musaffa/file_validators'
|
13
15
|
s.license = 'MIT'
|
14
16
|
|
15
|
-
s.files = `git ls-files`.split(
|
17
|
+
s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
16
18
|
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
19
|
s.test_files = s.files.grep(%r{^spec/})
|
18
|
-
s.require_paths
|
20
|
+
s.require_paths = ['lib']
|
19
21
|
|
20
22
|
s.add_dependency 'activemodel', '>= 3.2'
|
21
23
|
s.add_dependency 'mime-types', '>= 1.0'
|
22
24
|
|
23
|
-
s.add_development_dependency 'cocaine', '~> 0.5.4'
|
24
|
-
s.add_development_dependency 'rake'
|
25
|
-
s.add_development_dependency 'rspec', '~> 3.5.0'
|
26
25
|
s.add_development_dependency 'coveralls'
|
27
26
|
s.add_development_dependency 'rack-test'
|
27
|
+
s.add_development_dependency 'rake'
|
28
|
+
s.add_development_dependency 'rspec', '~> 3.5.0'
|
29
|
+
s.add_development_dependency 'rubocop', '~> 0.58.2'
|
30
|
+
s.add_development_dependency 'terrapin', '~> 0.6'
|
28
31
|
end
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file was generated by Appraisal
|
2
4
|
|
3
|
-
source
|
5
|
+
source 'https://rubygems.org'
|
4
6
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'activemodel', '3.2.22.5'
|
9
|
+
gem 'rack', '1.6.5'
|
8
10
|
|
9
|
-
gemspec :
|
11
|
+
gemspec path: '../'
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file was generated by Appraisal
|
2
4
|
|
3
|
-
source
|
5
|
+
source 'https://rubygems.org'
|
4
6
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'activemodel', '4.0.13'
|
9
|
+
gem 'rack', '1.6.5'
|
8
10
|
|
9
|
-
gemspec :
|
11
|
+
gemspec path: '../'
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file was generated by Appraisal
|
2
4
|
|
3
|
-
source
|
5
|
+
source 'https://rubygems.org'
|
4
6
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'activemodel', '4.1.6'
|
9
|
+
gem 'rack', '1.6.5'
|
8
10
|
|
9
|
-
gemspec :
|
11
|
+
gemspec path: '../'
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file was generated by Appraisal
|
2
4
|
|
3
|
-
source
|
5
|
+
source 'https://rubygems.org'
|
4
6
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'activemodel', '4.2.7.1'
|
9
|
+
gem 'rack', '1.6.5'
|
8
10
|
|
9
|
-
gemspec :
|
11
|
+
gemspec path: '../'
|
@@ -1,8 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file was generated by Appraisal
|
2
4
|
|
3
|
-
source
|
5
|
+
source 'https://rubygems.org'
|
4
6
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
+
gem 'appraisal'
|
8
|
+
gem 'activemodel', '5.0.1'
|
7
9
|
|
8
|
-
gemspec :
|
10
|
+
gemspec path: '../'
|
data/lib/file_validators.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'active_model'
|
2
4
|
require 'ostruct'
|
3
5
|
|
@@ -10,7 +12,7 @@ module FileValidators
|
|
10
12
|
end
|
11
13
|
end
|
12
14
|
|
13
|
-
Dir[File.dirname(__FILE__) +
|
15
|
+
Dir[File.dirname(__FILE__) + '/file_validators/validators/*.rb'].each { |file| require file }
|
14
16
|
|
15
17
|
locale_path = Dir.glob(File.dirname(__FILE__) + '/file_validators/locale/*.yml')
|
16
18
|
I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
|
@@ -1,14 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
|
3
5
|
begin
|
4
|
-
require '
|
6
|
+
require 'terrapin'
|
5
7
|
rescue LoadError
|
6
|
-
puts "file_validators requires '
|
8
|
+
puts "file_validators requires 'terrapin' gem as you are using" \
|
9
|
+
' file content type validations in strict mode'
|
7
10
|
end
|
8
11
|
|
9
12
|
module FileValidators
|
10
13
|
module Utils
|
11
|
-
|
12
14
|
class ContentTypeDetector
|
13
15
|
EMPTY_CONTENT_TYPE = 'inode/x-empty'
|
14
16
|
DEFAULT_CONTENT_TYPE = 'application/octet-stream'
|
@@ -50,18 +52,15 @@ module FileValidators
|
|
50
52
|
end
|
51
53
|
|
52
54
|
def type_from_file_command
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
DEFAULT_CONTENT_TYPE
|
58
|
-
end
|
55
|
+
Terrapin::CommandLine.new('file', '-b --mime-type :file').run(file: @file_path).strip
|
56
|
+
rescue Terrapin::CommandLineError => e
|
57
|
+
logger.info(e.message)
|
58
|
+
DEFAULT_CONTENT_TYPE
|
59
59
|
end
|
60
60
|
|
61
61
|
def logger
|
62
62
|
Logger.new(STDOUT)
|
63
63
|
end
|
64
64
|
end
|
65
|
-
|
66
65
|
end
|
67
66
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'mime/types'
|
2
4
|
|
3
5
|
module FileValidators
|
4
6
|
module Utils
|
5
|
-
|
6
7
|
class MediaTypeSpoofDetector
|
7
8
|
def initialize(content_type, file_name)
|
8
9
|
@content_type = content_type
|
@@ -18,12 +19,12 @@ module FileValidators
|
|
18
19
|
# for `image` of `image/png` match with `image` of `image/jpeg`.
|
19
20
|
|
20
21
|
def spoofed?
|
21
|
-
|
22
|
+
extension? && media_type_mismatch?
|
22
23
|
end
|
23
24
|
|
24
25
|
private
|
25
26
|
|
26
|
-
def
|
27
|
+
def extension?
|
27
28
|
# the following code replaced File.extname(@file_name).present? because it cannot
|
28
29
|
# return the extension of a extension-only file names, e.g. '.html', '.jpg' etc
|
29
30
|
@file_name.split('.').length > 1
|
@@ -41,6 +42,5 @@ module FileValidators
|
|
41
42
|
@content_type.split('/').first
|
42
43
|
end
|
43
44
|
end
|
44
|
-
|
45
45
|
end
|
46
46
|
end
|
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveModel
|
2
4
|
module Validations
|
3
|
-
|
4
5
|
class FileContentTypeValidator < ActiveModel::EachValidator
|
5
|
-
CHECKS = [
|
6
|
+
CHECKS = %i[allow exclude].freeze
|
6
7
|
|
7
8
|
def self.helper_method_name
|
8
9
|
:validates_file_content_type
|
@@ -10,16 +11,16 @@ module ActiveModel
|
|
10
11
|
|
11
12
|
def validate_each(record, attribute, value)
|
12
13
|
values = parse_values(value)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
14
|
+
return if values.empty?
|
15
|
+
|
16
|
+
mode = option_value(record, :mode)
|
17
|
+
allowed_types = option_content_types(record, :allow)
|
18
|
+
forbidden_types = option_content_types(record, :exclude)
|
19
|
+
|
20
|
+
values.each do |val|
|
21
|
+
content_type = get_content_type(val, mode)
|
22
|
+
validate_whitelist(record, attribute, content_type, allowed_types)
|
23
|
+
validate_blacklist(record, attribute, content_type, forbidden_types)
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
@@ -42,7 +43,7 @@ module ActiveModel
|
|
42
43
|
|
43
44
|
value = JSON.parse(value) if value.is_a?(String)
|
44
45
|
|
45
|
-
Array.wrap(value).reject
|
46
|
+
Array.wrap(value).reject(&:blank?)
|
46
47
|
end
|
47
48
|
|
48
49
|
def get_file_path(value)
|
@@ -85,7 +86,7 @@ module ActiveModel
|
|
85
86
|
end
|
86
87
|
|
87
88
|
def validate_whitelist(record, attribute, content_type, allowed_types)
|
88
|
-
if allowed_types.present?
|
89
|
+
if allowed_types.present? && allowed_types.none? { |type| type === content_type }
|
89
90
|
mark_invalid record, attribute, :allowed_file_content_types, allowed_types
|
90
91
|
end
|
91
92
|
end
|
@@ -130,6 +131,5 @@ module ActiveModel
|
|
130
131
|
validates_with FileContentTypeValidator, _merge_attributes(attr_names)
|
131
132
|
end
|
132
133
|
end
|
133
|
-
|
134
134
|
end
|
135
135
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module ActiveModel
|
2
4
|
module Validations
|
3
|
-
|
4
5
|
class FileSizeValidator < ActiveModel::EachValidator
|
5
6
|
CHECKS = { in: :===,
|
6
7
|
less_than: :<,
|
@@ -14,22 +15,18 @@ module ActiveModel
|
|
14
15
|
|
15
16
|
def validate_each(record, attribute, value)
|
16
17
|
values = parse_values(value)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
record.errors.add(attribute,
|
22
|
-
"file_size_is_#{option}".to_sym,
|
23
|
-
filtered_options(values).merge!(detect_error_options(option_value)))
|
24
|
-
end
|
25
|
-
end
|
18
|
+
return if values.empty?
|
19
|
+
|
20
|
+
options.slice(*CHECKS.keys).each do |option, option_value|
|
21
|
+
check_errors(record, attribute, values, option, option_value)
|
26
22
|
end
|
27
23
|
end
|
28
24
|
|
29
25
|
def check_validity!
|
30
26
|
unless (CHECKS.keys & options.keys).present?
|
31
|
-
raise ArgumentError, 'You must at least pass in one of these options
|
32
|
-
|
27
|
+
raise ArgumentError, 'You must at least pass in one of these options' \
|
28
|
+
' - :in, :less_than, :less_than_or_equal_to,' \
|
29
|
+
' :greater_than and :greater_than_or_equal_to'
|
33
30
|
end
|
34
31
|
|
35
32
|
check_options(Numeric, options.slice(*(CHECKS.keys - [:in])))
|
@@ -46,7 +43,7 @@ module ActiveModel
|
|
46
43
|
|
47
44
|
value = OpenStruct.new(value) if value.is_a?(Hash)
|
48
45
|
|
49
|
-
Array.wrap(value).reject
|
46
|
+
Array.wrap(value).reject(&:blank?)
|
50
47
|
end
|
51
48
|
|
52
49
|
def check_options(klass, options)
|
@@ -57,6 +54,18 @@ module ActiveModel
|
|
57
54
|
end
|
58
55
|
end
|
59
56
|
|
57
|
+
def check_errors(record, attribute, values, option, option_value)
|
58
|
+
option_value = option_value.call(record) if option_value.is_a?(Proc)
|
59
|
+
has_invalid_size = values.any? { |v| !valid_size?(value_byte_size(v), option, option_value) }
|
60
|
+
if has_invalid_size
|
61
|
+
record.errors.add(
|
62
|
+
attribute,
|
63
|
+
"file_size_is_#{option}".to_sym,
|
64
|
+
filtered_options(values).merge!(detect_error_options(option_value))
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
60
69
|
def value_byte_size(value)
|
61
70
|
if value.respond_to?(:byte_size)
|
62
71
|
value.byte_size
|
@@ -82,7 +91,7 @@ module ActiveModel
|
|
82
91
|
|
83
92
|
def detect_error_options(option_value)
|
84
93
|
if option_value.is_a?(Range)
|
85
|
-
{ min: human_size(option_value.min), max: human_size(option_value.max)
|
94
|
+
{ min: human_size(option_value.min), max: human_size(option_value.max) }
|
86
95
|
else
|
87
96
|
{ count: human_size(option_value) }
|
88
97
|
end
|
@@ -92,12 +101,22 @@ module ActiveModel
|
|
92
101
|
if defined?(ActiveSupport::NumberHelper) # Rails 4.0+
|
93
102
|
ActiveSupport::NumberHelper.number_to_human_size(size)
|
94
103
|
else
|
95
|
-
storage_units_format = I18n.translate(
|
96
|
-
|
104
|
+
storage_units_format = I18n.translate(
|
105
|
+
:'number.human.storage_units.format',
|
106
|
+
locale: options[:locale],
|
107
|
+
raise: true
|
108
|
+
)
|
109
|
+
|
110
|
+
unit = I18n.translate(
|
111
|
+
:'number.human.storage_units.units.byte',
|
112
|
+
locale: options[:locale],
|
113
|
+
count: size.to_i,
|
114
|
+
raise: true
|
115
|
+
)
|
116
|
+
|
97
117
|
storage_units_format.gsub(/%n/, size.to_i.to_s).gsub(/%u/, unit).html_safe
|
98
118
|
end
|
99
119
|
end
|
100
|
-
|
101
120
|
end
|
102
121
|
|
103
122
|
module HelperMethods
|
@@ -116,6 +135,5 @@ module ActiveModel
|
|
116
135
|
validates_with FileSizeValidator, _merge_attributes(attr_names)
|
117
136
|
end
|
118
137
|
end
|
119
|
-
|
120
138
|
end
|
121
139
|
end
|