paperclip 3.0.4 → 3.1.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 (56) hide show
  1. data/Appraisals +3 -3
  2. data/NEWS +43 -0
  3. data/README.md +81 -5
  4. data/features/basic_integration.feature +20 -2
  5. data/features/migration.feature +94 -0
  6. data/features/step_definitions/attachment_steps.rb +28 -0
  7. data/features/step_definitions/rails_steps.rb +19 -1
  8. data/features/step_definitions/web_steps.rb +3 -3
  9. data/gemfiles/3.0.gemfile +1 -1
  10. data/gemfiles/3.1.gemfile +1 -1
  11. data/gemfiles/3.2.gemfile +1 -1
  12. data/lib/generators/paperclip/templates/paperclip_migration.rb.erb +4 -8
  13. data/lib/paperclip.rb +2 -0
  14. data/lib/paperclip/attachment.rb +4 -0
  15. data/lib/paperclip/geometry.rb +33 -0
  16. data/lib/paperclip/glue.rb +2 -1
  17. data/lib/paperclip/io_adapters/abstract_adapter.rb +45 -0
  18. data/lib/paperclip/io_adapters/attachment_adapter.rb +13 -48
  19. data/lib/paperclip/io_adapters/file_adapter.rb +11 -61
  20. data/lib/paperclip/io_adapters/identity_adapter.rb +1 -1
  21. data/lib/paperclip/io_adapters/nil_adapter.rb +1 -1
  22. data/lib/paperclip/io_adapters/stringio_adapter.rb +11 -42
  23. data/lib/paperclip/io_adapters/uploaded_file_adapter.rb +6 -45
  24. data/lib/paperclip/matchers.rb +2 -2
  25. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +36 -17
  26. data/lib/paperclip/railtie.rb +5 -1
  27. data/lib/paperclip/schema.rb +59 -23
  28. data/lib/paperclip/storage/filesystem.rb +5 -0
  29. data/lib/paperclip/storage/fog.rb +36 -14
  30. data/lib/paperclip/storage/s3.rb +14 -16
  31. data/lib/paperclip/style.rb +2 -2
  32. data/lib/paperclip/tempfile_factory.rb +21 -0
  33. data/lib/paperclip/thumbnail.rb +10 -1
  34. data/lib/paperclip/version.rb +1 -1
  35. data/paperclip.gemspec +1 -1
  36. data/test/attachment_test.rb +56 -24
  37. data/test/fixtures/animated +0 -0
  38. data/test/fixtures/animated.unknown +0 -0
  39. data/test/generator_test.rb +26 -24
  40. data/test/geometry_test.rb +19 -0
  41. data/test/helper.rb +8 -0
  42. data/test/integration_test.rb +23 -23
  43. data/test/io_adapters/abstract_adapter_test.rb +44 -0
  44. data/test/io_adapters/attachment_adapter_test.rb +96 -34
  45. data/test/io_adapters/file_adapter_test.rb +13 -1
  46. data/test/io_adapters/stringio_adapter_test.rb +9 -10
  47. data/test/io_adapters/uploaded_file_adapter_test.rb +2 -1
  48. data/test/schema_test.rb +179 -77
  49. data/test/storage/filesystem_test.rb +18 -3
  50. data/test/storage/fog_test.rb +64 -1
  51. data/test/storage/s3_test.rb +38 -2
  52. data/test/tempfile_factory_test.rb +13 -0
  53. data/test/thumbnail_test.rb +45 -0
  54. metadata +16 -9
  55. data/features/support/fixtures/.boot_config.rb.swo +0 -0
  56. data/images.rake +0 -21
@@ -440,6 +440,10 @@ module Paperclip
440
440
 
441
441
  # called by storage after the writes are flushed and before @queued_for_writes is cleared
442
442
  def after_flush_writes
443
+ @queued_for_write.each do |style, file|
444
+ file.close unless file.closed?
445
+ file.unlink if file.respond_to?(:unlink) && file.path.present? && File.exist?(file.path)
446
+ end
443
447
  end
444
448
 
445
449
  def cleanup_filename(filename)
@@ -101,6 +101,33 @@ module Paperclip
101
101
  [ scale_geometry, crop_geometry ]
102
102
  end
103
103
 
