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,128 @@
1
+ require 'spec_helper'
2
+
3
+ describe Attachment::Image do
4
+ use_model_class(:Thing,
5
+ :photo_file_name => :string,
6
+ :photo_width => :integer,
7
+ :photo_height => :integer,
8
+ :photo_aspect_ratio => :float,
9
+ :photo_dimensions => :string)
10
+
11
+ before do
12
+ Thing.has_attachment :photo do
13
+ style :double, :size => '80x60'
14
+ style :filled, :size => '60x60', :filled => true
15
+ style :unfilled, :size => '120x120'
16
+ default_style :double
17
+ end
18
+ @thing = Thing.new(:photo => test_image_file)
19
+ end
20
+
21
+ def run(command)
22
+ `#{command}`
23
+ $?.success? or
24
+ raise "command failed: #{command}"
25
+ end
26
+
27
+ describe "#dimensions" do
28
+ it "should return 1x1 if the style is missing" do
29
+ Thing.attachment_reflections[:photo].configure do
30
+ detect_type_by{:image}
31
+ end
32
+ @thing.save.should be_true
33
+ File.unlink(@thing.photo.path(:original))
34
+ @thing = Thing.find(@thing.id)
35
+ @thing.photo.is_a?(Attachment::Image) # sanity check
36
+ @thing.photo.stream.missing? # sanity check
37
+ @thing.photo.dimensions(:original).should == [1, 1]
38
+ end
39
+
40
+ it "should return the width and height of the default style if no style name is given" do
41
+ @thing.photo.dimensions.should == [80, 60]
42
+ end
43
+
44
+ it "should return the width and height of the given style" do
45
+ @thing.photo.dimensions(:original).should == [40, 30]
46
+ @thing.photo.dimensions(:double).should == [80, 60]
47
+ end
48
+
49
+ it "should return the calculated width according to style filledness" do
50
+ @thing.photo.dimensions(:filled).should == [60, 60]
51
+ @thing.photo.dimensions(:unfilled).should == [120, 90]
52
+ end
53
+
54
+ it "should honor the exif:Orientation header" do
55
+ path = create_image("#{temporary_directory}/test.jpg", :size => '40x30')
56
+ rotated_path = "#{temporary_directory}/rotated-test.jpg"
57
+ run "exif --create-exif --ifd=EXIF --tag=Orientation --set-value=4 --output=#{rotated_path} #{path}"
58
+ open(rotated_path) do |file|
59
+ @thing.photo = file
60
+ @thing.photo.dimensions(:original).should == [30, 40]
61
+ end
62
+ end
63
+
64
+ it "should only invoke identify once"
65
+ it "should log the result"
66
+ end
67
+
68
+ describe "#width" do
69
+ it "should return the width of the default style if no style name is given" do
70
+ @thing.photo.width.should == 80
71
+ end
72
+
73
+ it "should return the width of the given style" do
74
+ @thing.photo.width(:original).should == 40
75
+ @thing.photo.width(:double).should == 80
76
+ end
77
+ end
78
+
79
+ describe "#height" do
80
+ it "should return the height of the default style if no style name is given" do
81
+ @thing.photo.height.should == 60
82
+ end
83
+
84
+ it "should return the height of the given style" do
85
+ @thing.photo.height(:original).should == 30
86
+ @thing.photo.height(:double).should == 60
87
+ end
88
+ end
89
+
90
+ describe "#aspect_ratio" do
91
+ it "should return the aspect ratio of the default style if no style name is given" do
92
+ @thing.photo.aspect_ratio.should be_close(4.0/3, 1e-5)
93
+ end
94
+
95
+ it "should return the aspect ratio of the given style" do
96
+ @thing.photo.aspect_ratio(:original).should be_close(4.0/3, 1e-5)
97
+ @thing.photo.aspect_ratio(:filled).should be_close(1, 1e-5)
98
+ end
99
+ end
100
+
101
+ describe "storable attributes" do
102
+ it "should set the stored attributes on assignment" do
103
+ @thing.photo_width.should == 40
104
+ @thing.photo_height.should == 30
105
+ @thing.photo_aspect_ratio.should be_close(4.0/3, 1e-5)
106
+ @thing.photo_dimensions.should == '40x30'
107
+ end
108
+
109
+ describe "after roundtripping through the database" do
110
+ before do
111
+ @thing.save
112
+ @thing = Thing.find(@thing.id)
113
+ end
114
+
115
+ it "should restore the stored attributes" do
116
+ @thing.photo_width.should == 40
117
+ @thing.photo_height.should == 30
118
+ @thing.photo_aspect_ratio.should be_close(4.0/3, 1e-5)
119
+ @thing.photo_dimensions.should == '40x30'
120
+ end
121
+
122
+ it "should recalculate the dimensions correctly" do
123
+ @thing.photo.dimensions(:filled).should == [60, 60]
124
+ @thing.photo.dimensions(:unfilled).should == [120, 90]
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,126 @@
1
+ require 'spec_helper'
2
+
3
+ describe Attachment::Maybe do
4
+ use_model_class(:Thing, :photo_file_name => :string)
5
+
6
+ before do
7
+ spec = self
8
+ Thing.has_attachment :photo do
9
+ path "#{spec.temporary_directory}/photo.:style.:extension"
10
+ end
11
+ end
12
+
13
+ def configure_attachment(&block)
14
+ spec = self
15
+ Thing.has_attachment :photo do
16
+ instance_exec(spec, &block)
17
+ end
18
+ @thing = Thing.new
19
+ end
20
+
21
+ describe "#interpolate_path" do
22
+ it "should return the path that the given style name would be stored at" do
23
+ spec = self
24
+ configure_attachment do
25
+ path "#{spec.temporary_directory}/:attachment.:style.jpg"
26
+ end
27
+ @thing.photo.interpolate_path(:original).should == "#{temporary_directory}/photo.original.jpg"
28
+ end
29
+
30
+ it "should use the given interpolation parameters" do
31
+ spec = self
32
+ configure_attachment do
33
+ path "#{spec.temporary_directory}/:attachment.:style.jpg"
34
+ end
35
+ @thing.photo.interpolate_path(:original, :style => 'STYLE').should == "#{temporary_directory}/photo.STYLE.jpg"
36
+ end
37
+
38
+ it "should use the style's format attribute for the extension by default" do
39
+ spec = self
40
+ configure_attachment do
41
+ style :processed, :format => 'png'
42
+ path "#{spec.temporary_directory}/:attachment.:style.:extension"
43
+ end
44
+ @thing.photo.interpolate_path(:processed, :format => 'png').should == "#{temporary_directory}/photo.processed.png"
45
+ end
46
+
47
+ describe "for the original style" do
48
+ it "should support the :basename interpolation key if the basename is given" do
49
+ spec = self
50
+ configure_attachment do
51
+ path "#{spec.temporary_directory}/:attachment.:style/:basename"
52
+ end
53
+ @thing.photo.interpolate_path(:original, :basename => 'file.xyz').should == "#{temporary_directory}/photo.original/file.xyz"
54
+ end
55
+
56
+ it "should support the :extension interpolation key if the basename is given" do
57
+ spec = self
58
+ configure_attachment do
59
+ path "#{spec.temporary_directory}/:attachment.:style.:extension"
60
+ end
61
+ @thing.photo.interpolate_path(:original, :basename => 'file.xyz').should == "#{temporary_directory}/photo.original.xyz"
62
+ end
63
+
64
+ it "should support the :extension interpolation key if the extension is given" do
65
+ spec = self
66
+ configure_attachment do
67
+ path "#{spec.temporary_directory}/:attachment.:style.:extension"
68
+ end
69
+ @thing.photo.interpolate_path(:original, :extension => 'xyz').should == "#{temporary_directory}/photo.original.xyz"
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "#interpolate_url" do
75
+ it "should return the url that the given style name would be found at" do
76
+ spec = self
77
+ configure_attachment do
78
+ url "/:attachment.:style.jpg"
79
+ end
80
+ @thing.photo.interpolate_url(:original).should == "/photo.original.jpg"
81
+ end
82
+
83
+ it "should use the given interpolation parameters" do
84
+ spec = self
85
+ configure_attachment do
86
+ url "/:attachment.:style.jpg"
87
+ end
88
+ @thing.photo.interpolate_url(:original, :style => 'STYLE').should == "/photo.STYLE.jpg"
89
+ end
90
+
91
+ it "should use the style's format attribute for the extension by default" do
92
+ spec = self
93
+ configure_attachment do
94
+ style :processed, :format => 'png'
95
+ url "/:attachment.:style.:extension"
96
+ end
97
+ @thing.photo.interpolate_url(:processed).should == "/photo.processed.png"
98
+ end
99
+
100
+ describe "for the original style" do
101
+ it "should support the :basename interpolation key if the basename is given" do
102
+ spec = self
103
+ configure_attachment do
104
+ url "/:attachment.:style/:basename"
105
+ end
106
+ @thing.photo.interpolate_url(:original, :basename => 'file.xyz').should == "/photo.original/file.xyz"
107
+ end
108
+
109
+ it "should support the :extension interpolation key if the basename is given" do
110
+ spec = self
111
+ configure_attachment do
112
+ url "/:attachment.:style.:extension"
113
+ end
114
+ @thing.photo.interpolate_url(:original, :basename => 'file.xyz').should == "/photo.original.xyz"
115
+ end
116
+
117
+ it "should support the :extension interpolation key if the extension is given" do
118
+ spec = self
119
+ configure_attachment do
120
+ url "/:attachment.:style.:extension"
121
+ end
122
+ @thing.photo.interpolate_url(:original, :extension => 'xyz').should == "/photo.original.xyz"
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,137 @@
1
+ require 'spec_helper'
2
+
3
+ describe Attachment::Pdf do
4
+ use_model_class(:Thing,
5
+ :attachment_file_name => :string,
6
+ :attachment_width => :integer,
7
+ :attachment_height => :integer,
8
+ :attachment_aspect_ratio => :float,
9
+ :attachment_dimensions => :string)
10
+
11
+ before do
12
+ Thing.has_attachment :attachment do
13
+ style :double, :size => '1224x1584'
14
+ style :filled, :size => '500x500', :filled => true
15
+ style :unfilled, :size => '1000x1000'
16
+ default_style :double
17
+ end
18
+ @thing = Thing.new(:attachment => test_file)
19
+ end
20
+
21
+ def test_file
22
+ path = "#{temporary_directory}/test.pdf"
23
+ FileUtils.cp("#{ROOT}/spec/data/test.pdf", path)
24
+ autoclose open(path)
25
+ end
26
+
27
+ def configure(&block)
28
+ Thing.attachment_reflections[:attachment].configure(&block)
29
+ end
30
+
31
+ describe "#process" do
32
+ it "should be processed with ImageMagick by default" do
33
+ context = nil
34
+ configure do
35
+ style :output
36
+ process :on => :event do
37
+ context = self
38
+ end
39
+ end
40
+
41
+ @thing.attachment.process(:event)
42
+ context.should be_a(Processor::ImageMagick)
43
+ end
44
+ end
45
+
46
+ describe "#dimensions" do
47
+ it "should return 1x1 if the style is missing" do
48
+ Thing.attachment_reflections[:attachment].configure do
49
+ detect_type_by{:pdf}
50
+ end
51
+ @thing.save.should be_true
52
+ File.unlink(@thing.attachment.path(:original))
53
+ @thing = Thing.find(@thing.id)
54
+ @thing.attachment.is_a?(Attachment::Pdf) # sanity check
55
+ @thing.attachment.stream.missing? # sanity check
56
+ @thing.attachment.dimensions(:original).should == [1, 1]
57
+ end
58
+
59
+ it "should return the width and height of the default style if no style name is given" do
60
+ @thing.attachment.dimensions.should == [1224, 1584]
61
+ end
62
+
63
+ it "should return the width and height of the given style" do
64
+ @thing.attachment.dimensions(:original).should == [612, 792]
65
+ @thing.attachment.dimensions(:double).should == [1224, 1584]
66
+ end
67
+
68
+ it "should return the calculated width according to style filledness" do
69
+ @thing.attachment.dimensions(:filled).should == [500, 500]
70
+ @thing.attachment.dimensions(:unfilled).should == [773, 1000]
71
+ end
72
+
73
+ it "should only invoke identify once"
74
+ it "should log the result"
75
+ end
76
+
77
+ describe "#width" do
78
+ it "should return the width of the default style if no style name is given" do
79
+ @thing.attachment.width.should == 1224
80
+ end
81
+
82
+ it "should return the width of the given style" do
83
+ @thing.attachment.width(:original).should == 612
84
+ @thing.attachment.width(:double).should == 1224
85
+ end
86
+ end
87
+
88
+ describe "#height" do
89
+ it "should return the height of the default style if no style name is given" do
90
+ @thing.attachment.height.should == 1584
91
+ end
92
+
93
+ it "should return the height of the given style" do
94
+ @thing.attachment.height(:original).should == 792
95
+ @thing.attachment.height(:double).should == 1584
96
+ end
97
+ end
98
+
99
+ describe "#aspect_ratio" do
100
+ it "should return the aspect ratio of the default style if no style name is given" do
101
+ @thing.attachment.aspect_ratio.should be_close(612.0/792, 1e-5)
102
+ end
103
+
104
+ it "should return the aspect ratio of the given style" do
105
+ @thing.attachment.aspect_ratio(:original).should be_close(612.0/792, 1e-5)
106
+ @thing.attachment.aspect_ratio(:filled).should be_close(1, 1e-5)
107
+ end
108
+ end
109
+
110
+ describe "storable attributes" do
111
+ it "should set the stored attributes on assignment" do
112
+ @thing.attachment_width.should == 612
113
+ @thing.attachment_height.should == 792
114
+ @thing.attachment_aspect_ratio.should be_close(612.0/792, 1e-5)
115
+ @thing.attachment_dimensions.should == '612x792'
116
+ end
117
+
118
+ describe "after roundtripping through the database" do
119
+ before do
120
+ @thing.save
121
+ @thing = Thing.find(@thing.id)
122
+ end
123
+
124
+ it "should restore the stored attributes" do
125
+ @thing.attachment_width.should == 612
126
+ @thing.attachment_height.should == 792
127
+ @thing.attachment_aspect_ratio.should be_close(612.0/792, 1e-5)
128
+ @thing.attachment_dimensions.should == '612x792'
129
+ end
130
+
131
+ it "should recalculate the dimensions correctly" do
132
+ @thing.attachment.dimensions(:filled).should == [500, 500]
133
+ @thing.attachment.dimensions(:unfilled).should == [773, 1000]
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+
3
+ describe Attachment::Video do
4
+ use_model_class(:Thing,
5
+ :video_file_name => :string,
6
+ :video_width => :integer,
7
+ :video_height => :integer,
8
+ :video_aspect_ratio => :float,
9
+ :video_dimensions => :string,
10
+ :video_duration => :string)
11
+
12
+ before do
13
+ Thing.has_attachment :video do
14
+ # original video is 640x480.
15
+ style :half, :size => '320x240'
16
+ style :filled, :size => '60x60', :filled => true
17
+ style :unfilled, :size => '120x120'
18
+ default_style :half
19
+ end
20
+ @thing = Thing.new(:video => test_file)
21
+ end
22
+
23
+ def test_file
24
+ path = "#{temporary_directory}/test.mov"
25
+ FileUtils.cp("#{ROOT}/spec/data/test.mov", path)
26
+ autoclose open(path)
27
+ end
28
+
29
+ def run(command)
30
+ `#{command}`
31
+ $?.success? or
32
+ raise "command failed: #{command}"
33
+ end
34
+
35
+ describe "#dimensions" do
36
+ it "should return 2x2 if the style is missing" do
37
+ Thing.attachment_reflections[:video].configure do
38
+ detect_type_by{:video}
39
+ end
40
+ @thing.save.should be_true
41
+ File.unlink(@thing.video.path(:original))
42
+ @thing = Thing.find(@thing.id)
43
+ @thing.video.is_a?(Attachment::Video) # sanity check
44
+ @thing.video.stream.missing? # sanity check
45
+ @thing.video.dimensions(:original).should == [2, 2]
46
+ end
47
+
48
+ it "should return the width and height of the default style if no style name is given" do
49
+ @thing.video.dimensions.should == [320, 240]
50
+ end
51
+
52
+ it "should return the width and height of the given style" do
53
+ @thing.video.dimensions(:original).should == [640, 480]
54
+ @thing.video.dimensions(:half).should == [320, 240]
55
+ end
56
+
57
+ it "should return the calculated width according to style filledness" do
58
+ @thing.video.dimensions(:filled).should == [60, 60]
59
+ @thing.video.dimensions(:unfilled).should == [120, 90]
60
+ end
61
+
62
+ it "should round calculated dimensions down to the nearest multiple of 2" do
63
+ # TODO: ick!
64
+ Thing.attachment_reflections[:video].styles[:filled][:size] = '59x59'
65
+ @thing.video.dimensions(:filled).should == [58, 58]
66
+ end
67
+
68
+ it "should only invoke ffmpeg once"
69
+ it "should log the result"
70
+ end
71
+
72
+ describe "#width" do
73
+ it "should return the width of the default style if no style name is given" do
74
+ @thing.video.width.should == 320
75
+ end
76
+
77
+ it "should return the width of the given style" do
78
+ @thing.video.width(:original).should == 640
79
+ @thing.video.width(:half).should == 320
80
+ end
81
+ end
82
+
83
+ describe "#height" do
84
+ it "should return the height of the default style if no style name is given" do
85
+ @thing.video.height.should == 240
86
+ end
87
+
88
+ it "should return the height of the given style" do
89
+ @thing.video.height(:original).should == 480
90
+ @thing.video.height(:half).should == 240
91
+ end
92
+ end
93
+
94
+ describe "#aspect_ratio" do
95
+ it "should return the aspect ratio of the default style if no style name is given" do
96
+ @thing.video.aspect_ratio.should be_close(4.0/3, 1e-5)
97
+ end
98
+
99
+ it "should return the aspect ratio of the given style" do
100
+ @thing.video.aspect_ratio(:original).should be_close(4.0/3, 1e-5)
101
+ @thing.video.aspect_ratio(:filled).should be_close(1, 1e-5)
102
+ end
103
+ end
104
+
105
+ describe "#duration" do
106
+ it "should return the duration of the original style if no style name is given" do
107
+ @thing.video.duration.should == 1.second
108
+ end
109
+
110
+ it "should return the duration of the original style if a style name is given" do
111
+ @thing.video.duration(:filled).should == 1.second
112
+ end
113
+
114
+ # TODO: make these work instead of the above
115
+ it "should return the duration of the default style if no style name is given"
116
+ it "should return the duration of the given style"
117
+ end
118
+
119
+ describe "#video_tracks" do
120
+ it "should return the video tracks of the original style if no style name is given" do
121
+ @thing.video.video_tracks.should have(1).video_track
122
+ @thing.video.video_tracks.first.dimensions.should == [320, 240]
123
+ end
124
+
125
+ it "should return the video tracks of the target style if a style name is given" do
126
+ @thing.video.video_tracks(:original).should have(1).video_track
127
+ @thing.video.video_tracks(:original).first.dimensions.should == [640, 480]
128
+
129
+ @thing.video.video_tracks(:filled).should have(1).video_track
130
+ @thing.video.video_tracks(:filled).first.dimensions.should == [60, 60]
131
+ end
132
+ end
133
+
134
+ describe "#audio_tracks" do
135
+ it "should return the audio tracks of the original style if no style name is given" do
136
+ @thing.video.video_tracks.should have(1).video_track
137
+ @thing.video.video_tracks.first.dimensions.should == [320, 240]
138
+ end
139
+
140
+ it "should return the audio tracks of the target style if a style name is given" do
141
+ @thing.video.video_tracks(:original).should have(1).video_track
142
+ @thing.video.video_tracks(:original).first.dimensions.should == [640, 480]
143
+
144
+ @thing.video.video_tracks(:filled).should have(1).video_track
145
+ @thing.video.video_tracks(:filled).first.dimensions.should == [60, 60]
146
+ end
147
+ end
148
+
149
+ describe "storable attributes" do
150
+ it "should set the stored attributes on assignment" do
151
+ @thing.video_width.should == 640
152
+ @thing.video_height.should == 480
153
+ @thing.video_aspect_ratio.should be_close(4.0/3, 1e-5)
154
+ @thing.video_dimensions.should == '640x480'
155
+ end
156
+
157
+ describe "after roundtripping through the database" do
158
+ before do
159
+ @thing.save
160
+ @thing = Thing.find(@thing.id)
161
+ end
162
+
163
+ it "should restore the stored attributes" do
164
+ @thing.video_width.should == 640
165
+ @thing.video_height.should == 480
166
+ @thing.video_aspect_ratio.should be_close(4.0/3, 1e-5)
167
+ @thing.video_dimensions.should == '640x480'
168
+ end
169
+
170
+ it "should recalculate the dimensions correctly" do
171
+ @thing.video.dimensions(:filled).should == [60, 60]
172
+ @thing.video.dimensions(:unfilled).should == [120, 90]
173
+ end
174
+ end
175
+ end
176
+ end