paperclip 5.0.0.beta1 → 5.0.0

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.

Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CONTRIBUTING.md +10 -4
  3. data/NEWS +22 -1
  4. data/README.md +49 -45
  5. data/UPGRADING +3 -0
  6. data/features/migration.feature +0 -24
  7. data/features/step_definitions/rails_steps.rb +0 -6
  8. data/features/step_definitions/s3_steps.rb +2 -6
  9. data/gemfiles/5.0.awsv2.0.gemfile +1 -1
  10. data/gemfiles/5.0.awsv2.1.gemfile +1 -1
  11. data/gemfiles/5.0.awsv2.gemfile +1 -6
  12. data/lib/paperclip/attachment.rb +3 -2
  13. data/lib/paperclip/content_type_detector.rb +3 -2
  14. data/lib/paperclip/errors.rb +3 -1
  15. data/lib/paperclip/helpers.rb +14 -10
  16. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +1 -1
  17. data/lib/paperclip/io_adapters/uri_adapter.rb +3 -1
  18. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
  19. data/lib/paperclip/schema.rb +1 -6
  20. data/lib/paperclip/storage/fog.rb +9 -6
  21. data/lib/paperclip/storage/s3.rb +16 -38
  22. data/lib/paperclip/validators/attachment_size_validator.rb +1 -7
  23. data/lib/paperclip/version.rb +3 -1
  24. data/lib/paperclip.rb +2 -1
  25. data/lib/tasks/paperclip.rake +1 -1
  26. data/paperclip.gemspec +1 -2
  27. data/spec/paperclip/attachment_processing_spec.rb +2 -4
  28. data/spec/paperclip/attachment_spec.rb +1 -4
  29. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +12 -0
  30. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +27 -0
  31. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +10 -0
  32. data/spec/paperclip/media_type_spoof_detector_spec.rb +12 -3
  33. data/spec/paperclip/paperclip_spec.rb +3 -1
  34. data/spec/paperclip/storage/fog_spec.rb +10 -0
  35. data/spec/paperclip/storage/s3_spec.rb +92 -215
  36. data/spec/paperclip/validators/attachment_size_validator_spec.rb +26 -20
  37. data/spec/paperclip/validators_spec.rb +1 -0
  38. metadata +10 -8
  39. data/cucumber/paperclip_steps.rb +0 -6
@@ -113,37 +113,15 @@ module Paperclip
113
113
 
114
114
  module S3
115
115
  def self.extended base
116
- unless defined?(AWS_CLASS)
117
- begin
118
- require 'aws-sdk'
119
- const_set('AWS_CLASS', defined?(::Aws) ? ::Aws : ::AWS)
120
- const_set('AWS_BASE_ERROR',
121
- defined?(::Aws) ? Aws::Errors::ServiceError : AWS::Errors::Base)
122
- const_set('DEFAULT_PERMISSION',
123
- defined?(::AWS) ? :public_read : :'public-read')
124
-
125
- rescue LoadError => e
126
- e.message << " (You may need to install the aws-sdk gem)"
127
- raise e
128
- end
129
- if Gem::Version.new(AWS_CLASS::VERSION) >= Gem::Version.new(2) && Gem::Version.new(AWS_CLASS::VERSION) <= Gem::Version.new("2.0.33")
130
- raise LoadError, "paperclip does not support aws-sdk versions 2.0.0 - 2.0.33. Please upgrade aws-sdk to a newer version."
131
- end
116
+ begin
117
+ require 'aws-sdk'
118
+ rescue LoadError => e
119
+ e.message << " (You may need to install the aws-sdk gem)"
120
+ raise e
132
121
  end
