paperclip 4.3.0 → 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 (81) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +14 -13
  3. data/Appraisals +22 -9
  4. data/CONTRIBUTING.md +16 -5
  5. data/Gemfile +2 -8
  6. data/LICENSE +1 -1
  7. data/NEWS +49 -2
  8. data/README.md +126 -102
  9. data/UPGRADING +12 -9
  10. data/features/basic_integration.feature +1 -0
  11. data/features/migration.feature +0 -24
  12. data/features/step_definitions/attachment_steps.rb +20 -20
  13. data/features/step_definitions/html_steps.rb +2 -2
  14. data/features/step_definitions/rails_steps.rb +11 -17
  15. data/features/step_definitions/s3_steps.rb +2 -2
  16. data/features/step_definitions/web_steps.rb +1 -103
  17. data/features/support/file_helpers.rb +2 -2
  18. data/gemfiles/4.2.awsv2.0.gemfile +17 -0
  19. data/gemfiles/4.2.awsv2.1.gemfile +17 -0
  20. data/gemfiles/{4.2.gemfile → 4.2.awsv2.gemfile} +1 -1
  21. data/gemfiles/5.0.awsv2.0.gemfile +17 -0
  22. data/gemfiles/5.0.awsv2.1.gemfile +17 -0
  23. data/gemfiles/{4.1.gemfile → 5.0.awsv2.gemfile} +2 -2
  24. data/lib/paperclip/attachment.rb +18 -17
  25. data/lib/paperclip/attachment_registry.rb +2 -1
  26. data/lib/paperclip/callbacks.rb +8 -6
  27. data/lib/paperclip/content_type_detector.rb +3 -2
  28. data/lib/paperclip/errors.rb +3 -1
  29. data/lib/paperclip/geometry_parser_factory.rb +1 -1
  30. data/lib/paperclip/glue.rb +1 -1
  31. data/lib/paperclip/has_attached_file.rb +7 -1
  32. data/lib/paperclip/helpers.rb +14 -10
  33. data/lib/paperclip/interpolations/plural_cache.rb +6 -5
  34. data/lib/paperclip/interpolations.rb +18 -13
  35. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +1 -1
  36. data/lib/paperclip/io_adapters/uri_adapter.rb +3 -1
  37. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
  38. data/lib/paperclip/media_type_spoof_detector.rb +1 -1
  39. data/lib/paperclip/rails_environment.rb +1 -1
  40. data/lib/paperclip/schema.rb +3 -9
  41. data/lib/paperclip/storage/fog.rb +17 -8
  42. data/lib/paperclip/storage/s3.rb +51 -49
  43. data/lib/paperclip/validators/attachment_size_validator.rb +1 -7
  44. data/lib/paperclip/version.rb +3 -1
  45. data/lib/paperclip.rb +2 -1
  46. data/lib/tasks/paperclip.rake +1 -1
  47. data/paperclip.gemspec +15 -11
  48. data/spec/paperclip/attachment_processing_spec.rb +2 -4
  49. data/spec/paperclip/attachment_registry_spec.rb +28 -0
  50. data/spec/paperclip/attachment_spec.rb +36 -14
  51. data/spec/paperclip/geometry_spec.rb +1 -1
  52. data/spec/paperclip/glue_spec.rb +44 -0
  53. data/spec/paperclip/has_attached_file_spec.rb +24 -8
  54. data/spec/paperclip/integration_spec.rb +4 -3
  55. data/spec/paperclip/interpolations_spec.rb +14 -4
  56. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +12 -0
  57. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +27 -0
  58. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +10 -0
  59. data/spec/paperclip/media_type_spoof_detector_spec.rb +12 -3
  60. data/spec/paperclip/paperclip_spec.rb +3 -28
  61. data/spec/paperclip/plural_cache_spec.rb +17 -16
  62. data/spec/paperclip/storage/fog_spec.rb +31 -1
  63. data/spec/paperclip/storage/s3_live_spec.rb +8 -4
  64. data/spec/paperclip/storage/s3_spec.rb +213 -156
  65. data/spec/paperclip/validators/attachment_size_validator_spec.rb +26 -20
  66. data/spec/paperclip/validators_spec.rb +3 -3
  67. data/spec/spec_helper.rb +6 -1
  68. data/spec/support/assertions.rb +7 -0
  69. data/spec/support/model_reconstruction.rb +9 -1
  70. data/spec/support/reporting.rb +11 -0
  71. metadata +74 -47
  72. data/cucumber/paperclip_steps.rb +0 -6
  73. data/gemfiles/3.2.gemfile +0 -20
  74. data/lib/paperclip/locales/de.yml +0 -18
  75. data/lib/paperclip/locales/es.yml +0 -18
  76. data/lib/paperclip/locales/ja.yml +0 -18
  77. data/lib/paperclip/locales/pt-BR.yml +0 -18
  78. data/lib/paperclip/locales/zh-CN.yml +0 -18
  79. data/lib/paperclip/locales/zh-HK.yml +0 -18
  80. data/lib/paperclip/locales/zh-TW.yml +0 -18
  81. data/spec/support/rails_helpers.rb +0 -7
