paperclip_with_versions 2.3.1.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 (48) hide show
  1. data/LICENSE +26 -0
  2. data/README.rdoc +174 -0
  3. data/Rakefile +103 -0
  4. data/generators/paperclip/USAGE +5 -0
  5. data/generators/paperclip/paperclip_generator.rb +27 -0
  6. data/generators/paperclip/templates/paperclip_migration.rb.erb +19 -0
  7. data/init.rb +1 -0
  8. data/lib/paperclip.rb +356 -0
  9. data/lib/paperclip/attachment.rb +414 -0
  10. data/lib/paperclip/callback_compatability.rb +33 -0
  11. data/lib/paperclip/geometry.rb +115 -0
  12. data/lib/paperclip/interpolations.rb +108 -0
  13. data/lib/paperclip/iostream.rb +58 -0
  14. data/lib/paperclip/matchers.rb +4 -0
  15. data/lib/paperclip/matchers/have_attached_file_matcher.rb +49 -0
  16. data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rb +66 -0
  17. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +48 -0
  18. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +83 -0
  19. data/lib/paperclip/processor.rb +49 -0
  20. data/lib/paperclip/storage.rb +243 -0
  21. data/lib/paperclip/thumbnail.rb +73 -0
  22. data/lib/paperclip/upfile.rb +49 -0
  23. data/shoulda_macros/paperclip.rb +117 -0
  24. data/tasks/paperclip_tasks.rake +79 -0
  25. data/test/attachment_test.rb +815 -0
  26. data/test/database.yml +4 -0
  27. data/test/fixtures/12k.png +0 -0
  28. data/test/fixtures/50x50.png +0 -0
  29. data/test/fixtures/5k.png +0 -0
  30. data/test/fixtures/bad.png +1 -0
  31. data/test/fixtures/s3.yml +8 -0
  32. data/test/fixtures/text.txt +0 -0
  33. data/test/fixtures/twopage.pdf +0 -0
  34. data/test/geometry_test.rb +177 -0
  35. data/test/helper.rb +108 -0
  36. data/test/integration_test.rb +483 -0
  37. data/test/interpolations_test.rb +124 -0
  38. data/test/iostream_test.rb +71 -0
  39. data/test/matchers/have_attached_file_matcher_test.rb +21 -0
  40. data/test/matchers/validate_attachment_content_type_matcher_test.rb +30 -0
  41. data/test/matchers/validate_attachment_presence_matcher_test.rb +21 -0
  42. data/test/matchers/validate_attachment_size_matcher_test.rb +50 -0
  43. data/test/paperclip_test.rb +327 -0
  44. data/test/processor_test.rb +10 -0
  45. data/test/storage_test.rb +303 -0
  46. data/test/thumbnail_test.rb +227 -0
  47. data/test/upfile_test.rb +28 -0
  48. metadata +161 -0