133
-
134
- # Overriding log formatter to make sure it return a UTF-8 string
135
- if defined?(AWS_CLASS::Core::LogFormatter)
136
- AWS_CLASS::Core::LogFormatter.class_eval do
137
- def summarize_hash(hash)
138
- hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
139
- end
140
- end
141
- elsif defined?(AWS_CLASS::Core::ClientLogging)
142
- AWS_CLASS::Core::ClientLogging.class_eval do
143
- def sanitize_hash(hash)
144
- hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
145
- end
146
- end
122
+ if Gem::Version.new(Aws::VERSION) >= Gem::Version.new(2) &&
123
+ Gem::Version.new(Aws::VERSION) <= Gem::Version.new("2.0.33")
124
+ raise LoadError, "paperclip does not support aws-sdk versions 2.0.0 - 2.0.33. Please upgrade aws-sdk to a newer version."
147
125
  end
148
126
 
149
127
  base.instance_eval do
@@ -153,7 +131,7 @@ module Paperclip
153
131
  Proc.new do |style, attachment|
154
132
  permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
155
133
  permission = permission.call(attachment, style) if permission.respond_to?(:call)
156
- (permission == DEFAULT_PERMISSION) ? 'http'.freeze : 'https'.freeze
134
+ (permission == :"public-read") ? 'http'.freeze : 'https'.freeze
157
135
  end
158
136
  @s3_metadata = @options[:s3_metadata] || {}
159
137
  @s3_headers = {}
@@ -266,7 +244,7 @@ module Paperclip
266
244
 
267
245
  def obtain_s3_instance_for(options)
268
246
  instances = (Thread.current[:paperclip_s3_instances] ||= {})
269
- instances[options] ||= AWS_CLASS::S3::Resource.new(options)
247
+ instances[options] ||= ::Aws::S3::Resource.new(options)
270
248
  end
271
249
 
272
250
  def s3_bucket
@@ -303,7 +281,7 @@ module Paperclip
303
281
 
304
282
  def set_permissions permissions
305
283
  permissions = { :default => permissions } unless permissions.respond_to?(:merge)
306
- permissions.merge :default => (permissions[:default] || DEFAULT_PERMISSION)
284
+ permissions.merge :default => (permissions[:default] || :"public-read")
307
285
  end
308
286
 
309
287
  def set_storage_class(storage_class)
@@ -323,7 +301,7 @@ module Paperclip
323
301
  else
324
302
  false
325
303
  end
326
- rescue AWS_BASE_ERROR => e
304
+ rescue Aws::Errors::ServiceError => e
327
305
  false
328
306
  end
329
307
 
@@ -381,10 +359,10 @@ module Paperclip
381
359
  write_options.merge!(@s3_headers)
382
360
 
383
361
  s3_object(style).upload_file(file.path, write_options)
384
- rescue AWS_CLASS::S3::Errors::NoSuchBucket
362
+ rescue ::Aws::S3::Errors::NoSuchBucket
385
363
  create_bucket
386
364
  retry
387
- rescue AWS_CLASS::S3::Errors::SlowDown
365
+ rescue ::Aws::S3::Errors::SlowDown
388
366
  retries += 1
389
367
  if retries <= 5
390
368
  sleep((2 ** retries) * 0.5)
@@ -407,7 +385,7 @@ module Paperclip
407
385
  begin
408
386
  log("deleting #{path}")
409
387
  s3_bucket.object(path.sub(%r{\A/}, "")).delete
410
- rescue AWS_BASE_ERROR => e
388
+ rescue Aws::Errors::ServiceError => e
411
389
  # Ignore this.
412
390
  end
413
391
  end
@@ -421,7 +399,7 @@ module Paperclip
421
399
  local_file.write(chunk)
422
400
  end
423
401
  end
424
- rescue AWS_BASE_ERROR => e
402
+ rescue Aws::Errors::ServiceError => e
425
403
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
426
404
  false
427
405
  end
@@ -71,13 +71,7 @@ module Paperclip
71
71
  end
72
72
 
73
73
  def human_size(size)
74
- if defined?(ActiveSupport::NumberHelper) # Rails 4.0+
75
- ActiveSupport::NumberHelper.number_to_human_size(size)
76
- else
77
- storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true)
78
- unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => size.to_i, :raise => true)
79
- storage_units_format.gsub(/%n/, size.to_i.to_s).gsub(/%u/, unit).html_safe
80
- end
74
+ ActiveSupport::NumberHelper.number_to_human_size(size)
81
75
  end
