paperclip 4.2.4 → 4.3.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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +1066 -0
  3. data/.rubocop.yml +1 -0
  4. data/.travis.yml +5 -3
  5. data/Appraisals +1 -6
  6. data/CONTRIBUTING.md +3 -3
  7. data/Gemfile +1 -0
  8. data/LICENSE +1 -1
  9. data/NEWS +14 -1
  10. data/README.md +137 -77
  11. data/RELEASING.md +17 -0
  12. data/Rakefile +1 -1
  13. data/features/basic_integration.feature +7 -4
  14. data/features/step_definitions/attachment_steps.rb +7 -1
  15. data/gemfiles/3.2.gemfile +2 -1
  16. data/gemfiles/4.1.gemfile +1 -0
  17. data/gemfiles/4.2.gemfile +2 -1
  18. data/lib/paperclip.rb +13 -3
  19. data/lib/paperclip/attachment.rb +3 -1
  20. data/lib/paperclip/attachment_registry.rb +1 -1
  21. data/lib/paperclip/content_type_detector.rb +26 -11
  22. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  23. data/lib/paperclip/glue.rb +1 -1
  24. data/lib/paperclip/has_attached_file.rb +2 -1
  25. data/lib/paperclip/interpolations.rb +1 -1
  26. data/lib/paperclip/io_adapters/abstract_adapter.rb +1 -0
  27. data/lib/paperclip/media_type_spoof_detector.rb +1 -1
  28. data/lib/paperclip/rails_environment.rb +25 -0
  29. data/lib/paperclip/storage/fog.rb +4 -4
  30. data/lib/paperclip/storage/s3.rb +2 -3
  31. data/lib/paperclip/thumbnail.rb +2 -3
  32. data/lib/paperclip/version.rb +1 -1
  33. data/lib/tasks/paperclip.rake +16 -0
  34. data/paperclip.gemspec +3 -4
  35. data/spec/paperclip/attachment_definitions_spec.rb +1 -1
  36. data/spec/paperclip/attachment_registry_spec.rb +56 -13
  37. data/spec/paperclip/attachment_spec.rb +57 -19
  38. data/spec/paperclip/content_type_detector_spec.rb +8 -1
  39. data/spec/paperclip/file_command_content_type_detector_spec.rb +0 -1
  40. data/spec/paperclip/interpolations_spec.rb +2 -9
  41. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +2 -1
  42. data/spec/paperclip/io_adapters/file_adapter_spec.rb +4 -1
  43. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +4 -0
  44. data/spec/paperclip/media_type_spoof_detector_spec.rb +16 -2
  45. data/spec/paperclip/rails_environment_spec.rb +33 -0
  46. data/spec/paperclip/storage/fog_spec.rb +11 -2
  47. data/spec/paperclip/storage/s3_spec.rb +43 -25
  48. data/spec/paperclip/tempfile_factory_spec.rb +4 -0
  49. data/spec/paperclip/thumbnail_spec.rb +16 -0
  50. data/spec/paperclip/url_generator_spec.rb +1 -1
  51. data/spec/support/fake_model.rb +4 -0
  52. data/spec/support/fixtures/empty.xlsx +0 -0
  53. data/spec/support/matchers/have_column.rb +11 -2
  54. metadata +30 -12
  55. data/RUNNING_TESTS.md +0 -4
  56. data/gemfiles/4.0.gemfile +0 -19
  57. data/spec/support/mock_model.rb +0 -2
@@ -0,0 +1,17 @@
1
+ Releasing paperclip
2
+
3
+ 1. Update `lib/paperclip/version.rb` file accordingly.
4
+ 2. Update `NEWS` to reflect the changes since last release.
5
+ 3. Commit changes. There shouldn’t be code changes, and thus CI doesn’t need to
6
+ run, you can then add “[ci skip]” to the commit message.
7
+ 4. Tag the release: `git tag -m 'vVERSION' vVERSION`
8
+ 5. Push changes: `git push --tags`
9
+ 6. Build and publish the gem:
10
+
11
+ ```bash
12
+ gem build paperclip.gemspec
13
+ gem push paperclip-VERSION.gem
14
+ ```
15
+
16
+ 7. Announce the new release, making sure to say “thank you” to the contributors
17
+ who helped shape this version.
data/Rakefile CHANGED
@@ -11,7 +11,7 @@ task :all do |t|
11
11
  if ENV['BUNDLE_GEMFILE']
