paperclip 4.2.2 → 5.0.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 (104) hide show
  1. checksums.yaml +4 -4
  2. data/.hound.yml +1066 -0
  3. data/.rubocop.yml +1 -0
  4. data/.travis.yml +18 -15
  5. data/Appraisals +20 -12
  6. data/CONTRIBUTING.md +19 -8
  7. data/Gemfile +4 -9
  8. data/LICENSE +1 -1
  9. data/NEWS +101 -31
  10. data/README.md +243 -159
  11. data/RELEASING.md +17 -0
  12. data/Rakefile +1 -1
  13. data/UPGRADING +12 -9
  14. data/features/basic_integration.feature +8 -4
  15. data/features/migration.feature +0 -24
  16. data/features/step_definitions/attachment_steps.rb +27 -21
  17. data/features/step_definitions/html_steps.rb +2 -2
  18. data/features/step_definitions/rails_steps.rb +11 -17
  19. data/features/step_definitions/s3_steps.rb +2 -2
  20. data/features/step_definitions/web_steps.rb +1 -103
  21. data/features/support/file_helpers.rb +2 -2
  22. data/gemfiles/4.2.awsv2.0.gemfile +17 -0
  23. data/gemfiles/4.2.awsv2.1.gemfile +17 -0
  24. data/gemfiles/{4.1.gemfile → 4.2.awsv2.gemfile} +4 -3
  25. data/gemfiles/5.0.awsv2.0.gemfile +17 -0
  26. data/gemfiles/5.0.awsv2.1.gemfile +17 -0
  27. data/gemfiles/{4.2.gemfile → 5.0.awsv2.gemfile} +4 -3
  28. data/lib/paperclip/attachment.rb +19 -16
  29. data/lib/paperclip/attachment_registry.rb +3 -2
  30. data/lib/paperclip/callbacks.rb +8 -6
  31. data/lib/paperclip/content_type_detector.rb +27 -11
  32. data/lib/paperclip/errors.rb +3 -1
  33. data/lib/paperclip/file_command_content_type_detector.rb +6 -8
  34. data/lib/paperclip/geometry_parser_factory.rb +1 -1
  35. data/lib/paperclip/glue.rb +1 -1
  36. data/lib/paperclip/has_attached_file.rb +9 -2
  37. data/lib/paperclip/helpers.rb +14 -10
  38. data/lib/paperclip/interpolations/plural_cache.rb +6 -5
  39. data/lib/paperclip/interpolations.rb +18 -13
  40. data/lib/paperclip/io_adapters/abstract_adapter.rb +1 -0
  41. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +1 -1
  42. data/lib/paperclip/io_adapters/uri_adapter.rb +3 -1
  43. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
  44. data/lib/paperclip/media_type_spoof_detector.rb +2 -2
  45. data/lib/paperclip/rails_environment.rb +25 -0
  46. data/lib/paperclip/schema.rb +3 -9
  47. data/lib/paperclip/storage/fog.rb +21 -12
  48. data/lib/paperclip/storage/s3.rb +51 -50
  49. data/lib/paperclip/thumbnail.rb +2 -3
  50. data/lib/paperclip/validators/attachment_size_validator.rb +1 -7
  51. data/lib/paperclip/version.rb +3 -1
  52. data/lib/paperclip.rb +15 -4
  53. data/lib/tasks/paperclip.rake +17 -1
  54. data/paperclip.gemspec +18 -15
  55. data/spec/paperclip/attachment_definitions_spec.rb +1 -1
  56. data/spec/paperclip/attachment_processing_spec.rb +2 -4
  57. data/spec/paperclip/attachment_registry_spec.rb +84 -13
  58. data/spec/paperclip/attachment_spec.rb +91 -31
  59. data/spec/paperclip/content_type_detector_spec.rb +8 -1
  60. data/spec/paperclip/file_command_content_type_detector_spec.rb +0 -1
  61. data/spec/paperclip/geometry_spec.rb +1 -1
  62. data/spec/paperclip/glue_spec.rb +44 -0
  63. data/spec/paperclip/has_attached_file_spec.rb +24 -8
  64. data/spec/paperclip/integration_spec.rb +4 -3
  65. data/spec/paperclip/interpolations_spec.rb +16 -13
  66. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +2 -1
  67. data/spec/paperclip/io_adapters/file_adapter_spec.rb +4 -1
  68. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +12 -0
  69. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +4 -0
  70. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +27 -0
  71. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +10 -0
  72. data/spec/paperclip/media_type_spoof_detector_spec.rb +34 -11
  73. data/spec/paperclip/paperclip_spec.rb +4 -29
  74. data/spec/paperclip/plural_cache_spec.rb +17 -16
  75. data/spec/paperclip/rails_environment_spec.rb +33 -0
  76. data/spec/paperclip/storage/fog_spec.rb +42 -3
  77. data/spec/paperclip/storage/s3_live_spec.rb +8 -4
  78. data/spec/paperclip/storage/s3_spec.rb +255 -180
  79. data/spec/paperclip/tempfile_factory_spec.rb +4 -0
  80. data/spec/paperclip/thumbnail_spec.rb +16 -0
  81. data/spec/paperclip/url_generator_spec.rb +1 -1
  82. data/spec/paperclip/validators/attachment_size_validator_spec.rb +26 -20
  83. data/spec/paperclip/validators_spec.rb +3 -3
  84. data/spec/spec_helper.rb +6 -1
  85. data/spec/support/assertions.rb +7 -0
  86. data/spec/support/fake_model.rb +4 -0
  87. data/spec/support/fixtures/empty.xlsx +0 -0
  88. data/spec/support/matchers/have_column.rb +11 -2
  89. data/spec/support/model_reconstruction.rb +9 -1
  90. data/spec/support/reporting.rb +11 -0
  91. metadata +105 -54
  92. data/RUNNING_TESTS.md +0 -4
  93. data/cucumber/paperclip_steps.rb +0 -6
  94. data/gemfiles/3.2.gemfile +0 -19
  95. data/gemfiles/4.0.gemfile +0 -19
  96. data/lib/paperclip/locales/de.yml +0 -18
  97. data/lib/paperclip/locales/es.yml +0 -18
  98. data/lib/paperclip/locales/ja.yml +0 -18
  99. data/lib/paperclip/locales/pt-BR.yml +0 -18
  100. data/lib/paperclip/locales/zh-CN.yml +0 -18
  101. data/lib/paperclip/locales/zh-HK.yml +0 -18
  102. data/lib/paperclip/locales/zh-TW.yml +0 -18
  103. data/spec/support/mock_model.rb +0 -2
  104. data/spec/support/rails_helpers.rb +0 -7
