paperclip 2.2.6 → 2.2.7
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of paperclip might be problematic. Click here for more details.
- data/lib/paperclip/attachment.rb +28 -9
- data/lib/paperclip/storage.rb +19 -1
- data/lib/paperclip.rb +1 -1
- data/shoulda_macros/paperclip.rb +1 -1
- data/test/attachment_test.rb +44 -4
- data/test/integration_test.rb +10 -22
- data/test/storage_test.rb +49 -0
- metadata +2 -2
data/lib/paperclip/attachment.rb
CHANGED
@@ -62,6 +62,9 @@ module Paperclip
|
|
62
62
|
# If the file that is assigned is not valid, the processing (i.e.
|
63
63
|
# thumbnailing, etc) will NOT be run.
|
64
64
|
def assign uploaded_file
|
65
|
+
# This is because of changes in Rails 2.3 that cause blank fields to send nil
|
66
|
+
return nil if uploaded_file.nil?
|
67
|
+
|
65
68
|
%w(file_name).each do |field|
|
66
69
|
unless @instance.class.column_names.include?("#{name}_#{field}")
|
67
70
|
raise PaperclipError.new("#{@instance.class} model does not have required column '#{name}_#{field}'")
|
@@ -92,7 +95,6 @@ module Paperclip
|
|
92
95
|
|
93
96
|
@dirty = true
|
94
97
|
|
95
|
-
solidify_style_definitions
|
96
98
|
post_process if valid?
|
97
99
|
|
98
100
|
# Reset the file size if the original file was reprocessed.
|
@@ -159,6 +161,23 @@ module Paperclip
|
|
159
161
|
end
|
160
162
|
end
|
161
163
|
|
164
|
+
# Clears out the attachment. Has the same effect as previously assigning
|
165
|
+
# nil to the attachment. Does NOT save. If you wish to clear AND save,
|
166
|
+
# use #destroy.
|
167
|
+
def clear
|
168
|
+
queue_existing_for_delete
|
169
|
+
@errors = {}
|
170
|
+
@validation_errors = nil
|
171
|
+
end
|
172
|
+
|
173
|
+
# Destroys the attachment. Has the same effect as previously assigning
|
174
|
+
# nil to the attachment *and saving*. This is permanent. If you wish to
|
175
|
+
# wipe out the existing attachment but not save, use #clear.
|
176
|
+
def destroy
|
177
|
+
clear
|
178
|
+
save
|
179
|
+
end
|
180
|
+
|
162
181
|
# Returns the name of the file as originally assigned, and lives in the
|
163
182
|
# <attachment>_file_name attribute of the model.
|
164
183
|
def original_filename
|
@@ -274,7 +293,7 @@ module Paperclip
|
|
274
293
|
end
|
275
294
|
|
276
295
|
def valid_assignment? file #:nodoc:
|
277
|
-
file.
|
296
|
+
file.respond_to?(:original_filename) && file.respond_to?(:content_type)
|
278
297
|
end
|
279
298
|
|
280
299
|
def validate #:nodoc:
|
@@ -314,9 +333,8 @@ module Paperclip
|
|
314
333
|
|
315
334
|
def solidify_style_definitions #:nodoc:
|
316
335
|
@styles.each do |name, args|
|
317
|
-
if @styles[name][:geometry].respond_to?(:call)
|
318
|
-
|
319
|
-
end
|
336
|
+
@styles[name][:geometry] = @styles[name][:geometry].call(instance) if @styles[name][:geometry].respond_to?(:call)
|
337
|
+
@styles[name][:processors] = @styles[name][:processors].call(instance) if @styles[name][:processors].respond_to?(:call)
|
320
338
|
end
|
321
339
|
end
|
322
340
|
|
@@ -336,6 +354,7 @@ module Paperclip
|
|
336
354
|
|
337
355
|
def post_process #:nodoc:
|
338
356
|
return if @queued_for_write[:original].nil?
|
357
|
+
solidify_style_definitions
|
339
358
|
return if fire_events(:before)
|
340
359
|
post_process_styles
|
341
360
|
return if fire_events(:after)
|
@@ -346,6 +365,10 @@ module Paperclip
|
|
346
365
|
return true if callback(:"#{which}_#{name}_post_process") == false
|
347
366
|
end
|
348
367
|
|
368
|
+
def callback which #:nodoc:
|
369
|
+
instance.run_callbacks(which, @queued_for_write){|result, obj| result == false }
|
370
|
+
end
|
371
|
+
|
349
372
|
def post_process_styles
|
350
373
|
log("Post-processing #{name}")
|
351
374
|
@styles.each do |name, args|
|
@@ -362,10 +385,6 @@ module Paperclip
|
|
362
385
|
end
|
363
386
|
end
|
364
387
|
|
365
|
-
def callback which #:nodoc:
|
366
|
-
instance.run_callbacks(which, @queued_for_write){|result, obj| result == false }
|
367
|
-
end
|
368
|
-
|
369
388
|
def interpolate pattern, style = default_style #:nodoc:
|
370
389
|
interpolations = self.class.interpolations.sort{|a,b| a.first.to_s <=> b.first.to_s }
|
371
390
|
interpolations.reverse.inject( pattern.dup ) do |result, interpolation|
|
data/lib/paperclip/storage.rb
CHANGED
@@ -108,11 +108,21 @@ module Paperclip
|
|
108
108
|
# Paperclip will attempt to create it. The bucket name will not be interpolated.
|
109
109
|
# You can define the bucket as a Proc if you want to determine it's name at runtime.
|
110
110
|
# Paperclip will call that Proc with attachment as the only argument.
|
111
|
-
# * +
|
111
|
+
# * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
|
112
|
+
# S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
|
113
|
+
# link in the +url+ entry for more information about S3 domains and buckets.
|
114
|
+
# * +url+: There are three options for the S3 url. You can choose to have the bucket's name
|
112
115
|
# placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
|
116
|
+
# Lastly, you can specify a CNAME (which requires the CNAME to be specified as
|
117
|
+
# :s3_alias_url. You can read more about CNAMEs and S3 at
|
118
|
+
# http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
|
113
119
|
# Normally, this won't matter in the slightest and you can leave the default (which is
|
114
120
|
# path-style, or :s3_path_url). But in some cases paths don't work and you need to use
|
115
121
|
# the domain-style (:s3_domain_url). Anything else here will be treated like path-style.
|
122
|
+
# NOTE: If you use a CNAME for use with CloudFront, you can NOT specify https as your
|
123
|
+
# :s3_protocol; This is *not supported* by S3/CloudFront. Finally, when using the host
|
124
|
+
# alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
|
125
|
+
# by S3.
|
116
126
|
# * +path+: This is the key under the bucket in which the file will be stored. The
|
117
127
|
# URL will be constructed from the bucket and the path. This is what you will want
|
118
128
|
# to interpolate. Keys should be unique, like filenames, and despite the fact that
|
@@ -129,8 +139,12 @@ module Paperclip
|
|
129
139
|
@s3_permissions = @options[:s3_permissions] || 'public-read'
|
130
140
|
@s3_protocol = @options[:s3_protocol] || (@s3_permissions == 'public-read' ? 'http' : 'https')
|
131
141
|
@s3_headers = @options[:s3_headers] || {}
|
142
|
+
@s3_host_alias = @options[:s3_host_alias]
|
132
143
|
@url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
|
133
144
|
end
|
145
|
+
base.class.interpolations[:s3_alias_url] = lambda do |attachment, style|
|
146
|
+
"#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
147
|
+
end
|
134
148
|
base.class.interpolations[:s3_path_url] = lambda do |attachment, style|
|
135
149
|
"#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
136
150
|
end
|
@@ -154,6 +168,10 @@ module Paperclip
|
|
154
168
|
@bucket
|
155
169
|
end
|
156
170
|
|
171
|
+
def s3_host_alias
|
172
|
+
@s3_host_alias
|
173
|
+
end
|
174
|
+
|
157
175
|
def parse_credentials creds
|
158
176
|
creds = find_credentials(creds).stringify_keys
|
159
177
|
(creds[ENV['RAILS_ENV']] || creds).symbolize_keys
|
data/lib/paperclip.rb
CHANGED
data/shoulda_macros/paperclip.rb
CHANGED
@@ -38,7 +38,7 @@ module Paperclip
|
|
38
38
|
klass = self.name.gsub(/Test$/, '').constantize
|
39
39
|
valid = [options[:valid]].flatten
|
40
40
|
invalid = [options[:invalid]].flatten
|
41
|
-
matcher =
|
41
|
+
matcher = validate_attachment_content_type(name).allowing(valid).rejecting(invalid)
|
42
42
|
should matcher.description do
|
43
43
|
assert_accepts(matcher, klass)
|
44
44
|
end
|
data/test/attachment_test.rb
CHANGED
@@ -265,7 +265,28 @@ class AttachmentTest < Test::Unit::TestCase
|
|
265
265
|
end
|
266
266
|
end
|
267
267
|
end
|
268
|
-
|
268
|
+
|
269
|
+
context "An attachment with :processors that is a proc" do
|
270
|
+
setup do
|
271
|
+
rebuild_model :styles => { :normal => '' }, :processors => lambda { |a| [ :test ] }
|
272
|
+
@attachment = Dummy.new.avatar
|
273
|
+
end
|
274
|
+
|
275
|
+
should "not run the proc immediately" do
|
276
|
+
assert_kind_of Proc, @attachment.styles[:normal][:processors]
|
277
|
+
end
|
278
|
+
|
279
|
+
context "when assigned" do
|
280
|
+
setup do
|
281
|
+
@attachment.assign(StringIO.new("."))
|
282
|
+
end
|
283
|
+
|
284
|
+
should "have the correct processors" do
|
285
|
+
assert_equal [ :test ], @attachment.styles[:normal][:processors]
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
269
290
|
context "An attachment with erroring processor" do
|
270
291
|
setup do
|
271
292
|
rebuild_model :processor => [:thumbnail], :styles => { :small => '' }, :whiny_thumbnails => true
|
@@ -571,20 +592,39 @@ class AttachmentTest < Test::Unit::TestCase
|
|
571
592
|
file.close
|
572
593
|
end
|
573
594
|
|
574
|
-
context "and
|
595
|
+
context "and trying to delete" do
|
575
596
|
setup do
|
576
597
|
@existing_names = @attachment.styles.keys.collect do |style|
|
577
598
|
@attachment.path(style)
|
578
599
|
end
|
600
|
+
end
|
601
|
+
|
602
|
+
should "not delete the files saving in a deprecated manner" do
|
603
|
+
@attachment.expects(:instance_write).with(:file_name, nil).never
|
604
|
+
@attachment.expects(:instance_write).with(:content_type, nil).never
|
605
|
+
@attachment.expects(:instance_write).with(:file_size, nil).never
|
606
|
+
@attachment.expects(:instance_write).with(:updated_at, nil).never
|
607
|
+
@attachment.assign nil
|
608
|
+
@attachment.save
|
609
|
+
@existing_names.each{|f| assert File.exists?(f) }
|
610
|
+
end
|
611
|
+
|
612
|
+
should "delete the files when you call #clear and #save" do
|
579
613
|
@attachment.expects(:instance_write).with(:file_name, nil)
|
580
614
|
@attachment.expects(:instance_write).with(:content_type, nil)
|
581
615
|
@attachment.expects(:instance_write).with(:file_size, nil)
|
582
616
|
@attachment.expects(:instance_write).with(:updated_at, nil)
|
583
|
-
@attachment.
|
617
|
+
@attachment.clear
|
584
618
|
@attachment.save
|
619
|
+
@existing_names.each{|f| assert ! File.exists?(f) }
|
585
620
|
end
|
586
621
|
|
587
|
-
should "delete the files" do
|
622
|
+
should "delete the files when you call #delete" do
|
623
|
+
@attachment.expects(:instance_write).with(:file_name, nil)
|
624
|
+
@attachment.expects(:instance_write).with(:content_type, nil)
|
625
|
+
@attachment.expects(:instance_write).with(:file_size, nil)
|
626
|
+
@attachment.expects(:instance_write).with(:updated_at, nil)
|
627
|
+
@attachment.destroy
|
588
628
|
@existing_names.each{|f| assert ! File.exists?(f) }
|
589
629
|
end
|
590
630
|
end
|
data/test/integration_test.rb
CHANGED
@@ -39,6 +39,7 @@ class IntegrationTest < Test::Unit::TestCase
|
|
39
39
|
setup do
|
40
40
|
Dummy.class_eval do
|
41
41
|
has_attached_file :avatar, :styles => { :thumb => "150x25#" }
|
42
|
+
has_attached_file :avatar, :styles => { :thumb => "150x25#", :dynamic => lambda { |a| '50x50#' } }
|
42
43
|
end
|
43
44
|
@d2 = Dummy.find(@dummy.id)
|
44
45
|
@d2.avatar.reprocess!
|
@@ -47,6 +48,7 @@ class IntegrationTest < Test::Unit::TestCase
|
|
47
48
|
|
48
49
|
should "create its thumbnails properly" do
|
49
50
|
assert_match /\b150x25\b/, `identify "#{@dummy.avatar.path(:thumb)}"`
|
51
|
+
assert_match /\b50x50\b/, `identify "#{@dummy.avatar.path(:dynamic)}"`
|
50
52
|
end
|
51
53
|
end
|
52
54
|
end
|
@@ -94,7 +96,7 @@ class IntegrationTest < Test::Unit::TestCase
|
|
94
96
|
|
95
97
|
context "and deleted" do
|
96
98
|
setup do
|
97
|
-
@dummy.avatar
|
99
|
+
@dummy.avatar.clear
|
98
100
|
@dummy.save
|
99
101
|
end
|
100
102
|
|
@@ -233,7 +235,7 @@ class IntegrationTest < Test::Unit::TestCase
|
|
233
235
|
assert File.exists?(p)
|
234
236
|
end
|
235
237
|
|
236
|
-
@dummy.avatar
|
238
|
+
@dummy.avatar.clear
|
237
239
|
assert_nil @dummy.avatar_file_name
|
238
240
|
assert @dummy.valid?
|
239
241
|
assert @dummy.save
|
@@ -256,7 +258,7 @@ class IntegrationTest < Test::Unit::TestCase
|
|
256
258
|
|
257
259
|
saved_paths = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.path(s) }
|
258
260
|
|
259
|
-
@d2.avatar
|
261
|
+
@d2.avatar.clear
|
260
262
|
assert @d2.save
|
261
263
|
|
262
264
|
saved_paths.each do |p|
|
@@ -264,7 +266,7 @@ class IntegrationTest < Test::Unit::TestCase
|
|
264
266
|
end
|
265
267
|
end
|
266
268
|
|
267
|
-
should "know the difference between good files, bad files, not files
|
269
|
+
should "know the difference between good files, bad files, and not files" do
|
268
270
|
expected = @dummy.avatar.to_file
|
269
271
|
@dummy.avatar = "not a file"
|
270
272
|
assert @dummy.valid?
|
@@ -273,25 +275,21 @@ class IntegrationTest < Test::Unit::TestCase
|
|
273
275
|
|
274
276
|
@dummy.avatar = @bad_file
|
275
277
|
assert ! @dummy.valid?
|
276
|
-
@dummy.avatar = nil
|
277
|
-
assert @dummy.valid?, @dummy.errors.inspect
|
278
278
|
end
|
279
279
|
|
280
|
-
should "know the difference between good files, bad files, not files
|
280
|
+
should "know the difference between good files, bad files, and not files when validating" do
|
281
281
|
Dummy.validates_attachment_presence :avatar
|
282
282
|
@d2 = Dummy.find(@dummy.id)
|
283
283
|
@d2.avatar = @file
|
284
284
|
assert @d2.valid?, @d2.errors.full_messages.inspect
|
285
285
|
@d2.avatar = @bad_file
|
286
286
|
assert ! @d2.valid?
|
287
|
-
@d2.avatar = nil
|
288
|
-
assert ! @d2.valid?
|
289
287
|
end
|
290
288
|
|
291
289
|
should "be able to reload without saving and not have the file disappear" do
|
292
290
|
@dummy.avatar = @file
|
293
291
|
assert @dummy.save
|
294
|
-
@dummy.avatar
|
292
|
+
@dummy.avatar.clear
|
295
293
|
assert_nil @dummy.avatar_file_name
|
296
294
|
@dummy.reload
|
297
295
|
assert_equal "5k.png", @dummy.avatar_file_name
|
@@ -314,16 +312,6 @@ class IntegrationTest < Test::Unit::TestCase
|
|
314
312
|
assert_equal `identify -format "%wx%h" "#{@dummy.avatar.path(:original)}"`,
|
315
313
|
`identify -format "%wx%h" "#{@dummy2.avatar.path(:original)}"`
|
316
314
|
end
|
317
|
-
|
318
|
-
should "work when assigned a nil file" do
|
319
|
-
@dummy2.avatar = nil
|
320
|
-
@dummy2.save
|
321
|
-
|
322
|
-
@dummy.avatar = @dummy2.avatar
|
323
|
-
@dummy.save
|
324
|
-
|
325
|
-
assert !@dummy.avatar?
|
326
|
-
end
|
327
315
|
end
|
328
316
|
|
329
317
|
end
|
@@ -421,7 +409,7 @@ class IntegrationTest < Test::Unit::TestCase
|
|
421
409
|
assert key.exists?
|
422
410
|
end
|
423
411
|
|
424
|
-
@dummy.avatar
|
412
|
+
@dummy.avatar.clear
|
425
413
|
assert_nil @dummy.avatar_file_name
|
426
414
|
assert @dummy.valid?
|
427
415
|
assert @dummy.save
|
@@ -444,7 +432,7 @@ class IntegrationTest < Test::Unit::TestCase
|
|
444
432
|
|
445
433
|
saved_keys = [:thumb, :medium, :large, :original].collect{|s| @dummy.avatar.to_file(s) }
|
446
434
|
|
447
|
-
@d2.avatar
|
435
|
+
@d2.avatar.clear
|
448
436
|
assert @d2.save
|
449
437
|
|
450
438
|
saved_keys.each do |key|
|
data/test/storage_test.rb
CHANGED
@@ -37,6 +37,55 @@ class StorageTest < Test::Unit::TestCase
|
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
context "" do
|
41
|
+
setup do
|
42
|
+
rebuild_model :storage => :s3,
|
43
|
+
:s3_credentials => {},
|
44
|
+
:bucket => "bucket",
|
45
|
+
:path => ":attachment/:basename.:extension",
|
46
|
+
:url => ":s3_path_url"
|
47
|
+
@dummy = Dummy.new
|
48
|
+
@dummy.avatar = StringIO.new(".")
|
49
|
+
end
|
50
|
+
|
51
|
+
should "return a url based on an S3 path" do
|
52
|
+
assert_match %r{^http://s3.amazonaws.com/bucket/avatars/stringio.txt}, @dummy.avatar.url
|
53
|
+
end
|
54
|
+
end
|
55
|
+
context "" do
|
56
|
+
setup do
|
57
|
+
rebuild_model :storage => :s3,
|
58
|
+
:s3_credentials => {},
|
59
|
+
:bucket => "bucket",
|
60
|
+
:path => ":attachment/:basename.:extension",
|
61
|
+
:url => ":s3_domain_url"
|
62
|
+
@dummy = Dummy.new
|
63
|
+
@dummy.avatar = StringIO.new(".")
|
64
|
+
end
|
65
|
+
|
66
|
+
should "return a url based on an S3 subdomain" do
|
67
|
+
assert_match %r{^http://bucket.s3.amazonaws.com/avatars/stringio.txt}, @dummy.avatar.url
|
68
|
+
end
|
69
|
+
end
|
70
|
+
context "" do
|
71
|
+
setup do
|
72
|
+
rebuild_model :storage => :s3,
|
73
|
+
:s3_credentials => {
|
74
|
+
:production => { :bucket => "prod_bucket" },
|
75
|
+
:development => { :bucket => "dev_bucket" }
|
76
|
+
},
|
77
|
+
:s3_host_alias => "something.something.com",
|
78
|
+
:path => ":attachment/:basename.:extension",
|
79
|
+
:url => ":s3_alias_url"
|
80
|
+
@dummy = Dummy.new
|
81
|
+
@dummy.avatar = StringIO.new(".")
|
82
|
+
end
|
83
|
+
|
84
|
+
should "return a url based on the host_alias" do
|
85
|
+
assert_match %r{^http://something.something.com/avatars/stringio.txt}, @dummy.avatar.url
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
40
89
|
context "Parsing S3 credentials with a bucket in them" do
|
41
90
|
setup do
|
42
91
|
rebuild_model :storage => :s3,
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: paperclip
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.2.
|
4
|
+
version: 2.2.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Yurek
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-03-12 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|