12
12
  exec('rake spec cucumber')
13
13
  else
14
- exec("rm gemfiles/*.lock")
14
+ exec("rm -f gemfiles/*.lock")
15
15
  Rake::Task["appraisal:gemfiles"].execute
16
16
  Rake::Task["appraisal:install"].execute
17
17
  exec('rake appraisal')
@@ -12,17 +12,20 @@ Feature: Rails integration
12
12
  Scenario: Configure defaults for all attachments through Railtie
13
13
  Given I add this snippet to config/application.rb:
14
14
  """
15
- config.paperclip_defaults = {:url => "/paperclip/custom/:attachment/:style/:filename"}
15
+ config.paperclip_defaults = {
16
+ :url => "/paperclip/custom/:attachment/:style/:filename",
17
+ :validate_media_type => false
18
+ }
16
19
  """
17
20
  And I attach :attachment
18
21
  And I start the rails application
19
22
  When I go to the new user page
20
23
  And I fill in "Name" with "something"
21
- And I attach the file "spec/support/fixtures/5k.png" to "Attachment"
24
+ And I attach the file "spec/support/fixtures/animated.unknown" to "Attachment"
22
25
  And I press "Submit"
23
26
  Then I should see "Name: something"
24
- And I should see an image with a path of "/paperclip/custom/attachments/original/5k.png"
25
- And the file at "/paperclip/custom/attachments/original/5k.png" should be the same as "spec/support/fixtures/5k.png"
27
+ And I should see an image with a path of "/paperclip/custom/attachments/original/animated.unknown"
28
+ And the file at "/paperclip/custom/attachments/original/animated.unknown" should be the same as "spec/support/fixtures/animated.unknown"
26
29
 
27
30
  Scenario: Add custom processors
28
31
  Given I add a "test" processor in "lib/paperclip"
@@ -49,7 +49,13 @@ end
49
49
 
50
50
  Then /^the attachment should have the same content type as the fixture "([^"]*)"$/ do |filename|
51
51
  in_current_dir do
52
- require 'mime/types'
52
+ begin
53
+ # Use mime/types/columnar if available, for reduced memory usage
54
+ require "mime/types/columnar"
55
+ rescue LoadError
56
+ require "mime/types"
57
+ end
58
+
53
59
  attachment_content_type = `bundle exec #{runner_command} "puts User.last.attachment_content_type"`.strip
54
60
  attachment_content_type.should == MIME::Types.type_for(filename).first.content_type
55
61
  end
@@ -8,12 +8,13 @@ gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
8
8
  gem "rubysl", :platforms => :rbx
9
9
  gem "racc", :platforms => :rbx
10
10
  gem "pry"
11
- gem "rails", "~> 3.2.0"
11
+ gem "rails", "~> 3.2.15"
12
12
  gem "paperclip", :path => "../"
13
13
 
14
14
  group :development, :test do
15
15
  gem "mime-types", "~> 1.16"
16
16
  gem "builder"
17
+ gem "rubocop", :require => false
17
18
  end
18
19
 
19
20
  gemspec :path => "../"
@@ -14,6 +14,7 @@ gem "paperclip", :path => "../"
14
14
  group :development, :test do
15
15
  gem "mime-types", "~> 1.16"
16
16
  gem "builder"
17
+ gem "rubocop", :require => false
17
18
  end
18
19
 
19
20
  gemspec :path => "../"
@@ -8,12 +8,13 @@ gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
8
8
  gem "rubysl", :platforms => :rbx
