paperclip-aws 1.4.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,5 +1,5 @@
1
1
  source "http://rubygems.org"
2
- gem 'paperclip', '>=2.4.0'
2
+ gem 'paperclip', '~>2.6.0'
3
3
  gem 'aws-sdk', '>=1.2.0'
4
4
 
5
5
  group :development do
@@ -8,7 +8,7 @@ group :development do
8
8
  end
9
9
 
10
10
  group :test do
11
- gem "sqlite3"
11
+ gem "sqlite3", '~> 1.3.4'
12
12
  gem 'shoulda-context'
13
13
  gem "mocha"
14
14
  end
data/Gemfile.lock CHANGED
@@ -1,25 +1,25 @@
1
1
  GEM
2
2
  remote: http://rubygems.org/
3
3
  specs:
4
- activemodel (3.1.3)
5
- activesupport (= 3.1.3)
4
+ activemodel (3.2.1)
5
+ activesupport (= 3.2.1)
6
6
  builder (~> 3.0.0)
7
- i18n (~> 0.6)
8
- activerecord (3.1.3)
9
- activemodel (= 3.1.3)
10
- activesupport (= 3.1.3)
11
- arel (~> 2.2.1)
7
+ activerecord (3.2.1)
8
+ activemodel (= 3.2.1)
9
+ activesupport (= 3.2.1)
10
+ arel (~> 3.0.0)
12
11
  tzinfo (~> 0.3.29)
13
- activesupport (3.1.3)
12
+ activesupport (3.2.1)
13
+ i18n (~> 0.6)
14
14
  multi_json (~> 1.0)
15
- arel (2.2.1)
16
- aws-sdk (1.2.3)
15
+ arel (3.0.0)
16
+ aws-sdk (1.3.4)
17
17
  httparty (~> 0.7)
18
18
  json (~> 1.4)
19
19
  nokogiri (>= 1.4.4)
20
20
  uuidtools (~> 2.1)
21
21
  builder (3.0.0)
22
- cocaine (0.2.0)
22
+ cocaine (0.2.1)
23
23
  git (1.2.5)
24
24
  httparty (0.8.1)
25
25
  multi_json
@@ -29,22 +29,22 @@ GEM
29
29
  bundler (~> 1.0)
30
30
  git (>= 1.2.5)
31
31
  rake
32
- json (1.6.2)
32
+ json (1.6.5)
33
33
  metaclass (0.0.1)
34
34
  mime-types (1.17.2)
35
- mocha (0.10.0)
35
+ mocha (0.10.4)
36
36
  metaclass (~> 0.0.1)
37
37
  multi_json (1.0.4)
38
38
  multi_xml (0.4.1)
39
39
  nokogiri (1.5.0)
40
- paperclip (2.4.5)
40
+ paperclip (2.6.0)
41
41
  activerecord (>= 2.3.0)
42
42
  activesupport (>= 2.3.2)
43
43
  cocaine (>= 0.0.2)
44
44
  mime-types
45
45
  rake (0.9.2.2)
46
46
  shoulda-context (1.0.0)
47
- sqlite3 (1.3.4)
47
+ sqlite3 (1.3.5)
48
48
  tzinfo (0.3.31)
49
49
  uuidtools (2.1.2)
50
50
 
@@ -56,6 +56,6 @@ DEPENDENCIES
56
56
  bundler (~> 1.0.0)
57
57
  jeweler (~> 1.6.4)
58
58
  mocha
59
- paperclip (>= 2.4.0)
59
+ paperclip (~> 2.6.0)
60
60
  shoulda-context
61
- sqlite3
61
+ sqlite3 (~> 1.3.4)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.4.1
1
+ 1.6.0
data/lib/paperclip-aws.rb CHANGED
@@ -13,47 +13,26 @@ module Paperclip
13
13
  rescue LoadError => e
14
14
  e.message << " (You may need to install the aws-sdk gem)"
15
15
  raise e
16
- end unless defined?(AWS)
16
+ end unless defined?(AWS::Core)
17
17
 
