paperclip 5.1.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -0
  3. data/.hound.yml +5 -16
  4. data/.travis.yml +14 -15
  5. data/Appraisals +3 -23
  6. data/Gemfile +1 -0
  7. data/NEWS +36 -1
  8. data/README.md +58 -10
  9. data/Rakefile +1 -1
  10. data/UPGRADING +1 -1
  11. data/features/step_definitions/attachment_steps.rb +6 -6
  12. data/features/step_definitions/rails_steps.rb +29 -22
  13. data/features/step_definitions/s3_steps.rb +1 -1
  14. data/features/support/env.rb +1 -0
  15. data/features/support/paths.rb +1 -1
  16. data/features/support/rails.rb +0 -24
  17. data/gemfiles/{4.2.awsv2.0.gemfile → 4.2.gemfile} +1 -1
  18. data/gemfiles/{5.0.awsv2.1.gemfile → 5.0.gemfile} +2 -2
  19. data/lib/paperclip.rb +12 -10
  20. data/lib/paperclip/attachment.rb +10 -4
  21. data/lib/paperclip/io_adapters/abstract_adapter.rb +24 -2
  22. data/lib/paperclip/io_adapters/attachment_adapter.rb +10 -5
  23. data/lib/paperclip/io_adapters/data_uri_adapter.rb +8 -8
  24. data/lib/paperclip/io_adapters/empty_string_adapter.rb +5 -4
  25. data/lib/paperclip/io_adapters/file_adapter.rb +12 -6
  26. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +7 -7
  27. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -6
  28. data/lib/paperclip/io_adapters/nil_adapter.rb +8 -5
  29. data/lib/paperclip/io_adapters/registry.rb +6 -2
  30. data/lib/paperclip/io_adapters/stringio_adapter.rb +9 -6
  31. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +9 -5
  32. data/lib/paperclip/io_adapters/uri_adapter.rb +13 -11
  33. data/lib/paperclip/storage/filesystem.rb +13 -2
  34. data/lib/paperclip/storage/fog.rb +7 -4
  35. data/lib/paperclip/storage/s3.rb +31 -3
  36. data/lib/paperclip/thumbnail.rb +14 -4
  37. data/lib/paperclip/version.rb +1 -1
  38. data/lib/tasks/paperclip.rake +17 -3
  39. data/paperclip.gemspec +3 -3
  40. data/spec/paperclip/attachment_spec.rb +39 -8
  41. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +44 -21
  42. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +6 -3
  43. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +7 -1
  44. data/spec/paperclip/io_adapters/file_adapter_spec.rb +2 -2
  45. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +6 -1
  46. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +1 -1
  47. data/spec/paperclip/io_adapters/registry_spec.rb +2 -2
  48. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +1 -1
  49. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +3 -3
  50. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +6 -1
  51. data/spec/paperclip/storage/fog_spec.rb +16 -0
  52. data/spec/paperclip/storage/s3_live_spec.rb +12 -10
  53. data/spec/paperclip/storage/s3_spec.rb +85 -4
  54. data/spec/paperclip/tempfile_spec.rb +35 -0
  55. data/spec/paperclip/thumbnail_spec.rb +35 -32
  56. data/spec/spec_helper.rb +3 -1
  57. data/spec/support/assertions.rb +5 -1
  58. data/spec/support/conditional_filter_helper.rb +5 -0
  59. metadata +31 -135
  60. data/gemfiles/4.2.awsv2.1.gemfile +0 -17
  61. data/gemfiles/4.2.awsv2.gemfile +0 -20
  62. data/gemfiles/5.0.awsv2.0.gemfile +0 -17
  63. data/gemfiles/5.0.awsv2.gemfile +0 -20
