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,260 @@
1
+ require 'spec_helper'
2
+
3
+ describe Processor::ImageMagick do
4
+ use_model_class(:Thing, :attachment_file_name => :string)
5
+
6
+ before do
7
+ spec = self
8
+ Thing.has_attachment :attachment do
9
+ path "#{spec.temporary_directory}/attachment.:style.:extension"
10
+ end
11
+ thing = Thing.create(:attachment => test_image_file('test.jpg'))
12
+ @thing = Thing.find(thing.id)
13
+ end
14
+
15
+ def convert
16
+ Processor::ImageMagick.convert_command
17
+ end
18
+
19
+ def original_path
20
+ "#{temporary_directory}/attachment.original.jpg"
21
+ end
22
+
23
+ def output_path(style_name=:output)
24
+ "#{temporary_directory}/attachment.#{style_name}.jpg"
25
+ end
26
+
27
+ def configure(&block)
28
+ Thing.attachment_reflections[:attachment].configure(&block)
29
+ end
30
+
31
+ def style(name, attributes={})
32
+ configure do
33
+ style name, attributes
34
+ end
35
+ end
36
+
37
+ def process(&block)
38
+ configure do
39
+ process(:on => :event, :with => :image_magick, &block)
40
+ end
41
+ @thing.attachment.process(:event)
42
+ end
43
+
44
+ describe "#dimensions" do
45
+ it "should yield the dimensions of the input file at that point in the processing pipeline if a block is given" do
46
+ style :output
47
+ values = []
48
+ process do
49
+ dimensions do |*args|
50
+ values << args
51
+ end
52
+ end
53
+ styles = Thing.attachment_reflections[:attachment].styles.to_a
54
+ values.should == [[styles, 40, 30]]
55
+ end
56
+
57
+ it "should yield the dimensions once per branch if called with a block after a branch in the pipeline" do
58
+ style :small, :size => '4x3'
59
+ style :large, :size => '400x300'
60
+ values = []
61
+ process do
62
+ resize
63
+ dimensions do |*args|
64
+ values << args
65
+ end
66
+ end
67
+ styles = Thing.attachment_reflections[:attachment].styles
68
+ values.should have(2).sets_of_arguments
69
+ values[0].should == [[styles[:small]], 4, 3]
70
+ values[1].should == [[styles[:large]], 400, 300]
71
+ end
72
+ end
73
+
74
+ describe "#process" do
75
+ it "should run convert if no block is given" do
76
+ style :output
77
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", output_path).returns('')
78
+ process
79
+ end
80
+
81
+ it "should use the given :quality style attribute" do
82
+ style :output, :quality => 50
83
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-quality', '50', output_path).returns('')
84
+ process
85
+ end
86
+
87
+ it "should use the :colorspace style attribute" do
88
+ style :output, :colorspace => 'rgb'
89
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-colorspace', 'rgb', output_path).returns('')
90
+ process
91
+ end
92
+
93
+ it "should use the :format style attribute to set the file extension if it's specified by the :extension interpolation key" do
94
+ style :output, :format => 'png'
95
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", output_path.sub(/jpg\z/, 'png')).returns('')
96
+ process
97
+ end
98
+
99
+ it "should log the command run if a logger is set" do
100
+ style :output
101
+ log_path = "#{temporary_directory}/log"
102
+ open(log_path, 'w') do |file|
103
+ Bulldog.logger = Logger.new(file)
104
+ process
105
+ end
106
+ File.read(log_path).should include("[Bulldog] Running: #{convert}")
107
+ end
108
+
109
+ it "should not blow up if the logger is set to nil" do
110
+ style :output
111
+ Bulldog.logger = nil
112
+ lambda{process}.should_not raise_error
113
+ end
114
+ end
115
+
116
+ describe "#resize" do
117
+ it "should resize the images to the style's size" do
118
+ style :output, :size => '10x10'
119
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-resize', '10x10', output_path).returns('')
120
+ process{resize}
121
+ end
122
+ end
123
+
124
+ describe "#auto_orient" do
125
+ it "should auto-orient the images" do
126
+ style :output
127
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-auto-orient', output_path).returns('')
128
+ process{auto_orient}
129
+ end
130
+ end
131
+
132
+ describe "#strip" do
133
+ it "should strip the images" do
134
+ style :output, :size => '10x10'
135
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-strip', output_path).returns('')
136
+ process{strip}
137
+ end
138
+ end
139
+
140
+ describe "#flip" do
141
+ it "should flip the image vertically" do
142
+ style :output
143
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-flip', output_path).returns('')
144
+ process{flip}
145
+ end
146
+ end
147
+
148
+ describe "#flop" do
149
+ it "should flip the image horizontally" do
150
+ style :output
151
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-flop', output_path).returns('')
152
+ process{flop}
153
+ end
154
+ end
155
+
156
+ describe "#thumbnail" do
157
+ describe "for filled styles" do
158
+ it "should resize the image to fill the rectangle of the specified size and crop off the edges" do
159
+ style :output, :size => '10x10', :filled => true
160
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-resize', '10x10^',
161
+ '-gravity', 'Center', '-crop', '10x10+0+0', '+repage', output_path).returns('')
162
+ process{thumbnail}
163
+ end
164
+ end
165
+
166
+ describe "for unfilled styles" do
167
+ it "should resize the image to fit inside the specified box size" do
168
+ style :output, :size => '10x10'
169
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-resize', '10x10', output_path).returns('')
170
+ process{thumbnail}
171
+ end
172
+ end
173
+ end
174
+
175
+ describe "#rotate" do
176
+ it "should rotate the image by the given angle" do
177
+ style :output
178
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-rotate', '90', output_path).returns('')
179
+ process{rotate 90}
180
+ end
181
+
182
+ it "should allow the angle to be given by a string" do
183
+ style :output
184
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-rotate', '90', output_path).returns('')
185
+ process{rotate '90'}
186
+ end
187
+
188
+ it "should not perform any rotation if the given angle is zero" do
189
+ style :output
190
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", output_path).returns('')
191
+ process{rotate 0}
192
+ end
193
+
194
+ it "should not perform any rotation if the given angle is blank" do
195
+ style :output
196
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", output_path).returns('')
197
+ process{rotate ''}
198
+ end
199
+ end
200
+
201
+ describe "#crop" do
202
+ it "should crop the image by the given size and origin, and repage" do
203
+ style :output
204
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-crop', '10x20+30-40', '+repage', output_path).returns('')
205
+ process do
206
+ crop(:size => '10x20', :origin => '30,-40')
207
+ end
208
+ end
209
+ end
210
+
211
+ it "should extract a common prefix if there are multiple styles which start with the same operations" do
212
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-auto-orient',
213
+ '(', '+clone', '-resize', '100x100', '-write', output_path(:big), '+delete', ')', '-resize', '40x40', output_path(:small)).returns('')
214
+ style :big, :size => '100x100'
215
+ style :small, :size => '40x40'
216
+ process do
217
+ auto_orient
218
+ resize
219
+ end
220
+ end
221
+
222
+ it "should handle a complex tree of arguments optimally" do
223
+ # The tree:
224
+ # auto-orient
225
+ # resize 10x20
226
+ # flip [:a]
227
+ # flop [:b]
228
+ # flip
229
+ # strip [:c]
230
+ # quality 75 [:d]
231
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-auto-orient',
232
+ '(', '+clone', '-resize', '10x20', '(', '+clone', '-flip', '-write', output_path(:a), '+delete', ')',
233
+ '-flop', '-write', output_path(:b), '+delete', ')',
234
+ '-flip', '(', '+clone', '-flop', '-write', output_path(:c), '+delete', ')',
235
+ '-quality', '75', output_path(:d)).returns('')
236
+ style :a, :size => '10x20'
237
+ style :b, :size => '10x20'
238
+ style :c, :size => '30x40'
239
+ style :d, :size => '30x40', :quality => 75
240
+ process do
241
+ auto_orient
242
+ resize if [:a, :b].include?(style.name)
243
+ flip unless style.name == :b
244
+ flop if [:b, :c].include?(style.name)
245
+ end
246
+ end
247
+
248
+ it "should allow specifying operations for some styles only by checking #style" do
249
+ Bulldog.expects(:run).once.with(convert, "#{original_path}[0]", '-auto-orient',
250
+ '(', '+clone', '-flip', '-write', output_path(:flipped), '+delete', ')',
251
+ '-flop', output_path(:flopped)).returns('')
252
+ style :flipped, :path => '/tmp/flipped.jpg'
253
+ style :flopped, :path => '/tmp/flopped.jpg'
254
+ process do
255
+ auto_orient
256
+ flip if style.name == :flipped
257
+ flop if style.name == :flopped
258
+ end
259
+ end
260
+ end
@@ -0,0 +1,70 @@
1
+ require 'spec_helper'
2
+
3
+ describe Processor::OneShot do
4
+ use_model_class(:Thing)
5
+
6
+ before do
7
+ Thing.has_attachment :attachment
8
+ @thing = Thing.create(:attachment => test_empty_file)
9
+ end
10
+
11
+ def configure(&block)
12
+ Thing.attachment_reflections[:attachment].configure(&block)
13
+ end
14
+
15
+ def process(&block)
16
+ configure do
17
+ process(:on => :event, :with => :one_shot, &block)
18
+ end
19
+ @thing.attachment.process(:event)
20
+ end
21
+
22
+ describe "any one shot processor", :shared => true do
23
+ it "should run the process block exactly once" do
24
+ num_runs = 0
25
+ process do
26
+ num_runs += 1
27
+ end
28
+ num_runs.should == 1
29
+ end
30
+
31
+ it "should provide no styles to the process block" do
32
+ styles = nil
33
+ process do
34
+ styles = self.styles
35
+ end
36
+ styles.should be_empty
37
+ end
38
+
39
+ it "should provide no style to the process block" do
40
+ style = nil
41
+ process do
42
+ style = self.style
43
+ end
44
+ style.should be_nil
45
+ end
46
+
47
+ it "should provide no output files to the process block" do
48
+ output_file = nil
49
+ process do
50
+ output_file = self.output_file(:one)
51
+ end
52
+ output_file.should be_nil
53
+ end
54
+ end
55
+
56
+ describe "when there are no styles defined" do
57
+ it_should_behave_like "any one shot processor"
58
+ end
59
+
60
+ describe "when there are multiple styles defined" do
61
+ before do
62
+ configure do
63
+ style :one
64
+ style :two
65
+ end
66
+ end
67
+
68
+ it_should_behave_like "any one shot processor"
69
+ end
70
+ end
@@ -0,0 +1,338 @@
1
+ require 'spec_helper'
2
+
3
+ describe Reflection do
4
+ use_model_class(:Thing,
5
+ :photo_file_name => :string,
6
+ :photo_content_type => :string,
7
+ :custom_file_name => :string,
8
+ :custom_content_type => :string)
9
+
10
+ def reflection
11
+ Thing.attachment_reflections[:photo]
12
+ end
13
+
14
+ describe "#configure" do
15
+ it "should append the configuration to any existing configuration" do
16
+ Thing.has_attachment :photo do
17
+ path "/custom/path"
18
+ end
19
+ Thing.attachment_reflections[:photo].configure do
20
+ url "/custom/url"
21
+ end
22
+ reflection.path_template.should == "/custom/path"
23
+ reflection.url_template.should == "/custom/url"
24
+ end
25
+
26
+ it "should overwrite any configuration items specified in later blocks" do
27
+ Thing.has_attachment :photo do
28
+ path "/custom/path"
29
+ end
30
+ Thing.has_attachment :photo do
31
+ path "/new/custom/path"
32
+ end
33
+ reflection.path_template.should == "/new/custom/path"
34
+ end
35
+ end
36
+
37
+ describe "configuration" do
38
+ # TODO: Restructure the tests so we test by configuration method,
39
+ # not reflection method.
40
+ describe "#process_once" do
41
+ it "should add a process event with a one_shot processor" do
42
+ Thing.has_attachment :photo do
43
+ process_once(:on => :test_event){}
44
+ end
45
+ events = reflection.events[:test_event]
46
+ events.map(&:processor_type).should == [:one_shot]
47
+ end
48
+
49
+ it "should raise an ArgumentError if a processor is specified" do
50
+ block_run = false
51
+ spec = self
52
+ Thing.has_attachment :photo do
53
+ block_run = true
54
+ lambda{process_once(:with => :image_magick){}}.should spec.raise_error(ArgumentError)
55
+ end
56
+ block_run.should be_true
57
+ end
58
+
59
+ it "should raise an ArgumentError if styles are specified" do
60
+ block_run = false
61
+ spec = self
62
+ Thing.has_attachment :photo do
63
+ block_run = true
64
+ style :one
65
+ lambda{process_once(:styles => [:one]){}}.should spec.raise_error(ArgumentError)
66
+ end
67
+ block_run.should be_true
68
+ end
69
+ end
70
+ end
71
+
72
+ describe "#path_template" do
73
+ describe "when a path has been confired for the attachment" do
74
+ before do
75
+ Thing.has_attachment :photo do
76
+ path "/configured/path"
77
+ end
78
+ end
79
+
80
+ it "should return the configured path" do
81
+ reflection.path_template.should == "/configured/path"
82
+ end
83
+ end
84
+
85
+ describe "when no path has been configured for the attachment, and there is a default path template" do
86
+ use_temporary_attribute_value Bulldog, :default_path_template, "/default/path/template"
87
+
88
+ before do
89
+ Thing.has_attachment :photo
90
+ end
91
+
92
+ it "should return the default path template" do
93
+ reflection.path_template.should == "/default/path/template"
94
+ end
95
+ end
96
+
97
+ describe "when no path has been configured, and there is no default path template" do
98
+ use_temporary_attribute_value Bulldog, :default_path_template, nil
99
+
100
+ before do
101
+ Thing.has_attachment :photo do
102
+ url "/configured/url"
103
+ end
104
+ end
105
+
106
+ it "should return the URL prefixed with the public path" do
107
+ reflection.path_template.should == ":public_path/configured/url"
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "#url_template" do
113
+ describe "when an URL has been configured for the attachment" do
114
+ before do
115
+ Thing.has_attachment :photo do
116
+ url "/path/to/somewhere"
117
+ end
118
+ end
119
+
120
+ it "should return the configured URL template" do
121
+ reflection.url_template.should == "/path/to/somewhere"
122
+ end
123
+ end
124
+
125
+ describe "when no URL has been configured for the attachment" do
126
+ use_temporary_attribute_value Bulldog, :default_url_template, "/default/url/template"
127
+
128
+ before do
129
+ Thing.has_attachment :photo
130
+ end
131
+
132
+ it "should return the default URL template" do
133
+ reflection.url_template.should == "/default/url/template"
134
+ end
135
+ end
136
+ end
137
+
138
+ describe "#style" do
139
+ it "should return the set of styles" do
140
+ Thing.has_attachment :photo do
141
+ style :small, :size => '32x32'
142
+ style :large, :size => '512x512'
143
+ end
144
+ reflection.styles.should == StyleSet[
145
+ Style.new(:small, {:size => '32x32'}),
146
+ Style.new(:large, {:size => '512x512'}),
147
+ ]
148
+ end
149
+ end
150
+
151
+ describe "#default_style" do
152
+ it "should return the configured default_style" do
153
+ Thing.has_attachment :photo do
154
+ style :small, :size => '32x32'
155
+ default_style :small
156
+ end
157
+ reflection.default_style.should == :small
158
+ end
159
+
160
+ it "should default to :original" do
161
+ Thing.has_attachment :photo
162
+ reflection.default_style.should == :original
163
+ end
164
+
165
+ it "should raise an error if the configured default_style is invalid" do
166
+ Thing.has_attachment :photo do
167
+ default_style :bad
168
+ end
169
+ lambda{reflection.default_style}.should raise_error(Error)
170
+ end
171
+ end
172
+
173
+ describe "#events" do
174
+ it "should return the map of configured events" do
175
+ Thing.has_attachment :photo do
176
+ process(:on => :test_event){}
177
+ end
178
+ events = reflection.events[:test_event]
179
+ events.should have(1).event
180
+ events.first.should be_a(Reflection::Event)
181
+ end
182
+
183
+ it "should provide access to the processor type of each event" do
184
+ Thing.has_attachment :photo do
185
+ process(:on => :test_event, :with => :test){}
186
+ end
187
+ event = reflection.events[:test_event].first
188
+ event.processor_type.should == :test
189
+ end
190
+
191
+ it "should have nil as the processor type if the default processor type is to be used" do
192
+ Thing.has_attachment :photo do
193
+ process(:on => :test_event){}
194
+ end
195
+ event = reflection.events[:test_event].first
196
+ event.processor_type.should be_nil
197
+ end
198
+
199
+ it "should have the configured styles" do
200
+ Thing.has_attachment :photo do
201
+ style :small, :size => '10x10'
202
+ style :large, :size => '1000x1000'
203
+ process(:on => :test_event, :styles => [:small]){}
204
+ end
205
+ event = reflection.events[:test_event].first
206
+ event.styles.should == [:small]
207
+ end
208
+
209
+ it "should provide access to the callback of each event" do
210
+ Thing.has_attachment :photo do
211
+ process(:on => :test_event){}
212
+ end
213
+ event = reflection.events[:test_event].first
214
+ event.callback.should be_a(Proc)
215
+ end
216
+ end
217
+
218
+ describe "#detect_type_by" do
219
+ describe "when configured with a symbol" do
220
+ it "should use the named registered type detection proc" do
221
+ args = nil
222
+ Bulldog::Reflection.to_detect_type_by :test_detector do |*args|
223
+ :type
224
+ end
225
+ Thing.has_attachment :photo do
226
+ detect_type_by :test_detector
227
+ end
228
+
229
+ thing = Thing.new
230
+ stream = mock
231
+ reflection.detect_attachment_type(thing, stream).should == :type
232
+ args.should == [thing, :photo, stream]
233
+ end
234
+ end
235
+
236
+ describe "when configured with a block" do
237
+ it "should call the given block" do
238
+ Thing.has_attachment :photo do
239
+ detect_type_by{:type}
240
+ end
241
+ reflection.detect_attachment_type(Thing.new, mock).should == :type
242
+ end
243
+ end
244
+
245
+ describe "when configured with a BoundMethod" do
246
+ it "should call the given method" do
247
+ Thing.stubs(:detect_type).returns(:type)
248
+ Thing.has_attachment :photo do
249
+ detect_type_by Thing.method(:detect_type)
250
+ end
251
+ reflection.detect_attachment_type(Thing.new, mock).should == :type
252
+ end
253
+ end
254
+
255
+ describe "when configured with a proc" do
256
+ it "should call the given proc" do
257
+ Thing.has_attachment :photo do
258
+ detect_type_by lambda{:type}
259
+ end
260
+ reflection.detect_attachment_type(Thing.new, mock).should == :type
261
+ end
262
+ end
263
+
264
+ describe "when configured with both an argument and a block" do
265
+ it "should raise an ArgumentError" do
266
+ block_run = false
267
+ spec = self
268
+ Thing.has_attachment :photo do
269
+ block_run = true
270
+ lambda do
271
+ detect_type_by(lambda{:type}){:type}
272
+ end.should spec.raise_error(ArgumentError)
273
+ end
274
+ block_run.should be_true
275
+ end
276
+ end
277
+
278
+ describe "when configured with #type" do
279
+ it "should return the given type" do
280
+ Thing.has_attachment :photo do
281
+ type :type
282
+ end
283
+ reflection.detect_attachment_type(Thing.new, mock).should == :type
284
+ end
285
+ end
286
+ end
287
+
288
+ describe "#stored_attributes" do
289
+ it "should return the configured stored attributes" do
290
+ Thing.has_attachment :photo do
291
+ store_attributes(
292
+ :file_name => :custom_file_name,
293
+ :content_type => :custom_content_type
294
+ )
295
+ end
296
+ reflection.stored_attributes.should == {
297
+ :file_name => :custom_file_name,
298
+ :content_type => :custom_content_type
299
+ }
300
+ end
301
+
302
+ it "should allow a shortcut if the field names follow convention" do
303
+ Thing.has_attachment :photo do
304
+ store_attributes :file_name, :content_type
305
+ end
306
+ reflection.stored_attributes.should == {
307
+ :file_name => :photo_file_name,
308
+ :content_type => :photo_content_type,
309
+ }
310
+ end
311
+ end
312
+
313
+ describe "#column_name_for_stored_attribute" do
314
+ it "should return nil if the stored attribute has been explicitly mapped to nil" do
315
+ Thing.has_attachment :photo do
316
+ store_attributes :file_name => nil
317
+ end
318
+ reflection.column_name_for_stored_attribute(:file_name).should be_nil
319
+ end
320
+
321
+ it "should return the column specified if has been mapped to a column name" do
322
+ Thing.has_attachment :photo do
323
+ store_attributes :file_name => :photo_content_type
324
+ end
325
+ reflection.column_name_for_stored_attribute(:file_name).should == :photo_content_type
326
+ end
327
+
328
+ it "should return the default column name if the stored attribute is unspecified, and the column exists" do
329
+ Thing.has_attachment :photo
330
+ reflection.column_name_for_stored_attribute(:file_name).should == :photo_file_name
331
+ end
332
+
333
+ it "should return nil if the stored attribute is unspecified, and the column does not exist" do
334
+ Thing.has_attachment :photo
335
+ reflection.column_name_for_stored_attribute(:file_size).should be_nil
336
+ end
337
+ end
338
+ end