paperclip 4.2.2 → 5.2.1

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 (127) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +17 -0
  3. data/.hound.yml +1055 -0
  4. data/.rubocop.yml +1 -0
  5. data/.travis.yml +17 -15
  6. data/Appraisals +4 -16
  7. data/CONTRIBUTING.md +19 -8
  8. data/Gemfile +5 -9
  9. data/LICENSE +1 -1
  10. data/NEWS +148 -31
  11. data/README.md +327 -191
  12. data/RELEASING.md +17 -0
  13. data/Rakefile +2 -2
  14. data/UPGRADING +12 -9
  15. data/features/basic_integration.feature +10 -6
  16. data/features/migration.feature +0 -24
  17. data/features/step_definitions/attachment_steps.rb +33 -27
  18. data/features/step_definitions/html_steps.rb +2 -2
  19. data/features/step_definitions/rails_steps.rb +39 -38
  20. data/features/step_definitions/s3_steps.rb +2 -2
  21. data/features/step_definitions/web_steps.rb +1 -103
  22. data/features/support/env.rb +1 -0
  23. data/features/support/file_helpers.rb +2 -2
  24. data/features/support/paths.rb +1 -1
  25. data/features/support/rails.rb +0 -24
  26. data/gemfiles/4.2.gemfile +6 -8
  27. data/gemfiles/5.0.gemfile +17 -0
  28. data/lib/paperclip/attachment.rb +32 -20
  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 +19 -14
  40. data/lib/paperclip/io_adapters/abstract_adapter.rb +26 -3
  41. data/lib/paperclip/io_adapters/attachment_adapter.rb +10 -5
  42. data/lib/paperclip/io_adapters/data_uri_adapter.rb +8 -8
  43. data/lib/paperclip/io_adapters/empty_string_adapter.rb +5 -4
  44. data/lib/paperclip/io_adapters/file_adapter.rb +12 -6
  45. data/lib/paperclip/io_adapters/http_url_proxy_adapter.rb +7 -7
  46. data/lib/paperclip/io_adapters/identity_adapter.rb +12 -6
  47. data/lib/paperclip/io_adapters/nil_adapter.rb +8 -5
  48. data/lib/paperclip/io_adapters/registry.rb +6 -2
  49. data/lib/paperclip/io_adapters/stringio_adapter.rb +9 -6
  50. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +10 -6
  51. data/lib/paperclip/io_adapters/uri_adapter.rb +41 -19
  52. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +4 -4
  53. data/lib/paperclip/media_type_spoof_detector.rb +2 -2
  54. data/lib/paperclip/processor.rb +5 -4
  55. data/lib/paperclip/rails_environment.rb +25 -0
  56. data/lib/paperclip/schema.rb +3 -9
  57. data/lib/paperclip/storage/filesystem.rb +13 -2
  58. data/lib/paperclip/storage/fog.rb +30 -18
  59. data/lib/paperclip/storage/s3.rb +92 -65
  60. data/lib/paperclip/thumbnail.rb +16 -7
  61. data/lib/paperclip/url_generator.rb +16 -13
  62. data/lib/paperclip/validators/attachment_size_validator.rb +1 -7
  63. data/lib/paperclip/validators.rb +1 -1
  64. data/lib/paperclip/version.rb +3 -1
  65. data/lib/paperclip.rb +25 -12
  66. data/lib/tasks/paperclip.rake +33 -3
  67. data/paperclip.gemspec +18 -15
  68. data/spec/paperclip/attachment_definitions_spec.rb +1 -1
  69. data/spec/paperclip/attachment_processing_spec.rb +2 -4
  70. data/spec/paperclip/attachment_registry_spec.rb +84 -13
  71. data/spec/paperclip/attachment_spec.rb +130 -39
  72. data/spec/paperclip/content_type_detector_spec.rb +8 -1
  73. data/spec/paperclip/file_command_content_type_detector_spec.rb +0 -1
  74. data/spec/paperclip/geometry_spec.rb +1 -1
  75. data/spec/paperclip/glue_spec.rb +44 -0
  76. data/spec/paperclip/has_attached_file_spec.rb +24 -8
  77. data/spec/paperclip/integration_spec.rb +4 -3
  78. data/spec/paperclip/interpolations_spec.rb +16 -13
  79. data/spec/paperclip/io_adapters/abstract_adapter_spec.rb +47 -23
  80. data/spec/paperclip/io_adapters/attachment_adapter_spec.rb +6 -3
  81. data/spec/paperclip/io_adapters/data_uri_adapter_spec.rb +7 -1
  82. data/spec/paperclip/io_adapters/file_adapter_spec.rb +6 -3
  83. data/spec/paperclip/io_adapters/http_url_proxy_adapter_spec.rb +26 -6
  84. data/spec/paperclip/io_adapters/identity_adapter_spec.rb +1 -1
  85. data/spec/paperclip/io_adapters/registry_spec.rb +2 -2
  86. data/spec/paperclip/io_adapters/stringio_adapter_spec.rb +5 -1
  87. data/spec/paperclip/io_adapters/uploaded_file_adapter_spec.rb +5 -5
  88. data/spec/paperclip/io_adapters/uri_adapter_spec.rb +77 -7
  89. data/spec/paperclip/matchers/validate_attachment_content_type_matcher_spec.rb +10 -0
  90. data/spec/paperclip/media_type_spoof_detector_spec.rb +34 -11
  91. data/spec/paperclip/paperclip_spec.rb +4 -29
  92. data/spec/paperclip/plural_cache_spec.rb +17 -16
  93. data/spec/paperclip/rails_environment_spec.rb +33 -0
  94. data/spec/paperclip/storage/fog_spec.rb +58 -3
  95. data/spec/paperclip/storage/s3_live_spec.rb +20 -14
  96. data/spec/paperclip/storage/s3_spec.rb +398 -213
  97. data/spec/paperclip/tempfile_factory_spec.rb +4 -0
  98. data/spec/paperclip/tempfile_spec.rb +35 -0
  99. data/spec/paperclip/thumbnail_spec.rb +51 -32
  100. data/spec/paperclip/url_generator_spec.rb +55 -44
  101. data/spec/paperclip/validators/attachment_size_validator_spec.rb +26 -20
  102. data/spec/paperclip/validators_spec.rb +5 -5
  103. data/spec/spec_helper.rb +8 -1
  104. data/spec/support/assertions.rb +12 -1
  105. data/spec/support/conditional_filter_helper.rb +5 -0
  106. data/spec/support/fake_model.rb +4 -0
  107. data/spec/support/fixtures/empty.xlsx +0 -0
  108. data/spec/support/matchers/have_column.rb +11 -2
  109. data/spec/support/mock_attachment.rb +2 -0
  110. data/spec/support/mock_url_generator_builder.rb +2 -2
  111. data/spec/support/model_reconstruction.rb +9 -1
  112. data/spec/support/reporting.rb +11 -0
  113. metadata +109 -162
  114. data/RUNNING_TESTS.md +0 -4
  115. data/cucumber/paperclip_steps.rb +0 -6
  116. data/gemfiles/3.2.gemfile +0 -19
  117. data/gemfiles/4.0.gemfile +0 -19
  118. data/gemfiles/4.1.gemfile +0 -19
  119. data/lib/paperclip/locales/de.yml +0 -18
  120. data/lib/paperclip/locales/es.yml +0 -18
  121. data/lib/paperclip/locales/ja.yml +0 -18
  122. data/lib/paperclip/locales/pt-BR.yml +0 -18
  123. data/lib/paperclip/locales/zh-CN.yml +0 -18
  124. data/lib/paperclip/locales/zh-HK.yml +0 -18
  125. data/lib/paperclip/locales/zh-TW.yml +0 -18
  126. data/spec/support/mock_model.rb +0 -2
  127. data/spec/support/rails_helpers.rb +0 -7