9
9
  gem "racc", :platforms => :rbx
10
10
  gem "pry"
11
- gem "rails", "~> 4.2.0.rc2"
11
+ gem "rails", "~> 4.2.0"
12
12
  gem "paperclip", :path => "../"
13
13
 
14
14
  group :development, :test do
15
15
  gem "mime-types", "~> 1.16"
16
16
  gem "builder"
17
+ gem "rubocop", :require => false
17
18
  end
18
19
 
19
20
  gemspec :path => "../"
@@ -55,11 +55,21 @@ require 'paperclip/helpers'
55
55
  require 'paperclip/has_attached_file'
56
56
  require 'paperclip/attachment_registry'
57
57
  require 'paperclip/filename_cleaner'
58
- require 'mime/types'
58
+ require 'paperclip/rails_environment'
59
+
60
+ begin
61
+ # Use mime/types/columnar if available, for reduced memory usage
62
+ require "mime/types/columnar"
63
+ rescue LoadError
64
+ require "mime/types"
65
+ end
66
+
67
+ require 'mimemagic'
68
+ require 'mimemagic/overlay'
59
69
  require 'logger'
60
70
  require 'cocaine'
61
71
 
62
- require 'paperclip/railtie' if defined?(Rails)
72
+ require 'paperclip/railtie' if defined?(Rails::Railtie)
63
73
 
64
74
  # The base module that gets included in ActiveRecord::Base. See the
65
75
  # documentation for Paperclip::ClassMethods for more useful information.
@@ -136,7 +146,7 @@ module Paperclip
136
146
  # user.avatar.url # => "/avatars/23/normal_me.png"
137
147
  # * +keep_old_files+: Keep the existing attachment files (original + resized) from
138
148
  # being automatically deleted when an attachment is cleared or updated. Defaults to +false+.
139
- # * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
149
+ # * +preserve_files+: Keep the existing attachment files in all cases, even if the parent
140
150
  # record is destroyed. Defaults to +false+.
141
151
  # * +whiny+: Will raise an error if Paperclip cannot post_process an uploaded file due
142
152
  # to a command line error. This will override the global setting for this attachment.
@@ -137,6 +137,8 @@ module Paperclip
137
137
  # +#for(style_name, options_hash)+
138
138
 
139
139
  def url(style_name = default_style, options = {})
140
+ return nil if @instance.new_record?
141
+
140
142
  if options == true || options == false # Backwards compatibility.
141
143
  @url_generator.for(style_name, default_options.merge(:timestamp => options))
142
144
  else
@@ -528,7 +530,7 @@ module Paperclip
528
530
  @queued_for_write[name] = Paperclip.io_adapters.for(@queued_for_write[name])
529
531
  unadapted_file.close if unadapted_file.respond_to?(:close)
530
532
  @queued_for_write[name]
531
- rescue Paperclip::Error => e
533
+ rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e
532
534
  log("An error was received while processing: #{e.inspect}")
533
535
  (@errors[:processing] ||= []) << e.message if @options[:whiny]
534
536
  ensure
@@ -52,7 +52,7 @@ module Paperclip
52
52
 
53
53
  def definitions_for(klass)
54
54
  klass.ancestors.each_with_object({}) do |ancestor, inherited_definitions|
55
- inherited_definitions.merge! @attachments[ancestor]
55
+ inherited_definitions.deep_merge! @attachments[ancestor]
56
56
  end
57
57
  end
58
58
  end
@@ -2,7 +2,7 @@ module Paperclip
2
2
  class ContentTypeDetector
3
3
  # The content-type detection strategy is as follows:
4
4
  #
5
- # 1. Blank/Empty files: If there's no filename or the file is empty,
5
+ # 1. Blank/Empty files: If there's no filepath or the file is empty,
6
6
  # provide a sensible default (application/octet-stream or inode/x-empty)
7
7
  #
8
8
  # 2. Calculated match: Return the first result that is found by both the
@@ -20,8 +20,8 @@ module Paperclip
20
20
  EMPTY_TYPE = "inode/x-empty"
