jmcnevin-paperclip 2.4.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/LICENSE +26 -0
  2. data/README.md +414 -0
  3. data/Rakefile +86 -0
  4. data/generators/paperclip/USAGE +5 -0
  5. data/generators/paperclip/paperclip_generator.rb +27 -0
  6. data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  7. data/init.rb +4 -0
  8. data/lib/generators/paperclip/USAGE +8 -0
  9. data/lib/generators/paperclip/paperclip_generator.rb +33 -0
  10. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  11. data/lib/paperclip.rb +480 -0
  12. data/lib/paperclip/attachment.rb +520 -0
  13. data/lib/paperclip/callback_compatibility.rb +61 -0
  14. data/lib/paperclip/geometry.rb +155 -0
  15. data/lib/paperclip/interpolations.rb +171 -0
  16. data/lib/paperclip/iostream.rb +45 -0
  17. data/lib/paperclip/matchers.rb +33 -0
  18. data/lib/paperclip/matchers/have_attached_file_matcher.rb +57 -0
  19. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +81 -0
  20. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +54 -0
  21. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +95 -0
  22. data/lib/paperclip/missing_attachment_styles.rb +87 -0
  23. data/lib/paperclip/options.rb +78 -0
  24. data/lib/paperclip/processor.rb +58 -0
  25. data/lib/paperclip/railtie.rb +26 -0
  26. data/lib/paperclip/storage.rb +3 -0
  27. data/lib/paperclip/storage/filesystem.rb +81 -0
  28. data/lib/paperclip/storage/fog.rb +163 -0
  29. data/lib/paperclip/storage/s3.rb +270 -0
  30. data/lib/paperclip/style.rb +95 -0
  31. data/lib/paperclip/thumbnail.rb +105 -0
  32. data/lib/paperclip/upfile.rb +62 -0
  33. data/lib/paperclip/version.rb +3 -0
  34. data/lib/tasks/paperclip.rake +101 -0
  35. data/rails/init.rb +2 -0
  36. data/shoulda_macros/paperclip.rb +124 -0
  37. data/test/attachment_test.rb +1161 -0
  38. data/test/database.yml +4 -0
  39. data/test/fixtures/12k.png +0 -0
  40. data/test/fixtures/50x50.png +0 -0
  41. data/test/fixtures/5k.png +0 -0
  42. data/test/fixtures/animated.gif +0 -0
  43. data/test/fixtures/bad.png +1 -0
  44. data/test/fixtures/double spaces in name.png +0 -0
  45. data/test/fixtures/fog.yml +8 -0
  46. data/test/fixtures/s3.yml +8 -0
  47. data/test/fixtures/spaced file.png +0 -0
  48. data/test/fixtures/text.txt +1 -0
  49. data/test/fixtures/twopage.pdf +0 -0
  50. data/test/fixtures/uppercase.PNG +0 -0
  51. data/test/fog_test.rb +192 -0
  52. data/test/geometry_test.rb +206 -0
  53. data/test/helper.rb +158 -0
  54. data/test/integration_test.rb +781 -0
  55. data/test/interpolations_test.rb +202 -0
  56. data/test/iostream_test.rb +71 -0
  57. data/test/matchers/have_attached_file_matcher_test.rb +24 -0
  58. data/test/matchers/validate_attachment_content_type_matcher_test.rb +87 -0
  59. data/test/matchers/validate_attachment_presence_matcher_test.rb +26 -0
  60. data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
  61. data/test/options_test.rb +75 -0
  62. data/test/paperclip_missing_attachment_styles_test.rb +80 -0
  63. data/test/paperclip_test.rb +340 -0
  64. data/test/processor_test.rb +10 -0
  65. data/test/storage/filesystem_test.rb +56 -0
  66. data/test/storage/s3_live_test.rb +88 -0
  67. data/test/storage/s3_test.rb +689 -0
  68. data/test/style_test.rb +180 -0
  69. data/test/thumbnail_test.rb +383 -0
  70. data/test/upfile_test.rb +53 -0
  71. metadata +294 -0
