alg-paperclip 2.3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/LICENSE +26 -0
  2. data/README.rdoc +195 -0
  3. data/Rakefile +103 -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/paperclip.rb +357 -0
  9. data/lib/paperclip/attachment.rb +345 -0
  10. data/lib/paperclip/callback_compatability.rb +33 -0
  11. data/lib/paperclip/geometry.rb +115 -0
  12. data/lib/paperclip/interpolations.rb +120 -0
  13. data/lib/paperclip/iostream.rb +59 -0
  14. data/lib/paperclip/matchers.rb +4 -0
  15. data/lib/paperclip/matchers/have_attached_file_matcher.rb +49 -0
  16. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +65 -0
  17. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +48 -0
  18. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +85 -0
  19. data/lib/paperclip/processor.rb +49 -0
  20. data/lib/paperclip/storage.rb +247 -0
  21. data/lib/paperclip/style.rb +90 -0
  22. data/lib/paperclip/thumbnail.rb +75 -0
  23. data/lib/paperclip/upfile.rb +49 -0
  24. data/shoulda_macros/paperclip.rb +117 -0
  25. data/tasks/paperclip_tasks.rake +79 -0
  26. data/test/attachment_test.rb +788 -0
  27. data/test/database.yml +4 -0
  28. data/test/fixtures/12k.png +0 -0
  29. data/test/fixtures/50x50.png +0 -0
  30. data/test/fixtures/5k.png +0 -0
  31. data/test/fixtures/bad.png +1 -0
  32. data/test/fixtures/s3.yml +8 -0
  33. data/test/fixtures/text.txt +0 -0
  34. data/test/fixtures/twopage.pdf +0 -0
  35. data/test/geometry_test.rb +177 -0
  36. data/test/helper.rb +108 -0
  37. data/test/integration_test.rb +483 -0
  38. data/test/interpolations_test.rb +124 -0
  39. data/test/iostream_test.rb +78 -0
  40. data/test/matchers/have_attached_file_matcher_test.rb +21 -0
  41. data/test/matchers/validate_attachment_content_type_matcher_test.rb +31 -0
  42. data/test/matchers/validate_attachment_presence_matcher_test.rb +23 -0
  43. data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
  44. data/test/paperclip_test.rb +298 -0
  45. data/test/processor_test.rb +10 -0
  46. data/test/storage_test.rb +330 -0
  47. data/test/style_test.rb +141 -0
  48. data/test/thumbnail_test.rb +227 -0
  49. data/test/upfile_test.rb +28 -0
  50. metadata +164 -0