21
21
  SENSIBLE_DEFAULT = "application/octet-stream"
22
22
 
23
- def initialize(filename)
24
- @filename = filename
23
+ def initialize(filepath)
24
+ @filepath = filepath
25
25
  end
26
26
 
27
27
  # Returns a String describing the file's content type
@@ -33,32 +33,47 @@ module Paperclip
33
33
  elsif calculated_type_matches.any?
34
34
  calculated_type_matches.first
35
35
  else
36
- type_from_file_command || SENSIBLE_DEFAULT
36
+ type_from_file_contents || SENSIBLE_DEFAULT
37
37
  end.to_s
38
38
  end
39
39
 
40
40
  private
41
41
 
42
+ def blank_name?
43
+ @filepath.nil? || @filepath.empty?
44
+ end
45
+
42
46
  def empty_file?
43
- File.exist?(@filename) && File.size(@filename) == 0
47
+ File.exist?(@filepath) && File.size(@filepath) == 0
44
48
  end
45
49
 
46
50
  alias :empty? :empty_file?
47
51
 
48
- def blank_name?
49
- @filename.nil? || @filename.empty?
52
+ def calculated_type_matches
53
+ possible_types.select do |content_type|
54
+ content_type == type_from_file_contents
55
+ end
50
56
  end
51
57
 
52
58
  def possible_types
53
- MIME::Types.type_for(@filename).collect(&:content_type)
59
+ MIME::Types.type_for(@filepath).collect(&:content_type)
54
60
  end
55
61
 
56
- def calculated_type_matches
57
- possible_types.select{|content_type| content_type == type_from_file_command }
62
+ def type_from_file_contents
63
+ type_from_mime_magic || type_from_file_command
64
+ rescue Errno::ENOENT => e
65
+ Paperclip.log("Error while determining content type: #{e}")
66
+ SENSIBLE_DEFAULT
67
+ end
68
+
69
+ def type_from_mime_magic
70
+ @type_from_mime_magic ||=
71
+ MimeMagic.by_magic(File.open(@filepath)).try(:type)
58
72
  end
59
73
 
60
74
  def type_from_file_command
61
- @type_from_file_command ||= FileCommandContentTypeDetector.new(@filename).detect
75
+ @type_from_file_command ||=
76
+ FileCommandContentTypeDetector.new(@filepath).detect
62
77
  end
63
78
  end
64
79
  end
@@ -13,20 +13,18 @@ module Paperclip
13
13
  private
14
14
 
15
15
  def type_from_file_command
16
+ # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
16
17
  type = begin
17
- # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
18
- Paperclip.run("file", "-b --mime :file", :file => @filename)
19
- rescue Cocaine::CommandLineError => e
20
- Paperclip.log("Error while determining content type: #{e}")
21
- SENSIBLE_DEFAULT
22
- end
18
+ Paperclip.run("file", "-b --mime :file", file: @filename)
19
+ rescue Cocaine::CommandLineError => e
20
+ Paperclip.log("Error while determining content type: #{e}")
21
+ SENSIBLE_DEFAULT
22
+ end
23
23
 
24
24
  if type.nil? || type.match(/\(.*?\)/)
25
25
  type = SENSIBLE_DEFAULT
26
26
  end
27
27
  type.split(/[:;\s]+/)[0]
28
28
  end
29
-
30
29
  end
31
30
  end
32
-
@@ -8,7 +8,7 @@ module Paperclip
8
8
  base.extend ClassMethods
9
9
  base.send :include, Callbacks
10
10
  base.send :include, Validators
11
- base.send :include, Schema if defined? ActiveRecord
11
+ base.send :include, Schema
12
12
 
13
13
  locale_path = Dir.glob(File.dirname(__FILE__) + "/locales/*.{rb,yml}")
14
14
  I18n.load_path += locale_path unless I18n.load_path.include?(locale_path)
@@ -79,7 +79,8 @@ module Paperclip
79
79
  end