82
76
 
83
77
  def min_value_in_human_size(record)
@@ -1,3 +1,5 @@
1
1
  module Paperclip
2
- VERSION = "5.0.0.beta1" unless defined? Paperclip::VERSION
2
+ unless defined?(Paperclip::VERSION)
3
+ VERSION = "5.0.0".freeze
4
+ end
3
5
  end
data/lib/paperclip.rb CHANGED
@@ -97,7 +97,8 @@ module Paperclip
97
97
  :log_command => true,
98
98
  :swallow_stderr => true,
99
99
  :content_type_mappings => {},
100
- :use_exif_orientation => true
100
+ :use_exif_orientation => true,
101
+ :read_timeout => nil
101
102
  }
102
103
  end
103
104
 
@@ -18,7 +18,7 @@ module Paperclip
18
18
  raise "Class #{klass.name} has no attachments specified"
19
19
  end
20
20
 
21
- if !name.blank? && attachment_names.map(&:to_s).include?(name.to_s)
21
+ if name.present? && attachment_names.map(&:to_s).include?(name.to_s)
22
22
  [ name ]
23
23
  else
24
24
  attachment_names
data/paperclip.gemspec CHANGED
@@ -35,12 +35,11 @@ Gem::Specification.new do |s|
35
35
  s.add_development_dependency('rspec', '~> 3.0')
36
36
  s.add_development_dependency('appraisal')
37
37
  s.add_development_dependency('mocha')
38
- s.add_development_dependency('aws-sdk', '>= 2.0.33', '< 3.0')
38
+ s.add_development_dependency('aws-sdk', '>= 2.0.34', '< 3.0')
39
39
  s.add_development_dependency('bourne')
40
40
  s.add_development_dependency('cucumber', '~> 1.3.18')
41
41
  s.add_development_dependency('aruba', '~> 0.9.0')
42
42
  s.add_development_dependency('nokogiri')
43
- # Ruby version < 1.9.3 can't install capybara > 2.0.3.
44
43
  s.add_development_dependency('capybara')
45
44
  s.add_development_dependency('bundler')
46
45
  s.add_development_dependency('fog-aws')
@@ -2,11 +2,9 @@
2
2
  require 'spec_helper'
3
3
 
4
4
  describe 'Attachment Processing' do
5
- context 'using validates_attachment_content_type' do
6
- before do
7
- rebuild_class
8
- end
5
+ before { rebuild_class }
9
6
 
7
+ context 'using validates_attachment_content_type' do
10
8
  it 'processes attachments given a valid assignment' do
11
9
  file = File.new(fixture_file("5k.png"))
12
10
  Dummy.validates_attachment_content_type :avatar, content_type: "image/png"
@@ -222,9 +222,6 @@ describe Paperclip::Attachment do
222
222
  dummy.avatar_file_name = "fake.jpg"
223
223
  dummy.stubs(:new_record?).returns(false)
224
224
  expected_string = '{"avatar":"/system/dummies/avatars/000/001/234/original/fake.jpg"}'
