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 CHANGED
@@ -43,7 +43,7 @@ end
43
43
  # documentation for Paperclip::ClassMethods for more useful information.
44
44
  module Paperclip
45
45
 
46
- VERSION = "2.2.6"
46
+ VERSION = "2.2.7"
47
47
 
48
48
  class << self
49
49
  # Provides configurability to Paperclip. There are a number of options available, such as:
@@ -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.nil? || (file.respond_to?(:original_filename) && file.respond_to?(:content_type))
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
- @styles[name][:geometry] = @styles[name][:geometry].call(instance)
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|
@@ -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
- # * +url+: There are two options for the S3 url. You can choose to have the bucket's name
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
@@ -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 = validate_attachment_presence(name).allows(valid).rejects(invalid)
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
@@ -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 deleted" do
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.assign nil
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
@@ -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 = nil
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 = nil
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 = nil
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, and nil" do
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, and nil when validating" do
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 = nil
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 = nil
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 = nil
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.6
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-02-17 00:00:00 -08:00
12
+ date: 2009-03-12 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency