alg-paperclip 2.3.1.1

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 (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