bulldog 0.0.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 (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