104
+ # resize to a new geometry
105
+ # @param geometry [String] the Paperclip geometry definition to resize to
106
+ # @example
107
+ # Paperclip::Geometry.new(150, 150).resize_to('50x50!')
108
+ # #=> Paperclip::Geometry(50, 50)
109
+ def resize_to(geometry)
110
+ new_geometry = Paperclip::Geometry.parse geometry
111
+ case new_geometry.modifier
112
+ when '!', '#'
113
+ new_geometry
114
+ when '>'
115
+ if new_geometry.width >= self.width && new_geometry.height >= self.height
116
+ self
117
+ else
118
+ scale_to new_geometry
119
+ end
120
+ when '<'
121
+ if new_geometry.width <= self.width || new_geometry.height <= self.height
122
+ self
123
+ else
124
+ scale_to new_geometry
125
+ end
126
+ else
127
+ scale_to new_geometry
128
+ end
129
+ end
130
+
104
131
  private
105
132
 
106
133
  def scaling dst, ratio
@@ -118,5 +145,11 @@ module Paperclip
118
145
  "%dx%d+%d+%d" % [ dst.width, dst.height, (self.width * scale - dst.width) / 2, 0 ]
119
146
  end
120
147
  end
148
+
149
+ # scale to the requested geometry and preserve the aspect ratio
150
+ def scale_to(new_geometry)
151
+ scale = [new_geometry.width.to_f / self.width.to_f , new_geometry.height.to_f / self.height.to_f].min
152
+ Paperclip::Geometry.new (self.width * scale).round, (self.height * scale).round
153
+ end
121
154
  end
122
155
  end
@@ -4,10 +4,11 @@ require 'paperclip/schema'
4
4
 
5
5
  module Paperclip
6
6
  module Glue
7
- def self.included base #:nodoc:
7
+ def self.included(base)
8
8
  base.extend ClassMethods
9
9
  base.send :include, Callbacks
10
10
  base.send :include, Validators
11
+ base.send :include, Schema
11
12
  base.class_attribute :attachment_definitions
12
13
 
13
14
  if defined?(ActiveRecord)
@@ -0,0 +1,45 @@
1
+ require 'active_support/core_ext/module/delegation'
2
+
3
+ module Paperclip
4
+ class AbstractAdapter
5
+ attr_reader :content_type, :original_filename, :size
6
+ delegate :close, :closed?, :eof?, :path, :rewind, :unlink, :to => :@tempfile
7
+
8
+ def fingerprint
9
+ @fingerprint ||= Digest::MD5.file(path).to_s
10
+ end
11
+
12
+ def read(length = nil, buffer = nil)
13
+ @tempfile.read(length, buffer)
14
+ end
15
+
16
+ private
17
+
18
+ def destination
19
+ @destination ||= TempfileFactory.new.generate(original_filename)
20
+ end
21
+
22
+ def copy_to_tempfile(src)
23
+ FileUtils.cp(src.path, destination.path)
24
+ destination
25
+ end
26
+
27
+ def best_content_type_option(types)
28
+ best = types.reject {|type| type.content_type.match(/\/x-/) }
29
+ if best.size == 0
30
+ types.first.content_type
31
+ else
32
+ best.first.content_type
33
+ end
34
+ end
35
+
36
+ def type_from_file_command
37
+ # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
38
+ type = (File.extname(self.path.to_s)).downcase
39
+ type = "octet-stream" if type.empty?
40
+ mime_type = Paperclip.run("file", "-b --mime :file", :file => self.path).split(/[:;\s]+/)[0]
41
+ mime_type = "application/x-#{type}" if mime_type.match(/\(.*?\)/)
42
+ mime_type
43
+ end
44
+ end
45
+ end
@@ -1,71 +1,36 @@
1
1
  module Paperclip
2
- class AttachmentAdapter
2
+ class AttachmentAdapter < AbstractAdapter
3
3
  def initialize(target)
4
- @target = target
5
- cache_current_values
6
- end
7
-
8
- def original_filename
9
- @original_filename
10
- end
11
-
12
- def content_type
13
- @content_type
14
- end
15
-
16
- def size
17
- @size
18
- end
19
-
20
- def nil?
21
- false
22
- end
23
-
24
- def fingerprint
25
- @fingerprint ||= Digest::MD5.file(path).to_s
26
- end
27
-
28
- def read(length = nil, buffer = nil)
29
- @tempfile.read(length, buffer)
30
- end
31
-
32
- # We don't use this directly, but aws/sdk does.
33
- def rewind
34
- @tempfile.rewind
35
- end
36
-
37
- def eof?
38
- @tempfile.eof?
39
- end
4
+ @target, @style = case target
5
+ when Paperclip::Attachment
6
+ [target, :original]
7
+ when Paperclip::Style
8
+ [target.attachment, target.name]
9
+ end
40
10
 
