paperclip 4.2.0 → 4.2.1

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 (40) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +6 -1
  3. data/Gemfile +1 -1
  4. data/NEWS +33 -0
  5. data/README.md +85 -7
  6. data/features/basic_integration.feature +15 -0
  7. data/features/step_definitions/rails_steps.rb +29 -0
  8. data/gemfiles/3.2.gemfile +13 -7
  9. data/gemfiles/4.0.gemfile +13 -7
  10. data/gemfiles/4.1.gemfile +15 -9
  11. data/gemfiles/4.2.gemfile +19 -0
  12. data/lib/paperclip.rb +1 -0
  13. data/lib/paperclip/attachment.rb +16 -8
  14. data/lib/paperclip/has_attached_file.rb +5 -3
  15. data/lib/paperclip/interpolations.rb +1 -1
  16. data/lib/paperclip/io_adapters/abstract_adapter.rb +1 -0
  17. data/lib/paperclip/locales/ja.yml +18 -0
  18. data/lib/paperclip/locales/pt-BR.yml +18 -0
  19. data/lib/paperclip/locales/zh-CN.yml +18 -0
  20. data/lib/paperclip/locales/zh-HK.yml +18 -0
  21. data/lib/paperclip/locales/zh-TW.yml +18 -0
  22. data/lib/paperclip/processor.rb +0 -37
  23. data/lib/paperclip/processor_helpers.rb +50 -0
  24. data/lib/paperclip/schema.rb +11 -3
  25. data/lib/paperclip/storage/fog.rb +6 -1
  26. data/lib/paperclip/storage/s3.rb +16 -6
  27. data/lib/paperclip/url_generator.rb +11 -3
  28. data/lib/paperclip/version.rb +1 -1
  29. data/spec/paperclip/has_attached_file_spec.rb +24 -0
  30. data/spec/paperclip/interpolations_spec.rb +11 -4
  31. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +7 -0
  32. data/spec/paperclip/meta_class_spec.rb +1 -1
  33. data/spec/paperclip/processor_helpers_spec.rb +57 -0
  34. data/spec/paperclip/schema_spec.rb +50 -8
  35. data/spec/paperclip/storage/fog_spec.rb +30 -2
  36. data/spec/paperclip/storage/s3_spec.rb +33 -0
  37. data/spec/paperclip/url_generator_spec.rb +25 -0
  38. data/spec/paperclip/validators/media_type_spoof_detection_validator_spec.rb +5 -0
  39. data/spec/support/matchers/have_column.rb +14 -0
  40. metadata +13 -2
@@ -0,0 +1,19 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", "1.3.8", :platforms => :ruby
6
+ gem "jruby-openssl", :platforms => :jruby
7
+ gem "activerecord-jdbcsqlite3-adapter", :platforms => :jruby
8
+ gem "rubysl", :platforms => :rbx
9
+ gem "racc", :platforms => :rbx
10
+ gem "pry"
11
+ gem "rails", "~> 4.2.0.rc2"
12
+ gem "paperclip", :path => "../"
13
+
14
+ group :development, :test do
15
+ gem "mime-types", "~> 1.16"
16
+ gem "builder"
17
+ end
18
+
19
+ gemspec :path => "../"
@@ -33,6 +33,7 @@ require 'paperclip/geometry_parser_factory'
33
33
  require 'paperclip/geometry_detector_factory'
34
34
  require 'paperclip/geometry'
35
35
  require 'paperclip/processor'
36
+ require 'paperclip/processor_helpers'
36
37
  require 'paperclip/tempfile'
37
38
  require 'paperclip/thumbnail'
38
39
  require 'paperclip/interpolations/plural_cache'
@@ -31,12 +31,13 @@ module Paperclip
31
31
  :use_default_time_zone => true,
32
32
  :use_timestamp => true,
33
33
  :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
34
+ :validate_media_type => true,
34
35
  :check_validity_before_processing => true
35
36
  }
36
37
  end
37
38
 
38
39
  attr_reader :name, :instance, :default_style, :convert_options, :queued_for_write, :whiny,
