alg-paperclip 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 (50) hide show
  1. data/LICENSE +26 -0
  2. data/README.rdoc +195 -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 +357 -0
  9. data/lib/paperclip/attachment.rb +345 -0
  10. data/lib/paperclip/callback_compatability.rb +33 -0
  11. data/lib/paperclip/geometry.rb +115 -0
  12. data/lib/paperclip/interpolations.rb +120 -0
  13. data/lib/paperclip/iostream.rb +59 -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 +65 -0
  17. data/lib/paperclip/matchers/validate_attachment_presence_matcher.rb +48 -0
  18. data/lib/paperclip/matchers/validate_attachment_size_matcher.rb +85 -0
  19. data/lib/paperclip/processor.rb +49 -0
  20. data/lib/paperclip/storage.rb +247 -0
  21. data/lib/paperclip/style.rb +90 -0
  22. data/lib/paperclip/thumbnail.rb +75 -0
  23. data/lib/paperclip/upfile.rb +49 -0
  24. data/shoulda_macros/paperclip.rb +117 -0
  25. data/tasks/paperclip_tasks.rake +79 -0
  26. data/test/attachment_test.rb +788 -0
  27. data/test/database.yml +4 -0
  28. data/test/fixtures/12k.png +0 -0
  29. data/test/fixtures/50x50.png +0 -0
  30. data/test/fixtures/5k.png +0 -0
  31. data/test/fixtures/bad.png +1 -0
  32. data/test/fixtures/s3.yml +8 -0
  33. data/test/fixtures/text.txt +0 -0
  34. data/test/fixtures/twopage.pdf +0 -0
  35. data/test/geometry_test.rb +177 -0
  36. data/test/helper.rb +108 -0
  37. data/test/integration_test.rb +483 -0
  38. data/test/interpolations_test.rb +124 -0
  39. data/test/iostream_test.rb +78 -0
  40. data/test/matchers/have_attached_file_matcher_test.rb +21 -0
  41. data/test/matchers/validate_attachment_content_type_matcher_test.rb +31 -0
  42. data/test/matchers/validate_attachment_presence_matcher_test.rb +23 -0
  43. data/test/matchers/validate_attachment_size_matcher_test.rb +51 -0
  44. data/test/paperclip_test.rb +298 -0
  45. data/test/processor_test.rb +10 -0
  46. data/test/storage_test.rb +330 -0
  47. data/test/style_test.rb +141 -0
  48. data/test/thumbnail_test.rb +227 -0
  49. data/test/upfile_test.rb +28 -0
  50. metadata +164 -0