@@ -2,6 +2,7 @@
2
2
  require 'uri'
3
3
  require 'paperclip/url_generator'
4
4
  require 'active_support/deprecation'
5
+ require 'active_support/core_ext/string/inflections'
5
6
 
6
7
  module Paperclip
7
8
  # The Attachment class manages the files for a given attachment. It saves
@@ -49,7 +50,8 @@ module Paperclip
49
50
  # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
50
51
  # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
51
52
  # +styles+ - a hash of options for processing the attachment. See +has_attached_file+ for the details
52
- # +only_process+ - style args to be run through the post-processor. This defaults to the empty list
53
+ # +only_process+ - style args to be run through the post-processor. This defaults to the empty list (which is
54
+ # a special case that indicates all styles should be processed)
53
55
  # +default_url+ - a URL for the missing image
54
56
  # +default_style+ - the style to use when an argument is not specified e.g. #url, #path
55
57
  # +storage+ - the storage mechanism. Defaults to :filesystem
@@ -68,7 +70,8 @@ module Paperclip
68
70
  # +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
69
71
  # +escape_url+ - Perform URI escaping to URLs. Defaults to true
70
72
  def initialize(name, instance, options = {})
71
- @name = name
73
+ @name = name.to_sym
74
+ @name_string = name.to_s
72
75
  @instance = instance
73
76
 
