davidray-paperclip 2.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/LICENSE +26 -0
  2. data/README.rdoc +179 -0
  3. data/Rakefile +76 -0
  4. data/generators/paperclip/USAGE +5 -0
  5. data/generators/paperclip/paperclip_generator.rb +27 -0
  6. data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  7. data/init.rb +1 -0
  8. data/lib/generators/paperclip/USAGE +8 -0
  9. data/lib/generators/paperclip/paperclip_generator.rb +31 -0
  10. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  11. data/lib/paperclip.rb +393 -0
  12. data/lib/paperclip/attachment.rb +326 -0
  13. data/lib/paperclip/callback_compatability.rb +61 -0
  14. data/lib/paperclip/geometry.rb +115 -0
  15. data/lib/paperclip/interpolations.rb +108 -0
  16. data/lib/paperclip/iostream.rb +59 -0
  17. data/lib/paperclip/matchers.rb +33 -0
  18. data/lib/paperclip/matchers/have_attached_file_matcher.rb +57 -0
  19. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +74 -0
  20. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +54 -0
  21. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +95 -0
  22. data/lib/paperclip/processor.rb +49 -0
  23. data/lib/paperclip/railtie.rb +24 -0
  24. data/lib/paperclip/storage.rb +247 -0
  25. data/lib/paperclip/style.rb +90 -0
  26. data/lib/paperclip/thumbnail.rb +78 -0
  27. data/lib/paperclip/upfile.rb +52 -0
  28. data/lib/paperclip/version.rb +3 -0
  29. data/lib/tasks/paperclip.rake +79 -0
  30. data/rails/init.rb +2 -0
  31. data/shoulda_macros/paperclip.rb +119 -0
  32. data/test/attachment_test.rb +758 -0
  33. data/test/database.yml +4 -0
  34. data/test/fixtures/12k.png +0 -0
  35. data/test/fixtures/50x50.png +0 -0
  36. data/test/fixtures/5k.png +0 -0
  37. data/test/fixtures/bad.png +1 -0
  38. data/test/fixtures/s3.yml +8 -0
  39. data/test/fixtures/text.txt +0 -0
  40. data/test/fixtures/twopage.pdf +0 -0
  41. data/test/geometry_test.rb +177 -0
  42. data/test/helper.rb +148 -0
  43. data/test/integration_test.rb +483 -0
  44. data/test/interpolations_test.rb +124 -0
  45. data/test/iostream_test.rb +78 -0
  46. data/test/matchers/have_attached_file_matcher_test.rb +24 -0
  47. data/test/matchers/validate_attachment_content_type_matcher_test.rb +37 -0
  48. data/test/matchers/validate_attachment_presence_matcher_test.rb +26 -0
  49. data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
  50. data/test/paperclip_test.rb +317 -0
  51. data/test/processor_test.rb +10 -0
  52. data/test/storage_test.rb +343 -0
  53. data/test/style_test.rb +141 -0
  54. data/test/thumbnail_test.rb +227 -0
  55. data/test/upfile_test.rb +36 -0
  56. metadata +205 -0