80
80
 
81
81
  def add_required_validations
82
- if @options[:validate_media_type] != false
82
+ options = Paperclip::Attachment.default_options.deep_merge(@options)
83
+ if options[:validate_media_type] != false
83
84
  name = @name
84
85
  @klass.validates_media_type_spoof_detection name,
85
86
  :if => ->(instance){ instance.send(name).dirty? }
@@ -172,7 +172,7 @@ module Paperclip
172
172
  when Integer
173
173
  ("%09d" % id).scan(/\d{3}/).join("/")
174
174
  when String
175
- ('%9.9s' % id).tr(" ", "0").scan(/.{3}/).join("/")
175
+ id.scan(/.{3}/).first(3).join("/")
176
176
  else
177
177
  nil
178
178
  end
@@ -6,6 +6,7 @@ module Paperclip
6
6
 
7
7
  attr_reader :content_type, :original_filename, :size
8
8
  delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :rewind, :unlink, :to => :@tempfile
9
+ alias :length :size
9
10
 
10
11
  def fingerprint
11
12
  @fingerprint ||= Digest::MD5.file(path).to_s
@@ -42,7 +42,7 @@ module Paperclip
42
42
  end
43
43
 
44
44
  def mapping_override_mismatch?
45
- mapped_content_type != calculated_content_type
45
+ !Array(mapped_content_type).include?(calculated_content_type)
46
46
  end
47
47
 
48
48
 
@@ -0,0 +1,25 @@
1
+ module Paperclip
2
+ class RailsEnvironment
3
+ def self.get
4
+ new.get
5
+ end
6
+
7
+ def get
8
+ if rails_exists? && rails_environment_exists?
9
+ Rails.env
10
+ else
11
+ nil
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def rails_exists?
18
+ Object.const_defined?("Rails")
19
+ end
20
+
21
+ def rails_environment_exists?
22
+ Rails.respond_to?(:env)
23
+ end
24
+ end
25
+ end
@@ -141,8 +141,9 @@ module Paperclip
141
141
 
142
142
  def expiring_url(time = (Time.now + 3600), style_name = default_style)
143
143
  time = convert_time(time)
144
- if path(style_name) && directory.files.respond_to?(:get_http_url)
145
- expiring_url = directory.files.get_http_url(path(style_name), time)
144
+ http_url_method = "get_#{scheme}_url"
145
+ if path(style_name) && directory.files.respond_to?(http_url_method)
146
+ expiring_url = directory.files.public_send(http_url_method, path(style_name), time)
146
147
 
147
148
  if @options[:fog_host]