18
- attr_accessor :s3_options
19
- base.instance_eval do
20
-
21
-
22
- @s3_credentials = parse_credentials(@options.s3_credentials)
23
- @s3_permissions = set_permissions(@options.s3_permissions)
24
-
25
- @s3_protocol = @options.s3_protocol ||
26
- Proc.new do |style, attachment|
27
- permission = (@s3_permissions[style.to_sym] || @s3_permissions[:default])
28
- permission = permission.call(attachment, style) if permission.is_a?(Proc)
29
- (permission == :public_read) ? 'http' : 'https'
30
- end
31
-
32
- @s3_headers = @options.s3_headers || {}
33
-
34
- @s3_bucket = @options.bucket
35
- @s3_bucket = @s3_bucket.call(self) if @s3_bucket.is_a?(Proc)
36
-
37
- @s3_options = @options.s3_options || {}
38
- # setup Amazon Server Side encryption
39
- @s3_options.reverse_merge!({
40
- :sse => false,
41
- :storage_class => :standard,
42
- :content_disposition => nil
43
- })
44
-
45
- @s3_endpoint = @s3_credentials[:endpoint] || 's3.amazonaws.com'
46
-
47
- @s3_host_alias = @options.s3_host_alias
48
- @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.is_a?(Proc)
49
-
50
- @s3 = AWS::S3.new(
51
- :access_key_id => @s3_credentials[:access_key_id],
52
- :secret_access_key => @s3_credentials[:secret_access_key],
53
- :s3_endpoint => @s3_endpoint
54
- )
55
- end
18
+ attr_accessor :s3_credentials, :s3_bucket, :s3_permissions, :s3_options, :s3_protocol, :s3_host_alias
56
19
 
20
+ base.instance_eval do
21
+ self.setup_credentials
22
+ self.setup_bucket
23
+ self.setup_permissions
24
+ self.setup_s3_protocol
25
+ self.setup_s3_options
26
+ self.setup_s3_host_alias
27
+ end
28
+ end
29
+
30
+ def s3
31
+ @s3 ||= AWS::S3.new(
32
+ :access_key_id => @s3_credentials[:access_key_id],
33
+ :secret_access_key => @s3_credentials[:secret_access_key],
34
+ :s3_endpoint => @s3_endpoint
35
+ )
57
36
  end
58
37
 
59
38
  def url(style=default_style, options={})
@@ -69,42 +48,23 @@ module Paperclip
69
48
  :action => :read
70
49
  })
71
50
  secure = ( self.choose_protocol(style, options) == 'https' )
72
- return @s3.buckets[@s3_bucket].objects[path(style).gsub(%r{^/}, "")].url_for(options[:action], { :secure => secure, :expires => options[:expires] }).to_s
51
+ return self.s3.buckets[@s3_bucket].objects[path(style).gsub(%r{^/}, "")].url_for(options[:action], { :secure => secure, :expires => options[:expires] }).to_s
73
52
  else
74
53
  if @s3_host_alias.present?
75
54
  url = "#{choose_protocol(style, options)}://#{@s3_host_alias}/#{path(style).gsub(%r{^/}, "")}"
76
55
  else
77
56
  url = "#{choose_protocol(style, options)}://#{@s3_endpoint}/#{@s3_bucket}/#{path(style).gsub(%r{^/}, "")}"
78
- end
57
+ end
79
58
  return URI.escape(url)
80
59
  end
81
60
  end
82
-
83
- def bucket_name
84
- @s3_bucket
85
- end
86
-
87
- def parse_credentials(creds)
88
- creds = find_credentials(creds).stringify_keys
89
- env = Object.const_defined?(:Rails) ? Rails.env : nil
90
- (creds[env] || creds).symbolize_keys
91
- end
92
-
93
- def set_permissions permissions
94
- if permissions.is_a?(Hash)
95
- permissions[:default] = permissions[:default] || :public_read
96
- else
97
- permissions = { :default => permissions || :public_read }
98
- end
99
- permissions
100
- end
101
-
61
+
102
62
  def exists?(style = default_style)
103
63
  if path(style).nil? || path(style).to_s.strip == ""
104
64
  return false
105
65
  end
106
66
  begin
107
- return @s3.buckets[@s3_bucket].objects[path(style)].exists?
67
+ return self.s3.buckets[@s3_bucket].objects[path(style)].exists?
108
68
  rescue AWS::S3::Errors::Base
109
69
  return false
110
70
  end
@@ -127,13 +87,13 @@ module Paperclip
127
87
  basename = File.basename(filename, extname)
128
88
  file = Tempfile.new([basename, extname])
129
89
  file.binmode
130
- file.write(@s3.buckets[@s3_bucket].objects[path(style)].read)
90
+ file.write(self.s3.buckets[@s3_bucket].objects[path(style)].read)
131
91
  file.rewind
132
92
  return file
133
93
  end
134
94
 
135
95
  def create_bucket
