ryansch-paperclip 2.3.10

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