paperclip 2.3.8 → 2.3.9
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.
- data/{README.rdoc → README.md} +104 -54
- data/lib/paperclip.rb +2 -0
- data/lib/paperclip/attachment.rb +67 -38
- data/lib/paperclip/command_line.rb +7 -1
- data/lib/paperclip/interpolations.rb +17 -1
- data/lib/paperclip/storage.rb +1 -0
- data/lib/paperclip/storage/fog.rb +98 -0
- data/lib/paperclip/storage/s3.rb +5 -5
- data/lib/paperclip/version.rb +1 -1
- data/test/attachment_test.rb +117 -0
- data/test/command_line_test.rb +5 -0
- data/test/fixtures/uppercase.PNG +0 -0
- data/test/fog_test.rb +107 -0
- data/test/helper.rb +1 -1
- data/test/integration_test.rb +88 -0
- data/test/interpolations_test.rb +17 -1
- data/test/paperclip_test.rb +30 -0
- data/test/storage_test.rb +23 -0
- metadata +47 -80
- data/lib/paperclip.rbc +0 -5202
- data/lib/paperclip/attachment.rbc +0 -6393
- data/lib/paperclip/callback_compatability.rbc +0 -1649
- data/lib/paperclip/command_line.rbc +0 -1952
- data/lib/paperclip/geometry.rbc +0 -2141
- data/lib/paperclip/glue.rbc +0 -685
- data/lib/paperclip/interpolations.rbc +0 -2205
- data/lib/paperclip/iostream.rbc +0 -905
- data/lib/paperclip/matchers.rbc +0 -267
- data/lib/paperclip/matchers/have_attached_file_matcher.rbc +0 -1258
- data/lib/paperclip/matchers/validate_attachment_content_type_matcher.rbc +0 -1660
- data/lib/paperclip/matchers/validate_attachment_presence_matcher.rbc +0 -1304
- data/lib/paperclip/matchers/validate_attachment_size_matcher.rbc +0 -2160
- data/lib/paperclip/processor.rbc +0 -911
- data/lib/paperclip/railtie.rbc +0 -680
- data/lib/paperclip/storage.rbc +0 -69
- data/lib/paperclip/storage/filesystem.rbc +0 -1402
- data/lib/paperclip/storage/s3.rbc +0 -3489
- data/lib/paperclip/style.rbc +0 -1571
- data/lib/paperclip/thumbnail.rbc +0 -1648
- data/lib/paperclip/upfile.rbc +0 -1619
- data/lib/paperclip/version.rbc +0 -176
- data/shoulda_macros/paperclip.rbc +0 -2415
- data/test/attachment_test.rbc +0 -22952
- data/test/command_line_test.rbc +0 -4307
- data/test/geometry_test.rbc +0 -5181
- data/test/helper.rbc +0 -3971
- data/test/integration_test.rbc +0 -13137
- data/test/interpolations_test.rbc +0 -3432
- data/test/iostream_test.rbc +0 -1889
- data/test/matchers/have_attached_file_matcher_test.rbc +0 -622
- data/test/matchers/validate_attachment_content_type_matcher_test.rbc +0 -1174
- data/test/matchers/validate_attachment_presence_matcher_test.rbc +0 -622
- data/test/matchers/validate_attachment_size_matcher_test.rbc +0 -1658
- data/test/paperclip_test.rbc +0 -7407
- data/test/processor_test.rbc +0 -314
- data/test/storage_test.rbc +0 -10294
- data/test/style_test.rbc +0 -3752
- data/test/thumbnail_test.rbc +0 -6803
- data/test/upfile_test.rbc +0 -1635
@@ -52,7 +52,7 @@ module Paperclip
|
|
52
52
|
raise PaperclipCommandLineError,
|
53
53
|
"Interpolation of #{key} isn't allowed."
|
54
54
|
end
|
55
|
-
|
55
|
+
interpolation(vars, key) || match
|
56
56
|
end
|
57
57
|
end
|
58
58
|
|
@@ -60,6 +60,12 @@ module Paperclip
|
|
60
60
|
%w(expected_outcodes swallow_stderr)
|
61
61
|
end
|
62
62
|
|
63
|
+
def interpolation(vars, key)
|
64
|
+
if vars.key?(key.to_sym)
|
65
|
+
shell_quote(vars[key.to_sym])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
63
69
|
def shell_quote(string)
|
64
70
|
return "" if string.nil? or string.blank?
|
65
71
|
if self.class.unix?
|
@@ -48,8 +48,18 @@ module Paperclip
|
|
48
48
|
end
|
49
49
|
|
50
50
|
# Returns the timestamp as defined by the <attachment>_updated_at field
|
51
|
+
# in the server default time zone unless :use_global_time_zone is set
|
52
|
+
# to false. Note that a Rails.config.time_zone change will still
|
53
|
+
# invalidate any path or URL that uses :timestamp. For a
|
54
|
+
# time_zone-agnostic timestamp, use #updated_at.
|
51
55
|
def timestamp attachment, style_name
|
52
|
-
attachment.instance_read(:updated_at).to_s
|
56
|
+
attachment.instance_read(:updated_at).in_time_zone(attachment.time_zone).to_s
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns an integer timestamp that is time zone-neutral, so that paths
|
60
|
+
# remain valid even if a server's time zone changes.
|
61
|
+
def updated_at attachment, style_name
|
62
|
+
attachment.updated_at
|
53
63
|
end
|
54
64
|
|
55
65
|
# Returns the Rails.root constant.
|
@@ -94,6 +104,12 @@ module Paperclip
|
|
94
104
|
attachment.fingerprint
|
95
105
|
end
|
96
106
|
|
107
|
+
# Returns a the attachment hash. See Paperclip::Attachment#hash for
|
108
|
+
# more details.
|
109
|
+
def hash attachment, style_name
|
110
|
+
attachment.hash(style_name)
|
111
|
+
end
|
112
|
+
|
97
113
|
# Returns the id of the instance in a split path form. e.g. returns
|
98
114
|
# 000/001/234 for an id of 1234.
|
99
115
|
def id_partition attachment, style_name
|
data/lib/paperclip/storage.rb
CHANGED
@@ -0,0 +1,98 @@
|
|
1
|
+
module Paperclip
|
2
|
+
module Storage
|
3
|
+
|
4
|
+
module Fog
|
5
|
+
def self.extended base
|
6
|
+
begin
|
7
|
+
require 'fog'
|
8
|
+
rescue LoadError => e
|
9
|
+
e.message << " (You may need to install the fog gem)"
|
10
|
+
raise e
|
11
|
+
end unless defined?(Fog)
|
12
|
+
|
13
|
+
base.instance_eval do
|
14
|
+
@fog_directory = @options[:fog_directory]
|
15
|
+
@fog_credentials = @options[:fog_credentials]
|
16
|
+
@fog_host = @options[:fog_host]
|
17
|
+
@fog_public = @options[:fog_public]
|
18
|
+
|
19
|
+
@url = ':fog_public_url'
|
20
|
+
Paperclip.interpolates(:fog_public_url) do |attachment, style|
|
21
|
+
attachment.public_url(style)
|
22
|
+
end unless Paperclip::Interpolations.respond_to? :fog_public_url
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def exists?(style = default_style)
|
27
|
+
if original_filename
|
28
|
+
!!directory.files.head(path(style))
|
29
|
+
else
|
30
|
+
false
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def flush_writes
|
35
|
+
for style, file in @queued_for_write do
|
36
|
+
log("saving #{path(style)}")
|
37
|
+
directory.files.create(
|
38
|
+
:body => file,
|
39
|
+
:key => path(style),
|
40
|
+
:public => @fog_public
|
41
|
+
)
|
42
|
+
end
|
43
|
+
@queued_for_write = {}
|
44
|
+
end
|
45
|
+
|
46
|
+
def flush_deletes
|
47
|
+
for path in @queued_for_delete do
|
48
|
+
log("deleting #{path}")
|
49
|
+
directory.files.new(:key => path).destroy
|
50
|
+
end
|
51
|
+
@queued_for_delete = []
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns representation of the data of the file assigned to the given
|
55
|
+
# style, in the format most representative of the current storage.
|
56
|
+
def to_file(style = default_style)
|
57
|
+
if @queued_for_write[style]
|
58
|
+
@queued_for_write[style]
|
59
|
+
else
|
60
|
+
body = directory.files.get(path(style)).body
|
61
|
+
filename = path(style)
|
62
|
+
extname = File.extname(filename)
|
63
|
+
basename = File.basename(filename, extname)
|
64
|
+
file = Tempfile.new([basename, extname])
|
65
|
+
file.binmode
|
66
|
+
file.write(body)
|
67
|
+
file.rewind
|
68
|
+
file
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def public_url(style = default_style)
|
73
|
+
if @fog_host
|
74
|
+
"#{@fog_host}/#{path(style)}"
|
75
|
+
else
|
76
|
+
directory.files.new(:key => path(style)).public_url
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def connection
|
83
|
+
@connection ||= ::Fog::Storage.new(@fog_credentials)
|
84
|
+
end
|
85
|
+
|
86
|
+
def directory
|
87
|
+
@directory ||= begin
|
88
|
+
connection.directories.get(@fog_directory) || connection.directories.create(
|
89
|
+
:key => @fog_directory,
|
90
|
+
:public => @fog_public
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
data/lib/paperclip/storage/s3.rb
CHANGED
@@ -25,7 +25,7 @@ module Paperclip
|
|
25
25
|
# development versus production.
|
26
26
|
# * +s3_permissions+: This is a String that should be one of the "canned" access
|
27
27
|
# policies that S3 provides (more information can be found here:
|
28
|
-
# http://docs.amazonwebservices.com/AmazonS3/
|
28
|
+
# http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAccessPolicy.html)
|
29
29
|
# The default for Paperclip is :public_read.
|
30
30
|
# * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
|
31
31
|
# 'http' or 'https'. Defaults to 'http' when your :s3_permissions are :public_read (the
|
@@ -63,7 +63,7 @@ module Paperclip
|
|
63
63
|
rescue LoadError => e
|
64
64
|
e.message << " (You may need to install the aws-s3 gem)"
|
65
65
|
raise e
|
66
|
-
end
|
66
|
+
end unless defined?(AWS::S3)
|
67
67
|
|
68
68
|
base.instance_eval do
|
69
69
|
@s3_credentials = parse_credentials(@options[:s3_credentials])
|
@@ -85,13 +85,13 @@ module Paperclip
|
|
85
85
|
end
|
86
86
|
Paperclip.interpolates(:s3_alias_url) do |attachment, style|
|
87
87
|
"#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
88
|
-
end
|
88
|
+
end unless Paperclip::Interpolations.respond_to? :s3_alias_url
|
89
89
|
Paperclip.interpolates(:s3_path_url) do |attachment, style|
|
90
90
|
"#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
|
91
|
-
end
|
91
|
+
end unless Paperclip::Interpolations.respond_to? :s3_path_url
|
92
92
|
Paperclip.interpolates(:s3_domain_url) do |attachment, style|
|
93
93
|
"#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
|
94
|
-
end
|
94
|
+
end unless Paperclip::Interpolations.respond_to? :s3_domain_url
|
95
95
|
end
|
96
96
|
|
97
97
|
def expiring_url(time = 3600)
|
data/lib/paperclip/version.rb
CHANGED
data/test/attachment_test.rb
CHANGED
@@ -97,6 +97,83 @@ class AttachmentTest < Test::Unit::TestCase
|
|
97
97
|
end
|
98
98
|
end
|
99
99
|
|
100
|
+
context "An attachment with :timestamp interpolations" do
|
101
|
+
setup do
|
102
|
+
@file = StringIO.new("...")
|
103
|
+
@zone = 'UTC'
|
104
|
+
Time.stubs(:zone).returns(@zone)
|
105
|
+
@zone_default = 'Eastern Time (US & Canada)'
|
106
|
+
Time.stubs(:zone_default).returns(@zone_default)
|
107
|
+
end
|
108
|
+
|
109
|
+
context "using default time zone" do
|
110
|
+
setup do
|
111
|
+
rebuild_model :path => ":timestamp", :use_default_time_zone => true
|
112
|
+
@dummy = Dummy.new
|
113
|
+
@dummy.avatar = @file
|
114
|
+
end
|
115
|
+
|
116
|
+
should "return a time in the default zone" do
|
117
|
+
assert_equal @dummy.avatar_updated_at.in_time_zone(@zone_default).to_s, @dummy.avatar.path
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context "using per-thread time zone" do
|
122
|
+
setup do
|
123
|
+
rebuild_model :path => ":timestamp", :use_default_time_zone => false
|
124
|
+
@dummy = Dummy.new
|
125
|
+
@dummy.avatar = @file
|
126
|
+
end
|
127
|
+
|
128
|
+
should "return a time in the per-thread zone" do
|
129
|
+
assert_equal @dummy.avatar_updated_at.in_time_zone(@zone).to_s, @dummy.avatar.path
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context "An attachment with :hash interpolations" do
|
135
|
+
setup do
|
136
|
+
@file = StringIO.new("...")
|
137
|
+
end
|
138
|
+
|
139
|
+
should "raise if no secret is provided" do
|
140
|
+
@attachment = attachment :path => ":hash"
|
141
|
+
@attachment.assign @file
|
142
|
+
|
143
|
+
assert_raise ArgumentError do
|
144
|
+
@attachment.path
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
context "when secret is set" do
|
149
|
+
setup do
|
150
|
+
@attachment = attachment :path => ":hash", :hash_secret => "w00t"
|
151
|
+
@attachment.stubs(:instance_read).with(:updated_at).returns(Time.at(1234567890))
|
152
|
+
@attachment.stubs(:instance_read).with(:file_name).returns("bla.txt")
|
153
|
+
@attachment.instance.id = 1234
|
154
|
+
@attachment.assign @file
|
155
|
+
end
|
156
|
+
|
157
|
+
should "interpolate the hash data" do
|
158
|
+
@attachment.expects(:interpolate).with(@attachment.options[:hash_data],anything).returns("interpolated_stuff")
|
159
|
+
@attachment.hash
|
160
|
+
end
|
161
|
+
|
162
|
+
should "result in the correct interpolation" do
|
163
|
+
assert_equal "fake_models/avatars/1234/original/1234567890", @attachment.send(:interpolate,@attachment.options[:hash_data])
|
164
|
+
end
|
165
|
+
|
166
|
+
should "result in a correct hash" do
|
167
|
+
assert_equal "d22b617d1bf10016aa7d046d16427ae203f39fce", @attachment.path
|
168
|
+
end
|
169
|
+
|
170
|
+
should "generate a hash digest with the correct style" do
|
171
|
+
OpenSSL::HMAC.expects(:hexdigest).with(anything, anything, "fake_models/avatars/1234/medium/1234567890")
|
172
|
+
@attachment.path("medium")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
100
177
|
context "An attachment with a :rails_env interpolation" do
|
101
178
|
setup do
|
102
179
|
@rails_env = "blah"
|
@@ -487,6 +564,46 @@ class AttachmentTest < Test::Unit::TestCase
|
|
487
564
|
end
|
488
565
|
end
|
489
566
|
|
567
|
+
context "Attachment with uppercase extension and a default style" do
|
568
|
+
setup do
|
569
|
+
@old_defaults = Paperclip::Attachment.default_options.dup
|
570
|
+
Paperclip::Attachment.default_options.merge!({
|
571
|
+
:path => ":rails_root/tmp/:attachment/:class/:style/:id/:basename.:extension"
|
572
|
+
})
|
573
|
+
FileUtils.rm_rf("tmp")
|
574
|
+
rebuild_model
|
575
|
+
@instance = Dummy.new
|
576
|
+
@instance.stubs(:id).returns 123
|
577
|
+
|
578
|
+
@file = File.new(File.join(File.dirname(__FILE__), "fixtures", "uppercase.PNG"), 'rb')
|
579
|
+
|
580
|
+
styles = {:styles => { :large => ["400x400", :jpg],
|
581
|
+
:medium => ["100x100", :jpg],
|
582
|
+
:small => ["32x32#", :jpg]},
|
583
|
+
:default_style => :small}
|
584
|
+
@attachment = Paperclip::Attachment.new(:avatar,
|
585
|
+
@instance,
|
586
|
+
styles)
|
587
|
+
now = Time.now
|
588
|
+
Time.stubs(:now).returns(now)
|
589
|
+
@attachment.assign(@file)
|
590
|
+
@attachment.save
|
591
|
+
end
|
592
|
+
|
593
|
+
teardown do
|
594
|
+
@file.close
|
595
|
+
Paperclip::Attachment.default_options.merge!(@old_defaults)
|
596
|
+
end
|
597
|
+
|
598
|
+
should "should have matching to_s and url methods" do
|
599
|
+
file = @attachment.to_file
|
600
|
+
assert file
|
601
|
+
assert_match @attachment.to_s, @attachment.url
|
602
|
+
assert_match @attachment.to_s(:small), @attachment.url(:small)
|
603
|
+
file.close
|
604
|
+
end
|
605
|
+
end
|
606
|
+
|
490
607
|
context "An attachment" do
|
491
608
|
setup do
|
492
609
|
@old_defaults = Paperclip::Attachment.default_options.dup
|
data/test/command_line_test.rb
CHANGED
@@ -6,6 +6,11 @@ class CommandLineTest < Test::Unit::TestCase
|
|
6
6
|
File.stubs(:exist?).with("/dev/null").returns(true)
|
7
7
|
end
|
8
8
|
|
9
|
+
should "allow colons in parameters" do
|
10
|
+
cmd = Paperclip::CommandLine.new("convert", "'a.jpg' -resize 175x220> -size 175x220 xc:black +swap -gravity center -composite 'b.jpg'", :swallow_stderr => false)
|
11
|
+
assert_equal "convert 'a.jpg' -resize 175x220> -size 175x220 xc:black +swap -gravity center -composite 'b.jpg'", cmd.command
|
12
|
+
end
|
13
|
+
|
9
14
|
should "take a command and parameters and produce a shell command for bash" do
|
10
15
|
cmd = Paperclip::CommandLine.new("convert", "a.jpg b.png", :swallow_stderr => false)
|
11
16
|
assert_equal "convert a.jpg b.png", cmd.command
|
Binary file
|
data/test/fog_test.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
require './test/helper'
|
2
|
+
require 'fog'
|
3
|
+
|
4
|
+
Fog.mock!
|
5
|
+
|
6
|
+
class FogTest < Test::Unit::TestCase
|
7
|
+
context "" do
|
8
|
+
|
9
|
+
setup do
|
10
|
+
@fog_directory = 'papercliptests'
|
11
|
+
|
12
|
+
@credentials = {
|
13
|
+
:provider => 'AWS',
|
14
|
+
:aws_access_key_id => 'ID',
|
15
|
+
:aws_secret_access_key => 'SECRET'
|
16
|
+
}
|
17
|
+
|
18
|
+
@connection = Fog::Storage.new(@credentials)
|
19
|
+
|
20
|
+
rebuild_model(
|
21
|
+
:fog_directory => @fog_directory,
|
22
|
+
:fog_credentials => @credentials,
|
23
|
+
:fog_host => nil,
|
24
|
+
:fog_public => true,
|
25
|
+
:path => ":attachment/:basename.:extension",
|
26
|
+
:storage => :fog
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
should "be extended by the Fog module" do
|
31
|
+
assert Dummy.new.avatar.is_a?(Paperclip::Storage::Fog)
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when assigned" do
|
35
|
+
setup do
|
36
|
+
@file = File.new(File.join(File.dirname(__FILE__), 'fixtures', '5k.png'), 'rb')
|
37
|
+
@dummy = Dummy.new
|
38
|
+
@dummy.avatar = @file
|
39
|
+
end
|
40
|
+
|
41
|
+
teardown do
|
42
|
+
@file.close
|
43
|
+
directory = @connection.directories.new(:key => @fog_directory)
|
44
|
+
directory.files.each {|file| file.destroy}
|
45
|
+
directory.destroy
|
46
|
+
end
|
47
|
+
|
48
|
+
context "without a bucket" do
|
49
|
+
should "succeed" do
|
50
|
+
assert @dummy.save
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "with a bucket" do
|
55
|
+
setup do
|
56
|
+
@connection.directories.create(:key => @fog_directory)
|
57
|
+
end
|
58
|
+
|
59
|
+
should "succeed" do
|
60
|
+
assert @dummy.save
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "without a fog_host" do
|
65
|
+
setup do
|
66
|
+
rebuild_model(
|
67
|
+
:fog_directory => @fog_directory,
|
68
|
+
:fog_credentials => @credentials,
|
69
|
+
:fog_host => nil,
|
70
|
+
:fog_public => true,
|
71
|
+
:path => ":attachment/:basename.:extension",
|
72
|
+
:storage => :fog
|
73
|
+
)
|
74
|
+
@dummy = Dummy.new
|
75
|
+
@dummy.avatar = StringIO.new('.')
|
76
|
+
@dummy.save
|
77
|
+
end
|
78
|
+
|
79
|
+
should "provide a public url" do
|
80
|
+
assert !@dummy.avatar.url.nil?
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "with a fog_host" do
|
85
|
+
setup do
|
86
|
+
rebuild_model(
|
87
|
+
:fog_directory => @fog_directory,
|
88
|
+
:fog_credentials => @credentials,
|
89
|
+
:fog_host => 'http://example.com',
|
90
|
+
:fog_public => true,
|
91
|
+
:path => ":attachment/:basename.:extension",
|
92
|
+
:storage => :fog
|
93
|
+
)
|
94
|
+
@dummy = Dummy.new
|
95
|
+
@dummy.avatar = StringIO.new('.')
|
96
|
+
@dummy.save
|
97
|
+
end
|
98
|
+
|
99
|
+
should "provide a public url" do
|
100
|
+
assert @dummy.avatar.url =~ /^http:\/\/example\.com\/avatars\/stringio\.txt\?\d*$/
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|