225
- if ActiveRecord::Base.include_root_in_json # This is true by default in Rails 3, and false in 4
226
- expected_string = %({"dummy":#{expected_string}})
227
- end
228
225
  # active_model pre-3.2 checks only by calling any? on it, thus it doesn't work if it is empty
229
226
  assert_equal expected_string, dummy.to_json(only: [:dummy_key_for_old_active_model], methods: [:avatar])
230
227
  end
@@ -1124,7 +1121,7 @@ describe Paperclip::Attachment do
1124
1121
  context "with a file assigned but not saved yet" do
1125
1122
  it "clears out any attached files" do
1126
1123
  @attachment.assign(@file)
1127
- assert !@attachment.queued_for_write.blank?
1124
+ assert @attachment.queued_for_write.present?
1128
1125
  @attachment.clear
1129
1126
  assert @attachment.queued_for_write.blank?
1130
1127
  end
@@ -98,4 +98,16 @@ describe Paperclip::HttpUrlProxyAdapter do
98
98
  end
99
99
  end
100
100
 
101
+ context "a url with special characters in the filename" do
102
+ it "returns a encoded filename" do
103
+ Paperclip::HttpUrlProxyAdapter.any_instance.stubs(:download_content).
104
+ returns(StringIO.new("x"))
105
+ url = "https://github.com/thoughtbot/paperclip-öäü字´½♥زÈ.png"
106
+ subject = Paperclip.io_adapters.for(url)
107
+ filename = "paperclip-%C3%B6%C3%A4%C3%BC%E5%AD%97%C2%B4%C2%BD%E2%99%A5"\
108
+ "%C3%98%C2%B2%C3%88.png"
109
+
110
+ assert_equal filename, subject.original_filename
111
+ end
112
+ end
101
113
  end
@@ -99,4 +99,31 @@ describe Paperclip::UriAdapter do
99
99
  end
100
100
  end
101
101
 
102
+ describe "#download_content" do
103
+ before do
104
+ Paperclip::UriAdapter.any_instance.stubs(:open).returns(StringIO.new("xxx"))
105
+ @uri = URI.parse("https://github.com/thoughtbot/paper:clip.jpg")
106
+ @subject = Paperclip.io_adapters.for(@uri)
107
+ end
108
+
109
+ after do
110
+ @subject.send(:download_content)
111
+ end
112
+
113
+ context "with default read_timeout" do
114
+ it "calls open without options" do
115
+ @subject.expects(:open).with(@uri, {}).at_least_once
116
+ end
117
+ end
118
+
119
+ context "with custom read_timeout" do
120
+ before do
121
+ Paperclip.options[:read_timeout] = 120
122
+ end
123
+
124
+ it "calls open with read_timeout option" do
125
+ @subject.expects(:open).with(@uri, read_timeout: 120).at_least_once
126
+ end
127
+ end
128
+ end
102
129
  end
@@ -17,22 +17,26 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
17
17
 
18
18
  it "rejects a class with no validation" do
19
19
  expect(matcher).to_not accept(Dummy)
20
+ expect { matcher.failure_message }.to_not raise_error
20
21
  end
21
22
 
22
23
  it 'rejects a class when the validation fails' do
23
24
  Dummy.validates_attachment_content_type :avatar, content_type: %r{audio/.*}
24
25
  expect(matcher).to_not accept(Dummy)
26
+ expect { matcher.failure_message }.to_not raise_error
25
27
  end
26
28
 
27
29
  it "accepts a class with a matching validation" do
28
30
  Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
29
31
  expect(matcher).to accept(Dummy)
32
+ expect { matcher.failure_message }.to_not raise_error
30
33
  end
31
34
 
32
35
  it "accepts a class with other validations but matching types" do
33
36
  Dummy.validates_presence_of :title
34
37
  Dummy.validates_attachment_content_type :avatar, content_type: %r{image/.*}
35
38
  expect(matcher).to accept(Dummy)
39
+ expect { matcher.failure_message }.to_not raise_error
36
40
  end
37
41
 
38
42
  it "accepts a class that matches and a matcher that only specifies 'allowing'" do
@@ -40,6 +44,7 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
40
44
  matcher = plain_matcher.allowing(%w(image/png image/jpeg))
41
45
 
42
46
  expect(matcher).to accept(Dummy)
47
+ expect { matcher.failure_message }.to_not raise_error
43
48
  end
44
49
 
45
50
  it "rejects a class that does not match and a matcher that only specifies 'allowing'" do
@@ -47,6 +52,7 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
47
52
  matcher = plain_matcher.allowing(%w(image/png image/jpeg))
48
53
 
49
54
  expect(matcher).to_not accept(Dummy)
55
+ expect { matcher.failure_message }.to_not raise_error
50
56
  end
51
57
 
52
58
  it "accepts a class that matches and a matcher that only specifies 'rejecting'" do
@@ -54,6 +60,7 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
54
60
  matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))
55
61
 
56
62
  expect(matcher).to accept(Dummy)
