thoughtbot-paperclip 2.2.6 → 2.2.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/paperclip.rb +1 -1
- data/lib/paperclip/attachment.rb +28 -9
- data/lib/paperclip/storage.rb +19 -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.rb
CHANGED
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/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: thoughtbot-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 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|