@@ -19,7 +19,9 @@ module Paperclip
19
19
  end
20
20
 
21
21
  # Will be thrown when ImageMagic cannot determine the uploaded file's
22
- # metadata, usually this would mean the file is not an image.
22
+ # metadata, usually this would mean the file is not an image. If you are
23
+ # consistently receiving this error on PDFs make sure that you have
24
+ # installed Ghostscript.
23
25
  class NotIdentifiedByImageMagickError < Paperclip::Error
24
26
  end
25
27
 
@@ -1,6 +1,6 @@
1
1
  module Paperclip
2
2
  class GeometryParser
3
- FORMAT = /\b(\d*)x?(\d*)\b(?:,(\d?))?([\>\<\#\@\%^!])?/i
3
+ FORMAT = /\b(\d*)x?(\d*)\b(?:,(\d?))?(\@\>|\>\@|[\>\<\#\@\%^!])?/i
4
4
  def initialize(string)
5
5
  @string = string
6
6
  end
@@ -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
11
+ base.send :include, Schema if defined? ActiveRecord::Base
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)
@@ -91,7 +91,13 @@ module Paperclip
91
91
  name = @name
92
92
  @klass.send(:after_save) { send(name).send(:save) }
93
93
  @klass.send(:before_destroy) { send(name).send(:queue_all_for_delete) }
94
- @klass.send(:after_commit, :on => :destroy) { send(name).send(:flush_deletes) }
94
+ if @klass.respond_to?(:after_commit)
95
+ @klass.send(:after_commit, on: :destroy) do
96
+ send(name).send(:flush_deletes)
97
+ end
98
+ else
99
+ @klass.send(:after_destroy) { send(name).send(:flush_deletes) }
100
+ end
95
101
  end
96
102
 
97
103
  def add_paperclip_callbacks
@@ -8,23 +8,27 @@ module Paperclip
8
8
  Paperclip::Interpolations[key] = block
9
9
  end
10
10
 
11
- # The run method takes the name of a binary to run, the arguments to that binary
12
- # and some options:
11
+ # The run method takes the name of a binary to run, the arguments
12
+ # to that binary, the values to interpolate and some local options.
13
13
  #
14
- # :command_path -> A $PATH-like variable that defines where to look for the binary
15
- # on the filesystem. Colon-separated, just like $PATH.
14
+ # :cmd -> The name of a binary to run.
16
15
  #
17
- # :expected_outcodes -> An array of integers that defines the expected exit codes
18
- # of the binary. Defaults to [0].
16
+ # :arguments -> The command line arguments to that binary.
19
17
  #
20
- # :log_command -> Log the command being run when set to true (defaults to true).
21
- # This will only log if logging in general is set to true as well.
18
+ # :interpolation_values -> Values to be interpolated into the arguments.
22
19
  #
23
- # :swallow_stderr -> Set to true if you don't care what happens on STDERR.
20
+ # :local_options -> The options to be used by Cocain::CommandLine.
21
+ # These could be: runner
22
+ # logger
23
+ # swallow_stderr
24
+ # expected_outcodes
25
+ # environment
26
+ # runner_options
24
27
  #
25
28
  def run(cmd, arguments = "", interpolation_values = {}, local_options = {})
26
29
  command_path = options[:command_path]
27
- Cocaine::CommandLine.path = [Cocaine::CommandLine.path, command_path].flatten.compact.uniq
30
+ cocaine_path_array = Cocaine::CommandLine.path.try(:split, Cocaine::OS.path_separator)
31
+ Cocaine::CommandLine.path = [cocaine_path_array, command_path].flatten.compact.uniq
28
32
  if logging? && (options[:log_command] || local_options[:log_command])
29
33
  local_options = local_options.merge(:logger => logger)
30
34
  end
@@ -2,15 +2,16 @@ module Paperclip
2
2
  module Interpolations
3
3
  class PluralCache
4
4
  def initialize
5
- @cache = {}
5
+ @symbol_cache = {}.compare_by_identity
6
+ @klass_cache = {}.compare_by_identity
6
7
  end
7
8
 
8
- def pluralize(word)
9
- @cache[word] ||= word.pluralize
9
+ def pluralize_symbol(symbol)
10
+ @symbol_cache[symbol] ||= symbol.to_s.downcase.pluralize
10
11
  end
11
12
 
12
- def underscore_and_pluralize(word)
13
- @cache[word] ||= word.underscore.pluralize
13
+ def underscore_and_pluralize_class(klass)
14
+ @klass_cache[klass] ||= klass.name.underscore.pluralize
14
15
  end
15
16
  end
16
17
  end
@@ -10,6 +10,7 @@ module Paperclip
10
10
  # and is not intended for normal use.
11
11
  def self.[]= name, block
12
12
  define_method(name, &block)
13
+ @interpolators_cache = nil
13
14
  end
14
15
 
15
16
  # Hash access of interpolations. Included only for compatibility,
@@ -20,7 +21,7 @@ module Paperclip
20
21
 
21
22
  # Returns a sorted list of all interpolations.
22
23
  def self.all
23
- self.instance_methods(false).sort
24
+ self.instance_methods(false).sort!
24
25
  end
25
26
 
26
27
  # Perform the actual interpolation. Takes the pattern to interpolate
@@ -29,11 +30,15 @@ module Paperclip
29
30
  # an interpolation pattern for Paperclip to use.
30
31
  def self.interpolate pattern, *args
31
32
  pattern = args.first.instance.send(pattern) if pattern.kind_of? Symbol
32
- all.reverse.inject(pattern) do |result, tag|
33
- result.gsub(/:#{tag}/) do |match|
34
- send( tag, *args )
35
- end
33
+ result = pattern.dup
34
+ interpolators_cache.each do |method, token|
35
+ result.gsub!(token) { send(method, *args) } if result.include?(token)
36
36
  end
37
+ result
38
+ end
39
+
40
+ def self.interpolators_cache
41
+ @interpolators_cache ||= all.reverse!.map! { |method| [method, ":#{method}"] }
37
42
  end
38
43
 
39
44
  def self.plural_cache
@@ -42,7 +47,7 @@ module Paperclip
42
47
 
43
48
  # Returns the filename, the same way as ":basename.:extension" would.
44
49
  def filename attachment, style_name
45
- [ basename(attachment, style_name), extension(attachment, style_name) ].reject(&:blank?).join(".")
50
+ [ basename(attachment, style_name), extension(attachment, style_name) ].delete_if(&:empty?).join(".".freeze)
46
51
  end
47
52
 
48
53
  # Returns the interpolated URL. Will raise an error if the url itself
@@ -85,12 +90,12 @@ module Paperclip
85
90
  # all class names. Calling #class will return the expected class.
86
91
  def class attachment = nil, style_name = nil
87
92
  return super() if attachment.nil? && style_name.nil?
88
- plural_cache.underscore_and_pluralize(attachment.instance.class.to_s)
93
+ plural_cache.underscore_and_pluralize_class(attachment.instance.class)
89
94
  end
90
95
 
91
96
  # Returns the basename of the file. e.g. "file" for "file.jpg"
92
97
  def basename attachment, style_name
93
- attachment.original_filename.gsub(/#{Regexp.escape(File.extname(attachment.original_filename))}\Z/, "")
98
+ File.basename(attachment.original_filename, ".*".freeze)
94
99
  end
95
100
 
96
101
  # Returns the extension of the file. e.g. "jpg" for "file.jpg"
@@ -98,7 +103,7 @@ module Paperclip
98
103
  # of the actual extension.
99
104
  def extension attachment, style_name
100
105
  ((style = attachment.styles[style_name.to_s.to_sym]) && style[:format]) ||
101
- File.extname(attachment.original_filename).gsub(/\A\.+/, "")
106
+ File.extname(attachment.original_filename).sub(/\A\.+/, "".freeze)
102
107
  end
103
108
 
104
109
  # Returns the dot+extension of the file. e.g. ".jpg" for "file.jpg"
@@ -106,7 +111,7 @@ module Paperclip
106
111
  # of the actual extension. If the extension is empty, no dot is added.
107
112
  def dotextension attachment, style_name
108
113
  ext = extension(attachment, style_name)
109
- ext.empty? ? "" : ".#{ext}"
114
+ ext.empty? ? ext : ".#{ext}"
110
115
  end
111
116
 
112
117
  # Returns an extension based on the content type. e.g. "jpeg" for
@@ -170,9 +175,9 @@ module Paperclip
170
175
  def id_partition attachment, style_name
171
176
  case id = attachment.instance.id
172
177
  when Integer
173
- ("%09d" % id).scan(/\d{3}/).join("/")
178
+ ("%09d".freeze % id).scan(/\d{3}/).join("/".freeze)
174
179
  when String
175
- id.scan(/.{3}/).first(3).join("/")
180
+ id.scan(/.{3}/).first(3).join("/".freeze)
176
181
  else
177
182
  nil
178
183
  end
@@ -181,7 +186,7 @@ module Paperclip
181
186
  # Returns the pluralized form of the attachment name. e.g.
182
187
  # "avatars" for an attachment of :avatar
183
188
  def attachment attachment, style_name
184
- plural_cache.pluralize(attachment.name.to_s.downcase)
189
+ plural_cache.pluralize_symbol(attachment.name)
185
190
  end
186
191
 
187
192
  # Returns the style, or the default style if nil is supplied.
@@ -4,7 +4,7 @@ module Paperclip
4
4
  REGEXP = /\Ahttps?:\/\//
5
5
 
6
6
  def initialize(target)
7
- super(URI(target))
7
+ super(URI(URI.escape(target)))
8
8
  end
9
9
 
10
10
  end
@@ -14,7 +14,9 @@ module Paperclip
14
14
  private
15
15
 
16
16
  def download_content
17
- open(@target)
17
+ options = { read_timeout: Paperclip.options[:read_timeout] }.compact
18
+
19
+ open(@target, **options)
18
20
  end
19
21
 
20
22
  def cache_current_values
@@ -40,9 +40,9 @@ module Paperclip
40
40
 
41
41
  def failure_message
42
42
  "#{expected_attachment}\n".tap do |message|
43
- message << accepted_types_and_failures
43
+ message << accepted_types_and_failures.to_s
44
44
  message << "\n\n" if @allowed_types.present? && @rejected_types.present?
45
- message << rejected_types_and_failures
45
+ message << rejected_types_and_failures.to_s
46
46
  end
47
47
  end
48
48
 
@@ -55,7 +55,7 @@ module Paperclip
55
55
  def accepted_types_and_failures
56
56
  if @allowed_types.present?
57
57
  "Accept content types: #{@allowed_types.join(", ")}\n".tap do |message|
58
- if @missing_allowed_types.any?
58
+ if @missing_allowed_types.present?
59
59
  message << " #{@missing_allowed_types.join(", ")} were rejected."
60
60
  else
61
61
  message << " All were accepted successfully."
@@ -66,7 +66,7 @@ module Paperclip
66
66
  def rejected_types_and_failures
67
67
  if @rejected_types.present?
68
68
  "Reject content types: #{@rejected_types.join(", ")}\n".tap do |message|
69
- if @missing_rejected_types.any?
69
+ if @missing_rejected_types.present?
70
70
  message << " #{@missing_rejected_types.join(", ")} were accepted."
71
71
  else
72
72
  message << " All were rejected successfully."
@@ -12,7 +12,7 @@ module Paperclip
12
12
 
13
13
  def spoofed?
14
14
  if has_name? && has_extension? && media_type_mismatch? && mapping_override_mismatch?
15
- Paperclip.log("Content Type Spoof: Filename #{File.basename(@name)} (#{supplied_content_type} from Headers, #{content_types_from_name} from Extension), content type discovered from file command: #{calculated_content_type}. See documentation to allow this combination.")
15
+ Paperclip.log("Content Type Spoof: Filename #{File.basename(@name)} (#{supplied_content_type} from Headers, #{content_types_from_name.map(&:to_s)} from Extension), content type discovered from file command: #{calculated_content_type}. See documentation to allow this combination.")
16
16
  true
17
17
  else
18
18
  false
@@ -15,7 +15,7 @@ module Paperclip
15
15
  private
16
16
 
17
17
  def rails_exists?
18
- Object.const_defined?("Rails")
18
+ Object.const_defined?(:Rails)
19
19
  end
20
20
 
21
21
  def rails_environment_exists?
@@ -12,10 +12,7 @@ module Paperclip
12
12
  ActiveRecord::ConnectionAdapters::Table.send :include, TableDefinition
13
13
  ActiveRecord::ConnectionAdapters::TableDefinition.send :include, TableDefinition
14
14
  ActiveRecord::ConnectionAdapters::AbstractAdapter.send :include, Statements
15
-
16
- if defined?(ActiveRecord::Migration::CommandRecorder) # Rails 3.1+
17
- ActiveRecord::Migration::CommandRecorder.send :include, CommandRecorder
18
- end
15
+ ActiveRecord::Migration::CommandRecorder.send :include, CommandRecorder
19
16
  end
20
17
 
21
18
  module Statements
@@ -35,12 +32,9 @@ module Paperclip
35
32
  def remove_attachment(table_name, *attachment_names)
36
33
  raise ArgumentError, "Please specify attachment name in your remove_attachment call in your migration." if attachment_names.empty?
37
34
 
38
- options = attachment_names.extract_options!
39
-
40
35
  attachment_names.each do |attachment_name|
41
- COLUMNS.each_pair do |column_name, column_type|
42
- column_options = options.merge(options[column_name.to_sym] || {})
43
- remove_column(table_name, "#{attachment_name}_#{column_name}", column_type, column_options)
36
+ COLUMNS.keys.each do |column_name|
37
+ remove_column(table_name, "#{attachment_name}_#{column_name}")
44
38
  end
45
39
  end
46
40
  end
@@ -33,6 +33,10 @@ module Paperclip
33
33
  # that is the alias to the S3 domain of your bucket, e.g.
34
34
  # 'http://images.example.com'. This can also be used in
35
35
  # conjunction with Cloudfront (http://aws.amazon.com/cloudfront)
36
+ # * +fog_options+: (optional) A hash of options that are passed
37
+ # to fog when the file is created. For example, you could set
38
+ # the multipart-chunk size to 100MB with a hash:
39
+ # { :multipart_chunk_size => 104857600 }
36
40
 
37
41
  module Fog
38
42
  def self.extended base
@@ -98,12 +102,14 @@ module Paperclip
98
102
  log("saving #{path(style)}")
99
103
  retried = false
100
104
  begin
101
- directory.files.create(fog_file.merge(
105
+ attributes = fog_file.merge(
102
106
  :body => file,
103
107
  :key => path(style),
104
108
  :public => fog_public(style),
105
109
  :content_type => file.content_type
106
- ))
110
+ )
111
+ attributes.merge!(@options[:fog_options]) if @options[:fog_options]
112
+ directory.files.create(attributes)
107
113
  rescue Excon::Errors::NotFound
108
114
  raise if retried
109
115
  retried = true
@@ -164,6 +170,7 @@ module Paperclip
164
170
  log("copying #{path(style)} to local file #{local_dest_path}")
165
171
  ::File.open(local_dest_path, 'wb') do |local_file|
166
172
  file = directory.files.get(path(style))
173
+ return false unless file
167
174
  local_file.write(file.body)
168
175
  end
169
176
  rescue ::Fog::Errors::Error => e
@@ -189,10 +196,10 @@ module Paperclip
189
196
  end
190
197
 
191
198
  def host_name_for_directory
192
- if @options[:fog_directory].to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX
193
- "#{@options[:fog_directory]}.s3.amazonaws.com"
199
+ if directory_name.to_s =~ Fog::AWS_BUCKET_SUBDOMAIN_RESTRICTON_REGEX
200
+ "#{directory_name}.s3.amazonaws.com"
194
201
  else
195
- "s3.amazonaws.com/#{@options[:fog_directory]}"
202
+ "s3.amazonaws.com/#{directory_name}"
196
203
  end
197
204
  end
198
205
 
@@ -218,13 +225,15 @@ module Paperclip
218
225
  end
219
226
 
220
227
  def directory
221
- dir = if @options[:fog_directory].respond_to?(:call)
228
+ @directory ||= connection.directories.new(key: directory_name)
229
+ end
230
+
231
+ def directory_name
232
+ if @options[:fog_directory].respond_to?(:call)
222
233
  @options[:fog_directory].call(self)
223
234
  else
224
235
  @options[:fog_directory]
225
236
  end
226
-
227
- @directory ||= connection.directories.new(:key => dir)
228
237
  end
229
238
 
230
239
  def scheme
@@ -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', '~> 1.6'
7
+ # gem 'aws-sdk'
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
@@ -41,7 +41,7 @@ module Paperclip
41
41
  # * +s3_permissions+: This is a String that should be one of the "canned" access
42
42
  # policies that S3 provides (more information can be found here:
43
43
  # http://docs.aws.amazon.com/AmazonS3/latest/dev/ACLOverview.html)
44
- # The default for Paperclip is :public_read.
44
+ # The default for Paperclip is public-read.
45
45
  #
46
46
  # You can set permission on a per style bases by doing the following:
47
47
  # :s3_permissions => {
@@ -61,7 +61,7 @@ module Paperclip
61
61
  # * +bucket+: This is the name of the S3 bucket that will store your files. Remember
62
62
  # that the bucket must be unique across all of Amazon S3. If the bucket does not exist
63
63
  # Paperclip will attempt to create it. The bucket name will not be interpolated.
64
- # You can define the bucket as a Proc if you want to determine it's name at runtime.
64
+ # You can define the bucket as a Proc if you want to determine its name at runtime.
65
65
  # Paperclip will call that Proc with attachment as the only argument.
66
66
  # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
67
67
  # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
@@ -93,6 +93,7 @@ module Paperclip
93
93
  # S3 (strictly speaking) does not support directories, you can still use a / to
94
94
  # separate parts of your file name.
95
95
  # * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name.
96
+ # * +s3_region+: For aws-sdk v2, s3_region is required.
96
97
  # * +s3_metadata+: These key/value pairs will be stored with the
97
98
  # object. This option works by prefixing each key with
98
99
  # "x-amz-meta-" before sending it as a header on the object
@@ -117,21 +118,10 @@ module Paperclip
117
118
  rescue LoadError => e
118
119
  e.message << " (You may need to install the aws-sdk gem)"
119
120
  raise e
120
- end unless defined?(AWS::Core)
121
-
122
- # Overriding log formatter to make sure it return a UTF-8 string
123
- if defined?(AWS::Core::LogFormatter)
124
- AWS::Core::LogFormatter.class_eval do
125
- def summarize_hash(hash)
126
- hash.map { |key, value| ":#{key}=>#{summarize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
127
- end
128
- end
129
- elsif defined?(AWS::Core::ClientLogging)
130
- AWS::Core::ClientLogging.class_eval do
131
- def sanitize_hash(hash)
132
- hash.map { |key, value| "#{sanitize_value(key)}=>#{sanitize_value(value)}".force_encoding('UTF-8') }.sort.join(',')
133
- end
134
- end
121
+ 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."
135
125
  end
136
126
 
137
127
  base.instance_eval do
@@ -141,7 +131,7 @@ module Paperclip
141
131
  Proc.new do |style, attachment|
142
132
  permission = (@s3_permissions[style.to_s.to_sym] || @s3_permissions[:default])
143
133
  permission = permission.call(attachment, style) if permission.respond_to?(:call)
144
- (permission == :public_read) ? 'http' : 'https'
134
+ (permission == :"public-read") ? 'http'.freeze : 'https'.freeze
145
135
  end
146
136
  @s3_metadata = @options[:s3_metadata] || {}
147
137
  @s3_headers = {}
@@ -149,7 +139,7 @@ module Paperclip
149
139
 
150
140
  @s3_storage_class = set_storage_class(@options[:s3_storage_class])
151
141
 
152
- @s3_server_side_encryption = :aes256
142
+ @s3_server_side_encryption = "AES256"
153
143
  if @options[:s3_server_side_encryption].blank?
154
144
  @s3_server_side_encryption = false
155
145
  end
@@ -157,9 +147,9 @@ module Paperclip
157
147
  @s3_server_side_encryption = @options[:s3_server_side_encryption]
158
148
  end
159
149
 
160
- unless @options[:url].to_s.match(/\A:s3.*url\Z/) || @options[:url] == ":asset_host"
161
- @options[:path] = path_option.gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system/, '')
162
- @options[:url] = ":s3_path_url"
150
+ unless @options[:url].to_s.match(/\A:s3.*url\Z/) || @options[:url] == ":asset_host".freeze
151
+ @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "".freeze)
152
+ @options[:url] = ":s3_path_url".freeze
163
153
  end
164
154
  @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
165
155
 
@@ -167,23 +157,26 @@ module Paperclip
167
157
  end
168
158
 
169
159
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
170
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{\A/}, "")}"
160
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_alias}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
171
161
  end unless Paperclip::Interpolations.respond_to? :s3_alias_url
172
162
  Paperclip.interpolates(:s3_path_url) do |attachment, style|
173
- "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{\A/}, "")}"
163
+ "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
174
164
  end unless Paperclip::Interpolations.respond_to? :s3_path_url
175
165
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
176
- "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{\A/}, "")}"
166
+ "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
177
167
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
178
168
  Paperclip.interpolates(:asset_host) do |attachment, style|
179
- "#{attachment.path(style).gsub(%r{\A/}, "")}"
169
+ "#{attachment.path(style).sub(%r{\A/}, "".freeze)}"
180
170
  end unless Paperclip::Interpolations.respond_to? :asset_host
181
171
  end
182
172
 
183
173
  def expiring_url(time = 3600, style_name = default_style)
184
174
  if path(style_name)
185
- base_options = { :expires => time, :secure => use_secure_protocol?(style_name) }
186
- s3_object(style_name).url_for(:read, base_options.merge(s3_url_options)).to_s
175
+ base_options = { expires_in: time }
176
+ s3_object(style_name).presigned_url(
177
+ :get,
178
+ base_options.merge(s3_url_options),
179
+ ).to_s
187
180
  else
188
181
  url(style_name)
189
182
  end
@@ -197,7 +190,14 @@ module Paperclip
197
190
  host_name = @options[:s3_host_name]
198
191
  host_name = host_name.call(self) if host_name.is_a?(Proc)
199
192
 
200
- host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com"
193
+ host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com".freeze
194
+ end
195
+
196
+ def s3_region
197
+ region = @options[:s3_region]
198
+ region = region.call(self) if region.is_a?(Proc)
199
+
200
+ region || s3_credentials[:s3_region]
201
201
  end
202
202
 
203
203
  def s3_host_alias
@@ -220,7 +220,7 @@ module Paperclip
220
220
 
221
221
  def s3_interface
222
222
  @s3_interface ||= begin
223
- config = { :s3_endpoint => s3_host_name }
223
+ config = { region: s3_region }
224
224
 
225
225
  if using_http_proxy?
226
226
 
@@ -234,7 +234,7 @@ module Paperclip
234
234
  config[:proxy_uri] = URI::HTTP.build(proxy_opts)
235
235
  end
236
236
 
237
- [:access_key_id, :secret_access_key, :credential_provider].each do |opt|
237
+ [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt|
238
238
  config[opt] = s3_credentials[opt] if s3_credentials[opt]
239
239
  end
240
240
 
@@ -244,15 +244,19 @@ module Paperclip
244
244
 
245
245
  def obtain_s3_instance_for(options)
246
246
  instances = (Thread.current[:paperclip_s3_instances] ||= {})
247
- instances[options] ||= AWS::S3.new(options)
247
+ instances[options] ||= ::Aws::S3::Resource.new(options)
248
248
  end
249
249
 
250
250
  def s3_bucket
251
- @s3_bucket ||= s3_interface.buckets[bucket_name]
251
+ @s3_bucket ||= s3_interface.bucket(bucket_name)
252
+ end
253
+
254
+ def style_name_as_path(style_name)
255
+ path(style_name).sub(%r{\A/},'')
252
256
  end
253
257
 
254
258
  def s3_object style_name = default_style
255
- s3_bucket.objects[path(style_name).sub(%r{\A/},'')]
259
+ s3_bucket.object style_name_as_path(style_name)
256
260
  end
257
261
 
258
262
  def using_http_proxy?
@@ -277,7 +281,7 @@ module Paperclip
277
281
 
278
282
  def set_permissions permissions
279
283
  permissions = { :default => permissions } unless permissions.respond_to?(:merge)
280
- permissions.merge :default => (permissions[:default] || :public_read)
284
+ permissions.merge :default => (permissions[:default] || :"public-read")
281
285
  end
282
286
 
283
287
  def set_storage_class(storage_class)
@@ -286,7 +290,7 @@ module Paperclip
286
290
  end
287
291
 
288
292
  def parse_credentials creds
289
- creds = creds.respond_to?('call') ? creds.call(self) : creds
293
+ creds = creds.respond_to?(:call) ? creds.call(self) : creds
290
294
  creds = find_credentials(creds).stringify_keys
291
295
  (creds[RailsEnvironment.get] || creds).symbolize_keys
292
296
  end
@@ -297,7 +301,7 @@ module Paperclip
297
301
  else
298
302
  false
299
303
  end
300
- rescue AWS::Errors::Base => e
304
+ rescue Aws::Errors::ServiceError => e
301
305
  false
302
306
  end
303
307
 
@@ -323,7 +327,7 @@ module Paperclip
323
327
  end
324
328
 
325
329
  def create_bucket
326
- s3_interface.buckets.create(bucket_name)
330
+ s3_interface.bucket(bucket_name).create
327
331
  end
328
332
 
329
333
  def flush_writes #:nodoc:
@@ -331,11 +335,9 @@ module Paperclip
331
335
  retries = 0
332
336
  begin
333
337
  log("saving #{path(style)}")
334
- acl = @s3_permissions[style] || @s3_permissions[:default]
335
- acl = acl.call(self, style) if acl.respond_to?(:call)
336
338
  write_options = {
337
339
  :content_type => file.content_type,
338
- :acl => acl
340
+ :acl => s3_permissions(style)
339
341
  }
340
342
 
341
343
  # add storage class for this style if defined
@@ -356,11 +358,11 @@ module Paperclip
356
358
  write_options[:metadata] = @s3_metadata unless @s3_metadata.empty?
357
359
  write_options.merge!(@s3_headers)
358
360
 
359
- s3_object(style).write(file, write_options)
360
- rescue AWS::S3::Errors::NoSuchBucket
361
+ s3_object(style).upload_file(file.path, write_options)
362
+ rescue ::Aws::S3::Errors::NoSuchBucket
361
363
  create_bucket
362
364
  retry
363
- rescue AWS::S3::Errors::SlowDown
365
+ rescue ::Aws::S3::Errors::SlowDown
364
366
  retries += 1
365
367
  if retries <= 5
366
368
  sleep((2 ** retries) * 0.5)
@@ -382,8 +384,8 @@ module Paperclip
382
384
  @queued_for_delete.each do |path|
383
385
  begin
384
386
  log("deleting #{path}")
385
- s3_bucket.objects[path.sub(%r{\A/},'')].delete
386
- rescue AWS::Errors::Base => e
387
+ s3_bucket.object(path.sub(%r{\A/}, "")).delete
388
+ rescue Aws::Errors::ServiceError => e
387
389
  # Ignore this.
388
390
  end
389
391
  end
@@ -393,11 +395,11 @@ module Paperclip
393
395
  def copy_to_local_file(style, local_dest_path)
394
396
  log("copying #{path(style)} to local file #{local_dest_path}")
395
397
  ::File.open(local_dest_path, 'wb') do |local_file|
396
- s3_object(style).read do |chunk|
398
+ s3_object(style).get do |chunk|
397
399
  local_file.write(chunk)
398
400
  end
399
401
  end
400
- rescue AWS::Errors::Base => e
402
+ rescue Aws::Errors::ServiceError => e
401
403
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
402
404
  false
403
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)