paperclip 3.4.2 → 3.5.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.
- checksums.yaml +15 -0
- data/.travis.yml +0 -1
- data/Appraisals +4 -4
- data/NEWS +16 -0
- data/README.md +21 -2
- data/features/step_definitions/rails_steps.rb +9 -0
- data/features/step_definitions/s3_steps.rb +1 -1
- data/features/support/fakeweb.rb +4 -1
- data/lib/paperclip.rb +6 -42
- data/lib/paperclip/attachment.rb +25 -27
- data/lib/paperclip/filename_cleaner.rb +16 -0
- data/lib/paperclip/glue.rb +0 -1
- data/lib/paperclip/has_attached_file.rb +86 -0
- data/lib/paperclip/io_adapters/abstract_adapter.rb +8 -0
- data/lib/paperclip/io_adapters/data_uri_adapter.rb +27 -0
- data/lib/paperclip/io_adapters/empty_string_adapter.rb +18 -0
- data/lib/paperclip/io_adapters/stringio_adapter.rb +3 -3
- data/lib/paperclip/matchers/have_attached_file_matcher.rb +1 -5
- data/lib/paperclip/missing_attachment_styles.rb +11 -16
- data/lib/paperclip/storage/filesystem.rb +7 -3
- data/lib/paperclip/storage/s3.rb +2 -0
- data/lib/paperclip/tasks/attachments.rb +59 -0
- data/lib/paperclip/validators.rb +21 -2
- data/lib/paperclip/validators/attachment_content_type_validator.rb +9 -2
- data/lib/paperclip/validators/attachment_presence_validator.rb +8 -8
- data/lib/paperclip/validators/attachment_size_validator.rb +12 -7
- data/lib/paperclip/version.rb +1 -1
- data/lib/tasks/paperclip.rake +24 -6
- data/paperclip.gemspec +5 -3
- data/test/attachment_processing_test.rb +69 -15
- data/test/attachment_test.rb +49 -0
- data/test/filename_cleaner_test.rb +14 -0
- data/test/has_attached_file_test.rb +109 -0
- data/test/helper.rb +1 -1
- data/test/integration_test.rb +1 -65
- data/test/io_adapters/abstract_adapter_test.rb +8 -0
- data/test/io_adapters/data_uri_adapter_test.rb +60 -0
- data/test/io_adapters/empty_string_adapter_test.rb +17 -0
- data/test/paperclip_missing_attachment_styles_test.rb +4 -8
- data/test/paperclip_test.rb +0 -5
- data/test/rake_test.rb +103 -0
- data/test/storage/s3_test.rb +13 -1
- data/test/tasks/attachments_test.rb +77 -0
- data/test/validators/attachment_content_type_validator_test.rb +26 -0
- data/test/validators/attachment_size_validator_test.rb +10 -0
- data/test/validators_test.rb +8 -1
- metadata +56 -77
- data/lib/paperclip/attachment_options.rb +0 -9
- data/lib/paperclip/instance_methods.rb +0 -35
- data/test/attachment_options_test.rb +0 -27
@@ -0,0 +1,27 @@
|
|
1
|
+
module Paperclip
|
2
|
+
class DataUriAdapter < StringioAdapter
|
3
|
+
|
4
|
+
REGEXP = /^data:([-\w]+\/[-\w\+]+);base64,(.*)/
|
5
|
+
|
6
|
+
def initialize(target_uri)
|
7
|
+
@target_uri = target_uri
|
8
|
+
cache_current_values
|
9
|
+
@tempfile = copy_to_tempfile
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def cache_current_values
|
15
|
+
self.original_filename = 'base64.txt'
|
16
|
+
data_uri_parts ||= @target_uri.match(REGEXP) || []
|
17
|
+
@content_type = data_uri_parts[1] || 'text/plain'
|
18
|
+
@target = StringIO.new(Base64.decode64(data_uri_parts[2] || ''))
|
19
|
+
@size = @target.size
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Paperclip.io_adapters.register Paperclip::DataUriAdapter do |target|
|
26
|
+
String === target && target =~ Paperclip::DataUriAdapter::REGEXP
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Paperclip
|
2
|
+
class EmptyStringAdapter < AbstractAdapter
|
3
|
+
def initialize(target)
|
4
|
+
end
|
5
|
+
|
6
|
+
def nil?
|
7
|
+
false
|
8
|
+
end
|
9
|
+
|
10
|
+
def assignment?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Paperclip.io_adapters.register Paperclip::EmptyStringAdapter do |target|
|
17
|
+
target.is_a?(String) && target.empty?
|
18
|
+
end
|
@@ -3,7 +3,7 @@ module Paperclip
|
|
3
3
|
def initialize(target)
|
4
4
|
@target = target
|
5
5
|
cache_current_values
|
6
|
-
@tempfile = copy_to_tempfile
|
6
|
+
@tempfile = copy_to_tempfile
|
7
7
|
end
|
8
8
|
|
9
9
|
attr_writer :content_type
|
@@ -21,8 +21,8 @@ module Paperclip
|
|
21
21
|
@size = @target.size
|
22
22
|
end
|
23
23
|
|
24
|
-
def copy_to_tempfile
|
25
|
-
while data =
|
24
|
+
def copy_to_tempfile
|
25
|
+
while data = @target.read(16*1024)
|
26
26
|
destination.write(data)
|
27
27
|
end
|
28
28
|
destination.rewind
|
@@ -20,7 +20,7 @@ module Paperclip
|
|
20
20
|
def matches? subject
|
21
21
|
@subject = subject
|
22
22
|
@subject = @subject.class unless Class === @subject
|
23
|
-
responds? && has_column?
|
23
|
+
responds? && has_column?
|
24
24
|
end
|
25
25
|
|
26
26
|
def failure_message
|
@@ -47,10 +47,6 @@ module Paperclip
|
|
47
47
|
def has_column?
|
48
48
|
@subject.column_names.include?("#{@attachment_name}_file_name")
|
49
49
|
end
|
50
|
-
|
51
|
-
def included?
|
52
|
-
@subject.ancestors.include?(Paperclip::InstanceMethods)
|
53
|
-
end
|
54
50
|
end
|
55
51
|
end
|
56
52
|
end
|
@@ -1,16 +1,14 @@
|
|
1
|
-
|
1
|
+
require 'paperclip/tasks/attachments'
|
2
2
|
require 'set'
|
3
|
+
|
3
4
|
module Paperclip
|
4
5
|
class << self
|
5
|
-
attr_accessor :classes_with_attachments
|
6
6
|
attr_writer :registered_attachments_styles_path
|
7
7
|
def registered_attachments_styles_path
|
8
8
|
@registered_attachments_styles_path ||= Rails.root.join('public/system/paperclip_attachments.yml').to_s
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
self.classes_with_attachments = Set.new
|
13
|
-
|
14
12
|
# Get list of styles saved on previous deploy (running rake paperclip:refresh:missing_styles)
|
15
13
|
def self.get_registered_attachments_styles
|
16
14
|
YAML.load_file(Paperclip.registered_attachments_styles_path)
|
@@ -36,18 +34,15 @@ module Paperclip
|
|
36
34
|
# }
|
37
35
|
def self.current_attachments_styles
|
38
36
|
Hash.new.tap do |current_styles|
|
39
|
-
Paperclip.
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
current_styles[klass_sym][attachment_name.to_sym] << style_name.to_sym
|
49
|
-
current_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
|
50
|
-
end
|
37
|
+
Paperclip::Tasks::Attachments.each_definition do |klass, attachment_name, attachment_attributes|
|
38
|
+
# TODO: is it even possible to take into account Procs?
|
39
|
+
next if attachment_attributes[:styles].kind_of?(Proc)
|
40
|
+
attachment_attributes[:styles].try(:keys).try(:each) do |style_name|
|
41
|
+
klass_sym = klass.to_s.to_sym
|
42
|
+
current_styles[klass_sym] ||= Hash.new
|
43
|
+
current_styles[klass_sym][attachment_name.to_sym] ||= Array.new
|
44
|
+
current_styles[klass_sym][attachment_name.to_sym] << style_name.to_sym
|
45
|
+
current_styles[klass_sym][attachment_name.to_sym].map!(&:to_s).sort!.map!(&:to_sym).uniq!
|
51
46
|
end
|
52
47
|
end
|
53
48
|
end
|
@@ -36,9 +36,13 @@ module Paperclip
|
|
36
36
|
def flush_writes #:nodoc:
|
37
37
|
@queued_for_write.each do |style_name, file|
|
38
38
|
FileUtils.mkdir_p(File.dirname(path(style_name)))
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
begin
|
40
|
+
FileUtils.mv(file.path, path(style_name))
|
41
|
+
rescue SystemCallError
|
42
|
+
File.open(path(style_name), "wb") do |new_file|
|
43
|
+
while chunk = file.read(16 * 1024)
|
44
|
+
new_file.write(chunk)
|
45
|
+
end
|
42
46
|
end
|
43
47
|
end
|
44
48
|
unless @options[:override_file_permissions] == false
|
data/lib/paperclip/storage/s3.rb
CHANGED
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module Paperclip
|
4
|
+
module Tasks
|
5
|
+
class Attachments
|
6
|
+
include Singleton
|
7
|
+
|
8
|
+
def self.add(klass, attachment_name, attachment_options)
|
9
|
+
instance.add(klass, attachment_name, attachment_options)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.clear
|
13
|
+
instance.clear
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.names_for(klass)
|
17
|
+
instance.names_for(klass)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.each_definition(&block)
|
21
|
+
instance.each_definition(&block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.definitions_for(klass)
|
25
|
+
instance.definitions_for(klass)
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
clear
|
30
|
+
end
|
31
|
+
|
32
|
+
def add(klass, attachment_name, attachment_options)
|
33
|
+
@attachments ||= {}
|
34
|
+
@attachments[klass] ||= {}
|
35
|
+
@attachments[klass][attachment_name] = attachment_options
|
36
|
+
end
|
37
|
+
|
38
|
+
def clear
|
39
|
+
@attachments = Hash.new { |h,k| h[k] = {} }
|
40
|
+
end
|
41
|
+
|
42
|
+
def names_for(klass)
|
43
|
+
@attachments[klass].keys
|
44
|
+
end
|
45
|
+
|
46
|
+
def each_definition
|
47
|
+
@attachments.each do |klass, attachments|
|
48
|
+
attachments.each do |name, options|
|
49
|
+
yield klass, name, options
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def definitions_for(klass)
|
55
|
+
@attachments[klass]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/lib/paperclip/validators.rb
CHANGED
@@ -34,13 +34,32 @@ module Paperclip
|
|
34
34
|
validator_kind = $1.underscore.to_sym
|
35
35
|
|
36
36
|
if options.has_key?(validator_kind)
|
37
|
-
|
37
|
+
validator_options = options.delete(validator_kind)
|
38
|
+
validator_options = {} if validator_options == true
|
39
|
+
local_options = attributes + [validator_options]
|
40
|
+
send(:"validates_attachment_#{validator_kind}", *local_options)
|
38
41
|
end
|
39
42
|
end
|
40
43
|
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def validate_before_processing(validator_class, options)
|
47
|
+
options = options.dup
|
48
|
+
attributes = options.delete(:attributes)
|
49
|
+
attributes.each do |attribute|
|
50
|
+
options[:attributes] = [attribute]
|
51
|
+
create_validating_before_filter(attribute, validator_class, options)
|
52
|
+
end
|
53
|
+
end
|
41
54
|
|
42
|
-
|
55
|
+
def create_validating_before_filter(attribute, validator_class, options)
|
56
|
+
if_clause = options.delete(:if)
|
57
|
+
unless_clause = options.delete(:unless)
|
58
|
+
send(:"before_#{attribute}_post_process", :if => if_clause, :unless => unless_clause) do |*args|
|
59
|
+
validator_class.new(options.dup).validate(self)
|
60
|
+
end
|
43
61
|
end
|
62
|
+
|
44
63
|
end
|
45
64
|
end
|
46
65
|
end
|
@@ -7,6 +7,7 @@ module Paperclip
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def validate_each(record, attribute, value)
|
10
|
+
base_attribute = attribute.to_sym
|
10
11
|
attribute = "#{attribute}_content_type".to_sym
|
11
12
|
value = record.send :read_attribute_for_validation, attribute
|
12
13
|
|
@@ -14,6 +15,10 @@ module Paperclip
|
|
14
15
|
|
15
16
|
validate_whitelist(record, attribute, value)
|
16
17
|
validate_blacklist(record, attribute, value)
|
18
|
+
|
19
|
+
if record.errors.include? attribute
|
20
|
+
record.errors.add base_attribute, record.errors[attribute]
|
21
|
+
end
|
17
22
|
end
|
18
23
|
|
19
24
|
def validate_whitelist(record, attribute, value)
|
@@ -48,7 +53,7 @@ module Paperclip
|
|
48
53
|
end
|
49
54
|
|
50
55
|
module HelperMethods
|
51
|
-
# Places
|
56
|
+
# Places ActiveModel validations on the content type of the file
|
52
57
|
# assigned. The possible options are:
|
53
58
|
# * +content_type+: Allowed content types. Can be a single content type
|
54
59
|
# or an array. Each type can be a String or a Regexp. It should be
|
@@ -68,7 +73,9 @@ module Paperclip
|
|
68
73
|
# You'll still need to have a virtual attribute (created by +attr_accessor+)
|
69
74
|
# name +[attachment]_content_type+ to be able to use this validator.
|
70
75
|
def validates_attachment_content_type(*attr_names)
|
71
|
-
|
76
|
+
options = _merge_attributes(attr_names)
|
77
|
+
validates_with AttachmentContentTypeValidator, options.dup
|
78
|
+
validate_before_processing AttachmentContentTypeValidator, options.dup
|
72
79
|
end
|
73
80
|
end
|
74
81
|
end
|
@@ -2,24 +2,24 @@ require 'active_model/validations/presence'
|
|
2
2
|
|
3
3
|
module Paperclip
|
4
4
|
module Validators
|
5
|
-
class AttachmentPresenceValidator < ActiveModel::
|
6
|
-
def
|
7
|
-
|
8
|
-
|
9
|
-
record.errors.add(attribute, :blank, options)
|
10
|
-
end
|
5
|
+
class AttachmentPresenceValidator < ActiveModel::EachValidator
|
6
|
+
def validate_each(record, attribute, value)
|
7
|
+
if record.send("#{attribute}_file_name").blank?
|
8
|
+
record.errors.add(attribute, :blank, options)
|
11
9
|
end
|
12
10
|
end
|
13
11
|
end
|
14
12
|
|
15
13
|
module HelperMethods
|
16
|
-
# Places
|
14
|
+
# Places ActiveModel validations on the presence of a file.
|
17
15
|
# Options:
|
18
16
|
# * +if+: A lambda or name of an instance method. Validation will only
|
19
17
|
# be run if this lambda or method returns true.
|
20
18
|
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
21
19
|
def validates_attachment_presence(*attr_names)
|
22
|
-
|
20
|
+
options = _merge_attributes(attr_names)
|
21
|
+
validates_with AttachmentPresenceValidator, options.dup
|
22
|
+
validate_before_processing AttachmentPresenceValidator, options.dup
|
23
23
|
end
|
24
24
|
end
|
25
25
|
end
|
@@ -11,6 +11,7 @@ module Paperclip
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def validate_each(record, attr_name, value)
|
14
|
+
base_attr_name = attr_name
|
14
15
|
attr_name = "#{attr_name}_file_size".to_sym
|
15
16
|
value = record.send(:read_attribute_for_validation, attr_name)
|
16
17
|
|
@@ -21,11 +22,13 @@ module Paperclip
|
|
21
22
|
|
22
23
|
unless value.send(CHECKS[option], option_value)
|
23
24
|
error_message_key = options[:in] ? :in_between : option
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
[ attr_name, base_attr_name ].each do |error_attr_name|
|
26
|
+
record.errors.add(error_attr_name, error_message_key, filtered_options(value).merge(
|
27
|
+
:min => min_value_in_human_size(record),
|
28
|
+
:max => max_value_in_human_size(record),
|
29
|
+
:count => human_size(option_value)
|
30
|
+
))
|
31
|
+
end
|
29
32
|
end
|
30
33
|
end
|
31
34
|
end
|
@@ -85,7 +88,7 @@ module Paperclip
|
|
85
88
|
end
|
86
89
|
|
87
90
|
module HelperMethods
|
88
|
-
# Places
|
91
|
+
# Places ActiveModel validations on the size of the file assigned. The
|
89
92
|
# possible options are:
|
90
93
|
# * +in+: a Range of bytes (i.e. +1..1.megabyte+),
|
91
94
|
# * +less_than+: equivalent to :in => 0..options[:less_than]
|
@@ -95,7 +98,9 @@ module Paperclip
|
|
95
98
|
# be run if this lambda or method returns true.
|
96
99
|
# * +unless+: Same as +if+ but validates if lambda or method returns false.
|
97
100
|
def validates_attachment_size(*attr_names)
|
98
|
-
|
101
|
+
options = _merge_attributes(attr_names)
|
102
|
+
validates_with AttachmentSizeValidator, options.dup
|
103
|
+
validate_before_processing AttachmentSizeValidator, options.dup
|
99
104
|
end
|
100
105
|
end
|
101
106
|
end
|
data/lib/paperclip/version.rb
CHANGED
data/lib/tasks/paperclip.rake
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'paperclip/tasks/attachments'
|
2
|
+
|
1
3
|
module Paperclip
|
2
4
|
module Task
|
3
5
|
def self.obtain_class
|
@@ -9,13 +11,23 @@ module Paperclip
|
|
9
11
|
def self.obtain_attachments(klass)
|
10
12
|
klass = Paperclip.class_for(klass.to_s)
|
11
13
|
name = ENV['ATTACHMENT'] || ENV['attachment']
|
12
|
-
|
13
|
-
|
14
|
+
|
15
|
+
attachment_names = Paperclip::Tasks::Attachments.names_for(klass)
|
16
|
+
|
17
|
+
if attachment_names.empty?
|
18
|
+
raise "Class #{klass.name} has no attachments specified"
|
19
|
+
end
|
20
|
+
|
21
|
+
if !name.blank? && attachment_names.map(&:to_s).include?(name.to_s)
|
14
22
|
[ name ]
|
15
23
|
else
|
16
|
-
|
24
|
+
attachment_names
|
17
25
|
end
|
18
26
|
end
|
27
|
+
|
28
|
+
def self.log_error(error)
|
29
|
+
$stderr.puts error
|
30
|
+
end
|
19
31
|
end
|
20
32
|
end
|
21
33
|
|
@@ -31,10 +43,16 @@ namespace :paperclip do
|
|
31
43
|
styles = (ENV['STYLES'] || ENV['styles'] || '').split(',').map(&:to_sym)
|
32
44
|
names.each do |name|
|
33
45
|
Paperclip.each_instance_with_attachment(klass, name) do |instance|
|
34
|
-
instance.send(name)
|
46
|
+
attachment = instance.send(name)
|
47
|
+
begin
|
48
|
+
attachment.reprocess!(*styles)
|
49
|
+
rescue Exception => e
|
50
|
+
Paperclip::Task.log_error("exception while processing #{klass} ID #{instance.id}:")
|
51
|
+
Paperclip::Task.log_error(" " + e.message + "\n")
|
52
|
+
end
|
35
53
|
unless instance.errors.blank?
|
36
|
-
|
37
|
-
|
54
|
+
Paperclip::Task.log_error("errors while processing #{klass} ID #{instance.id}:")
|
55
|
+
Paperclip::Task.log_error(" " + instance.errors.full_messages.join("\n ") + "\n")
|
38
56
|
end
|
39
57
|
end
|
40
58
|
end
|