bulldog 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. data/.gitignore +2 -0
  2. data/DESCRIPTION.txt +3 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +18 -0
  5. data/Rakefile +64 -0
  6. data/VERSION +1 -0
  7. data/bulldog.gemspec +157 -0
  8. data/lib/bulldog.rb +95 -0
  9. data/lib/bulldog/attachment.rb +49 -0
  10. data/lib/bulldog/attachment/base.rb +167 -0
  11. data/lib/bulldog/attachment/has_dimensions.rb +94 -0
  12. data/lib/bulldog/attachment/image.rb +63 -0
  13. data/lib/bulldog/attachment/maybe.rb +229 -0
  14. data/lib/bulldog/attachment/none.rb +37 -0
  15. data/lib/bulldog/attachment/pdf.rb +63 -0
  16. data/lib/bulldog/attachment/unknown.rb +11 -0
  17. data/lib/bulldog/attachment/video.rb +143 -0
  18. data/lib/bulldog/error.rb +5 -0
  19. data/lib/bulldog/has_attachment.rb +214 -0
  20. data/lib/bulldog/interpolation.rb +73 -0
  21. data/lib/bulldog/missing_file.rb +12 -0
  22. data/lib/bulldog/processor.rb +5 -0
  23. data/lib/bulldog/processor/argument_tree.rb +116 -0
  24. data/lib/bulldog/processor/base.rb +124 -0
  25. data/lib/bulldog/processor/ffmpeg.rb +172 -0
  26. data/lib/bulldog/processor/image_magick.rb +134 -0
  27. data/lib/bulldog/processor/one_shot.rb +19 -0
  28. data/lib/bulldog/reflection.rb +234 -0
  29. data/lib/bulldog/saved_file.rb +19 -0
  30. data/lib/bulldog/stream.rb +186 -0
  31. data/lib/bulldog/style.rb +38 -0
  32. data/lib/bulldog/style_set.rb +101 -0
  33. data/lib/bulldog/tempfile.rb +28 -0
  34. data/lib/bulldog/util.rb +92 -0
  35. data/lib/bulldog/validations.rb +68 -0
  36. data/lib/bulldog/vector2.rb +18 -0
  37. data/rails/init.rb +9 -0
  38. data/script/console +8 -0
  39. data/spec/data/empty.txt +0 -0
  40. data/spec/data/test.jpg +0 -0
  41. data/spec/data/test.mov +0 -0
  42. data/spec/data/test.pdf +0 -0
  43. data/spec/data/test.png +0 -0
  44. data/spec/data/test2.jpg +0 -0
  45. data/spec/helpers/image_creation.rb +8 -0
  46. data/spec/helpers/temporary_directory.rb +25 -0
  47. data/spec/helpers/temporary_models.rb +76 -0
  48. data/spec/helpers/temporary_values.rb +102 -0
  49. data/spec/helpers/test_upload_files.rb +108 -0
  50. data/spec/helpers/time_travel.rb +20 -0
  51. data/spec/integration/data/test.jpg +0 -0
  52. data/spec/integration/lifecycle_hooks_spec.rb +213 -0
  53. data/spec/integration/processing_image_attachments.rb +72 -0
  54. data/spec/integration/processing_video_attachments_spec.rb +82 -0
  55. data/spec/integration/saving_an_attachment_spec.rb +31 -0
  56. data/spec/matchers/file_operations.rb +159 -0
  57. data/spec/spec_helper.rb +76 -0
  58. data/spec/unit/attachment/base_spec.rb +311 -0
  59. data/spec/unit/attachment/image_spec.rb +128 -0
  60. data/spec/unit/attachment/maybe_spec.rb +126 -0
  61. data/spec/unit/attachment/pdf_spec.rb +137 -0
  62. data/spec/unit/attachment/video_spec.rb +176 -0
  63. data/spec/unit/attachment_spec.rb +61 -0
  64. data/spec/unit/has_attachment_spec.rb +700 -0
  65. data/spec/unit/interpolation_spec.rb +108 -0
  66. data/spec/unit/processor/argument_tree_spec.rb +159 -0
  67. data/spec/unit/processor/ffmpeg_spec.rb +467 -0
  68. data/spec/unit/processor/image_magick_spec.rb +260 -0
  69. data/spec/unit/processor/one_shot_spec.rb +70 -0
  70. data/spec/unit/reflection_spec.rb +338 -0
  71. data/spec/unit/stream_spec.rb +234 -0
  72. data/spec/unit/style_set_spec.rb +44 -0
  73. data/spec/unit/style_spec.rb +51 -0
  74. data/spec/unit/validations_spec.rb +491 -0
  75. data/spec/unit/vector2_spec.rb +27 -0
  76. data/tasks/bulldog_tasks.rake +4 -0
  77. metadata +193 -0
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Attachment do
4
+ use_model_class(:Thing)
5
+
6
+ before do
7
+ Thing.has_attachment :attachment
8
+ end
9
+
10
+ describe ".of_type" do
11
+ before do
12
+ @thing = Thing.new
13
+ end
14
+
15
+ describe "when the type is nil" do
16
+ before do
17
+ @type = nil
18
+ end
19
+
20
+ describe "when the stream is nil" do
21
+ before do
22
+ @stream = nil
23
+ end
24
+
25
+ it "should return a None attachment" do
26
+ attachment = Attachment.of_type(@type, @thing, :attachment, @stream)
27
+ attachment.should be_a(Attachment::None)
28
+ end
29
+ end
30
+
31
+ describe "when the stream is not nil" do
32
+ before do
33
+ @stream = Stream.new(test_image_file)
34
+ end
35
+
36
+ it "should return a Base attachment" do
37
+ attachment = Attachment.of_type(@type, @thing, :attachment, @stream)
38
+ attachment.should be_a(Attachment::Base)
39
+ end
40
+ end
41
+ end
42
+
43
+ describe "when the type is not nil" do
44
+ before do
45
+ @type = :video
46
+ end
47
+
48
+ it "should return an attachment of the specified type" do
49
+ attachment = Attachment.of_type(@type, @thing, :attachment, @stream)
50
+ attachment.should be_a(Attachment::Video)
51
+ end
52
+ end
53
+ end
54
+
55
+ describe ".none" do
56
+ it "should return a None attachment" do
57
+ attachment = Attachment.none(Thing.new, :attachment)
58
+ attachment.should be_a(Attachment::None)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,700 @@
1
+ require 'spec_helper'
2
+
3
+ describe HasAttachment do
4
+ describe ".has_attachment" do
5
+ use_model_class(:Thing)
6
+
7
+ it "should provide accessors for the attachment" do
8
+ Thing.has_attachment :photo
9
+ thing = Thing.new
10
+ thing.photo.should be_a(Attachment::Maybe)
11
+ end
12
+
13
+ it "should provide a query method for the attachment" do
14
+ Thing.has_attachment :photo
15
+ thing = Thing.new
16
+ file = uploaded_file
17
+ thing.photo?.should be_false
18
+ thing.photo = file
19
+ thing.photo?.should be_true
20
+ end
21
+
22
+ it "should configure the existing attachment declared if one exists" do
23
+ Thing.has_attachment :photo do
24
+ style :one
25
+ end
26
+ Thing.has_attachment :photo do
27
+ style :two
28
+ end
29
+ Thing.attachment_reflections[:photo].styles[:one].should_not be_blank
30
+ Thing.attachment_reflections[:photo].styles[:two].should_not be_blank
31
+ end
32
+
33
+ describe "when an attachment is inherited" do
34
+ use_model_class(:Parent)
35
+
36
+ before do
37
+ # Create the attachment before subclassing
38
+ Parent.has_attachment :photo do
39
+ style :one
40
+ end
41
+ end
42
+
43
+ use_model_class(:Child => :Parent)
44
+
45
+ it "should not affect the superclasses' attachment" do
46
+ Child.has_attachment :photo do
47
+ style :two
48
+ end
49
+ Child.attachment_reflections[:photo].styles[:two].should_not be_blank
50
+ Parent.attachment_reflections[:photo].styles[:two].should be_blank
51
+ end
52
+ end
53
+ end
54
+
55
+ describe ".attachment_reflections" do
56
+ use_model_class(:Thing)
57
+
58
+ it "should allow reflection on the field names" do
59
+ Thing.has_attachment :photo
60
+ Thing.attachment_reflections[:photo].name.should == :photo
61
+ end
62
+ end
63
+
64
+ describe "#process_attachment" do
65
+ use_model_class(:Thing)
66
+
67
+ describe "when there is an attachment set" do
68
+ it "should trigger the configured callbacks" do
69
+ args = nil
70
+ Thing.has_attachment :photo do
71
+ style :normal
72
+ process(:on => :my_event){|*args|}
73
+ end
74
+ thing = Thing.new(:photo => uploaded_file)
75
+ thing.process_attachment(:photo, :my_event, 1, 2)
76
+ args.should == [1, 2]
77
+ end
78
+ end
79
+
80
+ describe "when there is no attachment set" do
81
+ it "should not trigger any callbacks" do
82
+ args = nil
83
+ Thing.has_attachment :photo do
84
+ style :normal
85
+ process(:on => :my_event){|*args|}
86
+ end
87
+ thing = Thing.new(:photo => nil)
88
+ thing.process_attachment(:photo, :my_event, 1, 2)
89
+ args.should be_nil
90
+ end
91
+ end
92
+
93
+ it "should raise an ArgumentError if the attachment name is invalid" do
94
+ args = nil
95
+ Thing.has_attachment :photo do
96
+ style :normal
97
+ process(:on => :my_event){|*args|}
98
+ end
99
+ thing = Thing.new
100
+ lambda do
101
+ thing.process_attachment(:fail, :my_event, 1, 2)
102
+ end.should raise_error(ArgumentError)
103
+ end
104
+
105
+ it "should evaluate the callback in the context of the specified processor" do
106
+ with_temporary_constant_value Processor, :Test, Class.new(Processor::Base) do
107
+ context = nil
108
+ Thing.has_attachment :photo do
109
+ style :normal
110
+ process(:on => :my_event, :with => :test){context = self}
111
+ end
112
+ thing = Thing.new(:photo => uploaded_file)
113
+ thing.process_attachment(:photo, :my_event)
114
+ context.should be_a(Processor::Test)
115
+ end
116
+ end
117
+
118
+ it "should default to a base processor instance" do
119
+ context = nil
120
+ Thing.has_attachment :photo do
121
+ style :normal
122
+ process(:on => :my_event){context = self}
123
+ end
124
+ thing = Thing.new(:photo => uploaded_file)
125
+ thing.process_attachment(:photo, :my_event)
126
+ context.should be_a(Processor::Base)
127
+ end
128
+ end
129
+
130
+ describe "object lifecycle" do
131
+ outline "building a record" do
132
+ with_model_class :Thing do
133
+ spec = self
134
+
135
+ Thing.has_attachment :attachment do
136
+ detect_type_by{spec.detect_as}
137
+ path "#{spec.temporary_directory}/:style.jpg"
138
+ end
139
+
140
+ original_path = "#{temporary_directory}/original.jpg"
141
+
142
+ thing = Thing.new(:attachment => eval(value.to_s))
143
+ thing.attachment.should be_a(attachment_class)
144
+ end
145
+ end
146
+
147
+ fields :detect_as, :value , :attachment_class
148
+ values nil , nil , Attachment::None
149
+ values :image , nil , Attachment::None
150
+ values nil , :test_image_file, Attachment::Unknown
151
+ values :image , :test_image_file, Attachment::Image
152
+
153
+ outline "loading a record" do
154
+ columns = store_file_name ? {:attachment_file_name => :string} : {}
155
+ with_model_class :Thing, columns do
156
+ spec = self
157
+
158
+ Thing.has_attachment :attachment do
159
+ detect_type_by{spec.detect_as}
160
+ path "#{spec.temporary_directory}/:style.jpg"
161
+ end
162
+
163
+ original_path = "#{temporary_directory}/original.jpg"
164
+
165
+ thing = Thing.create(:attachment => eval(value_saved.to_s))
166
+ if file_exists?
167
+ FileUtils.touch(original_path)
168
+ else
169
+ FileUtils.rm_f(original_path)
170
+ end
171
+
172
+ thing = Thing.find(thing.id)
173
+ thing.attachment.should be_a(attachment_class)
174
+ if stream_missing.nil?
175
+ thing.attachment.stream.should be_nil
176
+ else
177
+ thing.attachment.stream.missing?.should == stream_missing
178
+ end
179
+ end
180
+ end
181
+
182
+ fields :store_file_name, :detect_as, :value_saved , :file_exists?, :attachment_class , :stream_missing
183
+ values false , nil , nil , false , Attachment::None , nil
184
+ values false , nil , nil , true , Attachment::Unknown, false
185
+ values false , nil , :test_image_file, false , Attachment::None , nil
186
+ values false , nil , :test_image_file, true , Attachment::Unknown, false
187
+ values false , :image , nil , false , Attachment::None , nil
188
+ values false , :image , nil , true , Attachment::Image , false
189
+ values false , :image , :test_image_file, false , Attachment::None , nil
190
+ values false , :image , :test_image_file, true , Attachment::Image , false
191
+ values true , nil , nil , false , Attachment::None , nil
192
+ values true , nil , nil , true , Attachment::None , nil
193
+ values true , nil , :test_image_file, false , Attachment::Unknown, true
194
+ values true , nil , :test_image_file, true , Attachment::Unknown, false
195
+ values true , :image , nil , false , Attachment::None , nil
196
+ values true , :image , nil , true , Attachment::None , nil
197
+ values true , :image , :test_image_file, false , Attachment::Image , true
198
+ values true , :image , :test_image_file, true , Attachment::Image , false
199
+
200
+ describe "when no attributes are stored" do
201
+ use_model_class(:Thing)
202
+
203
+ before do
204
+ @file = uploaded_file('test.jpg', 'test.jpg')
205
+ end
206
+
207
+ def configure(&block)
208
+ Thing.attachment_reflections[:photo].configure(&block)
209
+ end
210
+
211
+ describe "saving the record" do
212
+ it "should create the original file as long as :basename and :extension are not used" do
213
+ spec = self
214
+ Thing.has_attachment :photo do
215
+ path "#{spec.temporary_directory}/:style.jpg"
216
+ end
217
+ thing = Thing.new(:photo => @file)
218
+ lambda{thing.save}.should create_file("#{temporary_directory}/original.jpg")
219
+ end
220
+
221
+ it "should raise an error if :basename is used" do
222
+ spec = self
223
+ Thing.has_attachment :photo do
224
+ path "#{spec.temporary_directory}/:basename"
225
+ end
226
+ thing = Thing.new(:photo => @file)
227
+ lambda{thing.save}.should raise_error(Interpolation::Error)
228
+ end
229
+
230
+ it "should raise an error if :extension is used" do
231
+ spec = self
232
+ Thing.has_attachment :photo do
233
+ path "#{spec.temporary_directory}/photo.:extension"
234
+ end
235
+ thing = Thing.new(:photo => @file)
236
+ lambda{thing.save}.should raise_error(Interpolation::Error)
237
+ end
238
+
239
+ describe "when the record is reloaded" do
240
+ before do
241
+ spec = self
242
+ Thing.has_attachment :photo do
243
+ path "#{spec.temporary_directory}/:style.jpg"
244
+ end
245
+ @thing = Thing.new(:photo => @file)
246
+ @thing.save
247
+ end
248
+
249
+ it "should load the saved original file" do
250
+ @thing.photo.should be_present
251
+ @thing.photo.path.should == "#{temporary_directory}/original.jpg"
252
+ end
253
+ end
254
+ end
255
+ end
256
+
257
+ describe "when the file name is stored" do
258
+ use_model_class(:Thing,
259
+ :photo_file_name => :string,
260
+ :photo_content_type => :string,
261
+ :photo_file_size => :integer)
262
+
263
+ before do
264
+ spec = self
265
+ Thing.has_attachment :photo do
266
+ path "#{spec.temporary_directory}/photos/:id-:style.:extension"
267
+ style :small, :size => '10x10'
268
+ end
269
+ end
270
+
271
+ def configure(&block)
272
+ Thing.attachment_reflections[:photo].configure(&block)
273
+ end
274
+
275
+ def original_path
276
+ "#{temporary_directory}/photos/#{@thing.id}-original.jpg"
277
+ end
278
+
279
+ def small_path
280
+ "#{temporary_directory}/photos/#{@thing.id}-small.jpg"
281
+ end
282
+
283
+ describe "instantiating the record" do
284
+ describe "when the record is new" do
285
+ before do
286
+ @thing = Thing.new
287
+ end
288
+
289
+ it "should have no stored attributes set" do
290
+ @thing.photo_file_name.should be_nil
291
+ @thing.photo_content_type.should be_nil
292
+ @thing.photo_file_size.should be_nil
293
+ end
294
+ end
295
+
296
+ describe "when the record already exists" do
297
+ describe "when a file name is set, and the original file exists" do
298
+ def instantiate
299
+ file = uploaded_file('test.jpg', 'test.jpg')
300
+ thing = Thing.create(:photo => file)
301
+ @thing = Thing.find(thing.id)
302
+ end
303
+
304
+ it "should have stored attributes set" do
305
+ instantiate
306
+ @thing.photo_file_name.should == 'test.jpg'
307
+ @thing.photo_content_type.split(/;/).first.should == "image/jpeg"
308
+ @thing.photo_file_size.should == File.size(test_path('test.jpg'))
309
+ end
310
+ end
311
+
312
+ describe "when the no file name is set, and the original file does not exist" do
313
+ before do
314
+ thing = Thing.create
315
+ @thing = Thing.find(thing.id)
316
+ end
317
+
318
+ it "should have no stored attributes set" do
319
+ @thing.photo_file_name.should be_nil
320
+ @thing.photo_content_type.should be_nil
321
+ @thing.photo_file_size.should be_nil
322
+ end
323
+ end
324
+
325
+ describe "when a file name is set, but the original file is missing" do
326
+ def instantiate
327
+ file = uploaded_file('test.jpg', 'test.jpg')
328
+ @thing = Thing.create(:photo => file)
329
+ File.unlink(original_path)
330
+ @thing = Thing.find(@thing.id)
331
+ end
332
+
333
+ it "should have stored attributes set" do
334
+ instantiate
335
+ @thing.photo_file_name.should == 'test.jpg'
336
+ @thing.photo_content_type == "image/jpeg"
337
+ @thing.photo_file_size.should == File.size(test_path('test.jpg'))
338
+ end
339
+ end
340
+ end
341
+
342
+ describe "when the record exists and there is no attachment" do
343
+ before do
344
+ thing = Thing.create
345
+ @thing = Thing.find(thing.id)
346
+ end
347
+
348
+ describe "when an attachment is assigned" do
349
+ before do
350
+ @file = uploaded_file('test.jpg', 'test.jpg')
351
+ end
352
+
353
+ it "should set the stored attributes" do
354
+ @thing.photo = @file
355
+ @thing.photo_file_name.should == 'test.jpg'
356
+ @thing.photo_content_type.split(/;/).first.should == "image/jpeg"
357
+ @thing.photo_file_size.should == File.size(test_path('test.jpg'))
358
+ end
359
+
360
+ it "should not create the original file" do
361
+ lambda do
362
+ @thing.photo = @file
363
+ end.should_not create_file(original_path)
364
+ end
365
+
366
+ describe "when the record is saved" do
367
+ before do
368
+ @thing.photo = @file
369
+ end
370
+
371
+ it "should create the original file" do
372
+ lambda do
373
+ @thing.save.should be_true
374
+ end.should create_file(original_path)
375
+ end
376
+ end
377
+ end
378
+ end
379
+
380
+ describe "when the record exists and there is an attachment" do
381
+ before do
382
+ @old_file = test_file('test.jpg')
383
+ thing = Thing.create(:photo => @old_file)
384
+ @thing = Thing.find(thing.id)
385
+ end
386
+
387
+ def old_original_path
388
+ original_path
389
+ end
390
+
391
+ def new_original_path
392
+ new_dirname = File.dirname(original_path)
393
+ new_basename = File.basename(original_path, 'jpg') + 'png'
394
+ File.join(new_dirname, new_basename)
395
+ end
396
+
397
+ describe "when a new attachment is assigned" do
398
+ before do
399
+ @new_file = test_file('test.png')
400
+ end
401
+
402
+ it "should set the stored attributes" do
403
+ @thing.photo = @new_file
404
+ @thing.photo_file_name.should == 'test.png'
405
+ @thing.photo_content_type.split(/;/).first.should == 'image/png'
406
+ @thing.photo_file_size.should == File.size(test_path('test.png'))
407
+ end
408
+
409
+ it "should not create the new original file yet" do
410
+ lambda do
411
+ @thing.photo = @new_file
412
+ end.should_not create_file(new_original_path)
413
+ end
414
+
415
+ it "should not delete the old original file yet" do
416
+ lambda do
417
+ @thing.photo = @new_file
418
+ end.should_not delete_file(old_original_path)
419
+ end
420
+
421
+ describe "when the record is saved" do
422
+ before do
423
+ @thing.photo = @new_file
424
+ end
425
+
426
+ it "should create the new original file" do
427
+ lambda do
428
+ @thing.save.should be_true
429
+ end.should create_file(new_original_path)
430
+ end
431
+
432
+ it "should delete the old original file" do
433
+ lambda do
434
+ @thing.save.should be_true
435
+ end.should delete_file(old_original_path)
436
+ end
437
+
438
+ it "should remove any old processed files" do
439
+ FileUtils.touch small_path
440
+ lambda do
441
+ @thing.save.should be_true
442
+ end.should delete_file(small_path)
443
+ end
444
+ end
445
+ end
446
+
447
+ describe "when nil is assigned" do
448
+ it "should not remove the old original file yet" do
449
+ lambda do
450
+ @thing.photo = nil
451
+ end.should_not delete_file(original_path)
452
+ end
453
+
454
+ it "should not remove any old processed files yet" do
455
+ FileUtils.touch small_path
456
+ lambda do
457
+ @thing.photo = nil
458
+ end.should_not delete_file(small_path)
459
+ end
460
+
461
+ describe "when the record is saved" do
462
+ before do
463
+ @thing.photo = nil
464
+ end
465
+
466
+ it "should remove the old original file" do
467
+ lambda do
468
+ @thing.save
469
+ end.should delete_file(original_path)
470
+ end
471
+
472
+ it "should remove any old processed files" do
473
+ FileUtils.touch small_path
474
+ lambda do
475
+ @thing.save
476
+ end.should delete_file(small_path)
477
+ end
478
+
479
+ it "should remove any empty parent directories" do
480
+ lambda do
481
+ @thing.save
482
+ end.should delete_file("#{temporary_directory}/photos")
483
+ end
484
+ end
485
+ end
486
+ end
487
+ end
488
+
489
+ describe "#destroy" do
490
+ describe "when the record is new" do
491
+ before do
492
+ file = uploaded_file('test.jpg', 'test.jpg')
493
+ @thing = Thing.new(:photo => file)
494
+ end
495
+
496
+ it "should not raise an error" do
497
+ lambda{@thing.destroy}.should_not raise_error
498
+ end
499
+ end
500
+
501
+ describe "when the record existed but had no attachment" do
502
+ before do
503
+ thing = Thing.create
504
+ @thing = Thing.find(thing.id)
505
+ end
506
+
507
+ it "should not raise an error" do
508
+ lambda{@thing.destroy}.should_not raise_error
509
+ end
510
+
511
+ it "should not stop the record being destroyed" do
512
+ @thing.destroy
513
+ Thing.exists?(@thing).should be_false
514
+ end
515
+ end
516
+
517
+ describe "when the record existed and had an attachment" do
518
+ before do
519
+ file = uploaded_file('test.jpg', 'test.jpg')
520
+ thing = Thing.create(:photo => file)
521
+ @thing = Thing.find(thing.id)
522
+ end
523
+
524
+ it "should not stop the record being destroyed" do
525
+ @thing.destroy
526
+ Thing.exists?(@thing).should be_false
527
+ end
528
+
529
+ it "should remove the original file" do
530
+ lambda do
531
+ @thing.destroy
532
+ end.should delete_file(original_path)
533
+ end
534
+
535
+ it "should remove any processed files" do
536
+ FileUtils.touch small_path
537
+ lambda do
538
+ @thing.destroy
539
+ end.should delete_file(small_path)
540
+ end
541
+
542
+ it "should remove any empty parent directories" do
543
+ lambda do
544
+ @thing.destroy
545
+ end.should delete_file("#{temporary_directory}/photos")
546
+ end
547
+ end
548
+ end
549
+ end
550
+ end
551
+
552
+ describe "AR::Dirty" do
553
+ use_model_class(:Thing, :name => :string)
554
+
555
+ before do
556
+ spec = self
557
+ Thing.has_attachment :photo do
558
+ path "#{spec.temporary_directory}/:id.jpg"
559
+ end
560
+ thing = Thing.create(:name => 'old', :photo => uploaded_file)
561
+ @thing = Thing.find(thing.id)
562
+ end
563
+
564
+ def original_path
565
+ "#{temporary_directory}/#{@thing.id}.jpg"
566
+ end
567
+
568
+ describe "#ATTACHMENT_changed?" do
569
+ it "should return false if nothing has been assigned to the attachment" do
570
+ @thing.photo_changed?.should be_false
571
+ end
572
+
573
+ it "should return false if the same value has been assigned to the attachment" do
574
+ @thing.photo = @thing.photo.value
575
+ @thing.photo_changed?.should be_false
576
+ end
577
+
578
+ it "should return true if a new value has been assigned to the attachment" do
579
+ @thing.photo = uploaded_file
580
+ @thing.photo_changed?.should be_true
581
+ end
582
+ end
583
+
584
+ describe "#ATTACHMENT_was" do
585
+ it "should return the original value before assignment" do
586
+ original_photo = @thing.photo
587
+ @thing.photo_was.should equal(original_photo)
588
+ end
589
+
590
+ it "should return a clone of the original value after assignment" do
591
+ original_photo = @thing.photo
592
+ @thing.photo = uploaded_file
593
+ @thing.photo_was.should_not equal(original_photo)
594
+ @thing.photo_was.should == original_photo
595
+ end
596
+ end
597
+
598
+ describe "#changes" do
599
+ it "should return attachment changes along with other attribute changes" do
600
+ old_photo = @thing.photo
601
+ @thing.name = 'new'
602
+ @thing.photo = uploaded_file
603
+ @thing.changes.should == {
604
+ 'name' => ['old', 'new'],
605
+ 'photo' => [old_photo, @thing.photo],
606
+ }
607
+ end
608
+ end
609
+
610
+ describe "when the record is saved and only attachments have been modified" do
611
+ before do
612
+ @thing.photo = uploaded_file
613
+ end
614
+
615
+ it "should not hit the database"
616
+
617
+ it "should still save the attachment files" do
618
+ lambda do
619
+ @thing.save
620
+ end.should modify_file(original_path)
621
+ end
622
+ end
623
+
624
+ describe "#save" do
625
+ before do
626
+ @thing.name = 'new'
627
+ @thing.photo = uploaded_file
628
+ end
629
+
630
+ it "should clear all changes" do
631
+ @thing.save
632
+ @thing.changes.should == {}
633
+ end
634
+ end
635
+ end
636
+
637
+ describe "automatic timestamps" do
638
+ describe "#save" do
639
+ use_model_class(:Thing, :photo_updated_at => :datetime)
640
+
641
+ before do
642
+ Thing.has_attachment :photo
643
+ end
644
+
645
+ describe "when the record is new" do
646
+ before do
647
+ @thing = Thing.new
648
+ end
649
+
650
+ describe "when the attachment was not assigned to" do
651
+ it "should not set ATTACHMENT_updated_at" do
652
+ @thing.save.should be_true
653
+ @thing.photo_updated_at.should be_nil
654
+ end
655
+ end
656
+
657
+ describe "when nil was assigned to the attachment" do
658
+ it "should not set ATTACHMENT_updated_at" do
659
+ @thing.photo = nil
660
+ @thing.save.should be_true
661
+ @thing.photo_updated_at.should be_nil
662
+ end
663
+ end
664
+
665
+ describe "when a file was assigned to the attachment" do
666
+ it "should update ATTACHMENT_updated_at" do
667
+ @thing.photo = uploaded_file
668
+ @thing.save.should be_true
669
+ @thing.photo_updated_at.should == Time.now
670
+ end
671
+ end
672
+ end
673
+
674
+ describe "when the record already exists" do
675
+ before do
676
+ thing = Thing.create(:photo => uploaded_file('test.jpg', 'test.jpg'))
677
+ @thing = Thing.find(thing.id)
678
+ end
679
+
680
+ describe "when the attachment was not assigned to" do
681
+ it "should not update ATTACHMENT_updated_at" do
682
+ warp_ahead 1.minute
683
+ original_updated_at = @thing.photo_updated_at
684
+ @thing.save.should be_true
685
+ @thing.photo_updated_at.should == original_updated_at.drop_subseconds
686
+ end
687
+ end
688
+
689
+ describe "when a new file was assigned to the attachment" do
690
+ it "should update ATTACHMENT_updated_at" do
691
+ warp_ahead 1.minute
692
+ @thing.photo = uploaded_file('test.jpg', 'test.jpg')
693
+ @thing.save.should be_true
694
+ @thing.photo_updated_at.should == Time.now
695
+ end
696
+ end
697
+ end
698
+ end
699
+ end
700
+ end