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.
@@ -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
- # both StringIO and Tempfile, then either can have its data copied anywhere else without typing
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
@@ -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
- @bucket = @options[:bucket]
50
- @s3_credentials = parse_credentials(@options[:s3_credentials])
51
- @s3_options = @options[:s3_options] || {}
52
- @s3_permissions = @options[:s3_permissions] || 'public-read'
53
- @url = ":s3_url"
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[:s3_url] = lambda do |attachment, style|
56
- "https://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
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
- def initialize file, target_geometry, format = nil, whiny_thumbnails = true
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 = "-scale \"#{scale}\""
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
@@ -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 "jpg", "png", "gif" then "image/#{type}"
12
- when "txt" then "text/plain"
13
- when "csv", "xml", "html", "htm", "css", "js" then "text/#{type}"
14
- else "x-application/#{type}"
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(DataMapper::Inflection.humanize(@field_name), @options[:less_than]) unless @options[:less_than].nil?
20
- error_message ||= "%s must be greater than %s bytes".t(DataMapper::Inflection.humanize(@field_name), @options[:greater_than]) unless @options[:greater_than].nil?
21
- error_message ||= "%s must be between %s and %s bytes".t(DataMapper::Inflection.humanize(@field_name), @options[:in].first, @options[:in].last)
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(DataMapper::Inflection.humanize(@field_name))
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(DataMapper::Inflection.humanize(@field_name), content_type)
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