63
+ expect { matcher.failure_message }.to_not raise_error
57
64
  end
58
65
 
59
66
  it "rejects a class that does not match and a matcher that only specifies 'rejecting'" do
@@ -61,6 +68,7 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
61
68
  matcher = plain_matcher.rejecting(%w(audio/mp3 application/octet-stream))
62
69
 
63
70
  expect(matcher).to_not accept(Dummy)
71
+ expect { matcher.failure_message }.to_not raise_error
64
72
  end
65
73
 
66
74
  context "using an :if to control the validation" do
@@ -75,12 +83,14 @@ describe Paperclip::Shoulda::Matchers::ValidateAttachmentContentTypeMatcher do
75
83
  dummy = Dummy.new
76
84
  dummy.go = true
77
85
  expect(matcher).to accept(dummy)
86
+ expect { matcher.failure_message }.to_not raise_error
78
87
  end
79
88
 
80
89
  it "does not run the validation if the control is false" do
81
90
  dummy = Dummy.new
82
91
  dummy.go = false
83
92
  expect(matcher).to_not accept(dummy)
93
+ expect { matcher.failure_message }.to_not raise_error
84
94
  end
85
95
  end
86
96
 
@@ -44,9 +44,18 @@ describe Paperclip::MediaTypeSpoofDetector do
44
44
  end
45
45
  end
46
46
 
47
- it "rejects a file if named .html and is as HTML, but we're told JPG" do
48
- file = File.open(fixture_file("empty.html"))
49
- assert Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "image/jpg").spoofed?
47
+ context "file named .html and is as HTML, but we're told JPG" do
48
+ let(:file) { File.open(fixture_file("empty.html")) }
49
+ let(:spoofed?) { Paperclip::MediaTypeSpoofDetector.using(file, "empty.html", "image/jpg").spoofed? }
50
+
51
+ it "rejects the file" do
52
+ assert spoofed?
53
+ end
54
+
55
+ it "logs info about the detected spoof" do
56
+ Paperclip.expects(:log).with('Content Type Spoof: Filename empty.html (image/jpg from Headers, ["text/html"] from Extension), content type discovered from file command: text/html. See documentation to allow this combination.')
57
+ spoofed?
58
+ end
50
59
  end
51
60
 
52
61
  it "does not reject if content_type is empty but otherwise checks out" do
@@ -29,7 +29,9 @@ describe Paperclip do
29
29
  Paperclip.options[:command_path] = "/opt/my_app/bin"
30
30
  Paperclip.run("convert", "stuff")
31
31
  Paperclip.run("convert", "more_stuff")
32
- assert_equal 1, [Cocaine::CommandLine.path].flatten.size
32
+
33
+ cmd_path = Paperclip.options[:command_path]
34
+ assert_equal 1, Cocaine::CommandLine.path.scan(cmd_path).count
33
35
  end
34
36
  end
35
37
 
@@ -183,6 +183,13 @@ describe Paperclip::Storage::Fog do
183
183
  tempfile.close
184
184
  end
185
185
 
186
+ it 'is able to be handled when missing while copying to a local file' do
187
+ tempfile = Tempfile.new("known_location")
188
+ tempfile.binmode
189
+ assert_equal false, @dummy.avatar.copy_to_local_file(:original, tempfile.path)
190
+ tempfile.close
191
+ end
192
+
186
193
  it "passes the content type to the Fog::Storage::AWS::Files instance" do
187
194
  Fog::Storage::AWS::Files.any_instance.expects(:create).with do |hash|
188
195
  hash[:content_type]
@@ -418,6 +425,9 @@ describe Paperclip::Storage::Fog do
418
425
  assert @connection.directories.get(@dynamic_fog_directory).inspect
419
426
  end
420
427
 
428
+ it "provides an url using dynamic bucket name" do
429
+ assert_match(/^https:\/\/dynamicpaperclip.s3.amazonaws.com\/avatars\/5k.png\?\d*$/, @dummy.avatar.url)
430
+ end
421
431
  end
422
432
 
423
433
  context "with a proc for the fog_host evaluating a model method" do