@@ -0,0 +1,120 @@
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(attachment)
23
+ self.instance_methods(false).sort + attachment.tags.keys
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
+ attachment, style_name = *args
30
+ all(attachment).reverse.inject( pattern.dup ) do |result, tag|
31
+ result.gsub(/:#{tag}/) do |match|
32
+ send( tag, *args )
33
+ end
34
+ end
35
+ end
36
+
37
+ # Returns the filename, the same way as ":basename.:extension" would.
38
+ def filename attachment, style_name
39
+ "#{basename(attachment, style_name)}.#{extension(attachment, style_name)}"
40
+ end
41
+
42
+ # Returns the interpolated URL. Will raise an error if the url itself
43
+ # contains ":url" to prevent infinite recursion. This interpolation
44
+ # is used in the default :path to ease default specifications.
45
+ def url attachment, style_name
46
+ raise InfiniteInterpolationError if attachment.options[:url].include?(":url")
47
+ attachment.url(style_name, false)
48
+ end
49
+
50
+ # Returns the timestamp as defined by the <attachment>_updated_at field
51
+ def timestamp attachment, style_name
52
+ attachment.instance_read(:updated_at).to_s
53
+ end
54
+
55
+ # Returns the RAILS_ROOT constant.
56
+ def rails_root attachment, style_name
57
+ RAILS_ROOT
58
+ end
59
+
60
+ # Returns the RAILS_ENV constant.
61
+ def rails_env attachment, style_name
62
+ RAILS_ENV
63
+ end
64
+
65
+ # Returns the underscored, pluralized version of the class name.
66
+ # e.g. "users" for the User class.
67
+ # NOTE: The arguments need to be optional, because some tools fetch
68
+ # all class names. Calling #class will return the expected class.
69
+ def class attachment = nil, style_name = nil
70
+ return super() if attachment.nil? && style_name.nil?
71
+ attachment.instance.class.to_s.underscore.pluralize
72
+ end
73
+
74
+ # Returns the basename of the file. e.g. "file" for "file.jpg"
75
+ def basename attachment, style_name
76
+ attachment.original_filename.gsub(/#{File.extname(attachment.original_filename)}$/, "")
77
+ end
78
+
79
+ # Returns the extension of the file. e.g. "jpg" for "file.jpg"
80
+ # If the style has a format defined, it will return the format instead
81
+ # of the actual extension.
82
+ def extension attachment, style_name
83
+ ((style = attachment.styles[style_name]) && style[:format]) ||
84
+ File.extname(attachment.original_filename).gsub(/^\.+/, "")
85
+ end
86
+
87
+ # Returns the id of the instance.
88
+ def id attachment, style_name
89
+ attachment.instance.id
90
+ end
91
+
92
+ # Returns the id of the instance in a split path form. e.g. returns
93
+ # 000/001/234 for an id of 1234.
94
+ def id_partition attachment, style_name
95
+ ("%09d" % attachment.instance.id).scan(/\d{3}/).join("/")
96
+ end
97
+
98
+ # Returns the pluralized form of the attachment name. e.g.
99
+ # "avatars" for an attachment of :avatar
100
+ def attachment attachment, style_name
101
+ attachment.name.to_s.downcase.pluralize
102
+ end
103
+
104
+ # Returns the style, or the default style if nil is supplied.
105
+ def style attachment, style_name
106
+ style_name || attachment.default_style
107
+ end
108
+
109
+ # Looks up missing interpolations among the custom tags and returns if present
110
+ def method_missing(tag, attachment, style_name)
111
+ interpolation = attachment.tags[tag]
112
+
113
+ if interpolation.is_a?(Proc)
114
+ return interpolation.call(attachment, style_name)
115
+ else
116
+ return interpolation
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,59 @@
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
+ name = respond_to?(:original_filename) ? original_filename : (respond_to?(:path) ? path : "stream")
8
+ tempfile = Paperclip::Tempfile.new(File.basename(name))
9
+ tempfile.binmode
10
+ self.stream_to(tempfile)
11
+ end
12
+
13
+ # Copies one read-able object from one place to another in blocks, obviating the need to load
14
+ # the whole thing into memory. Defaults to 8k blocks. If this module is included in both
15
+ # StringIO and Tempfile, then either can have its data copied anywhere else without typing
16
+ # worries or memory overhead worries. Returns a File if a String is passed in as the destination
17
+ # and returns the IO or Tempfile as passed in if one is sent as the destination.
18
+ def stream_to path_or_file, in_blocks_of = 8192
19
+ dstio = case path_or_file
20
+ when String then File.new(path_or_file, "wb+")
21
+ when IO then path_or_file
22
+ when Tempfile then path_or_file
23
+ end
24
+ buffer = ""
25
+ self.rewind
26
+ while self.read(in_blocks_of, buffer) do
27
+ dstio.write(buffer)
28
+ end
29
+ dstio.rewind
30
+ dstio
31
+ end
32
+ end
33
+
34
+ class IO #:nodoc:
35
+ include IOStream
36
+ end
37
+
38
+ %w( Tempfile StringIO ).each do |klass|
39
+ if Object.const_defined? klass
40
+ Object.const_get(klass).class_eval do
41
+ include IOStream
42
+ end
43
+ end
44
+ end
45
+
46
+ # Corrects a bug in Windows when asking for Tempfile size.
47
+ if defined? Tempfile
48
+ class Tempfile
49
+ def size
50
+ if @tmpfile
51
+ @tmpfile.fsync
52
+ @tmpfile.flush
53
+ @tmpfile.stat.size
54
+ else
55
+ 0
56
+ end
57
+ end
58
+ end
59
+ 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,65 @@
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
+ (subject = @subject.new).attachment_for(@attachment_name).assign(file)
50
+ subject.valid? && subject.errors.on(:"#{@attachment_name}_content_type").blank?
51
+ end
52
+ end
53
+
54
+ def allowed_types_allowed?
55
+ allow_types?(@allowed_types)
56
+ end
57
+
58
+ def rejected_types_rejected?
59
+ not allow_types?(@rejected_types)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -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
+ (subject = @subject.new).send(@attachment_name).assign(nil)
34
+ subject.valid?
35
+ not subject.errors.on(:"#{@attachment_name}_file_name").blank?
36
+ end
37
+
38
+ def no_error_when_valid?
39
+ @file = StringIO.new(".")
40
+ (subject = @subject.new).send(@attachment_name).assign(@file)
41
+ subject.valid?
42
+ subject.errors.on(:"#{@attachment_name}_file_name").blank?
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+
@@ -0,0 +1,85 @@
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
+ override_method(file, :to_tempfile){ file }
58
+
59
+ (subject = @subject.new).send(@attachment_name).assign(file)
60
+ subject.valid?
61
+ subject.errors.on(:"#{@attachment_name}_file_size").blank?
62
+ end
63
+
64
+ def lower_than_low?
65
+ not passes_validation_with_size(@low - 1)
66
+ end
67
+
68
+ def higher_than_low?
69
+ passes_validation_with_size(@low + 1)
70
+ end
71
+
72
+ def lower_than_high?
73
+ return true if @high == (1.0/0)
74
+ passes_validation_with_size(@high - 1)
75
+ end
76
+
77
+ def higher_than_high?
78
+ return true if @high == (1.0/0)
79
+ not passes_validation_with_size(@high + 1)
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
@@ -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