136
- @s3.buckets.create(@s3_bucket)
96
+ self.s3.buckets.create(@s3_bucket)
137
97
  end
138
98
 
139
99
  def flush_writes #:nodoc:
@@ -141,14 +101,11 @@ module Paperclip
141
101
  begin
142
102
  log("saving #{path(style)}")
143
103
 
144
- @s3.buckets[@s3_bucket].objects[path(style)].write(
104
+ self.s3.buckets[@s3_bucket].objects[path(style)].write({
145
105
  :file => file.path,
146
106
  :acl => @s3_permissions[:style.to_sym] || @s3_permissions[:default],
147
- :storage_class => @s3_options[:storage_class],
148
- :content_type => file.content_type,
149
- :content_disposition => @s3_options[:content_disposition],
150
- :server_side_encryption => @s3_options[:sse]
151
- )
107
+ :content_type => file.content_type
108
+ }.reverse_merge(@s3_options))
152
109
  rescue AWS::S3::Errors::NoSuchBucket => e
153
110
  create_bucket
154
111
  retry
@@ -163,15 +120,29 @@ module Paperclip
163
120
  @queued_for_delete.each do |path|
164
121
  begin
165
122
  log("deleting #{path}")
166
- @s3.buckets[@s3_bucket].objects[path].delete
123
+ self.s3.buckets[@s3_bucket].objects[path].delete
167
124
  rescue AWS::Errors::Base => e
168
125
  raise
169
126
  end
170
127
  end
171
128
  @queued_for_delete = []
172
129
  end
173
-
174
- def find_credentials creds
130
+
131
+ # PRIVATE METHODS
132
+ def setup_credentials
133
+ if @options[:s3_credentials].present?
134
+ @s3_credentials = self.parse_credentials(@options[:s3_credentials]).stringify_keys
135
+ env = Object.const_defined?(:Rails) ? Rails.env : nil
136
+
137
+ @s3_credentials = (@s3_credentials[env] || @s3_credentials).symbolize_keys
138
+ @s3_endpoint = @s3_credentials[:endpoint] || 's3.amazonaws.com'
139
+ else
140
+ raise ArgumentError, "missing required :s3_credentials option"
141
+ end
142
+ end
143
+ protected :setup_credentials
144
+
145
+ def parse_credentials(creds)
175
146
  case creds
176
147
  when File
177
148
  YAML::load(ERB.new(File.read(creds.path)).result)
@@ -182,9 +153,63 @@ module Paperclip
182
153
  else
183
154
  raise ArgumentError, "Credentials are not a path, file, or hash."
184
155
  end
156
+ end
157
+ protected :parse_credentials
158
+
159
+ def setup_bucket
160
+ if @options[:bucket].present?
161
+ @s3_bucket = @options[:bucket] || @s3_credentials[:bucket]
162
+ @s3_bucket = @s3_bucket.call(self) if @s3_bucket.is_a?(Proc)
163
+ else
164
+ raise ArgumentError, "missing required :bucket option"
165
+ end
185
166
  end
186
- private :find_credentials
187
-
167
+ protected :setup_bucket
168
+
169
+ def setup_permissions
170
+ @s3_permissions = self.parse_permissions(@options[:s3_permissions])
171
+ end
172
+ protected :setup_permissions
173
+
174
+ def parse_permissions(permissions)
175
+ if permissions.is_a?(Hash)
176
+ permissions[:default] = permissions[:default] || :public_read
177
+ else
178
+ permissions = { :default => permissions || :public_read }
179
+ end
180
+ permissions
181
+ end
182
+ protected :parse_permissions
183
+
184
+ def setup_s3_protocol
185
+ @s3_protocol = @options[:s3_protocol] ||
186
+ Proc.new do |style, attachment|
187
+ permission = (@s3_permissions[style.to_sym] || @s3_permissions[:default])
188
+ permission = permission.call(attachment, style) if permission.is_a?(Proc)
189
+ (permission == :public_read) ? 'http' : 'https'
190
+ end
191
+ end
192
+ protected :setup_s3_protocol
193
+
194
+ def setup_s3_options
195
+ @s3_options = (@options[:s3_options] || {}).symbolize_keys
196
+
197
+ # setup Amazon Server Side encryption
198
+ @s3_options.reverse_merge!({
199
+ :sse => false,
200
+ :storage_class => :standard,
201
+ :content_disposition => nil
202
+ })
203
+ @s3_options[:server_side_encryption] ||= @s3_options.delete(:sse)
204
+ end
205
+ protected :setup_s3_options
206
+
207
+
208
+ def setup_s3_host_alias
209
+ @s3_host_alias = @options[:s3_host_alias]
210
+ @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.is_a?(Proc)
211
+ end
212
+ protected :setup_s3_host_alias
188
213
  end