39
- :options, :interpolator, :source_file_options, :whiny
40
+ :options, :interpolator, :source_file_options
40
41
  attr_accessor :post_processing
41
42
 
42
43
  # Creates an Attachment object. +name+ is the name of the attachment,
@@ -389,12 +390,8 @@ module Paperclip
389
390
  @instance.class.validators.map(&:class)
390
391
  end
391
392
 
392
- def required_validator_classes
393
- Paperclip::REQUIRED_VALIDATORS + Paperclip::REQUIRED_VALIDATORS.flat_map(&:descendants)
394
- end
395
-
396
393
  def missing_required_validator?
397
- (active_validator_classes & required_validator_classes).empty?
394
+ (active_validator_classes.flat_map(&:ancestors) & Paperclip::REQUIRED_VALIDATORS).empty?
398
395
  end
399
396
 
400
397
  def ensure_required_validations!
@@ -519,9 +516,14 @@ module Paperclip
519
516
  def post_process_style(name, style) #:nodoc:
520
517
  begin
521
518
  raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
519
+ intermediate_files = []
520
+
522
521
  @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
523
- Paperclip.processor(processor).make(file, style.processor_options, self)
522
+ file = Paperclip.processor(processor).make(file, style.processor_options, self)
523
+ intermediate_files << file
524
+ file
524
525
  end
526
+
525
527
  unadapted_file = @queued_for_write[name]
526
528
  @queued_for_write[name] = Paperclip.io_adapters.for(@queued_for_write[name])
527
529
  unadapted_file.close if unadapted_file.respond_to?(:close)
@@ -529,6 +531,8 @@ module Paperclip
529
531
  rescue Paperclip::Error => e
530
532
  log("An error was received while processing: #{e.inspect}")
531
533
  (@errors[:processing] ||= []) << e.message if @options[:whiny]
534
+ ensure
535
+ unlink_files(intermediate_files)
532
536
  end
533
537
  end
534
538
 
@@ -569,7 +573,11 @@ module Paperclip
569
573
 
570
574
  # called by storage after the writes are flushed and before @queued_for_write is cleared
571
575
  def after_flush_writes
572
- @queued_for_write.each do |style, file|
576
+ unlink_files(@queued_for_write.values)
577
+ end
578
+
579
+ def unlink_files(files)
580
+ Array(files).each do |file|
573
581
  file.close unless file.closed?
574
582
  file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
575
583
  end
@@ -79,9 +79,11 @@ module Paperclip
79
79
  end
80
80
 
81
81
  def add_required_validations
82
- name = @name
83
- @klass.validates_media_type_spoof_detection name,
84
- :if => ->(instance){ instance.send(name).dirty? }
82
+ if @options[:validate_media_type] != false
83
+ name = @name
84
+ @klass.validates_media_type_spoof_detection name,
85
+ :if => ->(instance){ instance.send(name).dirty? }
86
+ end
85
87
  end
86
88
 
87
89
  def add_active_record_callbacks
@@ -172,7 +172,7 @@ module Paperclip
172
172
  when Integer
173
173
  ("%09d" % id).scan(/\d{3}/).join("/")
174
174
  when String
175
- id.scan(/.{3}/).first(3).join("/")
175
+ ('%9.9s' % id).tr(" ", "0").scan(/.{3}/).join("/")
176
176
  else
177
177
  nil
178
178
  end
@@ -20,6 +20,7 @@ module Paperclip
20
20
  end
21
21
 
22
22
  def original_filename=(new_filename)
23
+ return unless new_filename
23
24
  @original_filename = new_filename.gsub(OS_RESTRICTED_CHARACTERS, "_")
24
25
  end
25
26
 