148
149
  expiring_url.gsub!(/#{host_name_for_directory}/, dynamic_fog_host_for_style(style_name))
@@ -156,8 +157,7 @@ module Paperclip
156
157
 
157
158
  def parse_credentials(creds)
158
159
  creds = find_credentials(creds).stringify_keys
159
- env = Object.const_defined?(:Rails) ? Rails.env : nil
160
- (creds[env] || creds).symbolize_keys
160
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
161
161
  end
162
162
 
163
163
  def copy_to_local_file(style, local_dest_path)
@@ -4,7 +4,7 @@ module Paperclip
4
4
  # distribution. You can find out more about it at http://aws.amazon.com/s3
5
5
  #
6
6
  # To use Paperclip with S3, include the +aws-sdk+ gem in your Gemfile:
7
- # gem 'aws-sdk'
7
+ # gem 'aws-sdk', '~> 1.6'
8
8
  # There are a few S3-specific options for has_attached_file:
9
9
  # * +s3_credentials+: Takes a path, a File, a Hash or a Proc. The path (or File) must point
10
10
  # to a YAML file containing the +access_key_id+ and +secret_access_key+ that Amazon
@@ -288,8 +288,7 @@ module Paperclip
288
288
  def parse_credentials creds
289
289
  creds = creds.respond_to?('call') ? creds.call(self) : creds
290
290
  creds = find_credentials(creds).stringify_keys
291
- env = Object.const_defined?(:Rails) ? Rails.env : nil
292
- (creds[env] || creds).symbolize_keys
291
+ (creds[RailsEnvironment.get] || creds).symbolize_keys
293
292
  end
294
293
 
295
294
  def exists?(style = default_style)
@@ -29,7 +29,6 @@ module Paperclip
29
29
  super
30
30
 
31
31
  geometry = options[:geometry].to_s
32
- @file = file
33
32
  @crop = geometry[-1,1] == '#'
34
33
  @target_geometry = options.fetch(:string_geometry_parser, Geometry).parse(geometry)
35
34
  @current_geometry = options.fetch(:file_geometry_parser, Geometry).from_file(@file)
@@ -64,8 +63,8 @@ module Paperclip
64
63
  # that contains the new image.
65
64
  def make
66
65
  src = @file
67
- dst = Tempfile.new([@basename, @format ? ".#{@format}" : ''])
68
- dst.binmode
66
+ filename = [@basename, @format ? ".#{@format}" : ""].join
67
+ dst = TempfileFactory.new.generate(filename)
69
68
 
70
69
  begin
71
70
  parameters = []
@@ -1,3 +1,3 @@
1
1
  module Paperclip
2
- VERSION = "4.2.4" unless defined? Paperclip::VERSION
2
+ VERSION = "4.3.0" unless defined? Paperclip::VERSION
3
3
  end
@@ -108,4 +108,20 @@ namespace :paperclip do
108
108
  end
109
109
  end
110
110
  end
111
+
112
+ desc "find missing attachments. Useful to know which attachments are broken"
113
+ task :find_broken_attachments => :environment do
114
+ klass = Paperclip::Task.obtain_class
115
+ names = Paperclip::Task.obtain_attachments(klass)
116
+ names.each do |name|
117
+ Paperclip.each_instance_with_attachment(klass, name) do |instance|
118
+ attachment = instance.send(name)
119
+ if attachment.exists?
120
+ print "."
121
+ else
122
+ Paperclip::Task.log_error("#{instance.class}##{attachment.name}, #{instance.id}, #{attachment.url}")
123
+ end
124
+ end
125
+ end
126
+ end
111
127
  end
@@ -12,8 +12,6 @@ Gem::Specification.new do |s|
12
12
  s.description = "Easy upload management for ActiveRecord"
13
13
  s.license = "MIT"
14
14
 
15
- s.rubyforge_project = "paperclip"
16
-
17
15
  s.files = `git ls-files`.split("\n")
18
16
  s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
19
17
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
@@ -26,6 +24,7 @@ Gem::Specification.new do |s|
26
24
  s.add_dependency('activesupport', '>= 3.2.0')
27
25
  s.add_dependency('cocaine', '~> 0.5.5')
28
26
  s.add_dependency('mime-types')
27
+ s.add_dependency('mimemagic', '0.3.0')
29
28
 
30
29
  s.add_development_dependency('activerecord', '>= 3.2.0')
31
30
  s.add_development_dependency('shoulda')
@@ -34,11 +33,11 @@ Gem::Specification.new do |s|
34
33
  s.add_development_dependency('mocha')
35
34
  s.add_development_dependency('aws-sdk', '>= 1.5.7', "<= 2.0")
36
35
  s.add_development_dependency('bourne')
37
- s.add_development_dependency('cucumber', '~> 1.3.11')
36
+ s.add_development_dependency('cucumber', '~> 1.3.18')
38
37
  s.add_development_dependency('aruba')
39
38
  s.add_development_dependency('nokogiri')
40
39
  # Ruby version < 1.9.3 can't install capybara > 2.0.3.
41
- s.add_development_dependency('capybara', '= 2.0.3')
40
+ s.add_development_dependency('capybara')
42
41
  s.add_development_dependency('bundler')
43
42
  s.add_development_dependency('fog', '~> 1.0')
44
43
  s.add_development_dependency('launchy')