data/gemfiles/4.2.gemfile CHANGED
@@ -2,18 +2,16 @@
2
2
 
3
3
  source "https://rubygems.org"
4
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
5
+ gem "sqlite3", "~> 1.3.8", :platforms => :ruby
10
6
  gem "pry"
11
- gem "rails", "~> 4.2.0.rc2"
12
- gem "paperclip", :path => "../"
7
+ gem "rails", "~> 4.2.0"
13
8
 
14
9
  group :development, :test do
15
- gem "mime-types", "~> 1.16"
10
+ gem "activerecord-import"
11
+ gem "mime-types"
16
12
  gem "builder"
13
+ gem "rubocop", :require => false
14
+ gem "rspec"
17
15
  end
18
16
 
19
17
  gemspec :path => "../"
@@ -0,0 +1,17 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "sqlite3", "~> 1.3.8", :platforms => :ruby
6
+ gem "pry"
7
+ gem "rails", "~> 5.0.0"
8
+
9
+ group :development, :test do
10
+ gem "activerecord-import"
11
+ gem "mime-types"
12
+ gem "builder"
13
+ gem "rubocop", :require => false
14
+ gem "rspec"
15
+ end
16
+
17
+ gemspec :path => "../"
@@ -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
@@ -32,6 +33,7 @@ module Paperclip
32
33
  :use_timestamp => true,
33
34
  :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
34
35
  :validate_media_type => true,