74
77
  options = self.class.default_options.deep_merge(options)
@@ -321,7 +324,7 @@ module Paperclip
321
324
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options[:hash_digest]).new, @options[:hash_secret], data)
322
325
  end
323
326
 
324
- # This method really shouldn't be called that often. It's expected use is
327
+ # This method really shouldn't be called that often. Its expected use is
325
328
  # in the paperclip:refresh rake task and that's it. It will regenerate all
326
329
  # thumbnails forcefully, by reobtaining the original file and going through
327
330
  # the post-process again.
@@ -346,7 +349,7 @@ module Paperclip
346
349
 
347
350
  # Returns true if a file has been assigned.
348
351
  def file?
349
- !original_filename.blank?
352
+ original_filename.present?
350
353
  end
351
354
 
352
355
  alias :present? :file?
@@ -365,7 +368,7 @@ module Paperclip
365
368
  # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
366
369
  # "avatar_file_name" field (assuming the attachment is called avatar).
367
370
  def instance_write(attr, value)
368
- setter = :"#{name}_#{attr}="
371
+ setter = :"#{@name_string}_#{attr}="
369
372
  if instance.respond_to?(setter)
370
373
  instance.send(setter, value)
371
374
  end
@@ -374,7 +377,7 @@ module Paperclip
374
377
  # Reads the attachment-specific attribute on the instance. See instance_write
375
378
  # for more details.
376
379
  def instance_read(attr)
377
- getter = :"#{name}_#{attr}"
380
+ getter = :"#{@name_string}_#{attr}"
378
381
  if instance.respond_to?(getter)
379
382
  instance.send(getter)
380
383
  end
@@ -402,8 +405,8 @@ module Paperclip
402
405
 
403
406
  def ensure_required_accessors! #:nodoc:
404
407
  %w(file_name).each do |field|
405
- unless @instance.respond_to?("#{name}_#{field}") && @instance.respond_to?("#{name}_#{field}=")
406
- raise Paperclip::Error.new("#{@instance.class} model missing required attr_accessor for '#{name}_#{field}'")
408
+ unless @instance.respond_to?("#{@name_string}_#{field}") && @instance.respond_to?("#{@name_string}_#{field}=")
409
+ raise Paperclip::Error.new("#{@instance.class} model missing required attr_accessor for '#{@name_string}_#{field}'")
407
410
  end
408
411
  end
409
412
  end
@@ -425,7 +428,7 @@ module Paperclip
425
428
  def assign_attributes
426
429
  @queued_for_write[:original] = @file
427
430
  assign_file_information
428
- assign_fingerprint(@file.fingerprint)
431
+ assign_fingerprint { @file.fingerprint }
429
432
  assign_timestamps
430
433
  end
431
434
 
@@ -435,9 +438,9 @@ module Paperclip
435
438
  instance_write(:file_size, @file.size)
436
439
  end
437
440
 
438
- def assign_fingerprint(fingerprint)
441
+ def assign_fingerprint
439
442
  if instance_respond_to?(:fingerprint)
440
- instance_write(:fingerprint, fingerprint)
443
+ instance_write(:fingerprint, yield)
441
444
  end
442
445
  end
443
446
 
@@ -463,7 +466,7 @@ module Paperclip
463
466
 
464
467
  def reset_file_if_original_reprocessed
465
468
  instance_write(:file_size, @queued_for_write[:original].size)
466
- assign_fingerprint(@queued_for_write[:original].fingerprint)
469
+ assign_fingerprint { @queued_for_write[:original].fingerprint }
467
470
  reset_updater
468
471
  end
469
472
 
@@ -499,7 +502,7 @@ module Paperclip
499
502
 
500
503
  instance.run_paperclip_callbacks(:post_process) do
501
504
  instance.run_paperclip_callbacks(:"#{name}_post_process") do
502
- unless @options[:check_validity_before_processing] && instance.errors.any?
505
+ if !@options[:check_validity_before_processing] || !instance.errors.any?
503
506
  post_process_styles(*style_args)
504
507
  end
505
508
  end