@@ -0,0 +1,117 @@
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
+ class ActionController::Integration::Session #:nodoc:
108
+ include Paperclip::Shoulda
109
+ end
110
+
111
+ class Factory
112
+ include Paperclip::Shoulda #:nodoc:
113
+ end
114
+
115
+ class Test::Unit::TestCase #:nodoc:
116
+ extend Paperclip::Shoulda
117
+ 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
@@ -0,0 +1,788 @@
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
+ should "return the path based on custom parameter" do
18
+ @attachment = attachment :url => "/:class/:custom_param", :tags => { :custom_param => lambda { |attachment, style_name| "sample" } }
19
+ @model = @attachment.instance
20
+ @model.avatar_file_name = "fake.jpg"
21
+
22
+ assert_equal "#{RAILS_ROOT}/public/fake_models/sample", @attachment.path
23
+ end
24
+
25
+ context "Attachment default_options" do
26
+ setup do
27
+ rebuild_model
28
+ @old_default_options = Paperclip::Attachment.default_options.dup
29
+ @new_default_options = @old_default_options.merge({
30
+ :path => "argle/bargle",
31
+ :url => "fooferon",
32
+ :default_url => "not here.png"
33
+ })
34
+ end
35
+
36
+ teardown do
37
+ Paperclip::Attachment.default_options.merge! @old_default_options
38
+ end
39
+
40
+ should "be overrideable" do
41
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
42
+ @new_default_options.keys.each do |key|
43
+ assert_equal @new_default_options[key],
44
+ Paperclip::Attachment.default_options[key]
45
+ end
46
+ end
47
+
48
+ context "without an Attachment" do
49
+ setup do
50
+ @dummy = Dummy.new
51
+ end
52
+
53
+ should "return false when asked exists?" do
54
+ assert !@dummy.avatar.exists?
55
+ end
56
+ end
57
+
58
+ context "on an Attachment" do
59
+ setup do
60
+ @dummy = Dummy.new
61
+ @attachment = @dummy.avatar
62
+ end
63
+
64
+ Paperclip::Attachment.default_options.keys.each do |key|
65
+ should "be the default_options for #{key}" do
66
+ assert_equal @old_default_options[key],
67
+ @attachment.instance_variable_get("@#{key}"),
68
+ key
69
+ end
70
+ end
71
+
72
+ context "when redefined" do
73
+ setup do
74
+ Paperclip::Attachment.default_options.merge!(@new_default_options)
75
+ @dummy = Dummy.new
76
+ @attachment = @dummy.avatar
77
+ end
78
+
79
+ Paperclip::Attachment.default_options.keys.each do |key|
80
+ should "be the new default_options for #{key}" do
81
+ assert_equal @new_default_options[key],
82
+ @attachment.instance_variable_get("@#{key}"),
83
+ key
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ context "An attachment with similarly named interpolations" do
91
+ setup do
92
+ rebuild_model :path => ":id.omg/:id-bbq/:idwhat/:id_partition.wtf"
93
+ @dummy = Dummy.new
94
+ @dummy.stubs(:id).returns(1024)
95
+ @file = File.new(File.join(File.dirname(__FILE__),
96
+ "fixtures",
97
+ "5k.png"), 'rb')
98
+ @dummy.avatar = @file
99
+ end
100
+
101
+ teardown { @file.close }
102
+
103
+ should "make sure that they are interpolated correctly" do
104
+ assert_equal "1024.omg/1024-bbq/1024what/000/001/024.wtf", @dummy.avatar.path
105
+ end
106
+ end
107
+
108
+ context "An attachment with a :rails_env interpolation" do
109
+ setup do
110
+ @rails_env = "blah"
111
+ @id = 1024
112
+ rebuild_model :path => ":rails_env/:id.png"
113
+ @dummy = Dummy.new
114
+ @dummy.stubs(:id).returns(@id)
115
+ @file = StringIO.new(".")
116
+ @dummy.avatar = @file
117
+ end
118
+
119
+ should "return the proper path" do
120
+ temporary_rails_env(@rails_env) {
121
+ assert_equal "#{@rails_env}/#{@id}.png", @dummy.avatar.path
122
+ }
123
+ end
124
+ end
125
+
126
+ context "An attachment with a default style and an extension interpolation" do
127
+ setup do
128
+ @attachment = attachment :path => ":basename.:extension",
129
+ :styles => { :default => ["100x100", :png] },
130
+ :default_style => :default
131
+ @file = StringIO.new("...")
132
+ @file.stubs(:original_filename).returns("file.jpg")
133
+ end
134
+ should "return the right extension for the path" do
135
+ @attachment.assign(@file)
136
+ assert_equal "file.png", @attachment.path
137
+ end
138
+ end
139
+
140
+ context "An attachment with :convert_options" do
141
+ setup do
142
+ rebuild_model :styles => {
143
+ :thumb => "100x100",
144
+ :large => "400x400"
145
+ },
146
+ :convert_options => {
147
+ :all => "-do_stuff",
148
+ :thumb => "-thumbnailize"
149
+ }
150
+ @dummy = Dummy.new
151
+ @dummy.avatar
152
+ end
153
+
154
+ should "report the correct options when sent #extra_options_for(:thumb)" do
155
+ assert_equal "-thumbnailize -do_stuff", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
156
+ end
157
+
158
+ should "report the correct options when sent #extra_options_for(:large)" do
159
+ assert_equal "-do_stuff", @dummy.avatar.send(:extra_options_for, :large)
160
+ end
161
+ end
162
+
163
+ context "An attachment with :convert_options that is a proc" do
164
+ setup do
165
+ rebuild_model :styles => {
166
+ :thumb => "100x100",
167
+ :large => "400x400"
168
+ },
169
+ :convert_options => {
170
+ :all => lambda{|i| i.all },
171
+ :thumb => lambda{|i| i.thumb }
172
+ }
173
+ Dummy.class_eval do
174
+ def all; "-all"; end
175
+ def thumb; "-thumb"; end
176
+ end
177
+ @dummy = Dummy.new
178
+ @dummy.avatar
179
+ end
180
+
181
+ should "report the correct options when sent #extra_options_for(:thumb)" do
182
+ assert_equal "-thumb -all", @dummy.avatar.send(:extra_options_for, :thumb), @dummy.avatar.convert_options.inspect
183
+ end
184
+
185
+ should "report the correct options when sent #extra_options_for(:large)" do
186
+ assert_equal "-all", @dummy.avatar.send(:extra_options_for, :large)
187
+ end
188
+ end
189
+
190
+ context "An attachment with :path that is a proc" do
191
+ setup do
192
+ rebuild_model :path => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
193
+
194
+ @file = File.new(File.join(File.dirname(__FILE__),
195
+ "fixtures",
196
+ "5k.png"), 'rb')
197
+ @dummyA = Dummy.new(:other => 'a')
198
+ @dummyA.avatar = @file
199
+ @dummyB = Dummy.new(:other => 'b')
200
+ @dummyB.avatar = @file
201
+ end
202
+
203
+ teardown { @file.close }
204
+
205
+ should "return correct path" do
206
+ assert_equal "path/a.png", @dummyA.avatar.path
207
+ assert_equal "path/b.png", @dummyB.avatar.path
208
+ end
209
+ end
210
+
211
+ context "An attachment with :styles that is a proc" do
212
+ setup do
213
+ rebuild_model :styles => lambda{ |attachment| {:thumb => "50x50#", :large => "400x400"} }
214
+
215
+ @attachment = Dummy.new.avatar
216
+ end
217
+
218
+ should "have the correct geometry" do
219
+ assert_equal "50x50#", @attachment.styles[:thumb][:geometry]
220
+ end
221
+ end
222
+
223
+ context "An attachment with :url that is a proc" do
224
+ setup do
225
+ rebuild_model :url => lambda{ |attachment| "path/#{attachment.instance.other}.:extension" }
226
+
227
+ @file = File.new(File.join(File.dirname(__FILE__),
228
+ "fixtures",
229
+ "5k.png"), 'rb')
230
+ @dummyA = Dummy.new(:other => 'a')
231
+ @dummyA.avatar = @file
232
+ @dummyB = Dummy.new(:other => 'b')
233
+ @dummyB.avatar = @file
234
+ end
235
+
236
+ teardown { @file.close }
237
+
238
+ should "return correct url" do
239
+ assert_equal "path/a.png", @dummyA.avatar.url(:original, false)
240
+ assert_equal "path/b.png", @dummyB.avatar.url(:original, false)
241
+ end
242
+ end
243
+
244
+ context "An image attachment" do
245
+ setup do
246
+ @instance = Dummy.new
247
+ @instance.stubs(:id).returns(123)
248
+ @instance.stubs(:avatar_file_name).returns("5k.png")
249
+
250
+ path = File.join(File.dirname(__FILE__), "fixtures", "5k.png")
251
+ @attachment = Paperclip::Attachment.new(:avatar, @instance, :path => path)
252
+ end
253
+
254
+ should "return width and height" do
255
+ assert_equal 434, @attachment.width(:original)
256
+ assert_equal 66, @attachment.height(:original)
257
+ end
258
+ end
259
+
260
+ geometry_specs = [
261
+ [ lambda{|z| "50x50#" }, :png ],
262
+ lambda{|z| "50x50#" },
263
+ { :geometry => lambda{|z| "50x50#" } }
264
+ ]
265
+ geometry_specs.each do |geometry_spec|
266
+ context "An attachment geometry like #{geometry_spec}" do
267
+ setup do
268
+ rebuild_model :styles => { :normal => geometry_spec }
269
+ @attachment = Dummy.new.avatar
270
+ end
271
+
272
+ context "when assigned" do
273
+ setup do
274
+ @file = StringIO.new(".")
275
+ @attachment.assign(@file)
276
+ end
277
+
278
+ should "have the correct geometry" do
279
+ assert_equal "50x50#", @attachment.styles[:normal][:geometry]
280
+ end
281
+ end
282
+ end
283
+ end
284
+
285
+ context "An attachment with both 'normal' and hash-style styles" do
286
+ setup do
287
+ rebuild_model :styles => {
288
+ :normal => ["50x50#", :png],
289
+ :hash => { :geometry => "50x50#", :format => :png }
290
+ }
291
+ @dummy = Dummy.new
292
+ @attachment = @dummy.avatar
293
+ end
294
+
295
+ [:processors, :whiny, :convert_options, :geometry, :format].each do |field|
296
+ should "have the same #{field} field" do
297
+ assert_equal @attachment.styles[:normal][field], @attachment.styles[:hash][field]
298
+ end
299
+ end
300
+ end
301
+
302
+ context "An attachment with :processors that is a proc" do
303
+ setup do
304
+ rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] }
305
+ @attachment = Dummy.new.avatar
306
+ end
307
+
308
+ context "when assigned" do
309
+ setup do
310
+ @attachment.assign(StringIO.new("."))
311
+ end
312
+
313
+ should "have the correct processors" do
314
+ assert_equal [ :test ], @attachment.styles[:normal][:processors]
315
+ end
316
+ end
317
+ end
318
+
319
+ context "An attachment with erroring processor" do
320
+ setup do
321
+ rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true
322
+ @dummy = Dummy.new
323
+ Paperclip::Thumbnail.expects(:make).raises(Paperclip::PaperclipError, "cannot be processed.")
324
+ @file = StringIO.new("...")
325
+ @file.stubs(:to_tempfile).returns(@file)
326
+ @dummy.avatar = @file
327
+ end
328
+
329
+ should "correctly forward processing error message to the instance" do
330
+ @dummy.valid?
331
+ assert_contains @dummy.errors.full_messages, "Avatar cannot be processed."
332
+ end
333
+ end
334
+
335
+ context "An attachment with multiple processors" do
336
+ setup do
337
+ class Paperclip::Test < Paperclip::Processor; end
338
+ @style_params = { :once => {:one => 1, :two => 2} }
339
+ rebuild_model :processors => [:thumbnail, :test], :styles => @style_params
340
+ @dummy = Dummy.new
341
+ @file = StringIO.new("...")
342
+ @file.stubs(:to_tempfile).returns(@file)
343
+ Paperclip::Test.stubs(:make).returns(@file)
344
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
345
+ end
346
+
347
+ context "when assigned" do
348
+ setup { @dummy.avatar = @file }
349
+
350
+ before_should "call #make on all specified processors" do
351
+ Paperclip::Thumbnail.expects(:make).with(any_parameters).returns(@file)
352
+ Paperclip::Test.expects(:make).with(any_parameters).returns(@file)
353
+ end
354
+
355
+ before_should "call #make with the right parameters passed as second argument" do
356
+ expected_params = @style_params[:once].merge({:processors => [:thumbnail, :test], :whiny => true, :convert_options => ""})
357
+ Paperclip::Thumbnail.expects(:make).with(anything, expected_params, anything).returns(@file)
358
+ end
359
+
360
+ before_should "call #make with attachment passed as third argument" do
361
+ Paperclip::Test.expects(:make).with(anything, anything, @dummy.avatar).returns(@file)
362
+ end
363
+ end
364
+ end
365
+
366
+ context "An attachment with styles but no processors defined" do
367
+ setup do
368
+ rebuild_model :processors => [], :styles => {:something => 1}
369
+ @dummy = Dummy.new
370
+ @file = StringIO.new("...")
371
+ end
372
+ should "raise when assigned to" do
373
+ assert_raises(RuntimeError){ @dummy.avatar = @file }
374
+ end
375
+ end
376
+
377
+ context "An attachment without styles and with no processors defined" do
378
+ setup do
379
+ rebuild_model :processors => [], :styles => {}
380
+ @dummy = Dummy.new
381
+ @file = StringIO.new("...")
382
+ end
383
+ should "not raise when assigned to" do
384
+ @dummy.avatar = @file
385
+ end
386
+ end
387
+
388
+ context "Assigning an attachment with post_process hooks" do
389
+ setup do
390
+ rebuild_model :styles => { :something => "100x100#" }
391
+ Dummy.class_eval do
392
+ before_avatar_post_process :do_before_avatar
393
+ after_avatar_post_process :do_after_avatar
394
+ before_post_process :do_before_all
395
+ after_post_process :do_after_all
396
+ def do_before_avatar; end
397
+ def do_after_avatar; end
398
+ def do_before_all; end
399
+ def do_after_all; end
400
+ end
401
+ @file = StringIO.new(".")
402
+ @file.stubs(:to_tempfile).returns(@file)
403
+ @dummy = Dummy.new
404
+ Paperclip::Thumbnail.stubs(:make).returns(@file)
405
+ @attachment = @dummy.avatar
406
+ end
407
+
408
+ should "call the defined callbacks when assigned" do
409
+ @dummy.expects(:do_before_avatar).with()
410
+ @dummy.expects(:do_after_avatar).with()
411
+ @dummy.expects(:do_before_all).with()
412
+ @dummy.expects(:do_after_all).with()
413
+ Paperclip::Thumbnail.expects(:make).returns(@file)
414
+ @dummy.avatar = @file
415
+ end
416
+
417
+ should "not cancel the processing if a before_post_process returns nil" do
418
+ @dummy.expects(:do_before_avatar).with().returns(nil)
419
+ @dummy.expects(:do_after_avatar).with()
420
+ @dummy.expects(:do_before_all).with().returns(nil)
421
+ @dummy.expects(:do_after_all).with()
422
+ Paperclip::Thumbnail.expects(:make).returns(@file)
423
+ @dummy.avatar = @file
424
+ end
425
+
426
+ should "cancel the processing if a before_post_process returns false" do
427
+ @dummy.expects(:do_before_avatar).never
428
+ @dummy.expects(:do_after_avatar).never
429
+ @dummy.expects(:do_before_all).with().returns(false)
430
+ @dummy.expects(:do_after_all).never
431
+ Paperclip::Thumbnail.expects(:make).never
432
+ @dummy.avatar = @file
433
+ end
434
+
435
+ should "cancel the processing if a before_avatar_post_process returns false" do
436
+ @dummy.expects(:do_before_avatar).with().returns(false)
437
+ @dummy.expects(:do_after_avatar).never
438
+ @dummy.expects(:do_before_all).with().returns(true)
439
+ @dummy.expects(:do_after_all).never
440
+ Paperclip::Thumbnail.expects(:make).never
441
+ @dummy.avatar = @file
442
+ end
443
+ end
444
+
445
+ context "Assigning an attachment" do
446
+ setup do
447
+ rebuild_model :styles => { :something => "100x100#" }
448
+ @file = StringIO.new(".")
449
+ @file.expects(:original_filename).returns("5k.png\n\n")
450
+ @file.expects(:content_type).returns("image/png\n\n")
451
+ @file.stubs(:to_tempfile).returns(@file)
452
+ @dummy = Dummy.new
453
+ Paperclip::Thumbnail.expects(:make).returns(@file)
454
+ @dummy.expects(:run_callbacks).with(:before_avatar_post_process, {:original => @file})
455
+ @dummy.expects(:run_callbacks).with(:before_post_process, {:original => @file})
456
+ @dummy.expects(:run_callbacks).with(:after_avatar_post_process, {:original => @file, :something => @file})
457
+ @dummy.expects(:run_callbacks).with(:after_post_process, {:original => @file, :something => @file})
458
+ @attachment = @dummy.avatar
459
+ @dummy.avatar = @file
460
+ end
461
+
462
+ should "strip whitespace from original_filename field" do
463
+ assert_equal "5k.png", @dummy.avatar.original_filename
464
+ end
465
+
466
+ should "strip whitespace from content_type field" do
467
+ assert_equal "image/png", @dummy.avatar.instance.avatar_content_type
468
+ end
469
+ end
470
+
471
+ context "Attachment with strange letters" do
472
+ setup do
473
+ rebuild_model
474
+
475
+ @not_file = mock
476
+ @tempfile = mock
477
+ @not_file.stubs(:nil?).returns(false)
478
+ @not_file.expects(:size).returns(10)
479
+ @tempfile.expects(:size).returns(10)
480
+ @not_file.expects(:to_tempfile).returns(@tempfile)
481
+ @not_file.expects(:original_filename).returns("sheep_say_bæ.png\r\n")
482
+ @not_file.expects(:content_type).returns("image/png\r\n")
483
+
484
+ @dummy = Dummy.new
485
+ @attachment = @dummy.avatar
486
+ @attachment.expects(:valid_assignment?).with(@not_file).returns(true)
487
+ @attachment.expects(:queue_existing_for_delete)
488
+ @attachment.expects(:post_process)
489
+ @dummy.avatar = @not_file
490
+ end
491
+
492
+ should "remove strange letters and replace with underscore (_)" do
493
+ assert_equal "sheep_say_b_.png", @dummy.avatar.original_filename
494
+ end
495
+
496
+ end
497
+
498
+ context "An attachment" do
499
+ setup do
500
+ @old_defaults = Paperclip::Attachment.default_options.dup
501
+ Paperclip::Attachment.default_options.merge!({
502
+ :path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
503
+ })
504
+ FileUtils.rm_rf("tmp")
505
+ rebuild_model
506
+ @instance = Dummy.new
507
+ @instance.stubs(:id).returns 123
508
+ @attachment = Paperclip::Attachment.new(:avatar, @instance)
509
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
510
+ end
511
+
512
+ teardown do
513
+ @file.close
514
+ Paperclip::Attachment.default_options.merge!(@old_defaults)
515
+ end
516
+
517
+ should "raise if there are not the correct columns when you try to assign" do
518
+ @other_attachment = Paperclip::Attachment.new(:not_here, @instance)
519
+ assert_raises(Paperclip::PaperclipError) do
520
+ @other_attachment.assign(@file)
521
+ end
522
+ end
523
+
524
+ should "return its default_url when no file assigned" do
525
+ assert @attachment.to_file.nil?
526
+ assert_equal "/avatars/original/missing.png", @attachment.url
527
+ assert_equal "/avatars/blah/missing.png", @attachment.url(:blah)
528
+ end
529
+
530
+ should "return nil as path when no file assigned" do
531
+ assert @attachment.to_file.nil?
532
+ assert_equal nil, @attachment.path
533
+ assert_equal nil, @attachment.path(:blah)
534
+ end
535
+
536
+ context "with a file assigned in the database" do
537
+ setup do
538
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.png")
539
+ @attachment.stubs(:instance_read).with(:content_type).returns("image/png")
540
+ @attachment.stubs(:instance_read).with(:file_size).returns(12345)
541
+ dtnow = DateTime.now
542
+ @now = Time.now
543
+ Time.stubs(:now).returns(@now)
544
+ @attachment.stubs(:instance_read).with(:updated_at).returns(dtnow)
545
+ end
546
+
547
+ should "return a correct url even if the file does not exist" do
548
+ assert_nil @attachment.to_file
549
+ assert_match %r{^/system/avatars/#{@instance.id}/blah/5k\.png}, @attachment.url(:blah)
550
+ end
551
+
552
+ should "make sure the updated_at mtime is in the url if it is defined" do
553
+ assert_match %r{#{@now.to_i}$}, @attachment.url(:blah)
554
+ end
555
+
556
+ should "make sure the updated_at mtime is NOT in the url if false is passed to the url method" do
557
+ assert_no_match %r{#{@now.to_i}$}, @attachment.url(:blah, false)
558
+ end
559
+
560
+ context "with the updated_at field removed" do
561
+ setup do
562
+ @attachment.stubs(:instance_read).with(:updated_at).returns(nil)
563
+ end
564
+
565
+ should "only return the url without the updated_at when sent #url" do
566
+ assert_match "/avatars/#{@instance.id}/blah/5k.png", @attachment.url(:blah)
567
+ end
568
+ end
569
+
570
+ should "return the proper path when filename has a single .'s" do
571
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.png"), File.expand_path(@attachment.path)
572
+ end
573
+
574
+ should "return the proper path when filename has multiple .'s" do
575
+ @attachment.stubs(:instance_read).with(:file_name).returns("5k.old.png")
576
+ assert_equal File.expand_path("./test/../tmp/avatars/dummies/original/#{@instance.id}/5k.old.png"), File.expand_path(@attachment.path)
577
+ end
578
+
579
+ context "when expecting three styles" do
580
+ setup do
581
+ styles = {:styles => { :large => ["400x400", :png],
582
+ :medium => ["100x100", :gif],
583
+ :small => ["32x32#", :jpg]}}
584
+ @attachment = Paperclip::Attachment.new(:avatar,
585
+ @instance,
586
+ styles)
587
+ end
588
+
589
+ context "and assigned a file" do
590
+ setup do
591
+ now = Time.now
592
+ Time.stubs(:now).returns(now)
593
+ @attachment.assign(@file)
594
+ end
595
+
596
+ should "be dirty" do
597
+ assert @attachment.dirty?
598
+ end
599
+
600
+ context "and saved" do
601
+ setup do
602
+ @attachment.save
603
+ end
604
+
605
+ should "return the real url" do
606
+ file = @attachment.to_file
607
+ assert file
608
+ assert_match %r{^/system/avatars/#{@instance.id}/original/5k\.png}, @attachment.url
609
+ assert_match %r{^/system/avatars/#{@instance.id}/small/5k\.jpg}, @attachment.url(:small)
610
+ file.close
611
+ end
612
+
613
+ should "commit the files to disk" do
614
+ [:large, :medium, :small].each do |style|
615
+ io = @attachment.to_file(style)
616
+ # p "in commit to disk test, io is #{io.inspect} and @instance.id is #{@instance.id}"
617
+ assert File.exists?(io)
618
+ assert ! io.is_a?(::Tempfile)
619
+ io.close
620
+ end
621
+ end
622
+
623
+ should "save the files as the right formats and sizes" do
624
+ [[:large, 400, 61, "PNG"],
625
+ [:medium, 100, 15, "GIF"],
626
+ [:small, 32, 32, "JPEG"]].each do |style|
627
+ cmd = %Q[identify -format "%w %h %b %m" "#{@attachment.path(style.first)}"]
628
+ out = `#{cmd}`
629
+ width, height, size, format = out.split(" ")
630
+ assert_equal style[1].to_s, width.to_s
631
+ assert_equal style[2].to_s, height.to_s
632
+ assert_equal style[3].to_s, format.to_s
633
+ end
634
+ end
635
+
636
+ should "still have its #file attribute not be nil" do
637
+ assert ! (file = @attachment.to_file).nil?
638
+ file.close
639
+ end
640
+
641
+ context "and trying to delete" do
642
+ setup do
643
+ @existing_names = @attachment.styles.keys.collect do |style|
644
+ @attachment.path(style)
645
+ end
646
+ end
647
+
648
+ should "delete the files after assigning nil" do
649
+ @attachment.expects(:instance_write).with(:file_name, nil)
650
+ @attachment.expects(:instance_write).with(:content_type, nil)
651
+ @attachment.expects(:instance_write).with(:file_size, nil)
652
+ @attachment.expects(:instance_write).with(:updated_at, nil)
653
+ @attachment.assign nil
654
+ @attachment.save
655
+ @existing_names.each{|f| assert ! File.exists?(f) }
656
+ end
657
+
658
+ should "delete the files when you call #clear and #save" do
659
+ @attachment.expects(:instance_write).with(:file_name, nil)
660
+ @attachment.expects(:instance_write).with(:content_type, nil)
661
+ @attachment.expects(:instance_write).with(:file_size, nil)
662
+ @attachment.expects(:instance_write).with(:updated_at, nil)
663
+ @attachment.clear
664
+ @attachment.save
665
+ @existing_names.each{|f| assert ! File.exists?(f) }
666
+ end
667
+
668
+ should "delete the files when you call #delete" do
669
+ @attachment.expects(:instance_write).with(:file_name, nil)
670
+ @attachment.expects(:instance_write).with(:content_type, nil)
671
+ @attachment.expects(:instance_write).with(:file_size, nil)
672
+ @attachment.expects(:instance_write).with(:updated_at, nil)
673
+ @attachment.destroy
674
+ @existing_names.each{|f| assert ! File.exists?(f) }
675
+ end
676
+ end
677
+ end
678
+ end
679
+ end
680
+
681
+ end
682
+
683
+ context "when trying a nonexistant storage type" do
684
+ setup do
685
+ rebuild_model :storage => :not_here
686
+ end
687
+
688
+ should "not be able to find the module" do
689
+ assert_raise(NameError){ Dummy.new.avatar }
690
+ end
691
+ end
692
+ end
693
+
694
+ context "An attachment with only a avatar_file_name column" do
695
+ setup do
696
+ ActiveRecord::Base.connection.create_table :dummies, :force => true do |table|
697
+ table.column :avatar_file_name, :string
698
+ end
699
+ rebuild_class
700
+ @dummy = Dummy.new
701
+ @file = File.new(File.join(File.dirname(__FILE__), "fixtures", "5k.png"), 'rb')
702
+ end
703
+
704
+ teardown { @file.close }
705
+
706
+ should "not error when assigned an attachment" do
707
+ assert_nothing_raised { @dummy.avatar = @file }
708
+ end
709
+
710
+ should "return the time when sent #avatar_updated_at" do
711
+ now = Time.now
712
+ Time.stubs(:now).returns(now)
713
+ @dummy.avatar = @file
714
+ assert now, @dummy.avatar.updated_at
715
+ end
716
+
717
+ should "return nil when reloaded and sent #avatar_updated_at" do
718
+ @dummy.save
719
+ @dummy.reload
720
+ assert_nil @dummy.avatar.updated_at
721
+ end
722
+
723
+ should "return the right value when sent #avatar_file_size" do
724
+ @dummy.avatar = @file
725
+ assert_equal @file.size, @dummy.avatar.size
726
+ end
727
+
728
+ context "and avatar_updated_at column" do
729
+ setup do
730
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_updated_at, :timestamp
731
+ rebuild_class
732
+ @dummy = Dummy.new
733
+ end
734
+
735
+ should "not error when assigned an attachment" do
736
+ assert_nothing_raised { @dummy.avatar = @file }
737
+ end
738
+
739
+ should "return the right value when sent #avatar_updated_at" do
740
+ now = Time.now
741
+ Time.stubs(:now).returns(now)
742
+ @dummy.avatar = @file
743
+ assert_equal now.to_i, @dummy.avatar.updated_at
744
+ end
745
+ end
746
+
747
+ context "and avatar_content_type column" do
748
+ setup do
749
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_content_type, :string
750
+ rebuild_class
751
+ @dummy = Dummy.new
752
+ end
753
+
754
+ should "not error when assigned an attachment" do
755
+ assert_nothing_raised { @dummy.avatar = @file }
756
+ end
757
+
758
+ should "return the right value when sent #avatar_content_type" do
759
+ @dummy.avatar = @file
760
+ assert_equal "image/png", @dummy.avatar.content_type
761
+ end
762
+ end
763
+
764
+ context "and avatar_file_size column" do
765
+ setup do
766
+ ActiveRecord::Base.connection.add_column :dummies, :avatar_file_size, :integer
767
+ rebuild_class
768
+ @dummy = Dummy.new
769
+ end
770
+
771
+ should "not error when assigned an attachment" do
772
+ assert_nothing_raised { @dummy.avatar = @file }
773
+ end
774
+
775
+ should "return the right value when sent #avatar_file_size" do
776
+ @dummy.avatar = @file
777
+ assert_equal @file.size, @dummy.avatar.size
778
+ end
779
+
780
+ should "return the right value when saved, reloaded, and sent #avatar_file_size" do
781
+ @dummy.avatar = @file
782
+ @dummy.save
783
+ @dummy = Dummy.find(@dummy.id)
784
+ assert_equal @file.size, @dummy.avatar.size
785
+ end
786
+ end
787
+ end
788
+ end