@@ -0,0 +1,18 @@
1
+ ja:
2
+ errors:
3
+ messages:
4
+ in_between: "の容量は%{min}以上%{max}以下にしてください。"
5
+ spoofed_media_type: "の拡張子と内容が一致していません。"
6
+
7
+ number:
8
+ human:
9
+ storage_units:
10
+ format: "%n %u"
11
+ units:
12
+ byte:
13
+ one: "Byte"
14
+ other: "Bytes"
15
+ kb: "KB"
16
+ mb: "MB"
17
+ gb: "GB"
18
+ tb: "TB"
@@ -0,0 +1,18 @@
1
+ pt-BR:
2
+ errors:
3
+ messages:
4
+ in_between: "deve ter entre %{min} e %{max}"
5
+ spoofed_media_type: "tem uma extensão que não corresponde ao seu conteúdo"
6
+
7
+ number:
8
+ human:
9
+ storage_units:
10
+ format: "%n %u"
11
+ units:
12
+ byte:
13
+ one: "Byte"
14
+ other: "Bytes"
15
+ kb: "KB"
16
+ mb: "MB"
17
+ gb: "GB"
18
+ tb: "TB"
@@ -0,0 +1,18 @@
1
+ zh-CN:
2
+ errors:
3
+ messages:
4
+ in_between: "文件大小必须介于 %{min} 到 %{max} 之间"
5
+ spoofed_media_type: "扩展名与内容类型不符"
6
+
7
+ number:
8
+ human:
9
+ storage_units:
10
+ format: "%n %u"
11
+ units:
12
+ byte:
13
+ one: "Byte"
14
+ other: "Bytes"
15
+ kb: "KB"
16
+ mb: "MB"
17
+ gb: "GB"
18
+ tb: "TB"
@@ -0,0 +1,18 @@
1
+ zh-HK:
2
+ errors:
3
+ messages:
4
+ in_between: "必須介於%{min}到%{max}之間"
5
+ spoofed_media_type: "副檔名與內容類型不匹配"
6
+
7
+ number:
8
+ human:
9
+ storage_units:
10
+ format: "%n %u"
11
+ units:
12
+ byte:
13
+ one: "Byte"
14
+ other: "Bytes"
15
+ kb: "KB"
16
+ mb: "MB"
17
+ gb: "GB"
18
+ tb: "TB"
@@ -0,0 +1,18 @@
1
+ zh-TW:
2
+ errors:
3
+ messages:
4
+ in_between: "檔案大小必須介於 %{min} 到 %{max} 之間"
5
+ spoofed_media_type: "副檔名與內容類型不符"
6
+
7
+ number:
8
+ human:
9
+ storage_units:
10
+ format: "%n %u"
11
+ units:
12
+ byte:
13
+ one: "Byte"
14
+ other: "Bytes"
15
+ kb: "KB"
16
+ mb: "MB"
17
+ gb: "GB"
18
+ tb: "TB"
@@ -45,41 +45,4 @@ module Paperclip
45
45
  Paperclip.run('identify', arguments, local_options)
46
46
  end
47
47
  end
48
-
49
- module ProcessorHelpers
50
- def processor(name) #:nodoc:
51
- @known_processors ||= {}
52
- if @known_processors[name.to_s]
53
- @known_processors[name.to_s]
54
- else
55
- name = name.to_s.camelize
56
- load_processor(name) unless Paperclip.const_defined?(name)
57
- processor = Paperclip.const_get(name)
58
- @known_processors[name.to_s] = processor
59
- end
60
- end
61
-
62
- def load_processor(name)
63
- if defined?(Rails.root) && Rails.root
64
- require File.expand_path(Rails.root.join("lib", "paperclip_processors", "#{name.underscore}.rb"))
65
- end
66
- end
67
-
68
- def clear_processors!
69
- @known_processors.try(:clear)
70
- end
71
-
72
- # You can add your own processor via the Paperclip configuration. Normally
73
- # Paperclip will load all processors from the
74
- # Rails.root/lib/paperclip_processors directory, but here you can add any
75
- # existing class using this mechanism.
76
- #
77
- # Paperclip.configure do |c|
78
- # c.register_processor :watermarker, WatermarkingProcessor.new
79
- # end
80
- def register_processor(name, processor)
81
- @known_processors ||= {}
82
- @known_processors[name.to_s] = processor
83
- end
84
- end
85
48
  end