41
- def path
42
- @tempfile.path
11
+ cache_current_values
43
12
  end
44
13
 
45
14
  private
46
15
 
47
16
  def cache_current_values
48
- @tempfile = copy_to_tempfile(@target)
49
17
  @original_filename = @target.original_filename
50
18
  @content_type = @target.content_type
19
+ @tempfile = copy_to_tempfile(@target)
51
20
  @size = @tempfile.size || @target.size
52
21
  end
53
22
 
54
23
  def copy_to_tempfile(src)
55
- extension = File.extname(src.original_filename)
56
- basename = File.basename(src.original_filename, extension)
57
- dest = Tempfile.new([basename, extension])
58
- dest.binmode
59
24
  if src.respond_to? :copy_to_local_file
60
- src.copy_to_local_file(:original, dest.path)
25
+ src.copy_to_local_file(@style, destination.path)
61
26
  else
62
- FileUtils.cp(src.path(:original), dest.path)
27
+ FileUtils.cp(src.path(@style), destination.path)
63
28
  end
64
- dest
29
+ destination
65
30
  end
66
31
  end
67
32
  end
68
33
 
69
34
  Paperclip.io_adapters.register Paperclip::AttachmentAdapter do |target|
70
- Paperclip::Attachment === target
35
+ Paperclip::Attachment === target || Paperclip::Style === target
71
36
  end
@@ -1,19 +1,21 @@
1
1
  module Paperclip
2
- class FileAdapter
2
+ class FileAdapter < AbstractAdapter
3
3
  def initialize(target)
4
4
  @target = target
5
- @tempfile = copy_to_tempfile(@target)
5
+ cache_current_values
6
6
  end
7
7
 
8
- def original_filename
9
- if @target.respond_to?(:original_filename)
10
- @target.original_filename
11
- else
12
- File.basename(@target.path)
13
- end
8
+ private
9
+
10
+ def cache_current_values
11
+ @original_filename = @target.original_filename if @target.respond_to?(:original_filename)
12
+ @original_filename ||= File.basename(@target.path)
13
+ @tempfile = copy_to_tempfile(@target)
14
+ @content_type = calculate_content_type
15
+ @size = File.size(@target)
14
16
  end
15
17
 
16
- def content_type
18
+ def calculate_content_type
17
19
  types = MIME::Types.type_for(original_filename)
18
20
  if types.length == 0
19
21
  type_from_file_command
@@ -23,58 +25,6 @@ module Paperclip
23
25
  best_content_type_option(types)
24
26
  end
25
27
  end
26
-
27
- def fingerprint
28
- @fingerprint ||= Digest::MD5.file(path).to_s
29
- end
30
-
31
- def size
32
- File.size(@tempfile)
33
- end
34
-
35
- def nil?
36
- @target.nil?
37
- end
38
-
39
- def read(length = nil, buffer = nil)
40
- @tempfile.read(length, buffer)
41
- end
42
-
43
- # We don't use this directly, but aws/sdk does.
44
- def rewind
45
- @tempfile.rewind
46
- end
47
-
48
- def eof?
49
- @tempfile.eof?
50
- end
51
-
52
- def path
53
- @tempfile.path
54
- end
55
-
56
- private
57
-
58
- def copy_to_tempfile(src)
59
- extension = File.extname(original_filename)
60
- basename = File.basename(original_filename, extension)
61
- dest = Tempfile.new([basename, extension])
62
- dest.binmode
63
- FileUtils.cp(src.path, dest.path)
64
- dest
65
- end
66
-
67
- def best_content_type_option(types)
68
- types.reject {|type| type.content_type.match(/\/x-/) }.first.content_type
69
- end
70
-
71
- def type_from_file_command
72
- # On BSDs, `file` doesn't give a result code of 1 if the file doesn't exist.
73
- type = (self.original_filename.match(/\.(\w+)$/)[1] rescue "octet-stream").downcase
74
- mime_type = (Paperclip.run("file", "-b --mime :file", :file => self.path).split(/[:;\s]+/)[0] rescue "application/x-#{type}")
75
- mime_type = "application/x-#{type}" if mime_type.match(/\(.*?\)/)
76
- mime_type
77
- end
78
28
  end
79
29
  end