@@ -520,7 +523,7 @@ module Paperclip
520
523
 
521
524
  @queued_for_write[name] = style.processors.inject(@queued_for_write[:original]) do |file, processor|
522
525
  file = Paperclip.processor(processor).make(file, style.processor_options, self)
523
- intermediate_files << file
526
+ intermediate_files << file unless file == @queued_for_write[:original]
524
527
  file
525
528
  end
526
529
 
@@ -528,7 +531,7 @@ module Paperclip
528
531
  @queued_for_write[name] = Paperclip.io_adapters.for(@queued_for_write[name])
529
532
  unadapted_file.close if unadapted_file.respond_to?(:close)
530
533
  @queued_for_write[name]
531
- rescue Paperclip::Error => e
534
+ rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e
532
535
  log("An error was received while processing: #{e.inspect}")
533
536
  (@errors[:processing] ||= []) << e.message if @options[:whiny]
534
537
  ensure
@@ -585,7 +588,7 @@ module Paperclip
585
588
 
586
589
  # You can either specifiy :restricted_characters or you can define your own
587
590
  # :filename_cleaner object. This object needs to respond to #call and takes
588
- # the filename that will be cleaned. It should return the cleaned filenme.
591
+ # the filename that will be cleaned. It should return the cleaned filename.
589
592
  def filename_cleaner
590
593
  @options[:filename_cleaner] || FilenameCleaner.new(@options[:restricted_characters])
591
594
  end
@@ -51,8 +51,9 @@ module Paperclip
51
51
  end
52
52
 
53
53
  def definitions_for(klass)
54
- klass.ancestors.each_with_object({}) do |ancestor, inherited_definitions|
55
- inherited_definitions.merge! @attachments[ancestor]
54
+ parent_classes = klass.ancestors.reverse
55
+ parent_classes.each_with_object({}) do |ancestor, inherited_definitions|
56
+ inherited_definitions.deep_merge! @attachments[ancestor]
56
57
  end
57
58
  end
58
59
  end
@@ -7,7 +7,7 @@ module Paperclip
7
7
 
8
8
  module Defining
9
9
  def define_paperclip_callbacks(*callbacks)
10
- define_callbacks(*[callbacks, {:terminator => callback_terminator}].flatten)
10
+ define_callbacks(*[callbacks, { terminator: hasta_la_vista_baby }].flatten)
11
11
  callbacks.each do |callback|
12
12
  eval <<-end_callbacks
13
13
  def before_#{callback}(*args, &blk)
@@ -22,11 +22,13 @@ module Paperclip
22
22
 
23
23
  private
24
24
 
25
- def callback_terminator
26
- if ::ActiveSupport::VERSION::STRING >= '4.1'
27
- lambda { |target, result| result == false }
28
- else
29
- 'result == false'
25
+ def hasta_la_vista_baby
26
+ lambda do |_, result|
27
+ if result.respond_to?(:call)
28
+ result.call == false
29
+ else
30
+ result == false
31
+ end
30
32
  end
31
33
  end
32
34
  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,48 @@ 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 ||= File.open(@filepath) do |file|
71
+ MimeMagic.by_magic(file).try(:type)
72
+ end
58
73
  end
59
74
 
60
75
  def type_from_file_command
61
- @type_from_file_command ||= FileCommandContentTypeDetector.new(@filename).detect
76
+ @type_from_file_command ||=
77
+ FileCommandContentTypeDetector.new(@filepath).detect
62
78
  end
63
79
  end
64
80
  end
@@ -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
 
@@ -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
-
@@ -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 if defined? ActiveRecord
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)
@@ -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? }
@@ -90,7 +91,13 @@ module Paperclip
90
91
  name = @name
91
92
  @klass.send(:after_save) { send(name).send(:save) }
92
93
  @klass.send(:before_destroy) { send(name).send(:queue_all_for_delete) }
93
- @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
94
101
  end
95
102
 
96
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
- ('%9.9s' % id).tr(" ", "0").scan(/.{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.
@@ -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
@@ -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
@@ -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
@@ -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