@@ -0,0 +1,50 @@
1
+ module Paperclip
2
+ module ProcessorHelpers
3
+ class NoSuchProcessor < StandardError; end
4
+
5
+ def processor(name) #:nodoc:
6
+ @known_processors ||= {}
7
+ if @known_processors[name.to_s]
8
+ @known_processors[name.to_s]
9
+ else
10
+ name = name.to_s.camelize
11
+ load_processor(name) unless Paperclip.const_defined?(name)
12
+ processor = Paperclip.const_get(name)
13
+ @known_processors[name.to_s] = processor
14
+ end
15
+ end
16
+
17
+ def load_processor(name)
18
+ if defined?(Rails.root) && Rails.root
19
+ filename = "#{name.to_s.underscore}.rb"
20
+ directories = %w(lib/paperclip lib/paperclip_processors)
21
+
22
+ required = directories.map do |directory|
23
+ pathname = File.expand_path(Rails.root.join(directory, filename))
24
+ file_exists = File.exist?(pathname)
25
+ require pathname if file_exists
26
+ file_exists
27
+ end
28
+
29
+ raise LoadError, "Could not find the '#{name}' processor in any of these paths: #{directories.join(', ')}" unless required.any?
30
+ end
31
+ end
32
+
33
+ def clear_processors!
34
+ @known_processors.try(:clear)
35
+ end
36
+
37
+ # You can add your own processor via the Paperclip configuration. Normally
38
+ # Paperclip will load all processors from the
39
+ # Rails.root/lib/paperclip_processors directory, but here you can add any
40
+ # existing class using this mechanism.
41
+ #
42
+ # Paperclip.configure do |c|
43
+ # c.register_processor :watermarker, WatermarkingProcessor.new
44
+ # end
45
+ def register_processor(name, processor)
46
+ @known_processors ||= {}
47
+ @known_processors[name.to_s] = processor
48
+ end
49
+ end
50
+ end
@@ -22,9 +22,12 @@ module Paperclip
22
22
  def add_attachment(table_name, *attachment_names)
23
23
  raise ArgumentError, "Please specify attachment name in your add_attachment call in your migration." if attachment_names.empty?
24
24
 
25
+ options = attachment_names.extract_options!
26
+
25
27
  attachment_names.each do |attachment_name|
26
28
  COLUMNS.each_pair do |column_name, column_type|
27
- add_column(table_name, "#{attachment_name}_#{column_name}", column_type)
29
+ column_options = options.merge(options[column_name.to_sym] || {})
30
+ add_column(table_name, "#{attachment_name}_#{column_name}", column_type, column_options)
28
31
  end
29
32
  end
30
33
  end
@@ -32,9 +35,12 @@ module Paperclip
32
35
  def remove_attachment(table_name, *attachment_names)
33
36
  raise ArgumentError, "Please specify attachment name in your remove_attachment call in your migration." if attachment_names.empty?
34
37
 
38
+ options = attachment_names.extract_options!
39
+
35
40
  attachment_names.each do |attachment_name|
36
41
  COLUMNS.each_pair do |column_name, column_type|
37
- remove_column(table_name, "#{attachment_name}_#{column_name}")
42
+ column_options = options.merge(options[column_name.to_sym] || {})
43
+ remove_column(table_name, "#{attachment_name}_#{column_name}", column_type, column_options)
38
44
  end
39
45
  end
40
46
  end
@@ -47,9 +53,11 @@ module Paperclip
47
53
 
48
54
  module TableDefinition
49
55
  def attachment(*attachment_names)
56
+ options = attachment_names.extract_options!
50
57
  attachment_names.each do |attachment_name|
51
58
  COLUMNS.each_pair do |column_name, column_type|
52
- column("#{attachment_name}_#{column_name}", column_type)
59
+ column_options = options.merge(options[column_name.to_sym] || {})
60
+ column("#{attachment_name}_#{column_name}", column_type, column_options)
53
61
  end
54
62
  end
55
63
  end
@@ -14,6 +14,7 @@ module Paperclip
14
14
  # aws_secret_access_key: '<your aws_secret_access_key>'
15
15
  # provider: 'AWS'
16
16
  # region: 'eu-west-1'
17
+ # scheme: 'https'
17
18
  # * +fog_directory+: This is the name of the S3 bucket that will
18
19
  # store your files. Remember that the bucket must be unique across
