paperclip 2.8.0 → 3.0.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of paperclip might be problematic. Click here for more details.
- data/.gitignore +1 -0
- data/.travis.yml +9 -7
- data/Appraisals +6 -12
- data/Gemfile +2 -0
- data/NEWS +24 -0
- data/README.md +53 -21
- data/Rakefile +7 -2
- data/UPGRADING +14 -0
- data/features/basic_integration.feature +8 -8
- data/features/rake_tasks.feature +1 -1
- data/features/step_definitions/attachment_steps.rb +11 -2
- data/features/step_definitions/rails_steps.rb +17 -79
- data/features/support/env.rb +3 -0
- data/features/support/file_helpers.rb +24 -0
- data/features/support/rails.rb +3 -3
- data/gemfiles/{rails3_1.gemfile → 3.0.gemfile} +3 -1
- data/gemfiles/{rails2.gemfile → 3.1.gemfile} +3 -1
- data/gemfiles/{rails3.gemfile → 3.2.gemfile} +3 -1
- data/images.rake +21 -0
- data/lib/generators/paperclip/paperclip_generator.rb +1 -2
- data/lib/paperclip.rb +48 -319
- data/lib/paperclip/attachment.rb +33 -81
- data/lib/paperclip/attachment_options.rb +0 -1
- data/lib/paperclip/callbacks.rb +30 -0
- data/lib/paperclip/errors.rb +27 -0
- data/lib/paperclip/geometry.rb +6 -4
- data/lib/paperclip/glue.rb +15 -0
- data/lib/paperclip/helpers.rb +71 -0
- data/lib/paperclip/instance_methods.rb +35 -0
- data/lib/paperclip/interpolations.rb +2 -2
- data/lib/paperclip/io_adapters/attachment_adapter.rb +62 -0
- data/lib/paperclip/io_adapters/file_adapter.rb +81 -0
- data/lib/paperclip/io_adapters/identity_adapter.rb +12 -0
- data/lib/paperclip/io_adapters/nil_adapter.rb +34 -0
- data/lib/paperclip/io_adapters/registry.rb +32 -0
- data/lib/paperclip/io_adapters/stringio_adapter.rb +64 -0
- data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +63 -0
- data/lib/paperclip/locales/en.yml +17 -0
- data/lib/paperclip/logger.rb +21 -0
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +1 -1
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +2 -2
- data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +7 -7
- data/lib/paperclip/processor.rb +32 -17
- data/lib/paperclip/railtie.rb +10 -15
- data/lib/paperclip/storage/filesystem.rb +5 -14
- data/lib/paperclip/storage/fog.rb +2 -21
- data/lib/paperclip/storage/s3.rb +12 -29
- data/lib/paperclip/tempfile.rb +41 -0
- data/lib/paperclip/thumbnail.rb +2 -3
- data/lib/paperclip/validators.rb +45 -0
- data/lib/paperclip/validators/attachment_content_type_validator.rb +47 -0
- data/lib/paperclip/validators/attachment_presence_validator.rb +26 -0
- data/lib/paperclip/validators/attachment_size_validator.rb +102 -0
- data/lib/paperclip/version.rb +1 -1
- data/lib/tasks/paperclip.rake +3 -11
- data/paperclip.gemspec +15 -5
- data/test/adapter_registry_test.rb +32 -0
- data/test/attachment_adapter_test.rb +48 -0
- data/test/attachment_options_test.rb +0 -13
- data/test/attachment_test.rb +27 -55
- data/test/file_adapter_test.rb +43 -0
- data/test/generator_test.rb +78 -0
- data/test/geometry_test.rb +5 -5
- data/test/helper.rb +9 -11
- data/test/identity_adapter_test.rb +8 -0
- data/test/integration_test.rb +39 -94
- data/test/interpolations_test.rb +8 -1
- data/test/matchers/validate_attachment_size_matcher_test.rb +16 -2
- data/test/nil_adapter_test.rb +25 -0
- data/test/paperclip_test.rb +30 -189
- data/test/storage/filesystem_test.rb +0 -14
- data/test/storage/fog_test.rb +0 -14
- data/test/storage/s3_live_test.rb +22 -9
- data/test/storage/s3_test.rb +70 -34
- data/test/stringio_adapter_test.rb +42 -0
- data/test/style_test.rb +10 -16
- data/test/thumbnail_test.rb +16 -10
- data/test/uploaded_file_adapter_test.rb +98 -0
- data/test/validators/attachment_content_type_validator_test.rb +140 -0
- data/test/validators/attachment_presence_validator_test.rb +85 -0
- data/test/validators/attachment_size_validator_test.rb +207 -0
- data/test/validators_test.rb +25 -0
- metadata +152 -30
- data/gemfiles/rails3_2.gemfile +0 -9
- data/generators/paperclip/USAGE +0 -5
- data/generators/paperclip/paperclip_generator.rb +0 -27
- data/generators/paperclip/templates/paperclip_migration.rb.erb +0 -19
- data/init.rb +0 -4
- data/lib/paperclip/callback_compatibility.rb +0 -61
- data/lib/paperclip/iostream.rb +0 -45
- data/lib/paperclip/upfile.rb +0 -64
- data/rails/init.rb +0 -2
- data/test/iostream_test.rb +0 -71
- data/test/upfile_test.rb +0 -53
data/lib/paperclip/railtie.rb
CHANGED
@@ -2,34 +2,29 @@ require 'paperclip'
|
|
2
2
|
require 'paperclip/schema'
|
3
3
|
|
4
4
|
module Paperclip
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
Paperclip::Railtie.insert
|
11
|
-
end
|
12
|
-
end
|
13
|
-
rake_tasks do
|
14
|
-
load "tasks/paperclip.rake"
|
5
|
+
require 'rails'
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
initializer 'paperclip.insert_into_active_record' do
|
8
|
+
ActiveSupport.on_load :active_record do
|
9
|
+
Paperclip::Railtie.insert
|
15
10
|
end
|
16
11
|
end
|
12
|
+
rake_tasks do
|
13
|
+
load "tasks/paperclip.rake"
|
14
|
+
end
|
17
15
|
end
|
18
16
|
|
19
17
|
class Railtie
|
20
18
|
def self.insert
|
21
19
|
Paperclip.options[:logger] = Rails.logger if defined?(Rails)
|
22
|
-
|
20
|
+
|
23
21
|
if defined?(ActiveRecord)
|
24
|
-
ActiveRecord::Base.send(:include, Paperclip::Glue)
|
25
22
|
Paperclip.options[:logger] = ActiveRecord::Base.logger
|
26
|
-
|
23
|
+
ActiveRecord::Base.send(:include, Paperclip::Glue)
|
27
24
|
ActiveRecord::ConnectionAdapters::AbstractAdapter.send(:include, Paperclip::Schema)
|
28
25
|
ActiveRecord::ConnectionAdapters::Table.send(:include, Paperclip::Schema)
|
29
26
|
ActiveRecord::ConnectionAdapters::TableDefinition.send(:include, Paperclip::Schema)
|
30
27
|
end
|
31
|
-
|
32
|
-
File.send(:include, Paperclip::Upfile)
|
33
28
|
end
|
34
29
|
end
|
35
30
|
end
|
@@ -27,23 +27,14 @@ module Paperclip
|
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
30
|
-
# Returns representation of the data of the file assigned to the given
|
31
|
-
# style, in the format most representative of the current storage.
|
32
|
-
def to_file style_name = default_style
|
33
|
-
if @queued_for_write[style_name]
|
34
|
-
@queued_for_write[style_name].rewind
|
35
|
-
@queued_for_write[style_name]
|
36
|
-
elsif exists?(style_name)
|
37
|
-
File.new(path(style_name), 'rb')
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
30
|
def flush_writes #:nodoc:
|
42
31
|
@queued_for_write.each do |style_name, file|
|
43
|
-
file.close
|
44
32
|
FileUtils.mkdir_p(File.dirname(path(style_name)))
|
45
|
-
|
46
|
-
|
33
|
+
File.open(path(style_name), "wb") do |new_file|
|
34
|
+
while chunk = file.read(16 * 1024)
|
35
|
+
new_file.write(chunk)
|
36
|
+
end
|
37
|
+
end
|
47
38
|
FileUtils.chmod(0666&~File.umask, path(style_name))
|
48
39
|
end
|
49
40
|
|
@@ -83,7 +83,7 @@ module Paperclip
|
|
83
83
|
:body => file,
|
84
84
|
:key => path(style),
|
85
85
|
:public => fog_public,
|
86
|
-
:content_type => file.content_type
|
86
|
+
:content_type => file.content_type
|
87
87
|
))
|
88
88
|
rescue Excon::Errors::NotFound
|
89
89
|
raise if retried
|
@@ -106,25 +106,6 @@ module Paperclip
|
|
106
106
|
@queued_for_delete = []
|
107
107
|
end
|
108
108
|
|
109
|
-
# Returns representation of the data of the file assigned to the given
|
110
|
-
# style, in the format most representative of the current storage.
|
111
|
-
def to_file(style = default_style)
|
112
|
-
if @queued_for_write[style]
|
113
|
-
@queued_for_write[style].rewind
|
114
|
-
@queued_for_write[style]
|
115
|
-
else
|
116
|
-
body = directory.files.get(path(style)).body
|
117
|
-
filename = path(style)
|
118
|
-
extname = File.extname(filename)
|
119
|
-
basename = File.basename(filename, extname)
|
120
|
-
file = Tempfile.new([basename, extname])
|
121
|
-
file.binmode
|
122
|
-
file.write(body)
|
123
|
-
file.rewind
|
124
|
-
file
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
109
|
def public_url(style = default_style)
|
129
110
|
if @options[:fog_host]
|
130
111
|
host = if @options[:fog_host].respond_to?(:call)
|
@@ -132,7 +113,7 @@ module Paperclip
|
|
132
113
|
else
|
133
114
|
(@options[:fog_host] =~ /%d/) ? @options[:fog_host] % (path(style).hash % 4) : @options[:fog_host]
|
134
115
|
end
|
135
|
-
|
116
|
+
|
136
117
|
"#{host}/#{path(style)}"
|
137
118
|
else
|
138
119
|
if fog_credentials[:provider] == 'AWS'
|
data/lib/paperclip/storage/s3.rb
CHANGED
@@ -95,7 +95,7 @@ module Paperclip
|
|
95
95
|
@s3_permissions = set_permissions(@options[:s3_permissions])
|
96
96
|
@s3_protocol = @options[:s3_protocol] ||
|
97
97
|
Proc.new do |style, attachment|
|
98
|
-
permission = (@s3_permissions[style.to_sym] || @s3_permissions[:default])
|
98
|
+
permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
|
99
99
|
permission = permission.call(attachment, style) if permission.is_a?(Proc)
|
100
100
|
(permission == :public_read) ? 'http' : 'https'
|
101
101
|
end
|
@@ -141,7 +141,8 @@ module Paperclip
|
|
141
141
|
|
142
142
|
def expiring_url(time = 3600, style_name = default_style)
|
143
143
|
if path
|
144
|
-
|
144
|
+
base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
|
145
|
+
s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
|
145
146
|
end
|
146
147
|
end
|
147
148
|
|
@@ -159,6 +160,12 @@ module Paperclip
|
|
159
160
|
@s3_host_alias
|
160
161
|
end
|
161
162
|
|
163
|
+
def s3_url_options
|
164
|
+
s3_url_options = @options[:s3_url_options] || {}
|
165
|
+
s3_url_options = s3_url_options.call(instance) if s3_url_options.is_a?(Proc)
|
166
|
+
s3_url_options
|
167
|
+
end
|
168
|
+
|
162
169
|
def bucket_name
|
163
170
|
@bucket = @options[:bucket] || s3_credentials[:bucket]
|
164
171
|
@bucket = @bucket.call(self) if @bucket.is_a?(Proc)
|
@@ -227,6 +234,7 @@ module Paperclip
|
|
227
234
|
end
|
228
235
|
|
229
236
|
def parse_credentials creds
|
237
|
+
creds = creds.respond_to?('call') ? creds.call(self) : creds
|
230
238
|
creds = find_credentials(creds).stringify_keys
|
231
239
|
env = Object.const_defined?(:Rails) ? Rails.env : nil
|
232
240
|
(creds[env] || creds).symbolize_keys
|
@@ -256,23 +264,6 @@ module Paperclip
|
|
256
264
|
end
|
257
265
|
end
|
258
266
|
|
259
|
-
# Returns representation of the data of the file assigned to the given
|
260
|
-
# style, in the format most representative of the current storage.
|
261
|
-
def to_file style = default_style
|
262
|
-
if @queued_for_write[style]
|
263
|
-
@queued_for_write[style].rewind
|
264
|
-
return @queued_for_write[style]
|
265
|
-
end
|
266
|
-
filename = path(style)
|
267
|
-
extname = File.extname(filename)
|
268
|
-
basename = File.basename(filename, extname)
|
269
|
-
file = Tempfile.new([basename, extname])
|
270
|
-
file.binmode
|
271
|
-
file.write(s3_object(style).read)
|
272
|
-
file.rewind
|
273
|
-
return file
|
274
|
-
end
|
275
|
-
|
276
267
|
def create_bucket
|
277
268
|
s3_interface.buckets.create(bucket_name)
|
278
269
|
end
|
@@ -284,7 +275,7 @@ module Paperclip
|
|
284
275
|
acl = @s3_permissions[style] || @s3_permissions[:default]
|
285
276
|
acl = acl.call(self, style) if acl.respond_to?(:call)
|
286
277
|
write_options = {
|
287
|
-
:content_type => file.content_type
|
278
|
+
:content_type => file.content_type,
|
288
279
|
:acl => acl
|
289
280
|
}
|
290
281
|
write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
|
@@ -325,19 +316,11 @@ module Paperclip
|
|
325
316
|
when Hash
|
326
317
|
creds
|
327
318
|
else
|
328
|
-
raise ArgumentError, "Credentials are not a path, file, or hash."
|
319
|
+
raise ArgumentError, "Credentials are not a path, file, proc, or hash."
|
329
320
|
end
|
330
321
|
end
|
331
322
|
private :find_credentials
|
332
323
|
|
333
|
-
def establish_connection!
|
334
|
-
@connection ||= AWS::S3::Base.establish_connection!( @s3_options.merge(
|
335
|
-
:access_key_id => s3_credentials[:access_key_id],
|
336
|
-
:secret_access_key => s3_credentials[:secret_access_key]
|
337
|
-
))
|
338
|
-
end
|
339
|
-
private :establish_connection!
|
340
|
-
|
341
324
|
def use_secure_protocol?(style_name)
|
342
325
|
s3_protocol(style_name) == "https"
|
343
326
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Paperclip
|
2
|
+
# Overriding some implementation of Tempfile
|
3
|
+
class Tempfile < ::Tempfile
|
4
|
+
# Due to how ImageMagick handles its image format conversion and how
|
5
|
+
# Tempfile handles its naming scheme, it is necessary to override how
|
6
|
+
# Tempfile makes # its names so as to allow for file extensions. Idea
|
7
|
+
# taken from the comments on this blog post:
|
8
|
+
# http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
|
9
|
+
#
|
10
|
+
# This is Ruby 1.8.7's implementation.
|
11
|
+
def make_tmpname(basename, n)
|
12
|
+
if RUBY_PLATFORM =~ /java/
|
13
|
+
case basename
|
14
|
+
when Array
|
15
|
+
prefix, suffix = *basename
|
16
|
+
else
|
17
|
+
prefix, suffix = basename, ''
|
18
|
+
end
|
19
|
+
|
20
|
+
t = Time.now.strftime("%y%m%d")
|
21
|
+
path = "#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}-#{n}#{suffix}"
|
22
|
+
else
|
23
|
+
super
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module TempfileEncoding
|
29
|
+
# This overrides Tempfile#binmode to make sure that the extenal encoding
|
30
|
+
# for binary mode is ASCII-8BIT. This behavior is what's in CRuby, but not
|
31
|
+
# in JRuby
|
32
|
+
def binmode
|
33
|
+
set_encoding('ASCII-8BIT')
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
if RUBY_PLATFORM =~ /java/
|
40
|
+
::Tempfile.send :include, Paperclip::TempfileEncoding
|
41
|
+
end
|
data/lib/paperclip/thumbnail.rb
CHANGED
@@ -44,7 +44,6 @@ module Paperclip
|
|
44
44
|
|
45
45
|
@current_format = File.extname(@file.path)
|
46
46
|
@basename = File.basename(@file.path, @current_format)
|
47
|
-
|
48
47
|
end
|
49
48
|
|
50
49
|
# Returns true if the +target_geometry+ is meant to crop.
|
@@ -76,9 +75,9 @@ module Paperclip
|
|
76
75
|
|
77
76
|
success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}#{'[0]' unless animated?}", :dest => File.expand_path(dst.path))
|
78
77
|
rescue Cocaine::ExitStatusError => e
|
79
|
-
raise
|
78
|
+
raise Paperclip::Error, "There was an error processing the thumbnail for #{@basename}" if @whiny
|
80
79
|
rescue Cocaine::CommandNotFoundError => e
|
81
|
-
raise Paperclip::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
|
80
|
+
raise Paperclip::Errors::CommandNotFoundError.new("Could not run the `convert` command. Please install ImageMagick.")
|
82
81
|
end
|
83
82
|
|
84
83
|
dst
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
require 'paperclip/validators/attachment_content_type_validator'
|
3
|
+
require 'paperclip/validators/attachment_presence_validator'
|
4
|
+
require 'paperclip/validators/attachment_size_validator'
|
5
|
+
|
6
|
+
module Paperclip
|
7
|
+
module Validators
|
8
|
+
extend ActiveSupport::Concern
|
9
|
+
|
10
|
+
included do
|
11
|
+
extend HelperMethods
|
12
|
+
include HelperMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# This method is a shortcut to validator classes that is in
|
17
|
+
# "Attachment...Validator" format. It is almost the same thing as the
|
18
|
+
# +validates+ method that shipped with Rails, but this is customized to
|
19
|
+
# be using with attachment validators. This is helpful when you're using
|
20
|
+
# multiple attachment validators on a single attachment.
|
21
|
+
#
|
22
|
+
# Example of using the validator:
|
23
|
+
#
|
24
|
+
# validates_attachment :avatar, :presence => true,
|
25
|
+
# :content_type => { :content_type => "image/jpg" },
|
26
|
+
# :size => { :in => 0..10.kilobytes }
|
27
|
+
#
|
28
|
+
def validates_attachment(*attributes)
|
29
|
+
options = attributes.extract_options!.dup
|
30
|
+
|
31
|
+
Paperclip::Validators.constants.each do |constant|
|
32
|
+
if constant.to_s =~ /^Attachment(.+)Validator$/
|
33
|
+
validator_kind = $1.underscore.to_sym
|
34
|
+
|
35
|
+
if options.has_key?(validator_kind)
|
36
|
+
options[:"attachment_#{validator_kind}"] = options.delete(validator_kind)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
validates(*attributes + [options])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Validators
|
3
|
+
class AttachmentContentTypeValidator < ActiveModel::EachValidator
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
attribute = "#{attribute}_content_type".to_sym
|
6
|
+
value = record.send(:read_attribute_for_validation, attribute)
|
7
|
+
allowed_types = [options[:content_type]].flatten
|
8
|
+
|
9
|
+
if value.present? && allowed_types.none? { |type| type === value }
|
10
|
+
record.errors.add(attribute, :invalid, options.merge(
|
11
|
+
:types => allowed_types.join(', ')
|
12
|
+
))
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def check_validity!
|
17
|
+
unless options.has_key?(:content_type)
|
18
|
+
raise ArgumentError, "You must pass in :content_type to the validator"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module HelperMethods
|
24
|
+
# Places ActiveRecord-style validations on the content type of the file
|
25
|
+
# assigned. The possible options are:
|
26
|
+
# * +content_type+: Allowed content types. Can be a single content type
|
27
|
+
# or an array. Each type can be a String or a Regexp. It should be
|
28
|
+
# noted that Internet Explorer uploads files with content_types that you
|
29
|
+
# may not expect. For example, JPEG images are given image/pjpeg and
|
30
|
+
# PNGs are image/x-png, so keep that in mind when determining how you
|
31
|
+
# match. Allows all by default.
|
32
|
+
# * +message+: The message to display when the uploaded file has an invalid
|
33
|
+
# content type.
|
34
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
35
|
+
# be run is this lambda or method returns true.
|
36
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
37
|
+
# NOTE: If you do not specify an [attachment]_content_type field on your
|
38
|
+
# model, content_type validation will work _ONLY upon assignment_ and
|
39
|
+
# re-validation after the instance has been reloaded will always succeed.
|
40
|
+
# You'll still need to have a virtual attribute (created by +attr_accessor+)
|
41
|
+
# name +[attachment]_content_type+ to be able to use this validator.
|
42
|
+
def validates_attachment_content_type(*attr_names)
|
43
|
+
validates_with AttachmentContentTypeValidator, _merge_attributes(attr_names)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'active_model/validations/presence'
|
2
|
+
|
3
|
+
module Paperclip
|
4
|
+
module Validators
|
5
|
+
class AttachmentPresenceValidator < ActiveModel::Validations::PresenceValidator
|
6
|
+
def validate(record)
|
7
|
+
[attributes].flatten.map do |attribute|
|
8
|
+
if record.send(:read_attribute_for_validation, "#{attribute}_file_name").blank?
|
9
|
+
record.errors.add(attribute, :blank, options)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module HelperMethods
|
16
|
+
# Places ActiveRecord-style validations on the presence of a file.
|
17
|
+
# Options:
|
18
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
19
|
+
# be run if this lambda or method returns true.
|
20
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
21
|
+
def validates_attachment_presence(*attr_names)
|
22
|
+
validates_with AttachmentPresenceValidator, _merge_attributes(attr_names)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'active_model/validations/numericality'
|
2
|
+
|
3
|
+
module Paperclip
|
4
|
+
module Validators
|
5
|
+
class AttachmentSizeValidator < ActiveModel::Validations::NumericalityValidator
|
6
|
+
AVAILABLE_CHECKS = [:less_than, :less_than_or_equal_to, :greater_than, :greater_than_or_equal_to]
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
extract_options(options)
|
10
|
+
super
|
11
|
+
end
|
12
|
+
|
13
|
+
def validate_each(record, attr_name, value)
|
14
|
+
attr_name = "#{attr_name}_file_size".to_sym
|
15
|
+
value = record.send(:read_attribute_for_validation, attr_name)
|
16
|
+
|
17
|
+
unless value.blank?
|
18
|
+
options.slice(*AVAILABLE_CHECKS).each do |option, option_value|
|
19
|
+
option_value = option_value.call(record) if option_value.is_a?(Proc)
|
20
|
+
option_value = extract_option_value(option, option_value)
|
21
|
+
|
22
|
+
unless value.send(CHECKS[option], option_value)
|
23
|
+
error_message_key = options[:in] ? :in_between : option
|
24
|
+
record.errors.add(attr_name, error_message_key, filtered_options(value).merge(
|
25
|
+
:min => min_value_in_human_size(record),
|
26
|
+
:max => max_value_in_human_size(record),
|
27
|
+
:count => human_size(option_value)
|
28
|
+
))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def check_validity!
|
35
|
+
unless (AVAILABLE_CHECKS + [:in]).any? { |argument| options.has_key?(argument) }
|
36
|
+
raise ArgumentError, "You must pass either :less_than, :greater_than, or :in to the validator"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def extract_options(options)
|
43
|
+
if range = options[:in]
|
44
|
+
if !options[:in].respond_to?(:call)
|
45
|
+
options[:less_than_or_equal_to] = range.max
|
46
|
+
options[:greater_than_or_equal_to] = range.min
|
47
|
+
else
|
48
|
+
options[:less_than_or_equal_to] = range
|
49
|
+
options[:greater_than_or_equal_to] = range
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def extract_option_value(option, option_value)
|
55
|
+
if option_value.is_a?(Range)
|
56
|
+
if [:less_than, :less_than_or_equal_to].include?(option)
|
57
|
+
option_value.max
|
58
|
+
else
|
59
|
+
option_value.min
|
60
|
+
end
|
61
|
+
else
|
62
|
+
option_value
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def human_size(size)
|
67
|
+
storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
|
68
|
+
unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => size.to_i, :raise => true)
|
69
|
+
storage_units_format.gsub(/%n/, size.to_i.to_s).gsub(/%u/, unit).html_safe
|
70
|
+
end
|
71
|
+
|
72
|
+
def min_value_in_human_size(record)
|
73
|
+
value = options[:greater_than_or_equal_to] || options[:greater_than]
|
74
|
+
value = value.call(record) if value.respond_to?(:call)
|
75
|
+
value = value.min if value.respond_to?(:min)
|
76
|
+
human_size(value)
|
77
|
+
end
|
78
|
+
|
79
|
+
def max_value_in_human_size(record)
|
80
|
+
value = options[:less_than_or_equal_to] || options[:less_than]
|
81
|
+
value = value.call(record) if value.respond_to?(:call)
|
82
|
+
value = value.max if value.respond_to?(:max)
|
83
|
+
human_size(value)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
module HelperMethods
|
88
|
+
# Places ActiveRecord-style validations on the size of the file assigned. The
|
89
|
+
# possible options are:
|
90
|
+
# * +in+: a Range of bytes (i.e. +1..1.megabyte+),
|
91
|
+
# * +less_than+: equivalent to :in => 0..options[:less_than]
|
92
|
+
# * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
|
93
|
+
# * +message+: error message to display, use :min and :max as replacements
|
94
|
+
# * +if+: A lambda or name of an instance method. Validation will only
|
95
|
+
# be run if this lambda or method returns true.
|
96
|
+
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
97
|
+
def validates_attachment_size(*attr_names)
|
98
|
+
validates_with AttachmentSizeValidator, _merge_attributes(attr_names)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|