@@ -0,0 +1,108 @@
1
+ module Paperclip
2
+ # This module contains all the methods that are available for interpolation
3
+ # in paths and urls. To add your own (or override an existing one), you
4
+ # can either open this module and define it, or call the
5
+ # Paperclip.interpolates method.
6
+ module Interpolations
7
+ extend self
8
+
9
+ # Hash assignment of interpolations. Included only for compatability,
10
+ # and is not intended for normal use.
11
+ def self.[]= name, block
12
+ define_method(name, &block)
13
+ end
14
+
15
+ # Hash access of interpolations. Included only for compatability,
16
+ # and is not intended for normal use.
17
+ def self.[] name
18
+ method(name)
19
+ end
20
+
21
+ # Returns a sorted list of all interpolations.
22
+ def self.all
23
+ self.instance_methods(false).sort
24
+ end
25
+
26
+ # Perform the actual interpolation. Takes the pattern to interpolate
27
+ # and the arguments to pass, which are the attachment and style name.
28
+ def self.interpolate pattern, *args
29
+ all.reverse.inject( pattern.dup ) do |result, tag|
30
+ result.gsub(/:#{tag}/) do |match|
31
+ send( tag, *args )
32
+ end
33
+ end
34
+ end
35
+
36
+ # Returns the filename, the same way as ":basename.:extension" would.
37
+ def filename attachment, style
38
+ "#{basename(attachment, style)}.#{extension(attachment, style)}"
39
+ end
40
+
41
+ # Returns the interpolated URL. Will raise an error if the url itself
42
+ # contains ":url" to prevent infinite recursion. This interpolation
43
+ # is used in the default :path to ease default specifications.
44
+ def url attachment, style
45
+ raise InfiniteInterpolationError if attachment.options[:url].include?(":url")
46
+ attachment.url(style, false)
47
+ end
48
+
49
+ # Returns the timestamp as defined by the <attachment>_updated_at field
50
+ def timestamp attachment, style
51
+ attachment.instance_read(:updated_at).to_s
52
+ end
53
+
54
+ # Returns the RAILS_ROOT constant.
55
+ def rails_root attachment, style
56
+ RAILS_ROOT
57
+ end
58
+
59
+ # Returns the RAILS_ENV constant.
60
+ def rails_env attachment, style
61
+ RAILS_ENV
62
+ end
63
+
64
+ # Returns the underscored, pluralized version of the class name.
65
+ # e.g. "users" for the User class.
66
+ # NOTE: The arguments need to be optional, because some tools fetch
67
+ # all class names. Calling #class will return the expected class.
68
+ def class attachment = nil, style = nil
69
+ return super() if attachment.nil? && style.nil?
70
+ attachment.instance.class.to_s.underscore.pluralize
71
+ end
72
+
73
+ # Returns the basename of the file. e.g. "file" for "file.jpg"
74
+ def basename attachment, style
75
+ attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
76
+ end
77
+
78
+ # Returns the extension of the file. e.g. "jpg" for "file.jpg"
79
+ # If the style has a format defined, it will return the format instead
80
+ # of the actual extension.
81
+ def extension attachment, style
82
+ ((style = attachment.styles[style]) && style[:format]) ||
83
+ File.extname(attachment.original_filename).gsub(/^\.+/, "")
84
+ end
85
+
86
+ # Returns the id of the instance.
87
+ def id attachment, style
88
+ attachment.instance.id
89
+ end
90
+
91
+ # Returns the id of the instance in a split path form. e.g. returns
92
+ # 000/001/234 for an id of 1234.
93
+ def id_partition attachment, style
94
+ ("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
95
+ end
96
+
97
+ # Returns the pluralized form of the attachment name. e.g.
98
+ # "avatars" for an attachment of :avatar
99
+ def attachment attachment, style
100
+ attachment.name.to_s.downcase.pluralize
101
+ end
102
+
103
+ # Returns the style, or the default style if nil is supplied.
104
+ def style attachment, style
105
+ style || attachment.default_style
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,58 @@
1
+ # Provides method that can be included on File-type objects (IO, StringIO, Tempfile, etc) to allow stream copying
2
+ # and Tempfile conversion.
3
+ module IOStream
4
+
5
+ # Returns a Tempfile containing the contents of the readable object.
6
+ def to_tempfile
7
+ tempfile = Tempfile.new("stream")
8
+ tempfile.binmode
9
+ self.stream_to(tempfile)
10
+ end
11
+
12
+ # Copies one read-able object from one place to another in blocks, obviating the need to load
13
+ # the whole thing into memory. Defaults to 8k blocks. If this module is included in both
14
+ # StringIO and Tempfile, then either can have its data copied anywhere else without typing
15
+ # worries or memory overhead worries. Returns a File if a String is passed in as the destination
16
+ # and returns the IO or Tempfile as passed in if one is sent as the destination.
17
+ def stream_to path_or_file, in_blocks_of = 8192
18
+ dstio = case path_or_file
19
+ when String then File.new(path_or_file, "wb+")
20
+ when IO then path_or_file
21
+ when Tempfile then path_or_file
22
+ end
23
+ buffer = ""
24
+ self.rewind
25
+ while self.read(in_blocks_of, buffer) do
26
+ dstio.write(buffer)
27
+ end
28
+ dstio.rewind
29
+ dstio
30
+ end
31
+ end
32
+
33
+ class IO #:nodoc:
34
+ include IOStream
35
+ end
36
+
37
+ %w( Tempfile StringIO ).each do |klass|
38
+ if Object.const_defined? klass
39
+ Object.const_get(klass).class_eval do
40
+ include IOStream
41
+ end
42
+ end
43
+ end
44
+
45
+ # Corrects a bug in Windows when asking for Tempfile size.
46
+ if defined? Tempfile
47
+ class Tempfile
48
+ def size
49
+ if @tmpfile
50
+ @tmpfile.fsync
51
+ @tmpfile.flush
52
+ @tmpfile.stat.size
53
+ else
54
+ 0
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,4 @@
1
+ require 'paperclip/matchers/have_attached_file_matcher'
2
+ require 'paperclip/matchers/validate_attachment_presence_matcher'
3
+ require 'paperclip/matchers/validate_attachment_content_type_matcher'
4
+ require 'paperclip/matchers/validate_attachment_size_matcher'
@@ -0,0 +1,49 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def have_attached_file name
5
+ HaveAttachedFileMatcher.new(name)
6
+ end
7
+
8
+ class HaveAttachedFileMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ end
12
+
13
+ def matches? subject
14
+ @subject = subject
15
+ responds? && has_column? && included?
16
+ end
17
+
18
+ def failure_message
19
+ "Should have an attachment named #{@attachment_name}"
20
+ end
21
+
22
+ def negative_failure_message
23
+ "Should not have an attachment named #{@attachment_name}"
24
+ end
25
+
26
+ def description
27
+ "have an attachment named #{@attachment_name}"
28
+ end
29
+
30
+ protected
31
+
32
+ def responds?
33
+ methods = @subject.instance_methods.map(&:to_s)
34
+ methods.include?("#{@attachment_name}") &&
35
+ methods.include?("#{@attachment_name}=") &&
36
+ methods.include?("#{@attachment_name}?")
37
+ end
38
+
39
+ def has_column?
40
+ @subject.column_names.include?("#{@attachment_name}_file_name")
41
+ end
42
+
43
+ def included?
44
+ @subject.ancestors.include?(Paperclip::InstanceMethods)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,66 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def validate_attachment_content_type name
5
+ ValidateAttachmentContentTypeMatcher.new(name)
6
+ end
7
+
8
+ class ValidateAttachmentContentTypeMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ end
12
+
13
+ def allowing *types
14
+ @allowed_types = types.flatten
15
+ self
16
+ end
17
+
18
+ def rejecting *types
19
+ @rejected_types = types.flatten
20
+ self
21
+ end
22
+
23
+ def matches? subject
24
+ @subject = subject
25
+ @allowed_types && @rejected_types &&
26
+ allowed_types_allowed? && rejected_types_rejected?
27
+ end
28
+
29
+ def failure_message
30
+ "Content types #{@allowed_types.join(", ")} should be accepted" +
31
+ " and #{@rejected_types.join(", ")} rejected by #{@attachment_name}"
32
+ end
33
+
34
+ def negative_failure_message
35
+ "Content types #{@allowed_types.join(", ")} should be rejected" +
36
+ " and #{@rejected_types.join(", ")} accepted by #{@attachment_name}"
37
+ end
38
+
39
+ def description
40
+ "validate the content types allowed on attachment #{@attachment_name}"
41
+ end
42
+
43
+ protected
44
+
45
+ def allow_types?(types)
46
+ types.all? do |type|
47
+ file = StringIO.new(".")
48
+ file.content_type = type
49
+ attachment = @subject.new.attachment_for(@attachment_name)
50
+ attachment.assign(file)
51
+ attachment.errors[:content_type].nil?
52
+ end
53
+ end
54
+
55
+ def allowed_types_allowed?
56
+ allow_types?(@allowed_types)
57
+ end
58
+
59
+ def rejected_types_rejected?
60
+ not allow_types?(@rejected_types)
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,48 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def validate_attachment_presence name
5
+ ValidateAttachmentPresenceMatcher.new(name)
6
+ end
7
+
8
+ class ValidateAttachmentPresenceMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ end
12
+
13
+ def matches? subject
14
+ @subject = subject
15
+ error_when_not_valid? && no_error_when_valid?
16
+ end
17
+
18
+ def failure_message
19
+ "Attachment #{@attachment_name} should be required"
20
+ end
21
+
22
+ def negative_failure_message
23
+ "Attachment #{@attachment_name} should not be required"
24
+ end
25
+
26
+ def description
27
+ "require presence of attachment #{@attachment_name}"
28
+ end
29
+
30
+ protected
31
+
32
+ def error_when_not_valid?
33
+ @attachment = @subject.new.send(@attachment_name)
34
+ @attachment.assign(nil)
35
+ not @attachment.errors[:presence].nil?
36
+ end
37
+
38
+ def no_error_when_valid?
39
+ @file = StringIO.new(".")
40
+ @attachment = @subject.new.send(@attachment_name)
41
+ @attachment.assign(@file)
42
+ @attachment.errors[:presence].nil?
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,83 @@
1
+ module Paperclip
2
+ module Shoulda
3
+ module Matchers
4
+ def validate_attachment_size name
5
+ ValidateAttachmentSizeMatcher.new(name)
6
+ end
7
+
8
+ class ValidateAttachmentSizeMatcher
9
+ def initialize attachment_name
10
+ @attachment_name = attachment_name
11
+ @low, @high = 0, (1.0/0)
12
+ end
13
+
14
+ def less_than size
15
+ @high = size
16
+ self
17
+ end
18
+
19
+ def greater_than size
20
+ @low = size
21
+ self
22
+ end
23
+
24
+ def in range
25
+ @low, @high = range.first, range.last
26
+ self
27
+ end
28
+
29
+ def matches? subject
30
+ @subject = subject
31
+ lower_than_low? && higher_than_low? && lower_than_high? && higher_than_high?
32
+ end
33
+
34
+ def failure_message
35
+ "Attachment #{@attachment_name} must be between #{@low} and #{@high} bytes"
36
+ end
37
+
38
+ def negative_failure_message
39
+ "Attachment #{@attachment_name} cannot be between #{@low} and #{@high} bytes"
40
+ end
41
+
42
+ def description
43
+ "validate the size of attachment #{@attachment_name}"
44
+ end
45
+
46
+ protected
47
+
48
+ def override_method object, method, &replacement
49
+ (class << object; self; end).class_eval do
50
+ define_method(method, &replacement)
51
+ end
52
+ end
53
+
54
+ def passes_validation_with_size(new_size)
55
+ file = StringIO.new(".")
56
+ override_method(file, :size){ new_size }
57
+ attachment = @subject.new.attachment_for(@attachment_name)
58
+ attachment.assign(file)
59
+ attachment.errors[:size].nil?
60
+ end
61
+
62
+ def lower_than_low?
63
+ not passes_validation_with_size(@low - 1)
64
+ end
65
+
66
+ def higher_than_low?
67
+ passes_validation_with_size(@low + 1)
68
+ end
69
+
70
+ def lower_than_high?
71
+ return true if @high == (1.0/0)
72
+ passes_validation_with_size(@high - 1)
73
+ end
74
+
75
+ def higher_than_high?
76
+ return true if @high == (1.0/0)
77
+ not passes_validation_with_size(@high + 1)
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+
@@ -0,0 +1,49 @@
1
+ module Paperclip
2
+ # Paperclip processors allow you to modify attached files when they are
3
+ # attached in any way you are able. Paperclip itself uses command-line
4
+ # programs for its included Thumbnail processor, but custom processors
5
+ # are not required to follow suit.
6
+ #
7
+ # Processors are required to be defined inside the Paperclip module and
8
+ # are also required to be a subclass of Paperclip::Processor. There is
9
+ # only one method you *must* implement to properly be a subclass:
10
+ # #make, but #initialize may also be of use. Both methods accept 3
11
+ # arguments: the file that will be operated on (which is an instance of
12
+ # File), a hash of options that were defined in has_attached_file's
13
+ # style hash, and the Paperclip::Attachment itself.
14
+ #
15
+ # All #make needs to return is an instance of File (Tempfile is
16
+ # acceptable) which contains the results of the processing.
17
+ #
18
+ # See Paperclip.run for more information about using command-line
19
+ # utilities from within Processors.
20
+ class Processor
21
+ attr_accessor :file, :options, :attachment
22
+
23
+ def initialize file, options = {}, attachment = nil
24
+ @file = file
25
+ @options = options
26
+ @attachment = attachment
27
+ end
28
+
29
+ def make
30
+ end
31
+
32
+ def self.make file, options = {}, attachment = nil
33
+ new(file, options, attachment).make
34
+ end
35
+ end
36
+
37
+ # Due to how ImageMagick handles its image format conversion and how Tempfile
38
+ # handles its naming scheme, it is necessary to override how Tempfile makes
39
+ # its names so as to allow for file extensions. Idea taken from the comments
40
+ # on this blog post:
41
+ # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
42
+ class Tempfile < ::Tempfile
43
+ # Replaces Tempfile's +make_tmpname+ with one that honors file extensions.
44
+ def make_tmpname(basename, n)
45
+ extension = File.extname(basename)
46
+ sprintf("%s,%d,%d%s", File.basename(basename, extension), $$, n, extension)
47
+ end
48
+ end
49
+ end