@@ -0,0 +1,52 @@
1
+ module Paperclip
2
+ # The Upfile module is a convenience module for adding uploaded-file-type methods
3
+ # to the +File+ class. Useful for testing.
4
+ # user.avatar = File.new("test/test_avatar.jpg")
5
+ module Upfile
6
+
7
+ # Infer the MIME-type of the file from the extension.
8
+ def content_type
9
+ type = (self.path.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
10
+ case type
11
+ when %r"jp(e|g|eg)" 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 "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", "--mime-type", self.path).split(':').last.strip rescue "application/x-#{type}")
21
+ content_type = "application/x-#{type}" if content_type.match(/\(.*?\)/)
22
+ content_type
23
+ end
24
+ end
25
+
26
+ # Returns the file's normal name.
27
+ def original_filename
28
+ File.basename(self.path)
29
+ end
30
+
31
+ # Returns the size of the file.
32
+ def size
33
+ File.size(self)
34
+ end
35
+ end
36
+ end
37
+
38
+ if defined? StringIO
39
+ class StringIO
40
+ attr_accessor :original_filename, :content_type
41
+ def original_filename
42
+ @original_filename ||= "stringio.txt"
43
+ end
44
+ def content_type
45
+ @content_type ||= "text/plain"
46
+ end
47
+ end
48
+ end
49
+
50
+ class File #:nodoc:
51
+ include Paperclip::Upfile
52
+ end
@@ -0,0 +1,3 @@
1
+ module Paperclip
2
+ VERSION = "2.3.3" unless defined? Paperclip::VERSION
3
+ end
@@ -0,0 +1,79 @@
1
+ def obtain_class
2
+ class_name = ENV['CLASS'] || ENV['class']
3
+ raise "Must specify CLASS" unless class_name
4
+ @klass = Object.const_get(class_name)
5
+ end
6
+
7
+ def obtain_attachments
8
+ name = ENV['ATTACHMENT'] || ENV['attachment']
9
+ raise "Class #{@klass.name} has no attachments specified" unless @klass.respond_to?(:attachment_definitions)
10
+ if !name.blank? && @klass.attachment_definitions.keys.include?(name)
11
+ [ name ]
12
+ else
13
+ @klass.attachment_definitions.keys
14
+ end
15
+ end
16
+
17
+ def for_all_attachments
18
+ klass = obtain_class
19
+ names = obtain_attachments
20
+ ids = klass.connection.select_values(klass.send(:construct_finder_sql, :select => 'id'))
21
+
22
+ ids.each do |id|
23
+ instance = klass.find(id)
24
+ names.each do |name|
25
+ result = if instance.send("#{ name }?")
26
+ yield(instance, name)
27
+ else
28
+ true
29
+ end
30
+ print result ? "." : "x"; $stdout.flush
31
+ end
32
+ end
33
+ puts " Done."
34
+ end
35
+
36
+ namespace :paperclip do
37
+ desc "Refreshes both metadata and thumbnails."
38
+ task :refresh => ["paperclip:refresh:metadata", "paperclip:refresh:thumbnails"]
39
+
40
+ namespace :refresh do
41
+ desc "Regenerates thumbnails for a given CLASS (and optional ATTACHMENT)."
42
+ task :thumbnails => :environment do
43
+ errors = []
44
+ for_all_attachments do |instance, name|
45
+ result = instance.send(name).reprocess!
46
+ errors << [instance.id, instance.errors] unless instance.errors.blank?
47
+ result
48
+ end
49
+ errors.each{|e| puts "#{e.first}: #{e.last.full_messages.inspect}" }
50
+ end
51
+
52
+ desc "Regenerates content_type/size metadata for a given CLASS (and optional ATTACHMENT)."
53
+ task :metadata => :environment do
54
+ for_all_attachments do |instance, name|
55
+ if file = instance.send(name).to_file
56
+ instance.send("#{name}_file_name=", instance.send("#{name}_file_name").strip)
57
+ instance.send("#{name}_content_type=", file.content_type.strip)
58
+ instance.send("#{name}_file_size=", file.size) if instance.respond_to?("#{name}_file_size")
59
+ instance.save(false)
60
+ else
61
+ true
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ desc "Cleans out invalid attachments. Useful after you've added new validations."
68
+ task :clean => :environment do
69
+ for_all_attachments do |instance, name|
70
+ instance.send(name).send(:validate)
71
+ if instance.send(name).valid?
72
+ true
73
+ else
74
+ instance.send("#{name}=", nil)
75
+ instance.save
76
+ end
77
+ end
78
+ end
79
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'paperclip/railtie'
2
+ Paperclip::Railtie.insert
@@ -0,0 +1,119 @@
1
+ require 'paperclip/matchers'
2
+ require 'action_controller'
3
+
4
+ module Paperclip
5
+ # =Paperclip Shoulda Macros
6
+ #
7
+ # These macros are intended for use with shoulda, and will be included into
8
+ # your tests automatically. All of the macros use the standard shoulda
9
+ # assumption that the name of the test is based on the name of the model
10
+ # you're testing (that is, UserTest is the test for the User model), and
11
+ # will load that class for testing purposes.
12
+ module Shoulda
13
+ include Matchers
14
+ # This will test whether you have defined your attachment correctly by
15
+ # checking for all the required fields exist after the definition of the
16
+ # attachment.
17
+ def should_have_attached_file name
18
+ klass = self.name.gsub(/Test$/, '').constantize
19
+ matcher = have_attached_file name
20
+ should matcher.description do
21
+ assert_accepts(matcher, klass)
22
+ end
23
+ end
24
+
25
+ # Tests for validations on the presence of the attachment.
26
+ def should_validate_attachment_presence name
27
+ klass = self.name.gsub(/Test$/, '').constantize
28
+ matcher = validate_attachment_presence name
29
+ should matcher.description do
30
+ assert_accepts(matcher, klass)
31
+ end
32
+ end
33
+
34
+ # Tests that you have content_type validations specified. There are two
35
+ # options, :valid and :invalid. Both accept an array of strings. The
36
+ # strings should be a list of content types which will pass and fail
37
+ # validation, respectively.
38
+ def should_validate_attachment_content_type name, options = {}
39
+ klass = self.name.gsub(/Test$/, '').constantize
40
+ valid = [options[:valid]].flatten
41
+ invalid = [options[:invalid]].flatten
42
+ matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)
43
+ should matcher.description do
44
+ assert_accepts(matcher, klass)
45
+ end
46
+ end
47
+
48
+ # Tests to ensure that you have file size validations turned on. You
49
+ # can pass the same options to this that you can to
50
+ # validate_attachment_file_size - :less_than, :greater_than, and :in.
51
+ # :less_than checks that a file is less than a certain size, :greater_than
52
+ # checks that a file is more than a certain size, and :in takes a Range or
53
+ # Array which specifies the lower and upper limits of the file size.
54
+ def should_validate_attachment_size name, options = {}
55
+ klass = self.name.gsub(/Test$/, '').constantize
56
+ min = options[:greater_than] || (options[:in] && options[:in].first) || 0
57
+ max = options[:less_than] || (options[:in] && options[:in].last) || (1.0/0)
58
+ range = (min..max)
59
+ matcher = validate_attachment_size(name).in(range)
60
+ should matcher.description do
61
+ assert_accepts(matcher, klass)
62
+ end
63
+ end
64
+
65
+ # Stubs the HTTP PUT for an attachment using S3 storage.
66
+ #
67
+ # @example
68
+ # stub_paperclip_s3('user', 'avatar', 'png')
69
+ def stub_paperclip_s3(model, attachment, extension)
70
+ definition = model.gsub(" ", "_").classify.constantize.
71
+ attachment_definitions[attachment.to_sym]
72
+
73
+ path = "http://s3.amazonaws.com/:id/#{definition[:path]}"
74
+ path.gsub!(/:([^\/\.]+)/) do |match|
75
+ "([^\/\.]+)"
76
+ end
77
+
78
+ begin
79
+ FakeWeb.register_uri(:put, Regexp.new(path), :body => "OK")
80
+ rescue NameError
81
+ raise NameError, "the stub_paperclip_s3 shoulda macro requires the fakeweb gem."
82
+ end
83
+ end
84
+
85
+ # Stub S3 and return a file for attachment. Best with Factory Girl.
86
+ # Uses a strict directory convention:
87
+ #
88
+ # features/support/paperclip
89
+ #
90
+ # This method is used by the Paperclip-provided Cucumber step:
91
+ #
92
+ # When I attach a "demo_tape" "mp3" file to a "band" on S3
93
+ #
94
+ # @example
95
+ # Factory.define :band_with_demo_tape, :parent => :band do |band|
96
+ # band.demo_tape { band.paperclip_fixture("band", "demo_tape", "png") }
97
+ # end
98
+ def paperclip_fixture(model, attachment, extension)
99
+ stub_paperclip_s3(model, attachment, extension)
100
+ base_path = File.join(File.dirname(__FILE__), "..", "..",
101
+ "features", "support", "paperclip")
102
+ File.new(File.join(base_path, model, "#{attachment}.#{extension}"))
103
+ end
104
+ end
105
+ end
106
+
107
+ if defined?(ActionController::Integration::Session)
108
+ class ActionController::Integration::Session #:nodoc:
109
+ include Paperclip::Shoulda
110
+ end
111
+ end
112
+
113
+ class Factory
114
+ include Paperclip::Shoulda #:nodoc:
115
+ end
116
+
117
+ class Test::Unit::TestCase #:nodoc:
118
+ extend Paperclip::Shoulda
119
+ end
@@ -0,0 +1,758 @@
1
+ # encoding: utf-8
2
+ require 'test/helper'
3
+
4
+ class Dummy
5
+ # This is a dummy class
6
+ end
7
+
8
+ class AttachmentTest < Test::Unit::TestCase
9
+ should "return the path based on the url by default" do
10
+ @attachment = attachment :url => "/:class/:id/:basename"
11
+ @model = @attachment.instance
12
+ @model.id = 1234
13
+ @model.avatar_file_name = "fake.jpg"
14
+ assert_equal "#{Rails.root}/public/fake_models/1234/fake", @attachment.path
15
+ end
16
+
17
+ context "Attachment default_options" do
18
+ setup do
19
+ rebuild_model
20
+ @old_default_options = Paperclip::Attachment.default_options.dup
21
+ @new_default_options = @old_default_options.merge({
22
+ :path => "argle/bargle",
23
+ :url => "fooferon",
24
+ :default_url => "not here.png"
25
+ })
26
+ end
27
+
28
+ teardown do
29
+ Paperclip::Attachment.default_options.merge! @old_default_options
30
+ end
31
+
32
+ should "be overrideable" do
33
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
34
+ @new_default_options.keys.each do |key|
35
+ assert_equal @new_default_options[key],
36
+ Paperclip::Attachment.default_options[key]
37
+ end
38
+ end
39
+
40
+ context "without an Attachment" do
41
+ setup do
42
+ @dummy = Dummy.new
43
+ end
44
+
45
+ should "return false when asked exists?" do
46
+ assert !@dummy.avatar.exists?
47
+ end
48
+ end
49
+
50
+ context "on an Attachment" do
51
+ setup do
52
+ @dummy = Dummy.new
53
+ @attachment = @dummy.avatar
54
+ end
55
+
56
+ Paperclip::Attachment.default_options.keys.each do |key|
57
+ should "be the default_options for #{key}" do
58
+ assert_equal @old_default_options[key],
59
+ @attachment.instance_variable_get("@#{key}"),
60
+ key
61
+ end
62
+ end
63
+
64
+ context "when redefined" do
65
+ setup do
66
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
67
+ @dummy = Dummy.new
68
+ @attachment = @dummy.avatar
69
+ end
70
+
71
+ Paperclip::Attachment.default_options.keys.each do |key|
72
+ should "be the new default_options for #{key}" do
73
+ assert_equal @new_default_options[key],
74
+ @attachment.instance_variable_get("@#{key}"),
75
+ key
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ context "An attachment with similarly named interpolations" do
83
+ setup do
84
+ rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
85
+ @dummy = Dummy.new
86
+ @dummy.stubs(:id).returns(1024)
87
+ @file = File.new(File.join(File.dirname(__FILE__),
88
+ "fixtures",
89
+ "5k.png"), 'rb')
90
+ @dummy.avatar = @file
91
+ end
92
+
93
+ teardown { @file.close }
94
+
95
+ should "make sure that they are interpolated correctly" do
96
+ assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
97
+ end
98
+ end
99
+
100
+ context "An attachment with a :rails_env interpolation" do
101
+ setup do
102
+ @rails_env = "blah"
103
+ @id = 1024
104
+ rebuild_model :path => ":rails_env/:id.png"
105
+ @dummy = Dummy.new
106
+ @dummy.stubs(:id).returns(@id)
107
+ @file = StringIO.new(".")
108
+ @dummy.avatar = @file
109
+ Rails.stubs(:env).returns(@rails_env)
110
+ end
111
+
112
+ should "return the proper path" do
113
+ assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
114
+ end
115
+ end
116
+
117
+ context "An attachment with a default style and an extension interpolation" do
118
+ setup do
119
+ @attachment = attachment :path => ":basename.:extension",
120
+ :styles => { :default => ["100x100", :png] },
121
+ :default_style => :default
122
+ @file = StringIO.new("...")
123
+ @file.stubs(:original_filename).returns("file.jpg")
124
+ end
125
+ should "return the right extension for the path" do
126
+ @attachment.assign(@file)
127
+ assert_equal "file.png", @attachment.path
128
+ end
129
+ end
130
+
131
+ context "An attachment with :convert_options" do
132
+ setup do
133
+ rebuild_model :styles => {
134
+ :thumb => "100x100",
135
+ :large => "400x400"
136
+ },
137
+ :convert_options => {
138
+ :all => "-do_stuff",
139
+ :thumb => "-thumbnailize"
140
+ }
141
+ @dummy = Dummy.new
142
+ @dummy.avatar
143
+ end
144
+
145
+ should "report the correct options when sent #extra_options_for(:thumb)" do
146
+ assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
147
+ end
148
+
149
+ should "report the correct options when sent #extra_options_for(:large)" do
150
+ assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large)
151
+ end
152
+ end
153
+
154
+ context "An attachment with :convert_options that is a proc" do
155
+ setup do
156
+ rebuild_model :styles => {
157
+ :thumb => "100x100",
158
+ :large => "400x400"
159
+ },
160
+ :convert_options => {
161
+ :all => lambda{|i| i.all },
162
+ :thumb => lambda{|i| i.thumb }
163
+ }
164
+ Dummy.class_eval do
165
+ def all; "-all"; end
166
+ def thumb; "-thumb"; end
167
+ end
168
+ @dummy = Dummy.new
169
+ @dummy.avatar
170
+ end
171
+
172
+ should "report the correct options when sent #extra_options_for(:thumb)" do
173
+ assert_equal "-thumb -all", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
174
+ end
175
+
176
+ should "report the correct options when sent #extra_options_for(:large)" do
177
+ assert_equal "-all", @dummy.avatar.send(:extra_options_for, :large)
178
+ end
179
+ end
180
+
181
+ context "An attachment with :path that is a proc" do
182
+ setup do
183
+ rebuild_model :path => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
184
+
185
+ @file = File.new(File.join(File.dirname(__FILE__),
186
+ "fixtures",
187
+ "5k.png"), 'rb')
188
+ @dummyA = Dummy.new(:other => 'a')
189
+ @dummyA.avatar = @file
190
+ @dummyB = Dummy.new(:other => 'b')
191
+ @dummyB.avatar = @file
192
+ end
193
+
194
+ teardown { @file.close }
195
+
196
+ should "return correct path" do
197
+ assert_equal "path/a.png", @dummyA.avatar.path
198
+ assert_equal "path/b.png", @dummyB.avatar.path
199
+ end
200
+ end
201
+
202
+ context "An attachment with :styles that is a proc" do
203
+ setup do
204
+ rebuild_model :styles => lambda{ |attachment| {:thumb => "50x50#", :large => "400x400"} }
205
+
206
+ @attachment = Dummy.new.avatar
207
+ end
208
+
209
+ should "have the correct geometry" do
210
+ assert_equal "50x50#", @attachment.styles[:thumb][:geometry]
211
+ end
212
+ end
213
+
214
+ context "An attachment with :url that is a proc" do
215
+ setup do
216
+ rebuild_model :url => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
217
+
218
+ @file = File.new(File.join(File.dirname(__FILE__),
219
+ "fixtures",
220
+ "5k.png"), 'rb')
221
+ @dummyA = Dummy.new(:other => 'a')
222
+ @dummyA.avatar = @file
223
+ @dummyB = Dummy.new(:other => 'b')
224
+ @dummyB.avatar = @file
225
+ end
226
+
227
+ teardown { @file.close }
228
+
229
+ should "return correct url" do
230
+ assert_equal "path/a.png", @dummyA.avatar.url(:original, false)
231
+ assert_equal "path/b.png", @dummyB.avatar.url(:original, false)
232
+ end
233
+ end
234
+
235
+ geometry_specs = [
236
+ [ lambda{|z| "50x50#" }, :png ],
237
+ lambda{|z| "50x50#" },
238
+ { :geometry => lambda{|z| "50x50#" } }
239
+ ]
240
+ geometry_specs.each do |geometry_spec|
241
+ context "An attachment geometry like #{geometry_spec}" do
242
+ setup do
243
+ rebuild_model :styles => { :normal => geometry_spec }
244
+ @attachment = Dummy.new.avatar
245
+ end
246
+
247
+ context "when assigned" do
248
+ setup do
249
+ @file = StringIO.new(".")
250
+ @attachment.assign(@file)
251
+ end
252
+
253
+ should "have the correct geometry" do
254
+ assert_equal "50x50#", @attachment.styles[:normal][:geometry]
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ context "An attachment with both 'normal' and hash-style styles" do
261
+ setup do
262
+ rebuild_model :styles => {
263
+ :normal => ["50x50#", :png],
264
+ :hash => { :geometry => "50x50#", :format => :png }
265
+ }
266
+ @dummy = Dummy.new
267
+ @attachment = @dummy.avatar
268
+ end
269
+
270
+ [:processors, :whiny, :convert_options, :geometry, :format].each do |field|
271
+ should "have the same #{field} field" do
272
+ assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field]
273
+ end
274
+ end
275
+ end
276
+
277
+ context "An attachment with :processors that is a proc" do
278
+ setup do
279
+ rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] }
280
+ @attachment = Dummy.new.avatar
281
+ end
282
+
283
+ context "when assigned" do
284
+ setup do
285
+ @attachment.assign(StringIO.new("."))
286
+ end
287
+
288
+ should "have the correct processors" do
289
+ assert_equal [ :test ], @attachment.styles[:normal][:processors]
290
+ end
291
+ end
292
+ end
293
+
294
+ context "An attachment with erroring processor" do
295
+ setup do
296
+ rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true
297
+ @dummy = Dummy.new
298
+ Paperclip::Thumbnail.expects(:make).raises(Paperclip::PaperclipError, "cannot be processed.")
299
+ @file = StringIO.new("...")
300
+ @file.stubs(:to_tempfile).returns(@file)
301
+ @dummy.avatar = @file
302
+ end
303
+
304
+ should "correctly forward processing error message to the instance" do
305
+ @dummy.valid?
306
+ assert_contains @dummy.errors.full_messages, "Avatar cannot be processed."
307
+ end
308
+ end
309
+
310
+ context "An attachment with multiple processors" do
311
+ setup do
312
+ class Paperclip::Test < Paperclip::Processor; end
313
+ @style_params = { :once => {:one => 1, :two => 2} }
314
+ rebuild_model :processors => [:thumbnail, :test], :styles => @style_params
315
+ @dummy = Dummy.new
316
+ @file = StringIO.new("...")
317
+ @file.stubs(:to_tempfile).returns(@file)
318
+ Paperclip::Test.stubs(:make).returns(@file)
319
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
320
+ end
321
+
322
+ context "when assigned" do
323
+ setup { @dummy.avatar = @file }
324
+
325
+ before_should "call #make on all specified processors" do
326
+ Paperclip::Thumbnail.expects(:make).with(any_parameters).returns(@file)
327
+ Paperclip::Test.expects(:make).with(any_parameters).returns(@file)
328
+ end
329
+
330
+ before_should "call #make with the right parameters passed as second argument" do
331
+ expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
332
+ Paperclip::Thumbnail.expects(:make).with(anything, expected_params, anything).returns(@file)
333
+ end
334
+
335
+ before_should "call #make with attachment passed as third argument" do
336
+ Paperclip::Test.expects(:make).with(anything, anything, @dummy.avatar).returns(@file)
337
+ end
338
+ end
339
+ end
340
+
341
+ context "An attachment with styles but no processors defined" do
342
+ setup do
343
+ rebuild_model :processors => [], :styles => {:something => 1}
344
+ @dummy = Dummy.new
345
+ @file = StringIO.new("...")
346
+ end
347
+ should "raise when assigned to" do
348
+ assert_raises(RuntimeError){ @dummy.avatar = @file }
349
+ end
350
+ end
351
+
352
+ context "An attachment without styles and with no processors defined" do
353
+ setup do
354
+ rebuild_model :processors => [], :styles => {}
355
+ @dummy = Dummy.new
356
+ @file = StringIO.new("...")
357
+ end
358
+ should "not raise when assigned to" do
359
+ @dummy.avatar = @file
360
+ end
361
+ end
362
+
363
+ context "Assigning an attachment with post_process hooks" do
364
+ setup do
365
+ rebuild_class :styles => { :something => "100x100#" }
366
+ Dummy.class_eval do
367
+ before_avatar_post_process :do_before_avatar
368
+ after_avatar_post_process :do_after_avatar
369
+ before_post_process :do_before_all
370
+ after_post_process :do_after_all
371
+ def do_before_avatar; end
372
+ def do_after_avatar; end
373
+ def do_before_all; end
374
+ def do_after_all; end
375
+ end
376
+ @file = StringIO.new(".")
377
+ @file.stubs(:to_tempfile).returns(@file)
378
+ @dummy = Dummy.new
379
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
380
+ @attachment = @dummy.avatar
381
+ end
382
+
383
+ should "call the defined callbacks when assigned" do
384
+ @dummy.expects(:do_before_avatar).with()
385
+ @dummy.expects(:do_after_avatar).with()
386
+ @dummy.expects(:do_before_all).with()
387
+ @dummy.expects(:do_after_all).with()
388
+ Paperclip::Thumbnail.expects(:make).returns(@file)
389
+ @dummy.avatar = @file
390
+ end
391
+
392
+ should "not cancel the processing if a before_post_process returns nil" do
393
+ @dummy.expects(:do_before_avatar).with().returns(nil)
394
+ @dummy.expects(:do_after_avatar).with()
395
+ @dummy.expects(:do_before_all).with().returns(nil)
396
+ @dummy.expects(:do_after_all).with()
397
+ Paperclip::Thumbnail.expects(:make).returns(@file)
398
+ @dummy.avatar = @file
399
+ end
400
+
401
+ should "cancel the processing if a before_post_process returns false" do
402
+ @dummy.expects(:do_before_avatar).never
403
+ @dummy.expects(:do_after_avatar).never
404
+ @dummy.expects(:do_before_all).with().returns(false)
405
+ @dummy.expects(:do_after_all)
406
+ Paperclip::Thumbnail.expects(:make).never
407
+ @dummy.avatar = @file
408
+ end
409
+
410
+ should "cancel the processing if a before_avatar_post_process returns false" do
411
+ @dummy.expects(:do_before_avatar).with().returns(false)
412
+ @dummy.expects(:do_after_avatar)
413
+ @dummy.expects(:do_before_all).with().returns(true)
414
+ @dummy.expects(:do_after_all)
415
+ Paperclip::Thumbnail.expects(:make).never
416
+ @dummy.avatar = @file
417
+ end
418
+ end
419
+
420
+ context "Assigning an attachment" do
421
+ setup do
422
+ rebuild_model :styles => { :something => "100x100#" }
423
+ @file = StringIO.new(".")
424
+ @file.stubs(:original_filename).returns("5k.png\n\n")
425
+ @file.stubs(:content_type).returns("image/png\n\n")
426
+ @file.stubs(:to_tempfile).returns(@file)
427
+ @dummy = Dummy.new
428
+ Paperclip::Thumbnail.expects(:make).returns(@file)
429
+ @attachment = @dummy.avatar
430
+ @dummy.avatar = @file
431
+ end
432
+
433
+ should "strip whitespace from original_filename field" do
434
+ assert_equal "5k.png", @dummy.avatar.original_filename
435
+ end
436
+
437
+ should "strip whitespace from content_type field" do
438
+ assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
439
+ end
440
+ end
441
+
442
+ context "Attachment with strange letters" do
443
+ setup do
444
+ rebuild_model
445
+
446
+ @not_file = mock
447
+ @tempfile = mock
448
+ @not_file.stubs(:nil?).returns(false)
449
+ @not_file.expects(:size).returns(10)
450
+ @tempfile.expects(:size).returns(10)
451
+ @not_file.expects(:to_tempfile).returns(@tempfile)
452
+ @not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n")
453
+ @not_file.expects(:content_type).returns("image/png\r\n")
454
+
455
+ @dummy = Dummy.new
456
+ @attachment = @dummy.avatar
457
+ @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
458
+ @attachment.expects(:queue_existing_for_delete)
459
+ @attachment.expects(:post_process)
460
+ @dummy.avatar = @not_file
461
+ end
462
+
463
+ should "not remove strange letters" do
464
+ assert_equal "sheep_say_bæ.png", @dummy.avatar.original_filename
465
+ end
466
+ end
467
+
468
+ context "An attachment" do
469
+ setup do
470
+ @old_defaults = Paperclip::Attachment.default_options.dup
471
+ Paperclip::Attachment.default_options.merge!({
472
+ :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
473
+ })
474
+ FileUtils.rm_rf("tmp")
475
+ rebuild_model
476
+ @instance = Dummy.new
477
+ @instance.stubs(:id).returns 123
478
+ @attachment = Paperclip::Attachment.new(:avatar, @instance)
479
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
480
+ end
481
+
482
+ teardown do
483
+ @file.close
484
+ Paperclip::Attachment.default_options.merge!(@old_defaults)
485
+ end
486
+
487
+ should "raise if there are not the correct columns when you try to assign" do
488
+ @other_attachment = Paperclip::Attachment.new(:not_here, @instance)
489
+ assert_raises(Paperclip::PaperclipError) do
490
+ @other_attachment.assign(@file)
491
+ end
492
+ end
493
+
494
+ should "return its default_url when no file assigned" do
495
+ assert @attachment.to_file.nil?
496
+ assert_equal "/avatars/original/missing.png", @attachment.url
497
+ assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
498
+ end
499
+
500
+ should "return nil as path when no file assigned" do
501
+ assert @attachment.to_file.nil?
502
+ assert_equal nil, @attachment.path
503
+ assert_equal nil, @attachment.path(:blah)
504
+ end
505
+
506
+ context "with a file assigned in the database" do
507
+ setup do
508
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.png")
509
+ @attachment.stubs(:instance_read).with(:content_type).returns("image/png")
510
+ @attachment.stubs(:instance_read).with(:file_size).returns(12345)
511
+ dtnow = DateTime.now
512
+ @now = Time.now
513
+ Time.stubs(:now).returns(@now)
514
+ @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
515
+ end
516
+
517
+ should "return a correct url even if the file does not exist" do
518
+ assert_nil @attachment.to_file
519
+ assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
520
+ end
521
+
522
+ should "make sure the updated_at mtime is in the url if it is defined" do
523
+ assert_match %r{#{@now.to_i}$}, @attachment.url(:blah)
524
+ end
525
+
526
+ should "make sure the updated_at mtime is NOT in the url if false is passed to the url method" do
527
+ assert_no_match %r{#{@now.to_i}$}, @attachment.url(:blah, false)
528
+ end
529
+
530
+ context "with the updated_at field removed" do
531
+ setup do
532
+ @attachment.stubs(:instance_read).with(:updated_at).returns(nil)
533
+ end
534
+
535
+ should "only return the url without the updated_at when sent #url" do
536
+ assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
537
+ end
538
+ end
539
+
540
+ should "return the proper path when filename has a single .'s" do
541
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path)
542
+ end
543
+
544
+ should "return the proper path when filename has multiple .'s" do
545
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.old.png")
546
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.old.png"), File.expand_path(@attachment.path)
547
+ end
548
+
549
+ context "when expecting three styles" do
550
+ setup do
551
+ styles = {:styles => { :large => ["400x400", :png],
552
+ :medium => ["100x100", :gif],
553
+ :small => ["32x32#", :jpg]}}
554
+ @attachment = Paperclip::Attachment.new(:avatar,
555
+ @instance,
556
+ styles)
557
+ end
558
+
559
+ context "and assigned a file" do
560
+ setup do
561
+ now = Time.now
562
+ Time.stubs(:now).returns(now)
563
+ @attachment.assign(@file)
564
+ end
565
+
566
+ should "be dirty" do
567
+ assert @attachment.dirty?
568
+ end
569
+
570
+ context "and saved" do
571
+ setup do
572
+ @attachment.save
573
+ end
574
+
575
+ should "return the real url" do
576
+ file = @attachment.to_file
577
+ assert file
578
+ assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
579
+ assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
580
+ file.close
581
+ end
582
+
583
+ should "commit the files to disk" do
584
+ [:large, :medium, :small].each do |style|
585
+ io = @attachment.to_file(style)
586
+ # p "in commit to disk test, io is #{io.inspect} and @instance.id is #{@instance.id}"
587
+ assert File.exists?(io)
588
+ assert ! io.is_a?(::Tempfile)
589
+ io.close
590
+ end
591
+ end
592
+
593
+ should "save the files as the right formats and sizes" do
594
+ [[:large, 400, 61, "PNG"],
595
+ [:medium, 100, 15, "GIF"],
596
+ [:small, 32, 32, "JPEG"]].each do |style|
597
+ cmd = %Q[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"]
598
+ out = `#{cmd}`
599
+ width, height, size, format = out.split(" ")
600
+ assert_equal style[1].to_s, width.to_s
601
+ assert_equal style[2].to_s, height.to_s
602
+ assert_equal style[3].to_s, format.to_s
603
+ end
604
+ end
605
+
606
+ should "still have its #file attribute not be nil" do
607
+ assert ! (file = @attachment.to_file).nil?
608
+ file.close
609
+ end
610
+
611
+ context "and trying to delete" do
612
+ setup do
613
+ @existing_names = @attachment.styles.keys.collect do |style|
614
+ @attachment.path(style)
615
+ end
616
+ end
617
+
618
+ should "delete the files after assigning nil" do
619
+ @attachment.expects(:instance_write).with(:file_name, nil)
620
+ @attachment.expects(:instance_write).with(:content_type, nil)
621
+ @attachment.expects(:instance_write).with(:file_size, nil)
622
+ @attachment.expects(:instance_write).with(:updated_at, nil)
623
+ @attachment.assign nil
624
+ @attachment.save
625
+ @existing_names.each{|f| assert ! File.exists?(f) }
626
+ end
627
+
628
+ should "delete the files when you call #clear and #save" do
629
+ @attachment.expects(:instance_write).with(:file_name, nil)
630
+ @attachment.expects(:instance_write).with(:content_type, nil)
631
+ @attachment.expects(:instance_write).with(:file_size, nil)
632
+ @attachment.expects(:instance_write).with(:updated_at, nil)
633
+ @attachment.clear
634
+ @attachment.save
635
+ @existing_names.each{|f| assert ! File.exists?(f) }
636
+ end
637
+
638
+ should "delete the files when you call #delete" do
639
+ @attachment.expects(:instance_write).with(:file_name, nil)
640
+ @attachment.expects(:instance_write).with(:content_type, nil)
641
+ @attachment.expects(:instance_write).with(:file_size, nil)
642
+ @attachment.expects(:instance_write).with(:updated_at, nil)
643
+ @attachment.destroy
644
+ @existing_names.each{|f| assert ! File.exists?(f) }
645
+ end
646
+ end
647
+ end
648
+ end
649
+ end
650
+
651
+ end
652
+
653
+ context "when trying a nonexistant storage type" do
654
+ setup do
655
+ rebuild_model :storage => :not_here
656
+ end
657
+
658
+ should "not be able to find the module" do
659
+ assert_raise(NameError){ Dummy.new.avatar }
660
+ end
661
+ end
662
+ end
663
+
664
+ context "An attachment with only a avatar_file_name column" do
665
+ setup do
666
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
667
+ table.column :avatar_file_name, :string
668
+ end
669
+ rebuild_class
670
+ @dummy = Dummy.new
671
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
672
+ end
673
+
674
+ teardown { @file.close }
675
+
676
+ should "not error when assigned an attachment" do
677
+ assert_nothing_raised { @dummy.avatar = @file }
678
+ end
679
+
680
+ should "return the time when sent #avatar_updated_at" do
681
+ now = Time.now
682
+ Time.stubs(:now).returns(now)
683
+ @dummy.avatar = @file
684
+ assert now, @dummy.avatar.updated_at
685
+ end
686
+
687
+ should "return nil when reloaded and sent #avatar_updated_at" do
688
+ @dummy.save
689
+ @dummy.reload
690
+ assert_nil @dummy.avatar.updated_at
691
+ end
692
+
693
+ should "return the right value when sent #avatar_file_size" do
694
+ @dummy.avatar = @file
695
+ assert_equal @file.size, @dummy.avatar.size
696
+ end
697
+
698
+ context "and avatar_updated_at column" do
699
+ setup do
700
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp
701
+ rebuild_class
702
+ @dummy = Dummy.new
703
+ end
704
+
705
+ should "not error when assigned an attachment" do
706
+ assert_nothing_raised { @dummy.avatar = @file }
707
+ end
708
+
709
+ should "return the right value when sent #avatar_updated_at" do
710
+ now = Time.now
711
+ Time.stubs(:now).returns(now)
712
+ @dummy.avatar = @file
713
+ assert_equal now.to_i, @dummy.avatar.updated_at
714
+ end
715
+ end
716
+
717
+ context "and avatar_content_type column" do
718
+ setup do
719
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string
720
+ rebuild_class
721
+ @dummy = Dummy.new
722
+ end
723
+
724
+ should "not error when assigned an attachment" do
725
+ assert_nothing_raised { @dummy.avatar = @file }
726
+ end
727
+
728
+ should "return the right value when sent #avatar_content_type" do
729
+ @dummy.avatar = @file
730
+ assert_equal "image/png", @dummy.avatar.content_type
731
+ end
732
+ end
733
+
734
+ context "and avatar_file_size column" do
735
+ setup do
736
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :integer
737
+ rebuild_class
738
+ @dummy = Dummy.new
739
+ end
740
+
741
+ should "not error when assigned an attachment" do
742
+ assert_nothing_raised { @dummy.avatar = @file }
743
+ end
744
+
745
+ should "return the right value when sent #avatar_file_size" do
746
+ @dummy.avatar = @file
747
+ assert_equal @file.size, @dummy.avatar.size
748
+ end
749
+
750
+ should "return the right value when saved, reloaded, and sent #avatar_file_size" do
751
+ @dummy.avatar = @file
752
+ @dummy.save
753
+ @dummy = Dummy.find(@dummy.id)
754
+ assert_equal @file.size, @dummy.avatar.size
755
+ end
756
+ end
757
+ end
758
+ end