jmcnevin-paperclip 2.4.5

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