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,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