dm-paperclip 2.4.1 → 2.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +29 -0
- data/Gemfile.lock +100 -0
- data/README.md +145 -0
- data/Rakefile +37 -71
- data/VERSION +1 -0
- data/dm-paperclip.gemspec +103 -0
- data/lib/dm-paperclip.rb +88 -74
- data/lib/dm-paperclip/attachment.rb +139 -102
- data/lib/dm-paperclip/callbacks.rb +55 -0
- data/lib/dm-paperclip/command_line.rb +86 -0
- data/lib/dm-paperclip/ext/blank.rb +24 -0
- data/lib/dm-paperclip/ext/class.rb +50 -0
- data/lib/dm-paperclip/ext/compatibility.rb +11 -0
- data/lib/dm-paperclip/ext/try_dup.rb +12 -0
- data/lib/dm-paperclip/geometry.rb +3 -5
- data/lib/dm-paperclip/interpolations.rb +57 -32
- data/lib/dm-paperclip/iostream.rb +12 -26
- data/lib/dm-paperclip/processor.rb +14 -4
- data/lib/dm-paperclip/storage.rb +2 -257
- data/lib/dm-paperclip/storage/filesystem.rb +73 -0
- data/lib/dm-paperclip/storage/s3.rb +209 -0
- data/lib/dm-paperclip/storage/s3/aws_library.rb +41 -0
- data/lib/dm-paperclip/storage/s3/aws_s3_library.rb +60 -0
- data/lib/dm-paperclip/style.rb +90 -0
- data/lib/dm-paperclip/thumbnail.rb +33 -24
- data/lib/dm-paperclip/upfile.rb +13 -5
- data/lib/dm-paperclip/validations.rb +40 -37
- data/lib/dm-paperclip/version.rb +4 -0
- data/test/attachment_test.rb +510 -67
- data/test/command_line_test.rb +138 -0
- data/test/fixtures/s3.yml +8 -0
- data/test/fixtures/twopage.pdf +0 -0
- data/test/fixtures/uppercase.PNG +0 -0
- data/test/geometry_test.rb +54 -19
- data/test/helper.rb +91 -28
- data/test/integration_test.rb +252 -79
- data/test/interpolations_test.rb +150 -0
- data/test/iostream_test.rb +8 -15
- data/test/paperclip_test.rb +222 -69
- data/test/processor_test.rb +10 -0
- data/test/storage_test.rb +102 -23
- data/test/style_test.rb +141 -0
- data/test/thumbnail_test.rb +106 -18
- data/test/upfile_test.rb +36 -0
- metadata +136 -121
- data/README.rdoc +0 -116
- data/init.rb +0 -1
- data/lib/dm-paperclip/callback_compatability.rb +0 -33
@@ -0,0 +1,41 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Storage
|
3
|
+
module S3
|
4
|
+
# Mixin which interfaces with the 'aws' and 'right_aws' libraries.
|
5
|
+
module AwsLibrary
|
6
|
+
protected
|
7
|
+
|
8
|
+
def s3_connect!
|
9
|
+
@s3 = Aws::S3.new(
|
10
|
+
@s3_credentials[:access_key_id],
|
11
|
+
@s3_credentials[:secret_access_key]
|
12
|
+
)
|
13
|
+
@s3_bucket = @s3.bucket(bucket_name)
|
14
|
+
end
|
15
|
+
|
16
|
+
def s3_expiring_url(key,time)
|
17
|
+
@s3.interface.get_link(bucket_name,key,time)
|
18
|
+
end
|
19
|
+
|
20
|
+
def s3_exists?(key)
|
21
|
+
@s3_bucket.keys(:prefix => key).any? { |s3_key| s3_key.name == key }
|
22
|
+
end
|
23
|
+
|
24
|
+
def s3_download(key,file)
|
25
|
+
@s3_bucket.key(key).get { |chunk| file.write(chunk) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def s3_store(key,file)
|
29
|
+
@s3_bucket.key(key).put(
|
30
|
+
file,
|
31
|
+
@s3_permissions.to_s.gsub('_','-')
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
def s3_delete(key)
|
36
|
+
@s3_bucket.key(key).delete
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Storage
|
3
|
+
module S3
|
4
|
+
# Mixin which interfaces with the 'aws-s3' library.
|
5
|
+
module AwsS3Library
|
6
|
+
protected
|
7
|
+
|
8
|
+
def s3_connect!
|
9
|
+
AWS::S3::Base.establish_connection!(@s3_options.merge(
|
10
|
+
:access_key_id => @s3_credentials[:access_key_id],
|
11
|
+
:secret_access_key => @s3_credentials[:secret_access_key]
|
12
|
+
))
|
13
|
+
end
|
14
|
+
|
15
|
+
def s3_expiring_url(key,time)
|
16
|
+
AWS::S3::S3Object.url_for(key, bucket_name, :expires_in => time)
|
17
|
+
end
|
18
|
+
|
19
|
+
def s3_exists?(key)
|
20
|
+
AWS::S3::S3Object.exists?(key, bucket_name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def s3_download(key,file)
|
24
|
+
file.write(AWS::S3::S3Object.value(key, bucket_name))
|
25
|
+
end
|
26
|
+
|
27
|
+
def s3_create_bucket
|
28
|
+
AWS::S3::Bucket.create(bucket_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def s3_store(key,file)
|
32
|
+
begin
|
33
|
+
AWS::S3::S3Object.store(
|
34
|
+
key,
|
35
|
+
file,
|
36
|
+
bucket_name,
|
37
|
+
{
|
38
|
+
:content_type => instance_read(:content_type),
|
39
|
+
:access => @s3_permissions,
|
40
|
+
}.merge(@s3_headers)
|
41
|
+
)
|
42
|
+
rescue AWS::S3::NoSuchBucket => e
|
43
|
+
s3_create_bucket
|
44
|
+
retry
|
45
|
+
rescue AWS::S3::ResponseError => e
|
46
|
+
raise
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def s3_delete(key)
|
51
|
+
begin
|
52
|
+
AWS::S3::S3Object.delete(key, bucket_name)
|
53
|
+
rescue AWS::S3::ResponseError
|
54
|
+
# Ignore this.
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Paperclip
|
3
|
+
# The Style class holds the definition of a thumbnail style, applying
|
4
|
+
# whatever processing is required to normalize the definition and delaying
|
5
|
+
# the evaluation of block parameters until useful context is available.
|
6
|
+
|
7
|
+
class Style
|
8
|
+
|
9
|
+
attr_reader :name, :attachment, :format
|
10
|
+
|
11
|
+
# Creates a Style object. +name+ is the name of the attachment,
|
12
|
+
# +definition+ is the style definition from has_attached_file, which
|
13
|
+
# can be string, array or hash
|
14
|
+
def initialize name, definition, attachment
|
15
|
+
@name = name
|
16
|
+
@attachment = attachment
|
17
|
+
if definition.is_a? Hash
|
18
|
+
@geometry = definition.delete(:geometry)
|
19
|
+
@format = definition.delete(:format)
|
20
|
+
@processors = definition.delete(:processors)
|
21
|
+
@other_args = definition
|
22
|
+
else
|
23
|
+
@geometry, @format = [definition, nil].flatten[0..1]
|
24
|
+
@other_args = {}
|
25
|
+
end
|
26
|
+
@format = nil if Paperclip::Ext.blank?(@format)
|
27
|
+
end
|
28
|
+
|
29
|
+
# retrieves from the attachment the processors defined in the has_attached_file call
|
30
|
+
# (which method (in the attachment) will call any supplied procs)
|
31
|
+
# There is an important change of interface here: a style rule can set its own processors
|
32
|
+
# by default we behave as before, though.
|
33
|
+
def processors
|
34
|
+
@processors || attachment.processors
|
35
|
+
end
|
36
|
+
|
37
|
+
# retrieves from the attachment the whiny setting
|
38
|
+
def whiny
|
39
|
+
attachment.whiny
|
40
|
+
end
|
41
|
+
|
42
|
+
# returns true if we're inclined to grumble
|
43
|
+
def whiny?
|
44
|
+
!!whiny
|
45
|
+
end
|
46
|
+
|
47
|
+
def convert_options
|
48
|
+
attachment.send(:extra_options_for, name)
|
49
|
+
end
|
50
|
+
|
51
|
+
# returns the geometry string for this style
|
52
|
+
# if a proc has been supplied, we call it here
|
53
|
+
def geometry
|
54
|
+
@geometry.respond_to?(:call) ? @geometry.call(attachment.instance) : @geometry
|
55
|
+
end
|
56
|
+
|
57
|
+
# Supplies the hash of options that processors expect to receive as their second argument
|
58
|
+
# Arguments other than the standard geometry, format etc are just passed through from
|
59
|
+
# initialization and any procs are called here, just before post-processing.
|
60
|
+
def processor_options
|
61
|
+
args = {}
|
62
|
+
@other_args.each do |k,v|
|
63
|
+
args[k] = v.respond_to?(:call) ? v.call(attachment) : v
|
64
|
+
end
|
65
|
+
[:processors, :geometry, :format, :whiny, :convert_options].each do |k|
|
66
|
+
(arg = send(k)) && args[k] = arg
|
67
|
+
end
|
68
|
+
args
|
69
|
+
end
|
70
|
+
|
71
|
+
# Supports getting and setting style properties with hash notation to ensure backwards-compatibility
|
72
|
+
# eg. @attachment.styles[:large][:geometry]@ will still work
|
73
|
+
def [](key)
|
74
|
+
if [:name, :convert_options, :whiny, :processors, :geometry, :format].include?(key)
|
75
|
+
send(key)
|
76
|
+
elsif defined? @other_args[key]
|
77
|
+
@other_args[key]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def []=(key, value)
|
82
|
+
if [:name, :convert_options, :whiny, :processors, :geometry, :format].include?(key)
|
83
|
+
send("#{key}=".intern, value)
|
84
|
+
else
|
85
|
+
@other_args[key] = value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -2,7 +2,7 @@ module Paperclip
|
|
2
2
|
# Handles thumbnailing images that are uploaded.
|
3
3
|
class Thumbnail < Processor
|
4
4
|
|
5
|
-
attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options
|
5
|
+
attr_accessor :current_geometry, :target_geometry, :format, :whiny, :convert_options, :source_file_options
|
6
6
|
|
7
7
|
# Creates a Thumbnail object set to work on the +file+ given. It
|
8
8
|
# will attempt to transform the image into one defined by +target_geometry+
|
@@ -12,17 +12,23 @@ module Paperclip
|
|
12
12
|
# set, the options will be appended to the convert command upon image conversion
|
13
13
|
def initialize file, options = {}, attachment = nil
|
14
14
|
super
|
15
|
-
geometry = options[:geometry]
|
16
|
-
@file = file
|
17
|
-
@crop = geometry[-1,1] == '#'
|
18
|
-
@target_geometry = Geometry.parse geometry
|
19
|
-
@current_geometry = Geometry.from_file @file
|
20
|
-
@convert_options = options[:convert_options]
|
21
|
-
@whiny = options[:whiny].nil? ? true : options[:whiny]
|
22
|
-
@format = options[:format]
|
23
15
|
|
24
|
-
|
25
|
-
@
|
16
|
+
geometry = options[:geometry]
|
17
|
+
@file = file
|
18
|
+
@crop = geometry[-1,1] == '#'
|
19
|
+
@target_geometry = Geometry.parse geometry
|
20
|
+
@current_geometry = Geometry.from_file @file
|
21
|
+
@source_file_options = options[:source_file_options]
|
22
|
+
@convert_options = options[:convert_options]
|
23
|
+
@whiny = options[:whiny].nil? ? true : options[:whiny]
|
24
|
+
@format = options[:format]
|
25
|
+
|
26
|
+
@source_file_options = @source_file_options.split(/\s+/) if @source_file_options.respond_to?(:split)
|
27
|
+
@convert_options = @convert_options.split(/\s+/) if @convert_options.respond_to?(:split)
|
28
|
+
|
29
|
+
@current_format = File.extname(@file.path)
|
30
|
+
@basename = File.basename(@file.path, @current_format)
|
31
|
+
|
26
32
|
end
|
27
33
|
|
28
34
|
# Returns true if the +target_geometry+ is meant to crop.
|
@@ -32,25 +38,28 @@ module Paperclip
|
|
32
38
|
|
33
39
|
# Returns true if the image is meant to make use of additional convert options.
|
34
40
|
def convert_options?
|
35
|
-
|
41
|
+
!@convert_options.nil? && !@convert_options.empty?
|
36
42
|
end
|
37
43
|
|
38
44
|
# Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
|
39
45
|
# that contains the new image.
|
40
46
|
def make
|
41
47
|
src = @file
|
42
|
-
dst = Tempfile.new([@basename, @format
|
48
|
+
dst = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
|
43
49
|
dst.binmode
|
44
50
|
|
45
|
-
command = <<-end_command
|
46
|
-
"#{ File.expand_path(src.path) }[0]"
|
47
|
-
#{ transformation_command }
|
48
|
-
"#{ File.expand_path(dst.path) }"
|
49
|
-
end_command
|
50
|
-
|
51
51
|
begin
|
52
|
-
|
53
|
-
|
52
|
+
parameters = []
|
53
|
+
parameters << source_file_options
|
54
|
+
parameters << ":source"
|
55
|
+
parameters << transformation_command
|
56
|
+
parameters << convert_options
|
57
|
+
parameters << ":dest"
|
58
|
+
|
59
|
+
parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
|
60
|
+
|
61
|
+
success = Paperclip.run("convert", parameters, :source => "#{File.expand_path(src.path)}[0]", :dest => File.expand_path(dst.path))
|
62
|
+
rescue PaperclipCommandLineError => e
|
54
63
|
raise PaperclipError, "There was an error processing the thumbnail for #{@basename}" if @whiny
|
55
64
|
end
|
56
65
|
|
@@ -61,9 +70,9 @@ module Paperclip
|
|
61
70
|
# into the thumbnail.
|
62
71
|
def transformation_command
|
63
72
|
scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
|
64
|
-
trans =
|
65
|
-
trans << "
|
66
|
-
trans << " #{
|
73
|
+
trans = []
|
74
|
+
trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
|
75
|
+
trans << "-crop" << %["#{crop}"] << "+repage" if crop
|
67
76
|
trans
|
68
77
|
end
|
69
78
|
end
|
data/lib/dm-paperclip/upfile.rb
CHANGED
@@ -8,13 +8,18 @@ module Paperclip
|
|
8
8
|
def content_type
|
9
9
|
type = (self.path.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
|
10
10
|
case type
|
11
|
-
when %r"
|
11
|
+
when %r"jp(e|g|eg)" then "image/jpeg"
|
12
12
|
when %r"tiff?" then "image/tiff"
|
13
13
|
when %r"png", "gif", "bmp" then "image/#{type}"
|
14
14
|
when "txt" then "text/plain"
|
15
15
|
when %r"html?" then "text/html"
|
16
|
-
when "
|
17
|
-
|
16
|
+
when "js" then "application/js"
|
17
|
+
when "csv", "xml", "css" then "text/#{type}"
|
18
|
+
else
|
19
|
+
# On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
|
20
|
+
content_type = (Paperclip.run("file", "-b --mime-type :file", :file => self.path).split(':').last.strip rescue "application/x-#{type}")
|
21
|
+
content_type = "application/x-#{type}" if content_type.match(/\(.*?\)/)
|
22
|
+
content_type
|
18
23
|
end
|
19
24
|
end
|
20
25
|
|
@@ -32,16 +37,19 @@ end
|
|
32
37
|
|
33
38
|
if defined? StringIO
|
34
39
|
class StringIO
|
35
|
-
attr_accessor :original_filename, :content_type
|
40
|
+
attr_accessor :original_filename, :content_type, :fingerprint
|
36
41
|
def original_filename
|
37
42
|
@original_filename ||= "stringio.txt"
|
38
43
|
end
|
39
44
|
def content_type
|
40
45
|
@content_type ||= "text/plain"
|
41
46
|
end
|
47
|
+
def fingerprint
|
48
|
+
@fingerprint ||= Digest::MD5.hexdigest(self.string)
|
49
|
+
end
|
42
50
|
end
|
43
51
|
end
|
44
52
|
|
45
53
|
class File #:nodoc:
|
46
54
|
include Paperclip::Upfile
|
47
|
-
end
|
55
|
+
end
|
@@ -10,8 +10,7 @@ module Paperclip
|
|
10
10
|
# * +greater_than+: equivalent to :in => options[:greater_than]..Infinity
|
11
11
|
# * +message+: error message to display, use :min and :max as replacements
|
12
12
|
def validates_attachment_size(*fields)
|
13
|
-
|
14
|
-
add_validator_to_context(opts, fields, Paperclip::Validate::SizeValidator)
|
13
|
+
validators.add(Paperclip::Validate::SizeValidator, *fields)
|
15
14
|
end
|
16
15
|
|
17
16
|
# Adds errors if thumbnail creation fails. The same as specifying :whiny_thumbnails => true.
|
@@ -21,8 +20,7 @@ module Paperclip
|
|
21
20
|
|
22
21
|
# Places ActiveRecord-style validations on the presence of a file.
|
23
22
|
def validates_attachment_presence(*fields)
|
24
|
-
|
25
|
-
add_validator_to_context(opts, fields, Paperclip::Validate::RequiredFieldValidator)
|
23
|
+
validators.add(Paperclip::Validate::RequiredFieldValidator, *fields)
|
26
24
|
end
|
27
25
|
|
28
26
|
# Places ActiveRecord-style validations on the content type of the file assigned. The
|
@@ -30,45 +28,41 @@ module Paperclip
|
|
30
28
|
# * +content_type+: Allowed content types. Can be a single content type or an array. Allows all by default.
|
31
29
|
# * +message+: The message to display when the uploaded file has an invalid content type.
|
32
30
|
def validates_attachment_content_type(*fields)
|
33
|
-
|
34
|
-
add_validator_to_context(opts, fields, Paperclip::Validate::ContentTypeValidator)
|
31
|
+
validators.add(Paperclip::Validate::ContentTypeValidator, *fields)
|
35
32
|
end
|
36
33
|
|
34
|
+
# Places ActiveRecord-style validations on the geometry of the file assigned. The
|
35
|
+
# required options are:
|
36
|
+
# * +height+: a Range of pixels (i.e. 100..300+),
|
37
|
+
# * +width+: a Range of pixels (i.e. 100..300+)
|
38
|
+
def validates_attachment_geometry(*fields)
|
39
|
+
validators.add(Paperclip::Validate::GeometryValidator, *fields)
|
40
|
+
end
|
37
41
|
end
|
38
42
|
|
39
43
|
class SizeValidator < DataMapper::Validate::GenericValidator #:nodoc:
|
40
|
-
def initialize(field_name, options={})
|
41
|
-
super
|
42
|
-
@field_name, @options = field_name, options
|
43
|
-
end
|
44
|
-
|
45
44
|
def call(target)
|
46
45
|
field_value = target.validation_property_value(:"#{@field_name}_file_size")
|
47
46
|
return true if field_value.nil?
|
48
47
|
|
49
|
-
@options[:in] = (@options[:greater_than]..(1/0)) unless @options[:greater_than].nil?
|
48
|
+
@options[:in] = (@options[:greater_than]..(1.0/0)) unless @options[:greater_than].nil?
|
50
49
|
@options[:in] = (0..@options[:less_than]) unless @options[:less_than].nil?
|
51
50
|
return true if @options[:in].include? field_value.to_i
|
52
51
|
|
53
52
|
error_message ||= @options[:message] unless @options[:message].nil?
|
54
|
-
error_message ||= "%s must be less than %s bytes"
|
55
|
-
error_message ||= "%s must be greater than %s bytes"
|
56
|
-
error_message ||= "%s must be between %s and %s bytes"
|
53
|
+
error_message ||= sprintf("%s must be less than %s bytes",DataMapper::Inflector.humanize(@field_name), @options[:less_than]) unless @options[:less_than].nil?
|
54
|
+
error_message ||= sprintf("%s must be greater than %s bytes",DataMapper::Inflector.humanize(@field_name), @options[:greater_than]) unless @options[:greater_than].nil?
|
55
|
+
error_message ||= sprintf("%s must be between %s and %s bytes",DataMapper::Inflector.humanize(@field_name), @options[:in].first, @options[:in].last)
|
57
56
|
add_error(target, error_message , @field_name)
|
58
57
|
return false
|
59
58
|
end
|
60
59
|
end
|
61
60
|
|
62
61
|
class RequiredFieldValidator < DataMapper::Validate::GenericValidator #:nodoc:
|
63
|
-
def initialize(field_name, options={})
|
64
|
-
super
|
65
|
-
@field_name, @options = field_name, options
|
66
|
-
end
|
67
|
-
|
68
62
|
def call(target)
|
69
63
|
field_value = target.validation_property_value(@field_name)
|
70
|
-
if field_value.nil? || field_value.original_filename
|
71
|
-
error_message = @options[:message] || "%s must be set"
|
64
|
+
if field_value.nil? || Paperclip::Ext.blank?(field_value.original_filename)
|
65
|
+
error_message = @options[:message] || sprintf("%s must be set",DataMapper::Inflector.humanize(@field_name))
|
72
66
|
add_error(target, error_message , @field_name)
|
73
67
|
return false
|
74
68
|
end
|
@@ -77,21 +71,16 @@ module Paperclip
|
|
77
71
|
end
|
78
72
|
|
79
73
|
class ContentTypeValidator < DataMapper::Validate::GenericValidator #:nodoc:
|
80
|
-
def initialize(field_name, options={})
|
81
|
-
super
|
82
|
-
@field_name, @options = field_name, options
|
83
|
-
end
|
84
|
-
|
85
74
|
def call(target)
|
86
75
|
valid_types = [@options[:content_type]].flatten
|
87
76
|
field_value = target.validation_property_value(@field_name)
|
88
77
|
|
89
|
-
unless field_value.nil? || field_value.original_filename
|
90
|
-
unless @options[:content_type]
|
78
|
+
unless field_value.nil? || Paperclip::Ext.blank?(field_value.original_filename)
|
79
|
+
unless Paperclip::Ext.blank?(@options[:content_type])
|
91
80
|
content_type = target.validation_property_value(:"#{@field_name}_content_type")
|
92
81
|
unless valid_types.any?{|t| t === content_type }
|
93
82
|
error_message ||= @options[:message] unless @options[:message].nil?
|
94
|
-
error_message ||= "%s's content type of '%s' is not a valid content type"
|
83
|
+
error_message ||= sprintf("%s's content type of '%s' is not a valid content type",DataMapper::Inflector.humanize(@field_name), content_type)
|
95
84
|
add_error(target, error_message , @field_name)
|
96
85
|
return false
|
97
86
|
end
|
@@ -103,22 +92,36 @@ module Paperclip
|
|
103
92
|
end
|
104
93
|
|
105
94
|
class CopyAttachmentErrors < DataMapper::Validate::GenericValidator #:nodoc:
|
106
|
-
def initialize(field_name, options={})
|
107
|
-
super
|
108
|
-
@field_name, @options = field_name, options
|
109
|
-
end
|
110
|
-
|
111
95
|
def call(target)
|
112
96
|
field_value = target.validation_property_value(@field_name)
|
113
|
-
unless field_value.nil? || field_value.original_filename
|
97
|
+
unless field_value.nil? || Paperclip::Ext.blank?(field_value.original_filename)
|
114
98
|
return true if field_value.errors.length == 0
|
115
|
-
field_value.errors.each
|
99
|
+
field_value.errors.each do |error, message|
|
100
|
+
[message].flatten.each { |m| add_error(target, m, @field_name) }
|
101
|
+
end
|
116
102
|
return false
|
117
103
|
end
|
118
104
|
return true
|
119
105
|
end
|
120
106
|
end
|
121
107
|
|
108
|
+
class GeometryValidator < DataMapper::Validate::GenericValidator #:nodoc:
|
109
|
+
def call(target)
|
110
|
+
field_value = target.validation_property_value(@field_name)
|
111
|
+
return true if field_value.queued_for_write[:original].nil?
|
112
|
+
|
113
|
+
geometry = Paperclip::Geometry.from_file(field_value.queued_for_write[:original].path)
|
114
|
+
|
115
|
+
return true if @options[:width].include?(geometry.width) && @options[:height].include?(geometry.height)
|
116
|
+
|
117
|
+
error_message ||= sprintf("%s width must be between %s and %s px", DataMapper::Inflector.humanize(@field_name), @options[:width].begin, @options[:width].end) unless @options[:width].include?(geometry.width)
|
118
|
+
error_message ||= sprintf("%s height must be between %s and %s px", DataMapper::Inflector.humanize(@field_name), @options[:height].begin, @options[:height].end) unless@options[:height].include?(geometry.height)
|
119
|
+
|
120
|
+
add_error(target, error_message , @field_name)
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
122
125
|
end
|
123
126
|
end
|
124
127
|
|