36
+ :adapter_options => { hash_digest: Digest::MD5 },
35
37
  :check_validity_before_processing => true
36
38
  }
37
39
  end
@@ -49,7 +51,8 @@ module Paperclip
49
51
  # +url+ - a relative URL of the attachment. This is interpolated using +interpolator+
50
52
  # +path+ - where on the filesystem to store the attachment. This is interpolated using +interpolator+
51
53
  # +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
54
+ # +only_process+ - style args to be run through the post-processor. This defaults to the empty list (which is
55
+ # a special case that indicates all styles should be processed)
53
56
  # +default_url+ - a URL for the missing image
54
57
  # +default_style+ - the style to use when an argument is not specified e.g. #url, #path
55
58
  # +storage+ - the storage mechanism. Defaults to :filesystem
@@ -68,7 +71,8 @@ module Paperclip
68
71
  # +url_generator+ - the object used to generate URLs, using the interpolator. Defaults to Paperclip::UrlGenerator
69
72
  # +escape_url+ - Perform URI escaping to URLs. Defaults to true
70
73
  def initialize(name, instance, options = {})
71
- @name = name
74
+ @name = name.to_sym
75
+ @name_string = name.to_s
72
76
  @instance = instance
73
77
 
74
78
  options = self.class.default_options.deep_merge(options)
@@ -80,7 +84,7 @@ module Paperclip
80
84
  @errors = {}
81
85
  @dirty = false
82
86
  @interpolator = options[:interpolator]
83
- @url_generator = options[:url_generator].new(self, @options)
87
+ @url_generator = options[:url_generator].new(self)
84
88
  @source_file_options = options[:source_file_options]
85
89
  @whiny = options[:whiny]
86
90
 
@@ -94,7 +98,8 @@ module Paperclip
94
98
  # attachment:
95
99
  # new_user.avatar = old_user.avatar
96
100
  def assign(uploaded_file)
97
- @file = Paperclip.io_adapters.for(uploaded_file)
101
+ @file = Paperclip.io_adapters.for(uploaded_file,
102
+ @options[:adapter_options])
98
103
  ensure_required_accessors!
99
104
  ensure_required_validations!
100
105
 
@@ -235,6 +240,10 @@ module Paperclip
235
240
  # the instance's errors and returns false, cancelling the save.
236
241
  def save
237
242
  flush_deletes unless @options[:keep_old_files]
243
+ process = only_process
244
+ if process.any? && !process.include?(:original)
245
+ @queued_for_write.except!(:original)
246
+ end
238
247
  flush_writes
239
248
  @dirty = false
240
249
  true
@@ -321,7 +330,7 @@ module Paperclip
321
330
  OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@options[:hash_digest]).new, @options[:hash_secret], data)
322
331
  end
323
332
 
324
- # This method really shouldn't be called that often. It's expected use is
333
+ # This method really shouldn't be called that often. Its expected use is
325
334
  # in the paperclip:refresh rake task and that's it. It will regenerate all
326
335
  # thumbnails forcefully, by reobtaining the original file and going through
327
336
  # the post-process again.
@@ -346,7 +355,7 @@ module Paperclip
346
355
 
347
356
  # Returns true if a file has been assigned.
348
357
  def file?
349
- !original_filename.blank?
358
+ original_filename.present?
350
359
  end
351
360
 
352
361
  alias :present? :file?
@@ -365,7 +374,7 @@ module Paperclip
365
374
  # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
366
375
  # "avatar_file_name" field (assuming the attachment is called avatar).
367
376
  def instance_write(attr, value)
368
- setter = :"#{name}_#{attr}="
377
+ setter = :"#{@name_string}_#{attr}="
369
378
  if instance.respond_to?(setter)
370
379
  instance.send(setter, value)
371
380
  end
@@ -374,7 +383,7 @@ module Paperclip
374
383
  # Reads the attachment-specific attribute on the instance. See instance_write
375
384
  # for more details.
376
385
  def instance_read(attr)
377
- getter = :"#{name}_#{attr}"
386
+ getter = :"#{@name_string}_#{attr}"
378
387
  if instance.respond_to?(getter)
379
388
  instance.send(getter)
380
389
  end
@@ -402,8 +411,8 @@ module Paperclip
402
411
 
403
412
  def ensure_required_accessors! #:nodoc:
404
413
  %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}'")
414
+ unless @instance.respond_to?("#{@name_string}_#{field}") && @instance.respond_to?("#{@name_string}_#{field}=")
415
+ raise Paperclip::Error.new("#{@instance.class} model missing required attr_accessor for '#{@name_string}_#{field}'")
407
416
  end
408
417
  end
409
418
  end
@@ -425,7 +434,7 @@ module Paperclip
425
434
  def assign_attributes