@@ -1,6 +1,6 @@
1
1
  When /^I attach the file "([^"]*)" to "([^"]*)" on S3$/ do |file_path, field|
2
2
  definition = Paperclip::AttachmentRegistry.definitions_for(User)[field.downcase.to_sym]
3
- path = "https://paperclip.s3-us-west-2.amazonaws.com#{definition[:path]}"
3
+ path = "https://paperclip.s3.us-west-2.amazonaws.com#{definition[:path]}"
4
4
  path.gsub!(':filename', File.basename(file_path))
5
5
  path.gsub!(/:([^\/\.]+)/) do |match|
6
6
  "([^\/\.]+)"
@@ -7,5 +7,6 @@ $CUCUMBER=1
7
7
  World(RSpec::Matchers)
8
8
 
9
9
  Before do
10
+ aruba.config.command_launcher = ENV.fetch("DEBUG", nil) ? :debug : :spawn
10
11
  @aruba_timeout_seconds = 120
11
12
  end
@@ -17,7 +17,7 @@ module NavigationHelpers
17
17
  page_name =~ /the (.*) page/
18
18
  path_components = $1.split(/\s+/)
19
19
  self.send(path_components.push('path').join('_').to_sym)
20
- rescue Object => e
20
+ rescue Object
21
21
  raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
22
22
  "Now, go and add a mapping in #{__FILE__}"
23
23
  end
@@ -35,29 +35,5 @@ module RailsCommandHelpers
35
35
  def framework_major_version
36
36
  framework_version.split(".").first.to_i
37
37
  end
38
-
39
- def using_protected_attributes?
40
- framework_major_version < 4
41
- end
42
-
43
- def new_application_command
44
- "rails new"
45
- end
46
-
47
- def generator_command
48
- if framework_major_version >= 4
49
- "rails generate"
50
- else
51
- "script/rails generate"
52
- end
53
- end
54
-
55
- def runner_command
56
- if framework_major_version >= 4
57
- "rails runner"
58
- else
59
- "script/rails runner"
60
- end
61
- end
62
38
  end
63
39
  World(RailsCommandHelpers)
@@ -5,13 +5,13 @@ source "https://rubygems.org"
5
5
  gem "sqlite3", "~> 1.3.8", :platforms => :ruby
6
6
  gem "pry"
7
7
  gem "rails", "~> 4.2.0"
8
- gem "aws-sdk", "~> 2.0.0"
9
8
 
10
9
  group :development, :test do
11
10
  gem "activerecord-import"
12
11
  gem "mime-types"
13
12
  gem "builder"
14
13
  gem "rubocop", :require => false
14
+ gem "rspec"
15
15
  end
16
16
 
17
17
  gemspec :path => "../"
@@ -4,14 +4,14 @@ source "https://rubygems.org"
4
4
 
5
5
  gem "sqlite3", "~> 1.3.8", :platforms => :ruby
6
6
  gem "pry"
7
- gem "rails", "5.0.0"
8
- gem "aws-sdk", "~> 2.1.0"
7
+ gem "rails", "~> 5.0.0"
9
8
 
10
9
  group :development, :test do
11
10
  gem "activerecord-import"
12
11
  gem "mime-types"
13
12
  gem "builder"
14
13
  gem "rubocop", :require => false
14
+ gem "rspec"
15
15
  end
16
16
 
17
17
  gemspec :path => "../"
@@ -90,15 +90,14 @@ module Paperclip
90
90
  # image's orientation. Defaults to true.
91
91
  def self.options
92
92
  @options ||= {
93
- :whiny => true,
94
- :image_magick_path => nil,
95
- :command_path => nil,
96
- :log => true,
97
- :log_command => true,
98
- :swallow_stderr => true,
99
- :content_type_mappings => {},
100
- :use_exif_orientation => true,
101
- :read_timeout => nil
93
+ command_path: nil,
94
+ content_type_mappings: {},
95
+ log: true,
96
+ log_command: true,
97
+ read_timeout: nil,
98
+ swallow_stderr: true,
99
+ use_exif_orientation: true,
100
+ whiny: true,
102
101
  }
103
102
  end
104
103
 
@@ -120,7 +119,7 @@ module Paperclip
120
119
  # called on it, the attachment will *not* be deleted until +save+ is called. See the
121
120
  # Paperclip::Attachment documentation for more specifics. There are a number of options
122
121
  # you can set to change the behavior of a Paperclip attachment:
123
- # * +url+: The full URL of where the attachment is publically accessible. This can just
122
+ # * +url+: The full URL of where the attachment is publicly accessible. This can just
124
123
  # as easily point to a directory served directly through Apache as it can to an action
125
124
  # that can control permissions. You can specify the full domain and path, but usually
126
125
  # just an absolute path is sufficient. The leading slash *must* be included manually for
@@ -129,6 +128,9 @@ module Paperclip
129
128
  # Paperclip::Attachment#interpolate for more information on variable interpolaton.
130
129
  # :url => "/:class/:attachment/:id/:style_:filename"
131
130
  # :url => "http://some.other.host/stuff/:class/:id_:extension"
131
+ # Note: When using the +s3+ storage option, the +url+ option expects
132
+ # particular values. See the Paperclip::Storage::S3#url documentation for
133
+ # specifics.
132
134
  # * +default_url+: The URL that will be returned if there is no attachment assigned.
133
135
  # This field is interpolated just as the url is. The default value is
134
136
  # "/:attachment/:style/missing.png"
@@ -33,6 +33,7 @@ module Paperclip
33
33
  :use_timestamp => true,
34
34
  :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
35
35
  :validate_media_type => true,
36
+ :adapter_options => { hash_digest: Digest::MD5 },
36
37
  :check_validity_before_processing => true
37
38
  }
38
39
  end
@@ -97,7 +98,8 @@ module Paperclip
97
98
  # attachment:
98
99
  # new_user.avatar = old_user.avatar
99
100
  def assign(uploaded_file)
100
- @file = Paperclip.io_adapters.for(uploaded_file)
101
+ @file = Paperclip.io_adapters.for(uploaded_file,
102
+ @options[:adapter_options])
101
103
  ensure_required_accessors!
102
104
  ensure_required_validations!
103
105
 
@@ -238,7 +240,8 @@ module Paperclip
238
240
  # the instance's errors and returns false, cancelling the save.
239
241
  def save
240
242
  flush_deletes unless @options[:keep_old_files]
241
- if @options[:only_process].any? && !@options[:only_process].include?(:original)
243
+ process = only_process
244
+ if process.any? && !process.include?(:original)
242
245
  @queued_for_write.except!(:original)
243
246
  end
244
247
  flush_writes
@@ -523,15 +526,18 @@ module Paperclip
523
526
  begin
524
527
  raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
525
528
  intermediate_files = []
529
+ original = @queued_for_write[:original]
526
530
 
527
- @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
531
+ @queued_for_write[name] = style.processors.
532
+ reduce(original) do |file, processor|
528
533
  file = Paperclip.processor(processor).make(file, style.processor_options, self)
529
534
  intermediate_files << file unless file == @queued_for_write[:original]
530
535
  file
531
536
  end
532
537
 
533
538
  unadapted_file = @queued_for_write[name]
534
- @queued_for_write[name] = Paperclip.io_adapters.for(@queued_for_write[name])
539
+ @queued_for_write[name] = Paperclip.io_adapters.
540
+ for(@queued_for_write[name], @options[:adapter_options])
535
541
  unadapted_file.close if unadapted_file.respond_to?(:close)
536
542
  @queued_for_write[name]
537
543
  rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e
@@ -8,8 +8,20 @@ module Paperclip
8
8
  delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink, :to => :@tempfile
9
9
  alias :length :size
10
10
 
11
+ def initialize(target, options = {})
12
+ @target = target
13
+ @options = options
14
+ end
15
+
11
16
  def fingerprint
12
- @fingerprint ||= Digest::MD5.file(path).to_s
17
+ @fingerprint ||= begin
18
+ digest = @options.fetch(:hash_digest).new
19
+ File.open(path, "rb") do |f|
20
+ buf = ""
21
+ digest.update(buf) while f.read(16384, buf)
22
+ end
23
+ digest.hexdigest
24
+ end
13
25
  end
14
26
 
15
27
  def read(length = nil, buffer = nil)
@@ -40,8 +52,18 @@ module Paperclip
40
52
  end
41
53
 
42
54
  def copy_to_tempfile(src)
43
- FileUtils.cp(src.path, destination.path)
55
+ link_or_copy_file(src.path, destination.path)
44
56
  destination
45
57
  end
58
+
59
+ def link_or_copy_file(src, dest)
60
+ Paperclip.log("Trying to link #{src} to #{dest}")
61
+ FileUtils.ln(src, dest, force: true) # overwrite existing
62
+ @destination.close
63
+ @destination.open.binmode
64
+ rescue Errno::EXDEV, Errno::EPERM, Errno::ENOENT => e
65
+ Paperclip.log("Link failed with #{e.message}; copying link #{src} to #{dest}")
66
+ FileUtils.cp(src, dest)
67
+ end
46
68
  end
47
69
  end
@@ -1,6 +1,13 @@
1
1
  module Paperclip
2
2
  class AttachmentAdapter < AbstractAdapter
3
- def initialize(target)
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ Paperclip::Attachment === target || Paperclip::Style === target
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
4
11
  @target, @style = case target
5
12
  when Paperclip::Attachment
6
13
  [target, :original]
@@ -22,7 +29,7 @@ module Paperclip
22
29
 
23
30
  def copy_to_tempfile(source)
24
31
  if source.staged?
25
- FileUtils.cp(source.staged_path(@style), destination.path)
32
+ link_or_copy_file(source.staged_path(@style), destination.path)
26
33
  else
27
34
  source.copy_to_local_file(@style, destination.path)
28
35
  end
@@ -31,6 +38,4 @@ module Paperclip
31
38
  end
32
39
  end
33
40
 
34
- Paperclip.io_adapters.register Paperclip::AttachmentAdapter do |target|
35
- Paperclip::Attachment === target || Paperclip::Style === target
36
- end
41
+ Paperclip::AttachmentAdapter.register
@@ -1,22 +1,22 @@
1
1
  module Paperclip
2
2
  class DataUriAdapter < StringioAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ String === target && target =~ REGEXP
6
+ end
7
+ end
3
8
 
4
9
  REGEXP = /\Adata:([-\w]+\/[-\w\+\.]+)?;base64,(.*)/m
5
10
 
6
- def initialize(target_uri)
7
- super(extract_target(target_uri))
11
+ def initialize(target_uri, options = {})
12
+ super(extract_target(target_uri), options)
8
13
  end
9
14
 
10
15
  private
11
16
 
12
17
  def extract_target(uri)
13
18
  data_uri_parts = uri.match(REGEXP) || []
14
- StringIO.new(Base64.decode64(data_uri_parts[2] || ''))
19
+ StringIO.new(Base64.decode64(data_uri_parts[2] || ""))
15
20
  end
16
-
17
21
  end
18
22
  end
19
-
20
- Paperclip.io_adapters.register Paperclip::DataUriAdapter do |target|
21
- String === target && target =~ Paperclip::DataUriAdapter::REGEXP
22
- end
@@ -1,6 +1,9 @@
1
1
  module Paperclip
2
2
  class EmptyStringAdapter < AbstractAdapter
3
- def initialize(target)
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ target.is_a?(String) && target.empty?
6
+ end
4
7
  end
5
8
 
6
9
  def nil?
@@ -13,6 +16,4 @@ module Paperclip
13
16
  end
14
17
  end
15
18
 
16
- Paperclip.io_adapters.register Paperclip::EmptyStringAdapter do |target|
17
- target.is_a?(String) && target.empty?
18
- end
19
+ Paperclip::EmptyStringAdapter.register
@@ -1,14 +1,22 @@
1
1
  module Paperclip
2
2
  class FileAdapter < AbstractAdapter
3
- def initialize(target)
4
- @target = target
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ File === target || ::Tempfile === target
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
5
11
  cache_current_values
6
12
  end
7
13
 
8
14
  private
9
15
 
10
16
  def cache_current_values
11
- self.original_filename = @target.original_filename if @target.respond_to?(:original_filename)
17
+ if @target.respond_to?(:original_filename)
18
+ self.original_filename = @target.original_filename
19
+ end
12
20
  self.original_filename ||= File.basename(@target.path)
13
21
  @tempfile = copy_to_tempfile(@target)
14
22
  @content_type = ContentTypeDetector.new(@target.path).detect
@@ -17,6 +25,4 @@ module Paperclip
17
25
  end
18
26
  end
19
27
 
20
- Paperclip.io_adapters.register Paperclip::FileAdapter do |target|
21
- File === target || Tempfile === target
22
- end
28
+ Paperclip::FileAdapter.register
@@ -1,15 +1,15 @@
1
1
  module Paperclip
2
2
  class HttpUrlProxyAdapter < UriAdapter
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ String === target && target =~ REGEXP
6
+ end
7
+ end
3
8
 
4
9
  REGEXP = /\Ahttps?:\/\//
5
10
 
6
- def initialize(target)
7
- super(URI(URI.escape(target)))
11
+ def initialize(target, options = {})
12
+ super(URI(URI.escape(target)), options)
8
13
  end
9
-
10
14
  end
11
15
  end
12
-
13
- Paperclip.io_adapters.register Paperclip::HttpUrlProxyAdapter do |target|
14
- String === target && target =~ Paperclip::HttpUrlProxyAdapter::REGEXP
15
- end
@@ -1,12 +1,18 @@
1
1
  module Paperclip
2
2
  class IdentityAdapter < AbstractAdapter
3
- def new(adapter)
4
- adapter
3
+ def self.register
4
+ Paperclip.io_adapters.register Paperclip::IdentityAdapter.new do |target|
5
+ Paperclip.io_adapters.registered?(target)
6
+ end
7
+ end
8
+
9
+ def initialize
5
10
  end
6
- end
7
- end
8
11
 
9
- Paperclip.io_adapters.register Paperclip::IdentityAdapter.new do |target|
10
- Paperclip.io_adapters.registered?(target)
12
+ def new(target, _)
13
+ target
14
+ end
15
+ end
11
16
  end
12
17
 
18
+ Paperclip::IdentityAdapter.register
@@ -1,8 +1,13 @@
1
1
  module Paperclip
2
2
  class NilAdapter < AbstractAdapter
3
- def initialize(target)
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ target.nil? || ((Paperclip::Attachment === target) && !target.present?)
6
+ end
4
7
  end
5
8
 
9
+ def initialize(_target, _options = {}); end
10
+
6
11
  def original_filename
7
12
  ""
8
13
  end
@@ -19,7 +24,7 @@ module Paperclip
19
24
  true
20
25
  end
21
26
 
22
- def read(*args)
27
+ def read(*_args)
23
28
  nil
24
29
  end
25
30
 
@@ -29,6 +34,4 @@ module Paperclip
29
34
  end
30
35
  end
31
36
 
32
- Paperclip.io_adapters.register Paperclip::NilAdapter do |target|
33
- target.nil? || ( (Paperclip::Attachment === target) && !target.present? )
34
- end
37
+ Paperclip::NilAdapter.register
@@ -12,6 +12,10 @@ module Paperclip
12
12
  @registered_handlers << [block, handler_class]
13
13
  end
14
14
 
15
+ def unregister(handler_class)
16
+ @registered_handlers.reject! { |_, klass| klass == handler_class }
17
+ end
18
+
15
19
  def handler_for(target)
16
20
  @registered_handlers.each do |tester, handler|
17
21
  return handler if tester.call(target)
@@ -25,8 +29,8 @@ module Paperclip
25
29
  end
26
30
  end
27
31
 
28
- def for(target)
29
- handler_for(target).new(target)
32
+ def for(target, options = {})
33
+ handler_for(target).new(target, options)
30
34
  end
31
35
  end
32
36
  end
@@ -1,7 +1,13 @@
1
1
  module Paperclip
2
2
  class StringioAdapter < AbstractAdapter
3
- def initialize(target)
4
- @target = target
3
+ def self.register
4
+ Paperclip.io_adapters.register self do |target|
5
+ StringIO === target
6
+ end
7
+ end
8
+
9
+ def initialize(target, options = {})
10
+ super
5
11
  cache_current_values
6
12
  end
7
13
 
@@ -24,10 +30,7 @@ module Paperclip
24
30
  destination.rewind
25
31
  destination
26
32
  end
27
-
28
33
  end
29
34
  end
30
35
 
31
- Paperclip.io_adapters.register Paperclip::StringioAdapter do |target|
32
- StringIO === target
33
- end
36
+ Paperclip::StringioAdapter.register