19
20
  # all of Amazon S3. If the bucket does not exist, Paperclip will
@@ -131,7 +132,7 @@ module Paperclip
131
132
  "#{dynamic_fog_host_for_style(style)}/#{path(style)}"
132
133
  else
133
134
  if fog_credentials[:provider] == 'AWS'
134
- "https://#{host_name_for_directory}/#{path(style)}"
135
+ "#{scheme}://#{host_name_for_directory}/#{path(style)}"
135
136
  else
136
137
  directory.files.new(:key => path(style)).public_url
137
138
  end
@@ -225,6 +226,10 @@ module Paperclip
225
226
 
226
227
  @directory ||= connection.directories.new(:key => dir)
227
228
  end
229
+
230
+ def scheme
231
+ @scheme ||= fog_credentials[:scheme] || 'https'
232
+ end
228
233
  end
229
234
  end
230
235
  end
@@ -158,7 +158,7 @@ module Paperclip
158
158
  end
159
159
 
160
160
  unless @options[:url].to_s.match(/\A:s3.*url\Z/) || @options[:url] == ":asset_host"
161
- @options[:path] = @options[:path].gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system/, '')
161
+ @options[:path] = path_option.gsub(/:url/, @options[:url]).gsub(/\A:rails_root\/public\/system/, '')
162
162
  @options[:url] = ":s3_path_url"
163
163
  end
164
164
  @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)
@@ -329,6 +329,7 @@ module Paperclip
329
329
 
330
330
  def flush_writes #:nodoc:
331
331
  @queued_for_write.each do |style, file|
332
+ retries = 0
332
333
  begin
333
334
  log("saving #{path(style)}")
334
335
  acl = @s3_permissions[style] || @s3_permissions[:default]
@@ -357,9 +358,17 @@ module Paperclip
357
358
  write_options.merge!(@s3_headers)
358
359
 
359
360
  s3_object(style).write(file, write_options)
360
- rescue AWS::S3::Errors::NoSuchBucket => e
361
+ rescue AWS::S3::Errors::NoSuchBucket
361
362
  create_bucket
362
363
  retry
364
+ rescue AWS::S3::Errors::SlowDown
365
+ retries += 1
366
+ if retries <= 5
367
+ sleep((2 ** retries) * 0.5)
368
+ retry
369
+ else
370
+ raise
371
+ end
363
372
  ensure
364
373
  file.rewind
365
374
  end
@@ -384,10 +393,11 @@ module Paperclip
384
393
 
385
394
  def copy_to_local_file(style, local_dest_path)
386
395
  log("copying #{path(style)} to local file #{local_dest_path}")
387
- local_file = ::File.open(local_dest_path, 'wb')
388
- file = s3_object(style)
389
- local_file.write(file.read)
390
- local_file.close
396
+ ::File.open(local_dest_path, 'wb') do |local_file|
397
+ s3_object(style).read do |chunk|
398
+ local_file.write(chunk)
399
+ end
400
+ end
391
401
  rescue AWS::Errors::Base => e
392
402
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
393
403
  false
@@ -8,8 +8,8 @@ module Paperclip
8
8
  end
9
9
 
10
10
  def for(style_name, options)
11
- escape_url_as_needed(
12
- timestamp_as_needed(
11
+ timestamp_as_needed(
12
+ escape_url_as_needed(
13
13
  @attachment_options[:interpolator].interpolate(most_appropriate_url, @attachment, style_name),
14
14
  options
15
15
  ), options)
@@ -58,7 +58,15 @@ module Paperclip
58
58
  end
59
59
 
60
60
  def escape_url(url)
61
- (url.respond_to?(:escape) ? url.escape : URI.escape(url)).gsub(/(\/.+)\?(.+\.)/, '\1%3F\2')
61
+ if url.respond_to?(:escape)
62
+ url.escape
63
+ else
64
+ URI.escape(url).gsub(escape_regex){|m| "%#{m.ord.to_s(16).upcase}" }
65
+ end
66
+ end
67
+
68
+ def escape_regex
69
+ /[\?\(\)\[\]\+]/
62
70
  end
63
71
  end
64
72
  end