426
435
  @queued_for_write[:original] = @file
427
436
  assign_file_information
428
- assign_fingerprint(@file.fingerprint)
437
+ assign_fingerprint { @file.fingerprint }
429
438
  assign_timestamps
430
439
  end
431
440
 
@@ -435,9 +444,9 @@ module Paperclip
435
444
  instance_write(:file_size, @file.size)
436
445
  end
437
446
 
438
- def assign_fingerprint(fingerprint)
447
+ def assign_fingerprint
439
448
  if instance_respond_to?(:fingerprint)
440
- instance_write(:fingerprint, fingerprint)
449
+ instance_write(:fingerprint, yield)
441
450
  end
442
451
  end
443
452
 
@@ -463,7 +472,7 @@ module Paperclip
463
472
 
464
473
  def reset_file_if_original_reprocessed
465
474
  instance_write(:file_size, @queued_for_write[:original].size)
466
- assign_fingerprint(@queued_for_write[:original].fingerprint)
475
+ assign_fingerprint { @queued_for_write[:original].fingerprint }
467
476
  reset_updater
468
477
  end
469
478
 
@@ -499,7 +508,7 @@ module Paperclip
499
508
 
500
509
  instance.run_paperclip_callbacks(:post_process) do
501
510
  instance.run_paperclip_callbacks(:"#{name}_post_process") do
502
- unless @options[:check_validity_before_processing] && instance.errors.any?
511
+ if !@options[:check_validity_before_processing] || !instance.errors.any?
503
512
  post_process_styles(*style_args)
504
513
  end
505
514
  end
@@ -517,18 +526,21 @@ module Paperclip
517
526
  begin
518
527
  raise RuntimeError.new("Style #{name} has no processors defined.") if style.processors.blank?
519
528
  intermediate_files = []
529
+ original = @queued_for_write[:original]
520
530
 
521
- @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|
522
533
  file = Paperclip.processor(processor).make(file, style.processor_options, self)
523
- intermediate_files << file
534
+ intermediate_files << file unless file == @queued_for_write[:original]
524
535
  file
525
536
  end
526
537
 
527
538
  unadapted_file = @queued_for_write[name]
528
- @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])
529
541
  unadapted_file.close if unadapted_file.respond_to?(:close)
530
542
  @queued_for_write[name]
531
- rescue Paperclip::Error => e
543
+ rescue Paperclip::Errors::NotIdentifiedByImageMagickError => e
532
544
  log("An error was received while processing: #{e.inspect}")
533
545
  (@errors[:processing] ||= []) << e.message if @options[:whiny]
534
546
  ensure
@@ -585,7 +597,7 @@ module Paperclip
585
597
 
586
598
  # You can either specifiy :restricted_characters or you can define your own
587
599
  # :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.
600
+ # the filename that will be cleaned. It should return the cleaned filename.
589
601
  def filename_cleaner
590
602
  @options[:filename_cleaner] || FilenameCleaner.new(@options[:restricted_characters])
591
603
  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
@@ -136,7 +141,7 @@ module Paperclip
136
141
  # It's possible, though unlikely, that the mime type is not in the
137
142
  # database, so just use the part after the '/' in the mime type as the
138
143
  # extension.
139
- %r{/([^/]*)\Z}.match(attachment.content_type)[1]
144
+ %r{/([^/]*)\z}.match(attachment.content_type)[1]
140
145
  end
141
146
  end
142
147
 
@@ -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.
@@ -5,10 +5,23 @@ module Paperclip
5
5
  OS_RESTRICTED_CHARACTERS = %r{[/:]}
6
6
 
7
7
  attr_reader :content_type, :original_filename, :size
8
- delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :rewind, :unlink, :to => :@tempfile
8
+ delegate :binmode, :binmode?, :close, :close!, :closed?, :eof?, :path, :readbyte, :rewind, :unlink, :to => :@tempfile
9
+ alias :length :size
10
+
11
+ def initialize(target, options = {})
12
+ @target = target
13
+ @options = options
14
+ end
9
15
 
10
16
  def fingerprint
11
- @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
12
25
  end
13
26
 
14
27
  def read(length = nil, buffer = nil)
@@ -39,8 +52,18 @@ module Paperclip
39
52
  end
40
53
 
41
54
  def copy_to_tempfile(src)
42
- FileUtils.cp(src.path, destination.path)
55
+ link_or_copy_file(src.path, destination.path)
43
56
  destination
44
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, Errno::EEXIST => e
65
+ Paperclip.log("Link failed with #{e.message}; copying link #{src} to #{dest}")
66
+ FileUtils.cp(src, dest)
67
+ end
45
68
  end
46
69
  end