@@ -0,0 +1,75 @@
1
+ # encoding: utf-8
2
+ require './test/helper'
3
+
4
+ class MockAttachment < Struct.new(:one, :two)
5
+ def instance
6
+ self
7
+ end
8
+ end
9
+
10
+ class OptionsTest < Test::Unit::TestCase
11
+ should "be able to set a value" do
12
+ @options = Paperclip::Options.new(nil, {})
13
+ assert_nil @options.path
14
+ @options.path = "this/is/a/path"
15
+ assert_equal "this/is/a/path", @options.path
16
+ end
17
+
18
+ context "#styles with a plain hash" do
19
+ setup do
20
+ @attachment = MockAttachment.new(nil, nil)
21
+ @options = Paperclip::Options.new(@attachment,
22
+ :styles => {
23
+ :something => ["400x400", :png]
24
+ })
25
+ end
26
+
27
+ should "return the right data for the style's geometry" do
28
+ assert_equal "400x400", @options.styles[:something][:geometry]
29
+ end
30
+
31
+ should "return the right data for the style's format" do
32
+ assert_equal :png, @options.styles[:something][:format]
33
+ end
34
+ end
35
+
36
+ context "#styles is a proc" do
37
+ setup do
38
+ @attachment = MockAttachment.new("123x456", :doc)
39
+ @options = Paperclip::Options.new(@attachment,
40
+ :styles => lambda {|att|
41
+ {:something => {:geometry => att.one, :format => att.two}}
42
+ })
43
+ end
44
+
45
+ should "return the right data for the style's geometry" do
46
+ assert_equal "123x456", @options.styles[:something][:geometry]
47
+ end
48
+
49
+ should "return the right data for the style's format" do
50
+ assert_equal :doc, @options.styles[:something][:format]
51
+ end
52
+
53
+ should "run the proc each time, giving dynamic results" do
54
+ assert_equal :doc, @options.styles[:something][:format]
55
+ @attachment.two = :pdf
56
+ assert_equal :pdf, @options.styles[:something][:format]
57
+ end
58
+ end
59
+
60
+ context "#processors" do
61
+ setup do
62
+ @attachment = MockAttachment.new(nil, nil)
63
+ end
64
+ should "return processors if not a proc" do
65
+ @options = Paperclip::Options.new(@attachment, :processors => [:one])
66
+ assert_equal [:one], @options.processors
67
+ end
68
+ should "return processors if it is a proc" do
69
+ @options = Paperclip::Options.new(@attachment, :processors => lambda{|att| [att.one]})
70
+ assert_equal [nil], @options.processors
71
+ @attachment.one = :other
72
+ assert_equal [:other], @options.processors
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,80 @@
1
+ require './test/helper'
2
+
3
+ class PaperclipMissingAttachmentStylesTest < Test::Unit::TestCase
4
+
5
+ context "Paperclip" do
6
+ setup do
7
+ Paperclip.classes_with_attachments = Set.new
8
+ end
9
+
10
+ teardown do
11
+ File.unlink(Paperclip.registered_attachments_styles_path) rescue nil
12
+ end
13
+
14
+ should "be able to keep list of models using it" do
15
+ assert_kind_of Set, Paperclip.classes_with_attachments
16
+ assert Paperclip.classes_with_attachments.empty?, 'list should be empty'
17
+ rebuild_model
18
+ assert_equal ['Dummy'].to_set, Paperclip.classes_with_attachments
19
+ end
20
+
21
+ should "enable to get and set path to registered styles file" do
22
+ assert_equal ROOT.join('public/system/paperclip_attachments.yml').to_s, Paperclip.registered_attachments_styles_path
23
+ Paperclip.registered_attachments_styles_path = '/tmp/config/paperclip_attachments.yml'
24
+ assert_equal '/tmp/config/paperclip_attachments.yml', Paperclip.registered_attachments_styles_path
25
+ Paperclip.registered_attachments_styles_path = nil
26
+ assert_equal ROOT.join('public/system/paperclip_attachments.yml').to_s, Paperclip.registered_attachments_styles_path
27
+ end
28
+
29
+ should "be able to get current attachment styles" do
30
+ assert_equal Hash.new, Paperclip.send(:current_attachments_styles)
31
+ rebuild_model :styles => {:croppable => '600x600>', :big => '1000x1000>'}
32
+ expected_hash = { :Dummy => {:avatar => [:big, :croppable]}}
33
+ assert_equal expected_hash, Paperclip.send(:current_attachments_styles)
34
+ end
35
+
36
+ should "be able to save current attachment styles for further comparison" do
37
+ rebuild_model :styles => {:croppable => '600x600>', :big => '1000x1000>'}
38
+ Paperclip.save_current_attachments_styles!
39
+ expected_hash = { :Dummy => {:avatar => [:big, :croppable]}}
40
+ assert_equal expected_hash, YAML.load_file(Paperclip.registered_attachments_styles_path)
41
+ end
42
+
43
+ should "be able to read registered attachment styles from file" do
44
+ rebuild_model :styles => {:croppable => '600x600>', :big => '1000x1000>'}
45
+ Paperclip.save_current_attachments_styles!
46
+ expected_hash = { :Dummy => {:avatar => [:big, :croppable]}}
47
+ assert_equal expected_hash, Paperclip.send(:get_registered_attachments_styles)
48
+ end
49
+
50
+ should "be able to calculate differences between registered styles and current styles" do
51
+ rebuild_model :styles => {:croppable => '600x600>', :big => '1000x1000>'}
52
+ Paperclip.save_current_attachments_styles!
53
+ rebuild_model :styles => {:thumb => 'x100', :export => 'x400>', :croppable => '600x600>', :big => '1000x1000>'}
54
+ expected_hash = { :Dummy => {:avatar => [:export, :thumb]} }
55
+ assert_equal expected_hash, Paperclip.missing_attachments_styles
56
+
57
+ ActiveRecord::Base.connection.create_table :books, :force => true
58
+ class ::Book < ActiveRecord::Base
59
+ has_attached_file :cover, :styles => {:small => 'x100', :large => '1000x1000>'}
60
+ has_attached_file :sample, :styles => {:thumb => 'x100'}
61
+ end
62
+
63
+ expected_hash = {
64
+ :Dummy => {:avatar => [:export, :thumb]},
65
+ :Book => {:sample => [:thumb], :cover => [:large, :small]}
66
+ }
67
+ assert_equal expected_hash, Paperclip.missing_attachments_styles
68
+ Paperclip.save_current_attachments_styles!
69
+ assert_equal Hash.new, Paperclip.missing_attachments_styles
70
+ end
71
+
72
+ # It's impossible to build styles hash without loading from database whole bunch of records
73
+ should "skip lambda-styles" do
74
+ rebuild_model :styles => lambda{ |attachment| attachment.instance.other == 'a' ? {:thumb => "50x50#"} : {:large => "400x400"} }
75
+ assert_equal Hash.new, Paperclip.send(:current_attachments_styles)
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,340 @@
1
+ require './test/helper'
2
+
3
+ class PaperclipTest < Test::Unit::TestCase
4
+ context "Calling Paperclip.run" do
5
+ setup do
6
+ Paperclip.options[:log_command] = false
7
+ Cocaine::CommandLine.expects(:new).with("convert", "stuff", {}).returns(stub(:run))
8
+ @original_command_line_path = Cocaine::CommandLine.path
9
+ end
10
+
11
+ teardown do
12
+ Paperclip.options[:log_command] = true
13
+ Cocaine::CommandLine.path = @original_command_line_path
14
+ end
15
+
16
+ should "run the command with Cocaine" do
17
+ Paperclip.run("convert", "stuff")
18
+ end
19
+
20
+ should "save Cocaine::CommandLine.path that set before" do
21
+ Cocaine::CommandLine.path = "/opt/my_app/bin"
22
+ Paperclip.run("convert", "stuff")
23
+ assert_equal [Cocaine::CommandLine.path].flatten.include?("/opt/my_app/bin"), true
24
+ end
25
+ end
26
+
27
+ context "Calling Paperclip.run with a logger" do
28
+ should "pass the defined logger if :log_command is set" do
29
+ Paperclip.options[:log_command] = true
30
+ Cocaine::CommandLine.expects(:new).with("convert", "stuff", :logger => Paperclip.logger).returns(stub(:run))
31
+ Paperclip.run("convert", "stuff")
32
+ end
33
+ end
34
+
35
+ context "Paperclip.each_instance_with_attachment" do
36
+ setup do
37
+ @file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
38
+ d1 = Dummy.create(:avatar => @file)
39
+ d2 = Dummy.create
40
+ d3 = Dummy.create(:avatar => @file)
41
+ @expected = [d1, d3]
42
+ end
43
+ should "yield every instance of a model that has an attachment" do
44
+ actual = []
45
+ Paperclip.each_instance_with_attachment("Dummy", "avatar") do |instance|
46
+ actual << instance
47
+ end
48
+ assert_same_elements @expected, actual
49
+ end
50
+ end
51
+
52
+ should "raise when sent #processor and the name of a class that doesn't exist" do
53
+ assert_raises(NameError){ Paperclip.processor(:boogey_man) }
54
+ end
55
+
56
+ should "return a class when sent #processor and the name of a class under Paperclip" do
57
+ assert_equal ::Paperclip::Thumbnail, Paperclip.processor(:thumbnail)
58
+ end
59
+
60
+ should "get a class from a namespaced class name" do
61
+ class ::One; class Two; end; end
62
+ assert_equal ::One::Two, Paperclip.class_for("One::Two")
63
+ end
64
+
65
+ should "raise when class doesn't exist in specified namespace" do
66
+ class ::Three; end
67
+ class ::Four; end
68
+ assert_raise NameError do
69
+ Paperclip.class_for("Three::Four")
70
+ end
71
+ end
72
+
73
+ context "Attachments with clashing URLs should raise error" do
74
+ setup do
75
+ class Dummy2 < ActiveRecord::Base
76
+ include Paperclip::Glue
77
+ end
78
+ end
79
+
80
+ should "generate warning if attachment is redefined with the same url string" do
81
+ Paperclip.expects(:log).with("Duplicate URL for blah with /system/:attachment/:id/:style/:filename. This will clash with attachment defined in Dummy class")
82
+ Dummy.class_eval do
83
+ has_attached_file :blah
84
+ end
85
+ Dummy2.class_eval do
86
+ has_attached_file :blah
87
+ end
88
+ end
89
+ end
90
+
91
+ context "An ActiveRecord model with an 'avatar' attachment" do
92
+ setup do
93
+ rebuild_model :path => "tmp/:class/omg/:style.:extension"
94
+ @file = File.new(File.join(FIXTURES_DIR, "5k.png"), 'rb')
95
+ end
96
+
97
+ teardown { @file.close }
98
+
99
+ should "not error when trying to also create a 'blah' attachment" do
100
+ assert_nothing_raised do
101
+ Dummy.class_eval do
102
+ has_attached_file :blah
103
+ end
104
+ end
105
+ end
106
+
107
+ context "that is attr_protected" do
108
+ setup do
109
+ Dummy.class_eval do
110
+ attr_protected :avatar
111
+ end
112
+ @dummy = Dummy.new
113
+ end
114
+
115
+ should "not assign the avatar on mass-set" do
116
+ @dummy.attributes = { :other => "I'm set!",
117
+ :avatar => @file }
118
+
119
+ assert_equal "I'm set!", @dummy.other
120
+ assert ! @dummy.avatar?
121
+ end
122
+
123
+ should "still allow assigment on normal set" do
124
+ @dummy.other = "I'm set!"
125
+ @dummy.avatar = @file
126
+
127
+ assert_equal "I'm set!", @dummy.other
128
+ assert @dummy.avatar?
129
+ end
130
+ end
131
+
132
+ context "with a subclass" do
133
+ setup do
134
+ class ::SubDummy < Dummy; end
135
+ end
136
+
137
+ should "be able to use the attachment from the subclass" do
138
+ assert_nothing_raised do
139
+ @subdummy = SubDummy.create(:avatar => @file)
140
+ end
141
+ end
142
+
143
+ should "be able to see the attachment definition from the subclass's class" do
144
+ assert_equal "tmp/:class/omg/:style.:extension",
145
+ SubDummy.attachment_definitions[:avatar][:path]
146
+ end
147
+
148
+ teardown do
149
+ Object.send(:remove_const, "SubDummy") rescue nil
150
+ end
151
+ end
152
+
153
+ should "have an #avatar method" do
154
+ assert Dummy.new.respond_to?(:avatar)
155
+ end
156
+
157
+ should "have an #avatar= method" do
158
+ assert Dummy.new.respond_to?(:avatar=)
159
+ end
160
+
161
+ context "that is valid" do
162
+ setup do
163
+ @dummy = Dummy.new
164
+ @dummy.avatar = @file
165
+ end
166
+
167
+ should "be valid" do
168
+ assert @dummy.valid?
169
+ end
170
+ end
171
+
172
+ context "a validation with an if guard clause" do
173
+ setup do
174
+ Dummy.send(:"validates_attachment_presence", :avatar, :if => lambda{|i| i.foo })
175
+ @dummy = Dummy.new
176
+ @dummy.stubs(:avatar_file_name).returns(nil)
177
+ end
178
+
179
+ should "attempt validation if the guard returns true" do
180
+ @dummy.expects(:foo).returns(true)
181
+ assert ! @dummy.valid?
182
+ end
183
+
184
+ should "not attempt validation if the guard returns false" do
185
+ @dummy.expects(:foo).returns(false)
186
+ assert @dummy.valid?
187
+ end
188
+ end
189
+
190
+ context "a validation with an unless guard clause" do
191
+ setup do
192
+ Dummy.send(:"validates_attachment_presence", :avatar, :unless => lambda{|i| i.foo })
193
+ @dummy = Dummy.new
194
+ @dummy.stubs(:avatar_file_name).returns(nil)
195
+ end
196
+
197
+ should "attempt validation if the guard returns true" do
198
+ @dummy.expects(:foo).returns(false)
199
+ assert ! @dummy.valid?
200
+ end
201
+
202
+ should "not attempt validation if the guard returns false" do
203
+ @dummy.expects(:foo).returns(true)
204
+ assert @dummy.valid?
205
+ end
206
+ end
207
+
208
+ should "not have Attachment in the ActiveRecord::Base namespace" do
209
+ assert_raises(NameError) do
210
+ ActiveRecord::Base::Attachment
211
+ end
212
+ end
213
+
214
+ def self.should_validate validation, options, valid_file, invalid_file
215
+ context "with #{validation} validation and #{options.inspect} options" do
216
+ setup do
217
+ rebuild_class
218
+ Dummy.send(:"validates_attachment_#{validation}", :avatar, options)
219
+ @dummy = Dummy.new
220
+ end
221
+ context "and assigning nil" do
222
+ setup do
223
+ @dummy.avatar = nil
224
+ @dummy.valid?
225
+ end
226
+ if validation == :presence
227
+ should "have an error on the attachment" do
228
+ assert @dummy.errors[:avatar]
229
+ assert @dummy.errors[:avatar_file_name]
230
+ end
231
+ else
232
+ should "not have an error on the attachment" do
233
+ assert @dummy.errors.blank?, @dummy.errors.full_messages.join(", ")
234
+ end
235
+ end
236
+ end
237
+ context "and assigned a valid file" do
238
+ setup do
239
+ @dummy.avatar = valid_file
240
+ @dummy.valid?
241
+ end
242
+ should "not have an error" do
243
+ assert_equal 0, @dummy.errors.size, @dummy.errors.full_messages.join(", ")
244
+ end
245
+ end
246
+ context "and assigned an invalid file" do
247
+ setup do
248
+ @dummy.avatar = invalid_file
249
+ @dummy.valid?
250
+ end
251
+ should "have an error" do
252
+ assert @dummy.errors.size > 0
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ [[:presence, {}, "5k.png", nil],
259
+ [:size, {:in => 1..10240}, "5k.png", "12k.png"],
260
+ [:size, {:less_than => 10240}, "5k.png", "12k.png"],
261
+ [:size, {:greater_than => 8096}, "12k.png", "5k.png"],
262
+ [:content_type, {:content_type => "image/png"}, "5k.png", "text.txt"],
263
+ [:content_type, {:content_type => "text/plain"}, "text.txt", "5k.png"],
264
+ [:content_type, {:content_type => %r{image/.*}}, "5k.png", "text.txt"]].each do |args|
265
+ validation, options, valid_file, invalid_file = args
266
+ valid_file &&= File.open(File.join(FIXTURES_DIR, valid_file), "rb")
267
+ invalid_file &&= File.open(File.join(FIXTURES_DIR, invalid_file), "rb")
268
+
269
+ should_validate validation, options, valid_file, invalid_file
270
+ end
271
+
272
+ context "with content_type validation and lambda message" do
273
+ context "and assigned an invalid file" do
274
+ setup do
275
+ Dummy.send(:"validates_attachment_content_type", :avatar, :content_type => %r{image/.*}, :message => lambda {'lambda content type message'})
276
+ @dummy = Dummy.new
277
+ @dummy.avatar &&= File.open(File.join(FIXTURES_DIR, "text.txt"), "rb")
278
+ @dummy.valid?
279
+ end
280
+
281
+ should "have a content type error message" do
282
+ assert [@dummy.errors[:avatar_content_type]].flatten.any?{|error| error =~ %r/lambda content type message/ }
283
+ end
284
+ end
285
+ end
286
+
287
+ context "with size validation and less_than 10240 option" do
288
+ context "and assigned an invalid file" do
289
+ setup do
290
+ Dummy.send(:"validates_attachment_size", :avatar, :less_than => 10240)
291
+ @dummy = Dummy.new
292
+ @dummy.avatar &&= File.open(File.join(FIXTURES_DIR, "12k.png"), "rb")
293
+ @dummy.valid?
294
+ end
295
+
296
+ should "have a file size min/max error message" do
297
+ assert [@dummy.errors[:avatar_file_size]].flatten.any?{|error| error =~ %r/between 0 and 10240 bytes/ }
298
+ end
299
+ end
300
+ end
301
+
302
+ context "with size validation and less_than 10240 option with lambda message" do
303
+ context "and assigned an invalid file" do
304
+ setup do
305
+ Dummy.send(:"validates_attachment_size", :avatar, :less_than => 10240, :message => lambda {'lambda between 0 and 10240 bytes'})
306
+ @dummy = Dummy.new
307
+ @dummy.avatar &&= File.open(File.join(FIXTURES_DIR, "12k.png"), "rb")
308
+ @dummy.valid?
309
+ end
310
+
311
+ should "have a file size min/max error message" do
312
+ assert [@dummy.errors[:avatar_file_size]].flatten.any?{|error| error =~ %r/lambda between 0 and 10240 bytes/ }
313
+ end
314
+ end
315
+ end
316
+
317
+ end
318
+
319
+ context "configuring a custom processor" do
320
+ setup do
321
+ @freedom_processor = Class.new do
322
+ def make(file, options = {}, attachment = nil)
323
+ file
324
+ end
325
+ end.new
326
+
327
+ Paperclip.configure do |config|
328
+ config.register_processor(:freedom, @freedom_processor)
329
+ end
330
+ end
331
+
332
+ should "be able to find the custom processor" do
333
+ assert_equal @freedom_processor, Paperclip.processor(:freedom)
334
+ end
335
+
336
+ teardown do
337
+ Paperclip.clear_processors!
338
+ end
339
+ end
340
+ end