189
214
  end
190
215
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "paperclip-aws"
8
- s.version = "1.4.1"
8
+ s.version = "1.6.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Igor Alexandrov"]
12
- s.date = "2011-12-06"
12
+ s.date = "2012-02-12"
13
13
  s.description = "'paperclip-aws' is a full featured storage module that supports all S3 locations (US, European and Tokio) without any additional hacking."
14
14
  s.email = "igor.alexandrov@gmail.com"
15
15
  s.extra_rdoc_files = [
@@ -28,7 +28,6 @@ Gem::Specification.new do |s|
28
28
  "paperclip-aws.gemspec",
29
29
  "test/aws_storage_test.rb",
30
30
  "test/database.yml",
31
- "test/debug.log",
32
31
  "test/fixtures/5k.png",
33
32
  "test/fixtures/spaced file.png",
34
33
  "test/helper.rb"
@@ -43,18 +42,18 @@ Gem::Specification.new do |s|
43
42
  s.specification_version = 3
44
43
 
45
44
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
46
- s.add_runtime_dependency(%q<paperclip>, [">= 2.4.0"])
45
+ s.add_runtime_dependency(%q<paperclip>, ["~> 2.6.0"])
47
46
  s.add_runtime_dependency(%q<aws-sdk>, [">= 1.2.0"])
48
47
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
49
48
  s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
50
49
  else
51
- s.add_dependency(%q<paperclip>, [">= 2.4.0"])
50
+ s.add_dependency(%q<paperclip>, ["~> 2.6.0"])
52
51
  s.add_dependency(%q<aws-sdk>, [">= 1.2.0"])
53
52
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
54
53
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
55
54
  end
56
55
  else
57
- s.add_dependency(%q<paperclip>, [">= 2.4.0"])
56
+ s.add_dependency(%q<paperclip>, ["~> 2.6.0"])
58
57
  s.add_dependency(%q<aws-sdk>, [">= 1.2.0"])
59
58
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
60
59
  s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
@@ -8,7 +8,7 @@ class AwsStorageTest < Test::Unit::TestCase
8
8
  end
9
9
 
10
10
  def default_model_options(options={})
11
- {
11
+ options.reverse_merge!({
12
12
  :storage => :aws,
13
13
  :bucket => "testing",
14
14
  :s3_credentials => {
@@ -16,44 +16,50 @@ class AwsStorageTest < Test::Unit::TestCase
16
16
  :secret_access_key => "SECRET_ACCESS_KEY"
17
17
  },
18
18
  :path => ":attachment/:basename.:extension"
19
- }.deep_merge!(options)
19
+ })
20
20
  end
21
21
 
22
- context "Parsing S3 credentials" do
23
- setup do
24
- rebuild_model default_model_options
25
-
26
- @dummy = Dummy.new
27
- @avatar = @dummy.avatar
28
- end
29
-
22
+ context "Parsing S3 credentials" do
30
23
  should "get the correct credentials when RAILS_ENV is production" do
31
24
  rails_env("production")
32
25
 
33
- assert_equal(
34
- { :access_key_id => "12345" },
35
- @avatar.parse_credentials(
26
+ rebuild_model default_model_options(
27
+ :s3_credentials => {
36
28
  :production => { :access_key_id => '12345' },
37
- :development => { :access_key_id => '54321' }
38
- )
29
+ :development => { :access_key_id => '54321' }
30
+ }
39
31
  )
32
+
33
+ @dummy = Dummy.new
34
+ assert_equal({ :access_key_id => "12345" }, @dummy.avatar.s3_credentials)
40
35
  end
41
36
 
42
37
  should "get the correct credentials when RAILS_ENV is development" do
43
38
  rails_env("development")
44
39
 
45
- assert_equal(
46
- { :access_key_id => "54321" },
47
- @avatar.parse_credentials(
40
+ rebuild_model default_model_options(
41
+ :s3_credentials => {
48
42
  :production => { :access_key_id => '12345' },
49
- :development => { :access_key_id => '54321' }
50
- )
51
- )
43
+ :development => { :access_key_id => '54321' }
44
+ }
45
+ )
46
+
47
+ @dummy = Dummy.new
48
+ assert_equal({ :access_key_id => "54321" }, @dummy.avatar.s3_credentials)
52
49
  end
53
50
 
54
51
  should "return the argument if the key does not exist" do
55
52
  rails_env("not really an env")
56
- assert_equal({:test => "12345"}, @avatar.parse_credentials(:test => "12345"))
53
+
54
+ rebuild_model default_model_options(
55
+ :s3_credentials => {
56
+ :test => "12345"
57
+ }
58
+ )
59
+
60
+ @dummy = Dummy.new
61
+
62
+ assert_equal({:test => "12345"}, @dummy.avatar.s3_credentials)
57
63
  end
58
64
 
59
65
  end
@@ -193,7 +199,10 @@ class AwsStorageTest < Test::Unit::TestCase
193
199
 
194
200
  context "An attachment that uses S3 for storage and has spaces in file name" do
195
201
  setup do
196
- rebuild_model default_model_options(:styles => { :large => ['500x500#', :jpg] })
202
+ rebuild_model default_model_options(
203
+ :styles => { :large => ['500x500#', :jpg] },
204
+ :restricted_characters => /[&$+,\/:;=?@<>\[\]\{\}\|\\\^~%#]/
205
+ )
197
206
 
198
207
  @dummy = Dummy.new
199
208
  @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
@@ -221,4 +230,93 @@ class AwsStorageTest < Test::Unit::TestCase
221
230
  assert ! Dummy.new.avatar.is_a?(Paperclip::Storage::Filesystem)
222
231
  end
223
232
  end
224
- end
233
+
234
+ context "S3 options" do
235
+ setup do
236
+ @writer = mock()
237
+ AWS::S3.expects(:new).returns(
238
+ stub(:buckets =>
239
+ stub(:[] =>
240
+ stub(:objects =>
241
+ stub(:[] => @writer)
242
+ )
243
+ )
244
+ )
245
+ )
246
+ end
247
+
248
+ should "be set to default" do
249
+ rebuild_model default_model_options
250
+
251
+ @writer.expects(:write).with do |value|
252
+ value[:sse] == nil &&
253
+ value[:server_side_encryption] == false &&
254
+ value[:storage_class] == :standard &&
255
+ value[:content_disposition] == nil &&
256
+ value[:expires] == nil
257
+ end
258
+
259
+ @dummy = Dummy.new
260
+ @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
261
+ @dummy.save
262
+ end
263
+
264
+ should "be configured" do
265
+ rebuild_model default_model_options(:s3_options => {
266
+ :sse => 'AES256',
267
+ :storage_class => :reduced_redundancy,
268
+ :content_disposition => :attachment,
269
+ :cache_control => 'max-age=86400'
270
+ })
271
+
272
+ @writer.expects(:write).with do |value|
273
+ value[:sse] == nil &&
274
+ value[:server_side_encryption] == 'AES256' &&
275
+ value[:storage_class] == :reduced_redundancy &&
276
+ value[:content_disposition] == :attachment &&
277
+ value[:cache_control] == 'max-age=86400'
278
+ end
279
+
280
+ @dummy = Dummy.new
281
+ @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
282
+ @dummy.save
283
+ end
284
+
285
+ should "work with string-keyed hash" do
286
+ rebuild_model default_model_options(:s3_options => {
287
+ 'cache_control' => 'max-age=86400'
288
+ })
289
+
290
+ @writer.expects(:write).with do |value|
291
+ value['cache_control'] == nil &&
292
+ value[:cache_control] == 'max-age=86400'
293
+ end
294
+
295
+ @dummy = Dummy.new
296
+ @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
297
+ @dummy.save
298
+ end
299
+
300
+ should "allow to modify options in instance" do
301
+ rebuild_model default_model_options
302
+
303
+ @writer.expects(:write).with do |value|
304
+ value[:sse] == nil &&
305
+ value[:server_side_encryption] == false &&
306
+ value[:storage_class] == :standard &&
307
+ value[:content_disposition] == "attachment; filename=spaced_file.png" &&
308
+ value[:expires] == nil
309
+ end
310
+
311
+ Dummy.class_eval do
312
+ before_save do
313
+ self.avatar.s3_options[:content_disposition] = "attachment; filename=#{self.avatar_file_name}"
314
+ end
315
+ end
316
+
317
+ @dummy = Dummy.new
318
+ @dummy.avatar = File.new(fixture_file('spaced file.png'), 'rb')
319
+ @dummy.save
320
+ end
321
+ end
322
+ end