80
30
 
@@ -1,5 +1,5 @@
1
1
  module Paperclip
2
- class IdentityAdapter
2
+ class IdentityAdapter < AbstractAdapter
3
3
  def new(adapter)
4
4
  adapter
5
5
  end
@@ -1,5 +1,5 @@
1
1
  module Paperclip
2
- class NilAdapter
2
+ class NilAdapter < AbstractAdapter
3
3
  def initialize(target)
4
4
  end
5
5
 
@@ -1,61 +1,30 @@
1
1
  module Paperclip
2
- class StringioAdapter
2
+ class StringioAdapter < AbstractAdapter
3
3
  def initialize(target)
4
4
  @target = target
5
+ cache_current_values
5
6
  @tempfile = copy_to_tempfile(@target)
6
7
  end
7
8
 
8
- attr_writer :original_filename, :content_type
9
+ private
9
10
 
10
- def original_filename
11
- @original_filename ||= @target.original_filename if @target.respond_to?(:original_filename)
11
+ def cache_current_values
12
+ @original_filename = @target.original_filename if @target.respond_to?(:original_filename)
12
13
  @original_filename ||= "stringio.txt"
13
- @original_filename.strip
14
- end
14
+ @original_filename = @original_filename.strip
15
15
 
16
- def content_type
17
- @content_type ||= @target.content_type if @target.respond_to?(:content_type)
16
+ @content_type = @target.content_type if @target.respond_to?(:content_type)
18
17
  @content_type ||= "text/plain"
19
- @content_type
20
- end
21
-
22
- def size
23
- @target.size
24
- end
25
-
26
- def fingerprint
27
- Digest::MD5.hexdigest(read)
28
- end
29
18
 
30
- def read(length = nil, buffer = nil)
31
- @tempfile.read(length, buffer)
19
+ @size = @target.size
32
20
  end
33
21
 
34
- # We don't use this directly, but aws/sdk does.
35
- def rewind
36
- @tempfile.rewind
37
- end
38
-
39
- def eof?
40
- @tempfile.eof?
41
- end
42
-
43
- def path
44
- @tempfile.path
45
- end
46
-
47
- private
48
-
49
22
  def copy_to_tempfile(src)
50
- extension = File.extname(original_filename)
51
- basename = File.basename(original_filename, extension)
52
- dest = Tempfile.new([basename, extension])
53
- dest.binmode
54
23
  while data = src.read(16*1024)
55
- dest.write(data)
24
+ destination.write(data)
56
25
  end
57
- dest.rewind
58
- dest
26
+ destination.rewind
27
+ destination
59
28
  end
60
29
 
61
30
  end
@@ -1,7 +1,8 @@
1
1
  module Paperclip
2
- class UploadedFileAdapter
2
+ class UploadedFileAdapter < AbstractAdapter
3
3
  def initialize(target)
4
4
  @target = target
5
+ cache_current_values
5
6
 
6
7
  if @target.respond_to?(:tempfile)
7
8
  @tempfile = copy_to_tempfile(@target.tempfile)
@@ -10,52 +11,12 @@ module Paperclip
10
11
  end
11
12
  end
12
13
 
13
- def original_filename
14
- @target.original_filename
15
- end
16
-
17
- def content_type
18
- @target.content_type
19
- end
20
-
21
- def fingerprint
22
- @fingerprint ||= Digest::MD5.file(path).to_s
23
- end
24
-
25
- def size
26
- File.size(path)
27
- end
28
-
29
- def nil?
30
- false
31
- end
32
-
33
- def read(length = nil, buffer = nil)
34
- @tempfile.read(length, buffer)
35
- end
36
-
37
- # We don't use this directly, but aws/sdk does.
38
- def rewind
39
- @tempfile.rewind
40
- end
41
-
42
- def eof?
43
- @tempfile.eof?
44
- end
45
-
46
- def path
47
- @tempfile.path
48
- end
49
-
50
14
  private
51
15
 
52
- def copy_to_tempfile(src)
53
- extension = File.extname(original_filename)
54
- basename = File.basename(original_filename, extension)
55
- dest = Tempfile.new([basename, extension])
56
- dest.binmode
57
- FileUtils.cp(src.path, dest.path)
58
- dest
16
+ def cache_current_values
17
+ @original_filename = @target.original_filename
18
+ @content_type = @target.content_type
19
+ @size = File.size(@target.path)
59
20
  end
60
21
  end
61
22
  end