dm-paperclip 2.1.2.1 → 2.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/{README.textile → README.rdoc} +9 -7
- data/Rakefile +5 -5
- data/lib/dm-paperclip.rb +36 -20
- data/lib/dm-paperclip/attachment.rb +81 -23
- data/lib/dm-paperclip/geometry.rb +1 -1
- data/lib/dm-paperclip/iostream.rb +1 -1
- data/lib/dm-paperclip/storage.rb +82 -7
- data/lib/dm-paperclip/thumbnail.rb +16 -8
- data/lib/dm-paperclip/upfile.rb +8 -5
- data/lib/dm-paperclip/validations.rb +5 -5
- data/test/attachment_test.rb +363 -0
- data/test/{test_geometry.rb → geometry_test.rb} +0 -0
- data/test/helper.rb +10 -18
- data/test/{test_integration.rb → integration_test.rb} +73 -11
- data/test/{test_iostream.rb → iostream_test.rb} +0 -0
- data/test/{test_paperclip.rb → paperclip_test.rb} +60 -16
- data/test/{test_storage.rb → storage_test.rb} +38 -1
- data/test/{test_thumbnail.rb → thumbnail_test.rb} +39 -1
- metadata +15 -21
- data/test/test_attachment.rb +0 -225
@@ -11,7 +11,7 @@ module IOStream
|
|
11
11
|
|
12
12
|
# Copies one read-able object from one place to another in blocks, obviating the need to load
|
13
13
|
# the whole thing into memory. Defaults to 8k blocks. If this module is included in both
|
14
|
-
#
|
14
|
+
# StringIO and Tempfile, then either can have its data copied anywhere else without typing
|
15
15
|
# worries or memory overhead worries. Returns a File if a String is passed in as the destination
|
16
16
|
# and returns the IO or Tempfile as passed in if one is sent as the destination.
|
17
17
|
def stream_to path_or_file, in_blocks_of = 8192
|
data/lib/dm-paperclip/storage.rb
CHANGED
@@ -1,6 +1,21 @@
|
|
1
1
|
module Paperclip
|
2
2
|
module Storage
|
3
3
|
|
4
|
+
# The default place to store attachments is in the filesystem. Files on the local
|
5
|
+
# filesystem can be very easily served by Apache without requiring a hit to your app.
|
6
|
+
# They also can be processed more easily after they've been saved, as they're just
|
7
|
+
# normal files. There is one Filesystem-specific option for has_attached_file.
|
8
|
+
# * +path+: The location of the repository of attachments on disk. This can (and, in
|
9
|
+
# almost all cases, should) be coordinated with the value of the +url+ option to
|
10
|
+
# allow files to be saved into a place where Apache can serve them without
|
11
|
+
# hitting your app. Defaults to
|
12
|
+
# ":rails_root/public/:attachment/:id/:style/:basename.:extension"
|
13
|
+
# By default this places the files in the app's public directory which can be served
|
14
|
+
# directly. If you are using capistrano for deployment, a good idea would be to
|
15
|
+
# make a symlink to the capistrano-created system directory from inside your app's
|
16
|
+
# public directory.
|
17
|
+
# See Paperclip::Attachment#interpolate for more information on variable interpolaton.
|
18
|
+
# :path => "/var/app/attachments/:class/:id/:style/:filename"
|
4
19
|
module Filesystem
|
5
20
|
def self.extended base
|
6
21
|
end
|
@@ -21,8 +36,10 @@ module Paperclip
|
|
21
36
|
alias_method :to_io, :to_file
|
22
37
|
|
23
38
|
def flush_writes #:nodoc:
|
39
|
+
#logger.info("[paperclip] Writing files for #{name}")
|
24
40
|
@queued_for_write.each do |style, file|
|
25
41
|
FileUtils.mkdir_p(File.dirname(path(style)))
|
42
|
+
#logger.info("[paperclip] -> #{path(style)}")
|
26
43
|
result = file.stream_to(path(style))
|
27
44
|
file.close
|
28
45
|
result.close
|
@@ -31,8 +48,10 @@ module Paperclip
|
|
31
48
|
end
|
32
49
|
|
33
50
|
def flush_deletes #:nodoc:
|
51
|
+
#logger.info("[paperclip] Deleting files for #{name}")
|
34
52
|
@queued_for_delete.each do |path|
|
35
53
|
begin
|
54
|
+
#logger.info("[paperclip] -> #{path}")
|
36
55
|
FileUtils.rm(path) if File.exist?(path)
|
37
56
|
rescue Errno::ENOENT => e
|
38
57
|
# ignore file-not-found, let everything else pass
|
@@ -42,19 +61,67 @@ module Paperclip
|
|
42
61
|
end
|
43
62
|
end
|
44
63
|
|
64
|
+
# Amazon's S3 file hosting service is a scalable, easy place to store files for
|
65
|
+
# distribution. You can find out more about it at http://aws.amazon.com/s3
|
66
|
+
# There are a few S3-specific options for has_attached_file:
|
67
|
+
# * +s3_credentials+: Takes a path, a File, or a Hash. The path (or File) must point
|
68
|
+
# to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
|
69
|
+
# gives you. You can 'environment-space' this just like you do to your
|
70
|
+
# database.yml file, so different environments can use different accounts:
|
71
|
+
# development:
|
72
|
+
# access_key_id: 123...
|
73
|
+
# secret_access_key: 123...
|
74
|
+
# test:
|
75
|
+
# access_key_id: abc...
|
76
|
+
# secret_access_key: abc...
|
77
|
+
# production:
|
78
|
+
# access_key_id: 456...
|
79
|
+
# secret_access_key: 456...
|
80
|
+
# This is not required, however, and the file may simply look like this:
|
81
|
+
# access_key_id: 456...
|
82
|
+
# secret_access_key: 456...
|
83
|
+
# In which case, those access keys will be used in all environments. You can also
|
84
|
+
# put your bucket name in this file, instead of adding it to the code directly.
|
85
|
+
# This is useful when you want the same account but a different bucket for
|
86
|
+
# development versus production.
|
87
|
+
# * +s3_permissions+: This is a String that should be one of the "canned" access
|
88
|
+
# policies that S3 provides (more information can be found here:
|
89
|
+
# http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAccessPolicy.html#RESTCannedAccessPolicies)
|
90
|
+
# The default for Paperclip is "public-read".
|
91
|
+
# * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
|
92
|
+
# 'http' or 'https'. Defaults to 'http' when your :s3_permissions are 'public-read' (the
|
93
|
+
# default), and 'https' when your :s3_permissions are anything else.
|
94
|
+
# * +bucket+: This is the name of the S3 bucket that will store your files. Remember
|
95
|
+
# that the bucket must be unique across all of Amazon S3. If the bucket does not exist
|
96
|
+
# Paperclip will attempt to create it. The bucket name will not be interpolated.
|
97
|
+
# * +url+: There are two options for the S3 url. You can choose to have the bucket's name
|
98
|
+
# placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
|
99
|
+
# Normally, this won't matter in the slightest and you can leave the default (which is
|
100
|
+
# path-style, or :s3_path_url). But in some cases paths don't work and you need to use
|
101
|
+
# the domain-style (:s3_domain_url). Anything else here will be treated like path-style.
|
102
|
+
# * +path+: This is the key under the bucket in which the file will be stored. The
|
103
|
+
# URL will be constructed from the bucket and the path. This is what you will want
|
104
|
+
# to interpolate. Keys should be unique, like filenames, and despite the fact that
|
105
|
+
# S3 (strictly speaking) does not support directories, you can still use a / to
|
106
|
+
# separate parts of your file name.
|
45
107
|
module S3
|
46
108
|
def self.extended base
|
47
109
|
require 'right_aws'
|
48
110
|
base.instance_eval do
|
49
|
-
@
|
50
|
-
@
|
51
|
-
@s3_options
|
52
|
-
@s3_permissions
|
53
|
-
@
|
111
|
+
@s3_credentials = parse_credentials(@options[:s3_credentials])
|
112
|
+
@bucket = @options[:bucket] || @s3_credentials[:bucket]
|
113
|
+
@s3_options = @options[:s3_options] || {}
|
114
|
+
@s3_permissions = @options[:s3_permissions] || 'public-read'
|
115
|
+
@s3_protocol = @options[:s3_protocol] || (@s3_permissions == 'public-read' ? 'http' : 'https')
|
116
|
+
@url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
|
54
117
|
end
|
55
|
-
base.class.interpolations[:
|
56
|
-
"
|
118
|
+
base.class.interpolations[:s3_path_url] = lambda do |attachment, style|
|
119
|
+
"#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
57
120
|
end
|
121
|
+
base.class.interpolations[:s3_domain_url] = lambda do |attachment, style|
|
122
|
+
"#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
|
123
|
+
end
|
124
|
+
#ActiveRecord::Base.logger.info("[paperclip] S3 Storage Initalized.")
|
58
125
|
end
|
59
126
|
|
60
127
|
def s3
|
@@ -80,6 +147,10 @@ module Paperclip
|
|
80
147
|
s3_bucket.key(path(style)) ? true : false
|
81
148
|
end
|
82
149
|
|
150
|
+
def s3_protocol
|
151
|
+
@s3_protocol
|
152
|
+
end
|
153
|
+
|
83
154
|
# Returns representation of the data of the file assigned to the given
|
84
155
|
# style, in the format most representative of the current storage.
|
85
156
|
def to_file style = default_style
|
@@ -88,8 +159,10 @@ module Paperclip
|
|
88
159
|
alias_method :to_io, :to_file
|
89
160
|
|
90
161
|
def flush_writes #:nodoc:
|
162
|
+
#logger.info("[paperclip] Writing files for #{name}")
|
91
163
|
@queued_for_write.each do |style, file|
|
92
164
|
begin
|
165
|
+
#logger.info("[paperclip] -> #{path(style)}")
|
93
166
|
key = s3_bucket.key(path(style))
|
94
167
|
key.data = file
|
95
168
|
key.put(nil, @s3_permissions)
|
@@ -101,8 +174,10 @@ module Paperclip
|
|
101
174
|
end
|
102
175
|
|
103
176
|
def flush_deletes #:nodoc:
|
177
|
+
#logger.info("[paperclip] Writing files for #{name}")
|
104
178
|
@queued_for_delete.each do |path|
|
105
179
|
begin
|
180
|
+
#logger.info("[paperclip] -> #{path}")
|
106
181
|
if file = s3_bucket.key(path)
|
107
182
|
file.delete
|
108
183
|
end
|
@@ -2,18 +2,20 @@ module Paperclip
|
|
2
2
|
# Handles thumbnailing images that are uploaded.
|
3
3
|
class Thumbnail
|
4
4
|
|
5
|
-
attr_accessor :file, :current_geometry, :target_geometry, :format, :whiny_thumbnails
|
5
|
+
attr_accessor :file, :current_geometry, :target_geometry, :format, :whiny_thumbnails, :convert_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+
|
9
9
|
# which is a "WxH"-style string. +format+ will be inferred from the +file+
|
10
10
|
# unless specified. Thumbnail creation will raise no errors unless
|
11
|
-
# +whiny_thumbnails+ is true (which it is, by default.
|
12
|
-
|
11
|
+
# +whiny_thumbnails+ is true (which it is, by default. If +convert_options+ is
|
12
|
+
# set, the options will be appended to the convert command upon image conversion
|
13
|
+
def initialize file, target_geometry, format = nil, convert_options = nil, whiny_thumbnails = true
|
13
14
|
@file = file
|
14
15
|
@crop = target_geometry[-1,1] == '#'
|
15
16
|
@target_geometry = Geometry.parse target_geometry
|
16
17
|
@current_geometry = Geometry.from_file file
|
18
|
+
@convert_options = convert_options
|
17
19
|
@whiny_thumbnails = whiny_thumbnails
|
18
20
|
|
19
21
|
@current_format = File.extname(@file.path)
|
@@ -24,14 +26,19 @@ module Paperclip
|
|
24
26
|
|
25
27
|
# Creates a thumbnail, as specified in +initialize+, +make+s it, and returns the
|
26
28
|
# resulting Tempfile.
|
27
|
-
def self.make file, dimensions, format = nil, whiny_thumbnails = true
|
28
|
-
new(file, dimensions, format, whiny_thumbnails).make
|
29
|
+
def self.make file, dimensions, format = nil, convert_options = nil, whiny_thumbnails = true
|
30
|
+
new(file, dimensions, format, convert_options, whiny_thumbnails).make
|
29
31
|
end
|
30
32
|
|
31
33
|
# Returns true if the +target_geometry+ is meant to crop.
|
32
34
|
def crop?
|
33
35
|
@crop
|
34
36
|
end
|
37
|
+
|
38
|
+
# Returns true if the image is meant to make use of additional convert options.
|
39
|
+
def convert_options?
|
40
|
+
not @convert_options.blank?
|
41
|
+
end
|
35
42
|
|
36
43
|
# Performs the conversion of the +file+ into a thumbnail. Returns the Tempfile
|
37
44
|
# that contains the new image.
|
@@ -42,13 +49,13 @@ module Paperclip
|
|
42
49
|
|
43
50
|
command = <<-end_command
|
44
51
|
#{ Paperclip.path_for_command('convert') }
|
45
|
-
"#{ File.expand_path(src.path) }"
|
52
|
+
"#{ File.expand_path(src.path) }[0]"
|
46
53
|
#{ transformation_command }
|
47
54
|
"#{ File.expand_path(dst.path) }"
|
48
55
|
end_command
|
49
56
|
success = system(command.gsub(/\s+/, " "))
|
50
57
|
|
51
|
-
if success && $?.exitstatus != 0 && @whiny_thumbnails
|
58
|
+
if !success && $?.exitstatus != 0 && @whiny_thumbnails
|
52
59
|
raise PaperclipError, "There was an error processing this thumbnail"
|
53
60
|
end
|
54
61
|
|
@@ -59,8 +66,9 @@ module Paperclip
|
|
59
66
|
# into the thumbnail.
|
60
67
|
def transformation_command
|
61
68
|
scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
|
62
|
-
trans = "-
|
69
|
+
trans = "-resize \"#{scale}\""
|
63
70
|
trans << " -crop \"#{crop}\" +repage" if crop
|
71
|
+
trans << " #{convert_options}" if convert_options?
|
64
72
|
trans
|
65
73
|
end
|
66
74
|
end
|
data/lib/dm-paperclip/upfile.rb
CHANGED
@@ -6,12 +6,15 @@ module Paperclip
|
|
6
6
|
|
7
7
|
# Infer the MIME-type of the file from the extension.
|
8
8
|
def content_type
|
9
|
-
type = self.path.match(/\.(\w+)$/)[1] rescue "octet-stream"
|
9
|
+
type = (self.path.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
|
10
10
|
case type
|
11
|
-
when "
|
12
|
-
when "
|
13
|
-
when "
|
14
|
-
|
11
|
+
when %r"jpe?g" then "image/jpeg"
|
12
|
+
when %r"tiff?" then "image/tiff"
|
13
|
+
when %r"png", "gif", "bmp" then "image/#{type}"
|
14
|
+
when "txt" then "text/plain"
|
15
|
+
when %r"html?" then "text/html"
|
16
|
+
when "csv", "xml", "css", "js" then "text/#{type}"
|
17
|
+
else "application/x-#{type}"
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
@@ -16,9 +16,9 @@ module Paperclip
|
|
16
16
|
return true if @options[:in].include? field_value.to_i
|
17
17
|
|
18
18
|
error_message ||= @options[:message] unless @options[:message].nil?
|
19
|
-
error_message ||= "%s must be less than %s bytes".t(
|
20
|
-
error_message ||= "%s must be greater than %s bytes".t(
|
21
|
-
error_message ||= "%s must be between %s and %s bytes".t(
|
19
|
+
error_message ||= "%s must be less than %s bytes".t(Extlib::Inflection.humanize(@field_name), @options[:less_than]) unless @options[:less_than].nil?
|
20
|
+
error_message ||= "%s must be greater than %s bytes".t(Extlib::Inflection.humanize(@field_name), @options[:greater_than]) unless @options[:greater_than].nil?
|
21
|
+
error_message ||= "%s must be between %s and %s bytes".t(Extlib::Inflection.humanize(@field_name), @options[:in].first, @options[:in].last)
|
22
22
|
add_error(target, error_message , @field_name)
|
23
23
|
return false
|
24
24
|
end
|
@@ -33,7 +33,7 @@ module Paperclip
|
|
33
33
|
def call(target)
|
34
34
|
field_value = target.validation_property_value(@field_name)
|
35
35
|
if field_value.nil? || field_value.original_filename.blank?
|
36
|
-
error_message = @options[:message] || "%s must be set".t(
|
36
|
+
error_message = @options[:message] || "%s must be set".t(Extlib::Inflection.humanize(@field_name))
|
37
37
|
add_error(target, error_message , @field_name)
|
38
38
|
return false
|
39
39
|
end
|
@@ -56,7 +56,7 @@ module Paperclip
|
|
56
56
|
content_type = target.validation_property_value(:"#{@field_name}_content_type")
|
57
57
|
unless valid_types.any?{|t| t === content_type }
|
58
58
|
error_message ||= @options[:message] unless @options[:message].nil?
|
59
|
-
error_message ||= "%s's content type of '%s' is not a valid content type".t(
|
59
|
+
error_message ||= "%s's content type of '%s' is not a valid content type".t(Extlib::Inflection.humanize(@field_name), content_type)
|
60
60
|
add_error(target, error_message , @field_name)
|
61
61
|
return false
|
62
62
|
end
|
@@ -0,0 +1,363 @@
|
|
1
|
+
require 'test/helper'
|
2
|
+
|
3
|
+
class Dummy
|
4
|
+
# This is a dummy class
|
5
|
+
end
|
6
|
+
|
7
|
+
class AttachmentTest < Test::Unit::TestCase
|
8
|
+
context "Attachment default_options" do
|
9
|
+
setup do
|
10
|
+
rebuild_model
|
11
|
+
@old_default_options = Paperclip::Attachment.default_options.dup
|
12
|
+
@new_default_options = @old_default_options.merge({
|
13
|
+
:path => "argle/bargle",
|
14
|
+
:url => "fooferon",
|
15
|
+
:default_url => "not here.png"
|
16
|
+
})
|
17
|
+
end
|
18
|
+
|
19
|
+
teardown do
|
20
|
+
Paperclip::Attachment.default_options.merge! @old_default_options
|
21
|
+
end
|
22
|
+
|
23
|
+
should "be overrideable" do
|
24
|
+
Paperclip::Attachment.default_options.merge!(@new_default_options)
|
25
|
+
@new_default_options.keys.each do |key|
|
26
|
+
assert_equal @new_default_options[key],
|
27
|
+
Paperclip::Attachment.default_options[key]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "without an Attachment" do
|
32
|
+
setup do
|
33
|
+
@dummy = Dummy.new
|
34
|
+
end
|
35
|
+
|
36
|
+
should "return false when asked exists?" do
|
37
|
+
assert !@dummy.avatar.exists?
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "on an Attachment" do
|
42
|
+
setup do
|
43
|
+
@dummy = Dummy.new
|
44
|
+
@attachment = @dummy.avatar
|
45
|
+
end
|
46
|
+
|
47
|
+
Paperclip::Attachment.default_options.keys.each do |key|
|
48
|
+
should "be the default_options for #{key}" do
|
49
|
+
assert_equal @old_default_options[key],
|
50
|
+
@attachment.instance_variable_get("@#{key}"),
|
51
|
+
key
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when redefined" do
|
56
|
+
setup do
|
57
|
+
Paperclip::Attachment.default_options.merge!(@new_default_options)
|
58
|
+
@dummy = Dummy.new
|
59
|
+
@attachment = @dummy.avatar
|
60
|
+
end
|
61
|
+
|
62
|
+
Paperclip::Attachment.default_options.keys.each do |key|
|
63
|
+
should "be the new default_options for #{key}" do
|
64
|
+
assert_equal @new_default_options[key],
|
65
|
+
@attachment.instance_variable_get("@#{key}"),
|
66
|
+
key
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "An attachment with similarly named interpolations" do
|
74
|
+
setup do
|
75
|
+
rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
|
76
|
+
@dummy = Dummy.new
|
77
|
+
@dummy.stubs(:id).returns(1024)
|
78
|
+
@file = File.new(File.join(File.dirname(__FILE__),
|
79
|
+
"fixtures",
|
80
|
+
"5k.png"))
|
81
|
+
@dummy.avatar = @file
|
82
|
+
end
|
83
|
+
|
84
|
+
should "make sure that they are interpolated correctly" do
|
85
|
+
assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "An attachment with a :rails_env interpolation" do
|
90
|
+
setup do
|
91
|
+
@rails_env = "blah"
|
92
|
+
@id = 1024
|
93
|
+
rebuild_model :path => ":merb_env/:id.png"
|
94
|
+
@dummy = Dummy.new
|
95
|
+
@dummy.stubs(:id).returns(@id)
|
96
|
+
@file = File.new(File.join(File.dirname(__FILE__),
|
97
|
+
"fixtures",
|
98
|
+
"5k.png"))
|
99
|
+
@dummy.avatar = @file
|
100
|
+
end
|
101
|
+
|
102
|
+
should "return the proper path" do
|
103
|
+
temporary_env(@rails_env) {
|
104
|
+
assert_equal "#{Merb.env}/#{@id}.png", @dummy.avatar.path
|
105
|
+
}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "An attachment with :convert_options" do
|
110
|
+
setup do
|
111
|
+
rebuild_model :styles => {
|
112
|
+
:thumb => "100x100",
|
113
|
+
:large => "400x400"
|
114
|
+
},
|
115
|
+
:convert_options => {
|
116
|
+
:all => "-do_stuff",
|
117
|
+
:thumb => "-thumbnailize"
|
118
|
+
}
|
119
|
+
@dummy = Dummy.new
|
120
|
+
end
|
121
|
+
|
122
|
+
should "report the correct options when sent #extra_options_for(:thumb)" do
|
123
|
+
assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
|
124
|
+
end
|
125
|
+
|
126
|
+
should "report the correct options when sent #extra_options_for(:large)" do
|
127
|
+
assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large)
|
128
|
+
end
|
129
|
+
|
130
|
+
context "when given a file" do
|
131
|
+
setup do
|
132
|
+
@file = File.new(File.join(File.dirname(__FILE__),
|
133
|
+
"fixtures",
|
134
|
+
"5k.png"))
|
135
|
+
Paperclip::Thumbnail.stubs(:make)
|
136
|
+
[:thumb, :large].each do |style|
|
137
|
+
@dummy.avatar.stubs(:extra_options_for).with(style)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
[:thumb, :large].each do |style|
|
142
|
+
should "call extra_options_for(#{style})" do
|
143
|
+
@dummy.avatar.expects(:extra_options_for).with(style)
|
144
|
+
@dummy.avatar = @file
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
context "Assigning an attachment" do
|
151
|
+
setup do
|
152
|
+
rebuild_model
|
153
|
+
|
154
|
+
@not_file = mock
|
155
|
+
@not_file.stubs(:nil?).returns(false)
|
156
|
+
@not_file.expects(:to_tempfile).returns(self)
|
157
|
+
@not_file.expects(:original_filename).returns("filename.png\r\n")
|
158
|
+
@not_file.expects(:content_type).returns("image/png\r\n")
|
159
|
+
@not_file.expects(:size).returns(10)
|
160
|
+
|
161
|
+
@dummy = Dummy.new
|
162
|
+
@attachment = @dummy.avatar
|
163
|
+
@attachment.expects(:valid_assignment?).with(@not_file).returns(true)
|
164
|
+
@attachment.expects(:queue_existing_for_delete)
|
165
|
+
@attachment.expects(:post_process)
|
166
|
+
@attachment.expects(:validate)
|
167
|
+
@dummy.avatar = @not_file
|
168
|
+
end
|
169
|
+
|
170
|
+
should "strip whitespace from original_filename field" do
|
171
|
+
assert_equal "filename.png", @dummy.avatar.original_filename
|
172
|
+
end
|
173
|
+
|
174
|
+
should "strip whitespace from content_type field" do
|
175
|
+
assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
|
180
|
+
context "Attachment with strange letters" do
|
181
|
+
setup do
|
182
|
+
rebuild_model
|
183
|
+
|
184
|
+
@not_file = mock
|
185
|
+
@not_file.stubs(:nil?).returns(false)
|
186
|
+
@not_file.expects(:to_tempfile).returns(self)
|
187
|
+
@not_file.expects(:original_filename).returns("sheep_say_b�.png\r\n")
|
188
|
+
@not_file.expects(:content_type).returns("image/png\r\n")
|
189
|
+
@not_file.expects(:size).returns(10)
|
190
|
+
|
191
|
+
@dummy = Dummy.new
|
192
|
+
@attachment = @dummy.avatar
|
193
|
+
@attachment.expects(:valid_assignment?).with(@not_file).returns(true)
|
194
|
+
@attachment.expects(:queue_existing_for_delete)
|
195
|
+
@attachment.expects(:post_process)
|
196
|
+
@attachment.expects(:validate)
|
197
|
+
@dummy.avatar = @not_file
|
198
|
+
end
|
199
|
+
|
200
|
+
should "remove strange letters and replace with underscore (_)" do
|
201
|
+
assert_equal "sheep_say_b_.png", @dummy.avatar.original_filename
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
context "An attachment" do
|
207
|
+
setup do
|
208
|
+
Paperclip::Attachment.default_options.merge!({
|
209
|
+
:path => ":merb_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
|
210
|
+
})
|
211
|
+
FileUtils.rm_rf("tmp")
|
212
|
+
rebuild_model
|
213
|
+
@instance = Dummy.new
|
214
|
+
@instance.stubs(:id).returns(1024)
|
215
|
+
@attachment = Paperclip::Attachment.new(:avatar, @instance)
|
216
|
+
@file = File.new(File.join(File.dirname(__FILE__),
|
217
|
+
"fixtures",
|
218
|
+
"5k.png"))
|
219
|
+
end
|
220
|
+
|
221
|
+
should "raise if there are not the correct columns when you try to assign" do
|
222
|
+
@other_attachment = Paperclip::Attachment.new(:not_here, @instance)
|
223
|
+
assert_raises(Paperclip::PaperclipError) do
|
224
|
+
@other_attachment.assign(@file)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
should "return its default_url when no file assigned" do
|
229
|
+
assert @attachment.to_file.nil?
|
230
|
+
assert_equal "/avatars/original/missing.png", @attachment.url
|
231
|
+
assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
|
232
|
+
end
|
233
|
+
|
234
|
+
context "with a file assigned in the database" do
|
235
|
+
setup do
|
236
|
+
@instance.stubs(:attribute_get).with(:avatar_file_name).returns('5k.png')
|
237
|
+
@instance.stubs(:attribute_get).with(:avatar_content_type).returns("image/png")
|
238
|
+
@instance.stubs(:attribute_get).with(:avatar_file_size).returns(12345)
|
239
|
+
now = Time.now
|
240
|
+
Time.stubs(:now).returns(now)
|
241
|
+
@instance.stubs(:attribute_get).with(:avatar_updated_at).returns(Time.now)
|
242
|
+
end
|
243
|
+
|
244
|
+
should "return a correct url even if the file does not exist" do
|
245
|
+
assert_nil @attachment.to_file
|
246
|
+
assert_match %r{^/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
|
247
|
+
end
|
248
|
+
|
249
|
+
should "make sure the updated_at mtime is in the url if it is defined" do
|
250
|
+
time = Time.now
|
251
|
+
assert_match %r{#{time.year}#{time.month}#{time.day}#{time.hour}#{time.min}#{time.sec}$}, @attachment.url(:blah)
|
252
|
+
end
|
253
|
+
|
254
|
+
context "with the updated_at field removed" do
|
255
|
+
setup do
|
256
|
+
@instance.stubs(:[]).with(:avatar_updated_at).returns(nil)
|
257
|
+
end
|
258
|
+
|
259
|
+
should "only return the url without the updated_at when sent #url" do
|
260
|
+
assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
should "return the proper path when filename has a single .'s" do
|
265
|
+
assert_equal "./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png", @attachment.path
|
266
|
+
end
|
267
|
+
|
268
|
+
should "return the proper path when filename has multiple .'s" do
|
269
|
+
@instance.stubs(:attribute_get).with(:avatar_file_name).returns("5k.old.png")
|
270
|
+
assert_equal "./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.old.png", @attachment.path
|
271
|
+
end
|
272
|
+
|
273
|
+
context "when expecting three styles" do
|
274
|
+
setup do
|
275
|
+
styles = {:styles => { :large => ["400x400", :png],
|
276
|
+
:medium => ["100x100", :gif],
|
277
|
+
:small => ["32x32#", :jpg]}}
|
278
|
+
@attachment = Paperclip::Attachment.new(:avatar,
|
279
|
+
@instance,
|
280
|
+
styles)
|
281
|
+
end
|
282
|
+
|
283
|
+
context "and assigned a file" do
|
284
|
+
setup do
|
285
|
+
now = Time.now
|
286
|
+
Time.stubs(:now).returns(now)
|
287
|
+
@attachment.assign(@file)
|
288
|
+
end
|
289
|
+
|
290
|
+
should "be dirty" do
|
291
|
+
assert @attachment.dirty?
|
292
|
+
end
|
293
|
+
|
294
|
+
context "and saved" do
|
295
|
+
setup do
|
296
|
+
@attachment.save
|
297
|
+
end
|
298
|
+
|
299
|
+
should "return the real url" do
|
300
|
+
assert @attachment.to_file
|
301
|
+
assert_match %r{^/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
|
302
|
+
assert_match %r{^/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
|
303
|
+
end
|
304
|
+
|
305
|
+
should "commit the files to disk" do
|
306
|
+
[:large, :medium, :small].each do |style|
|
307
|
+
io = @attachment.to_io(style)
|
308
|
+
assert File.exists?(io)
|
309
|
+
assert ! io.is_a?(::Tempfile)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
should "save the files as the right formats and sizes" do
|
314
|
+
[[:large, 400, 61, "PNG"],
|
315
|
+
[:medium, 100, 15, "GIF"],
|
316
|
+
[:small, 32, 32, "JPEG"]].each do |style|
|
317
|
+
cmd = "identify -format '%w %h %b %m' " +
|
318
|
+
"#{@attachment.to_io(style.first).path}"
|
319
|
+
out = `#{cmd}`
|
320
|
+
width, height, size, format = out.split(" ")
|
321
|
+
assert_equal style[1].to_s, width.to_s
|
322
|
+
assert_equal style[2].to_s, height.to_s
|
323
|
+
assert_equal style[3].to_s, format.to_s
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
should "still have its #file attribute not be nil" do
|
328
|
+
assert ! @attachment.to_file.nil?
|
329
|
+
end
|
330
|
+
|
331
|
+
context "and deleted" do
|
332
|
+
setup do
|
333
|
+
@existing_names = @attachment.styles.keys.collect do |style|
|
334
|
+
@attachment.path(style)
|
335
|
+
end
|
336
|
+
@instance.expects(:attributes=).with({ :avatar_file_name => nil,
|
337
|
+
:avatar_content_type => nil,
|
338
|
+
:avatar_file_size => nil })
|
339
|
+
@attachment.assign nil
|
340
|
+
@attachment.save
|
341
|
+
end
|
342
|
+
|
343
|
+
should "delete the files" do
|
344
|
+
@existing_names.each{|f| assert ! File.exists?(f) }
|
345
|
+
end
|
346
|
+
end
|
347
|
+
end
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
end
|
352
|
+
|
353
|
+
context "when trying a nonexistant storage type" do
|
354
|
+
setup do
|
355
|
+
rebuild_model :storage => :not_here
|
356
|
+
end
|
357
|
+
|
358
|
+
should "not be able to find the module" do
|
359
|
+
assert_raise(NameError){ Dummy.new.avatar }
|
360
|
+
end
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|