paperclip 2.3.11 → 2.3.16

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.md CHANGED
@@ -36,13 +36,23 @@ In development mode, you might add this line to `config/environments/development
36
36
  Installation
37
37
  ------------
38
38
 
39
+ Paperclip is distributed as a gem, which is how it should be used in your app. It's
40
+ technically still installable as a plugin, but that's discouraged, as Rails plays
41
+ well with gems.
42
+
39
43
  Include the gem in your Gemfile:
40
44
 
41
45
  gem "paperclip", "~> 2.3"
42
46
 
43
- Or as a plugin:
47
+ Or, if you don't use Bundler (though you probably should, even in Rails 2), with config.gem
44
48
 
45
- ruby script/plugin install git://github.com/thoughtbot/paperclip.git
49
+ # In config/environment.rb
50
+ ...
51
+ Rails::Initializer.run do |config|
52
+ ...
53
+ config.gem "paperclip", :version => "~> 2.3"
54
+ ...
55
+ end
46
56
 
47
57
  Quick Start
48
58
  -----------
@@ -95,7 +105,7 @@ Usage
95
105
  The basics of paperclip are quite simple: Declare that your model has an
96
106
  attachment with the has_attached_file method, and give it a name. Paperclip
97
107
  will wrap up up to four attributes (all prefixed with that attachment's name,
98
- so you can have multiple attachments per model if you wish) and give the a
108
+ so you can have multiple attachments per model if you wish) and give them a
99
109
  friendly front end. The attributes are `<attachment>_file_name`,
100
110
  `<attachment>_file_size`, `<attachment>_content_type`, and `<attachment>_updated_at`.
101
111
  Only `<attachment>_file_name` is required for paperclip to operate. More
@@ -178,6 +188,12 @@ or more or the processors, and they are expected to ignore it.
178
188
  _NOTE: Because processors operate by turning the original attachment into the
179
189
  styles, no processors will be run if there are no styles defined._
180
190
 
191
+ If you're interested in caching your thumbnail's width, height and size in the
192
+ database, take a look at the [paperclip-meta](https://github.com/y8/paperclip-meta) gem.
193
+
194
+ Also, if you're interesting to generate the thumbnail on-the-fly, you might want
195
+ to look into the [attachment_on_the_fly](https://github.com/drpentode/Attachment-on-the-Fly) gem.
196
+
181
197
  Events
182
198
  ------
183
199
 
@@ -196,11 +212,19 @@ _NOTE: Post processing will not even *start* if the attachment is not valid
196
212
  according to the validations. Your callbacks and processors will *only* be
197
213
  called with valid attachments._
198
214
 
215
+ URI Obfuscation
216
+ ---------------
217
+
218
+ Paperclip has an interpolation called `:hash` for obfuscating filenames of publicly-available files. For more on this feature read author's own explanation.
219
+
220
+ [https://github.com/thoughtbot/paperclip/pull/416](https://github.com/thoughtbot/paperclip/pull/416)
221
+
199
222
  Testing
200
223
  -------
201
224
 
202
225
  Paperclip provides rspec-compatible matchers for testing attachments. See the
203
- documentation on Paperclip::Shoulda::Matchers for more information.
226
+ documentation on [Paperclip::Shoulda::Matchers](http://rubydoc.info/gems/paperclip/Paperclip/Shoulda/Matchers)
227
+ for more information.
204
228
 
205
229
  Contributing
206
230
  ------------
@@ -215,6 +239,8 @@ guidelines:
215
239
  It's a rare time when explicit tests aren't needed. If you have questions
216
240
  about writing tests for paperclip, please ask the mailing list.
217
241
 
242
+ Please see CONTRIBUTING.md for details.
243
+
218
244
  Credits
219
245
  -------
220
246
 
data/Rakefile CHANGED
@@ -4,13 +4,13 @@ require 'bundler/setup'
4
4
 
5
5
  require 'rake'
6
6
  require 'rake/testtask'
7
- require 'rake/rdoctask'
7
+ require 'rdoc/task'
8
8
 
9
9
  $LOAD_PATH << File.join(File.dirname(__FILE__), 'lib')
10
10
  require 'paperclip'
11
11
 
12
12
  desc 'Default: run unit tests.'
13
- task :default => [:clean, :all]
13
+ task :default => [:clean, 'appraisal:install', :all]
14
14
 
15
15
  desc 'Test the paperclip plugin under all supported Rails versions.'
16
16
  task :all do |t|
@@ -31,7 +31,7 @@ task :shell do |t|
31
31
  end
32
32
 
33
33
  desc 'Generate documentation for the paperclip plugin.'
34
- Rake::RDocTask.new(:rdoc) do |rdoc|
34
+ RDoc::Task.new(:rdoc) do |rdoc|
35
35
  rdoc.rdoc_dir = 'doc'
36
36
  rdoc.title = 'Paperclip'
37
37
  rdoc.options << '--line-numbers' << '--inline-source'
data/init.rb CHANGED
@@ -1 +1,4 @@
1
1
  require File.join(File.dirname(__FILE__), "lib", "paperclip")
2
+ require 'paperclip/railtie'
3
+
4
+ Paperclip::Railtie.insert
@@ -1,7 +1,9 @@
1
1
  require 'rails/generators/active_record'
2
2
 
3
3
  class PaperclipGenerator < ActiveRecord::Generators::Base
4
- desc "Create a migration to add paperclip-specific fields to your model."
4
+ desc "Create a migration to add paperclip-specific fields to your model. " +
5
+ "The NAME argument is the name of your model, and the following " +
6
+ "arguments are the name of the attachments"
5
7
 
6
8
  argument :attachment_names, :required => true, :type => :array, :desc => "The names of the attachment(s) to add.",
7
9
  :banner => "attachment_one attachment_two attachment_three ..."
@@ -11,6 +11,7 @@ module Paperclip
11
11
  :url => "/system/:attachment/:id/:style/:filename",
12
12
  :path => ":rails_root/public:url",
13
13
  :styles => {},
14
+ :only_process => [],
14
15
  :processors => [:thumbnail],
15
16
  :convert_options => {},
16
17
  :default_url => "/:attachment/:style/missing.png",
@@ -20,7 +21,8 @@ module Paperclip
20
21
  :whiny => Paperclip.options[:whiny] || Paperclip.options[:whiny_thumbnails],
21
22
  :use_default_time_zone => true,
22
23
  :hash_digest => "SHA1",
23
- :hash_data => ":class/:attachment/:id/:style/:updated_at"
24
+ :hash_data => ":class/:attachment/:id/:style/:updated_at",
25
+ :preserve_files => false
24
26
  }
25
27
  end
26
28
 
@@ -41,6 +43,7 @@ module Paperclip
41
43
  @path = options[:path]
42
44
  @path = @path.call(self) if @path.is_a?(Proc)
43
45
  @styles = options[:styles]
46
+ @only_process = options[:only_process]
44
47
  @normalized_styles = nil
45
48
  @default_url = options[:default_url]
46
49
  @default_style = options[:default_style]
@@ -53,6 +56,7 @@ module Paperclip
53
56
  @hash_secret = options[:hash_secret]
54
57
  @convert_options = options[:convert_options]
55
58
  @processors = options[:processors]
59
+ @preserve_files = options[:preserve_files]
56
60
  @options = options
57
61
  @post_processing = true
58
62
  @queued_for_delete = []
@@ -64,8 +68,8 @@ module Paperclip
64
68
  end
65
69
 
66
70
  def styles
67
- unless @normalized_styles
68
- @normalized_styles = {}
71
+ if @styles.respond_to?(:call) || !@normalized_styles
72
+ @normalized_styles = ActiveSupport::OrderedHash.new
69
73
  (@styles.respond_to?(:call) ? @styles.call(self) : @styles).each do |name, args|
70
74
  @normalized_styles[name] = Paperclip::Style.new(name, args.dup, self)
71
75
  end
@@ -107,7 +111,7 @@ module Paperclip
107
111
 
108
112
  @dirty = true
109
113
 
110
- post_process if @post_processing
114
+ post_process(*@only_process) if @post_processing
111
115
 
112
116
  # Reset the file size if the original file was reprocessed.
113
117
  instance_write(:file_size, @queued_for_write[:original].size.to_i)
@@ -123,7 +127,8 @@ module Paperclip
123
127
  # security, however, for performance reasons. Set use_timestamp to false
124
128
  # if you want to stop the attachment update time appended to the url
125
129
  def url(style_name = default_style, use_timestamp = @use_timestamp)
126
- url = original_filename.nil? ? interpolate(@default_url, style_name) : interpolate(@url, style_name)
130
+ default_url = @default_url.is_a?(Proc) ? @default_url.call(self) : @default_url
131
+ url = original_filename.nil? ? interpolate(default_url, style_name) : interpolate(@url, style_name)
127
132
  use_timestamp && updated_at ? [url, updated_at].compact.join(url.include?("?") ? "&" : "?") : url
128
133
  end
129
134
 
@@ -164,6 +169,7 @@ module Paperclip
164
169
  # use #destroy.
165
170
  def clear
166
171
  queue_existing_for_delete
172
+ @queued_for_write = {}
167
173
  @errors = {}
168
174
  end
169
175
 
@@ -171,8 +177,10 @@ module Paperclip
171
177
  # nil to the attachment *and saving*. This is permanent. If you wish to
172
178
  # wipe out the existing attachment but not save, use #clear.
173
179
  def destroy
174
- clear
175
- save
180
+ unless @preserve_files
181
+ clear
182
+ save
183
+ end
176
184
  end
177
185
 
178
186
  # Returns the name of the file as originally assigned, and lives in the
@@ -222,9 +230,13 @@ module Paperclip
222
230
  end
223
231
 
224
232
  def generate_fingerprint(source)
225
- data = source.read
226
- source.rewind if source.respond_to?(:rewind)
227
- Digest::MD5.hexdigest(data)
233
+ if source.respond_to?(:path) && source.path && !source.path.blank?
234
+ Digest::MD5.file(source.path).to_s
235
+ else
236
+ data = source.read
237
+ source.rewind if source.respond_to?(:rewind)
238
+ Digest::MD5.hexdigest(data)
239
+ end
228
240
  end
229
241
 
230
242
  # Paths and URLs can have a number of variables interpolated into them
@@ -251,6 +263,7 @@ module Paperclip
251
263
  new_original.rewind
252
264
 
253
265
  @queued_for_write = { :original => new_original }
266
+ instance_write(:updated_at, Time.now)
254
267
  post_process(*style_args)
255
268
 
256
269
  old_original.close if old_original.respond_to?(:close)
@@ -269,6 +282,8 @@ module Paperclip
269
282
  !original_filename.blank?
270
283
  end
271
284
 
285
+ alias :present? :file?
286
+
272
287
  # Writes the attachment-specific attribute on the instance. For example,
273
288
  # instance_write(:file_name, "me.jpg") will write "me.jpg" to the instance's
274
289
  # "avatar_file_name" field (assuming the attachment is called avatar).
@@ -308,7 +323,7 @@ module Paperclip
308
323
  end
309
324
 
310
325
  def initialize_storage #:nodoc:
311
- storage_class_name = @storage.to_s.capitalize
326
+ storage_class_name = @storage.to_s.downcase.camelize
312
327
  begin
313
328
  @storage_module = Paperclip::Storage.const_get(storage_class_name)
314
329
  rescue NameError
@@ -356,7 +371,7 @@ module Paperclip
356
371
  end
357
372
 
358
373
  def queue_existing_for_delete #:nodoc:
359
- return unless file?
374
+ return unless (file? && @preserve_files==false)
360
375
  @queued_for_delete += [:original, *styles.keys].uniq.map do |style|
361
376
  path(style) if exists?(style)
362
377
  end.compact
@@ -15,10 +15,13 @@ module Paperclip
15
15
  # File or path.
16
16
  def self.from_file file
17
17
  file = file.path if file.respond_to? "path"
18
+ raise(Paperclip::NotIdentifiedByImageMagickError.new("Cannot find the geometry of a file with a blank name")) if file.blank?
18
19
  geometry = begin
19
20
  Paperclip.run("identify", "-format %wx%h :file", :file => "#{file}[0]")
20
- rescue PaperclipCommandLineError
21
+ rescue Cocaine::ExitStatusError
21
22
  ""
23
+ rescue Cocaine::CommandNotFoundError => e
24
+ raise Paperclip::CommandNotFoundError.new("Could not run the `identify` command. Please install ImageMagick.")
22
25
  end
23
26
  parse(geometry) ||
24
27
  raise(NotIdentifiedByImageMagickError.new("#{file} is not recognized by the 'identify' command."))
@@ -6,13 +6,13 @@ module Paperclip
6
6
  module Interpolations
7
7
  extend self
8
8
 
9
- # Hash assignment of interpolations. Included only for compatability,
9
+ # Hash assignment of interpolations. Included only for compatibility,
10
10
  # and is not intended for normal use.
11
11
  def self.[]= name, block
12
12
  define_method(name, &block)
13
13
  end
14
14
 
15
- # Hash access of interpolations. Included only for compatability,
15
+ # Hash access of interpolations. Included only for compatibility,
16
16
  # and is not intended for normal use.
17
17
  def self.[] name
18
18
  method(name)
@@ -35,7 +35,7 @@ module Paperclip
35
35
 
36
36
  # Returns the filename, the same way as ":basename.:extension" would.
37
37
  def filename attachment, style_name
38
- "#{basename(attachment, style_name)}.#{extension(attachment, style_name)}"
38
+ [ basename(attachment, style_name), extension(attachment, style_name) ].reject(&:blank?).join(".")
39
39
  end
40
40
 
41
41
  # Returns the interpolated URL. Will raise an error if the url itself
@@ -99,6 +99,11 @@ module Paperclip
99
99
  attachment.instance.id
100
100
  end
101
101
 
102
+ # Returns the #to_param of the instance.
103
+ def param attachment, style_name
104
+ attachment.instance.to_param
105
+ end
106
+
102
107
  # Returns the fingerprint of the instance.
103
108
  def fingerprint attachment, style_name
104
109
  attachment.fingerprint
@@ -30,7 +30,7 @@ module IOStream
30
30
  end
31
31
 
32
32
  # Corrects a bug in Windows when asking for Tempfile size.
33
- if defined? Tempfile
33
+ if defined?(Tempfile) && RUBY_PLATFORM !~ /java/
34
34
  class Tempfile
35
35
  def size
36
36
  if @tmpfile
@@ -17,6 +17,8 @@ module Paperclip
17
17
  class ValidateAttachmentContentTypeMatcher
18
18
  def initialize attachment_name
19
19
  @attachment_name = attachment_name
20
+ @allowed_types = []
21
+ @rejected_types = []
20
22
  end
21
23
 
22
24
  def allowing *types
@@ -37,13 +39,19 @@ module Paperclip
37
39
  end
38
40
 
39
41
  def failure_message
40
- "Content types #{@allowed_types.join(", ")} should be accepted" +
41
- " and #{@rejected_types.join(", ")} rejected by #{@attachment_name}"
42
+ "".tap do |str|
43
+ str << "Content types #{@allowed_types.join(", ")} should be accepted" if @allowed_types.present?
44
+ str << "\n" if @allowed_types.present && @rejected_types.present?
45
+ str << "Content types #{@rejected_types.join(", ")} should be rejected by #{@attachment_name}" if @rejected_types.present?
46
+ end
42
47
  end
43
48
 
44
49
  def negative_failure_message
45
- "Content types #{@allowed_types.join(", ")} should be rejected" +
46
- " and #{@rejected_types.join(", ")} accepted by #{@attachment_name}"
50
+ "".tap do |str|
51
+ str << "Content types #{@allowed_types.join(", ")} should be rejected" if @allowed_types.present?
52
+ str << "\n" if @allowed_types.present && @rejected_types.present?
53
+ str << "Content types #{@rejected_types.join(", ")} should be accepted by #{@attachment_name}" if @rejected_types.present?
54
+ end
47
55
  end
48
56
 
49
57
  def description
@@ -52,22 +60,20 @@ module Paperclip
52
60
 
53
61
  protected
54
62
 
55
- def allow_types?(types)
56
- types.all? do |type|
57
- file = StringIO.new(".")
58
- file.content_type = type
59
- (subject = @subject.new).attachment_for(@attachment_name).assign(file)
60
- subject.valid?
61
- subject.errors[:"#{@attachment_name}_content_type"].blank?
62
- end
63
+ def type_allowed?(type)
64
+ file = StringIO.new(".")
65
+ file.content_type = type
66
+ (subject = @subject.new).attachment_for(@attachment_name).assign(file)
67
+ subject.valid?
68
+ subject.errors[:"#{@attachment_name}_content_type"].blank?
63
69
  end
64
70
 
65
71
  def allowed_types_allowed?
66
- allow_types?(@allowed_types)
72
+ @allowed_types.all? { |type| type_allowed?(type) }
67
73
  end
68
74
 
69
75
  def rejected_types_rejected?
70
- not allow_types?(@rejected_types)
76
+ !@rejected_types.any? { |type| type_allowed?(type) }
71
77
  end
72
78
  end
73
79
  end
@@ -41,7 +41,7 @@ module Paperclip
41
41
  # http://marsorange.com/archives/of-mogrify-ruby-tempfile-dynamic-class-definitions
42
42
  class Tempfile < ::Tempfile
43
43
  # This is Ruby 1.8.7's implementation.
44
- if RUBY_VERSION <= "1.8.6"
44
+ if RUBY_VERSION <= "1.8.6" || RUBY_PLATFORM =~ /java/
45
45
  def make_tmpname(basename, n)
46
46
  case basename
47
47
  when Array
@@ -38,8 +38,13 @@ module Paperclip
38
38
  file.close
39
39
  FileUtils.mkdir_p(File.dirname(path(style_name)))
40
40
  log("saving #{path(style_name)}")
41
- FileUtils.mv(file.path, path(style_name))
42
- FileUtils.chmod(0644, path(style_name))
41
+ begin
42
+ FileUtils.mv(file.path, path(style_name))
43
+ rescue SystemCallError
44
+ FileUtils.cp(file.path, path(style_name))
45
+ FileUtils.rm(file.path)
46
+ end
47
+ FileUtils.chmod(0666&~File.umask, path(style_name))
43
48
  end
44
49
  @queued_for_write = {}
45
50
  end
@@ -58,7 +63,7 @@ module Paperclip
58
63
  FileUtils.rmdir(path)
59
64
  break if File.exists?(path) # Ruby 1.9.2 does not raise if the removal failed.
60
65
  end
61
- rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR
66
+ rescue Errno::EEXIST, Errno::ENOTEMPTY, Errno::ENOENT, Errno::EINVAL, Errno::ENOTDIR, Errno::EACCES
62
67
  # Stop trying to remove parent directories
63
68
  rescue SystemCallError => e
64
69
  log("There was an unexpected error while deleting directories: #{e.class}")
@@ -1,5 +1,35 @@
1
1
  module Paperclip
2
2
  module Storage
3
+ # fog is a modern and versatile cloud computing library for Ruby.
4
+ # Among others, it supports Amazon S3 to store your files. In
5
+ # contrast to the outdated AWS-S3 gem it is actively maintained and
6
+ # supports multiple locations.
7
+ # Amazon's S3 file hosting service is a scalable, easy place to
8
+ # store files for distribution. You can find out more about it at
9
+ # http://aws.amazon.com/s3 There are a few fog-specific options for
10
+ # has_attached_file, which will be explained using S3 as an example:
11
+ # * +fog_credentials+: Takes a Hash with your credentials. For S3,
12
+ # you can use the following format:
13
+ # aws_access_key_id: '<your aws_access_key_id>'
14
+ # aws_secret_access_key: '<your aws_secret_access_key>'
15
+ # provider: 'AWS'
16
+ # region: 'eu-west-1'
17
+ # * +fog_directory+: This is the name of the S3 bucket that will
18
+ # store your files. Remember that the bucket must be unique across
19
+ # all of Amazon S3. If the bucket does not exist, Paperclip will
20
+ # attempt to create it.
21
+ # * +path+: This is the key under the bucket in which the file will
22
+ # be stored. The URL will be constructed from the bucket and the
23
+ # path. This is what you will want to interpolate. Keys should be
24
+ # unique, like filenames, and despite the fact that S3 (strictly
25
+ # speaking) does not support directories, you can still use a / to
26
+ # separate parts of your file name.
27
+ # * +fog_public+: (optional, defaults to true) Should the uploaded
28
+ # files be public or not? (true/false)
29
+ # * +fog_host+: (optional) The fully-qualified domain name (FQDN)
30
+ # that is the alias to the S3 domain of your bucket, e.g.
31
+ # 'http://images.example.com'. This can also be used in
32
+ # conjunction with Cloudfront (http://aws.amazon.com/cloudfront)
3
33
 
4
34
  module Fog
5
35
  def self.extended base
@@ -14,7 +44,8 @@ module Paperclip
14
44
  @fog_directory = @options[:fog_directory]
15
45
  @fog_credentials = @options[:fog_credentials]
16
46
  @fog_host = @options[:fog_host]
17
- @fog_public = @options[:fog_public]
47
+ @fog_public = @options[:fog_public] || true
48
+ @fog_file = @options[:fog_file] || {}
18
49
 
19
50
  @url = ':fog_public_url'
20
51
  Paperclip.interpolates(:fog_public_url) do |attachment, style|
@@ -34,11 +65,19 @@ module Paperclip
34
65
  def flush_writes
35
66
  for style, file in @queued_for_write do
36
67
  log("saving #{path(style)}")
37
- directory.files.create(
38
- :body => file,
39
- :key => path(style),
40
- :public => @fog_public
41
- )
68
+ retried = false
69
+ begin
70
+ directory.files.create(@fog_file.merge(
71
+ :body => file,
72
+ :key => path(style),
73
+ :public => @fog_public
74
+ ))
75
+ rescue Excon::Errors::NotFound
76
+ raise if retried
77
+ retried = true
78
+ directory.save
79
+ retry
80
+ end
42
81
  end
43
82
  @queued_for_write = {}
44
83
  end
@@ -71,7 +110,8 @@ module Paperclip
71
110
 
72
111
  def public_url(style = default_style)
73
112
  if @fog_host
74
- "#{@fog_host}/#{path(style)}"
113
+ host = (@fog_host =~ /%d/) ? @fog_host % (path(style).hash % 4) : @fog_host
114
+ "#{host}/#{path(style)}"
75
115
  else
76
116
  directory.files.new(:key => path(style)).public_url
77
117
  end
@@ -84,12 +124,7 @@ module Paperclip
84
124
  end
85
125
 
86
126
  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
127
+ @directory ||= connection.directories.new(:key => @fog_directory)
93
128
  end
94
129
 
95
130
  end
@@ -27,6 +27,14 @@ module Paperclip
27
27
  # policies that S3 provides (more information can be found here:
28
28
  # http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAccessPolicy.html)
29
29
  # The default for Paperclip is :public_read.
30
+ #
31
+ # You can set permission on a per style bases by doing the following:
32
+ # :s3_permissions => {
33
+ # :original => :private
34
+ # }
35
+ # Or globaly:
36
+ # :s3_permissions => :private
37
+ #
30
38
  # * +s3_protocol+: The protocol for the URLs generated to your S3 assets. Can be either
31
39
  # 'http' or 'https'. Defaults to 'http' when your :s3_permissions are :public_read (the
32
40
  # default), and 'https' when your :s3_permissions are anything else.
@@ -39,9 +47,9 @@ module Paperclip
39
47
  # * +s3_host_alias+: The fully-qualified domain name (FQDN) that is the alias to the
40
48
  # S3 domain of your bucket. Used with the :s3_alias_url url interpolation. See the
41
49
  # link in the +url+ entry for more information about S3 domains and buckets.
42
- # * +url+: There are three options for the S3 url. You can choose to have the bucket's name
50
+ # * +url+: There are four options for the S3 url. You can choose to have the bucket's name
43
51
  # placed domain-style (bucket.s3.amazonaws.com) or path-style (s3.amazonaws.com/bucket).
44
- # Lastly, you can specify a CNAME (which requires the CNAME to be specified as
52
+ # You can also specify a CNAME (which requires the CNAME to be specified as
45
53
  # :s3_alias_url. You can read more about CNAMEs and S3 at
46
54
  # http://docs.amazonwebservices.com/AmazonS3/latest/index.html?VirtualHosting.html
47
55
  # Normally, this won't matter in the slightest and you can leave the default (which is
@@ -50,12 +58,15 @@ module Paperclip
50
58
  # NOTE: If you use a CNAME for use with CloudFront, you can NOT specify https as your
51
59
  # :s3_protocol; This is *not supported* by S3/CloudFront. Finally, when using the host
52
60
  # alias, the :bucket parameter is ignored, as the hostname is used as the bucket name
53
- # by S3.
61
+ # by S3. The fourth option for the S3 url is :asset_host, which uses Rails' built-in
62
+ # asset_host settings. NOTE: To get the full url from a paperclip'd object, use the
63
+ # image_path helper; this is what image_tag uses to generate the url for an img tag.
54
64
  # * +path+: This is the key under the bucket in which the file will be stored. The
55
65
  # URL will be constructed from the bucket and the path. This is what you will want
56
66
  # to interpolate. Keys should be unique, like filenames, and despite the fact that
57
67
  # S3 (strictly speaking) does not support directories, you can still use a / to
58
68
  # separate parts of your file name.
69
+ # * +s3_host_name+: If you are using your bucket in Tokyo region etc, write host_name.
59
70
  module S3
60
71
  def self.extended base
61
72
  begin
@@ -67,48 +78,71 @@ module Paperclip
67
78
 
68
79
  base.instance_eval do
69
80
  @s3_credentials = parse_credentials(@options[:s3_credentials])
81
+ @s3_host_name = @options[:s3_host_name] || @s3_credentials[:s3_host_name]
70
82
  @bucket = @options[:bucket] || @s3_credentials[:bucket]
71
83
  @bucket = @bucket.call(self) if @bucket.is_a?(Proc)
72
84
  @s3_options = @options[:s3_options] || {}
73
- @s3_permissions = @options[:s3_permissions] || :public_read
74
- @s3_protocol = @options[:s3_protocol] || (@s3_permissions == :public_read ? 'http' : 'https')
85
+ @s3_permissions = set_permissions(@options[:s3_permissions])
86
+ @s3_protocol = @options[:s3_protocol] ||
87
+ Proc.new do |style|
88
+ (@s3_permissions[style.to_sym] || @s3_permissions[:default]) == :public_read ? 'http' : 'https'
89
+ end
75
90
  @s3_headers = @options[:s3_headers] || {}
76
91
  @s3_host_alias = @options[:s3_host_alias]
92
+ @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.is_a?(Proc)
77
93
  unless @url.to_s.match(/^:s3.*url$/)
78
- @path = @path.gsub(/:url/, @url)
79
- @url = ":s3_path_url"
94
+ @path = @path.gsub(/:url/, @url)
95
+ @url = ":s3_path_url"
80
96
  end
97
+ @url = ":asset_host" if @options[:url].to_s == ":asset_host"
81
98
  AWS::S3::Base.establish_connection!( @s3_options.merge(
82
99
  :access_key_id => @s3_credentials[:access_key_id],
83
100
  :secret_access_key => @s3_credentials[:secret_access_key]
84
101
  ))
85
102
  end
86
103
  Paperclip.interpolates(:s3_alias_url) do |attachment, style|
87
- "#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
104
+ "#{attachment.s3_protocol(style)}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
88
105
  end unless Paperclip::Interpolations.respond_to? :s3_alias_url
89
106
  Paperclip.interpolates(:s3_path_url) do |attachment, style|
90
- "#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
107
+ "#{attachment.s3_protocol(style)}://#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
91
108
  end unless Paperclip::Interpolations.respond_to? :s3_path_url
92
109
  Paperclip.interpolates(:s3_domain_url) do |attachment, style|
93
- "#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
110
+ "#{attachment.s3_protocol(style)}://#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
94
111
  end unless Paperclip::Interpolations.respond_to? :s3_domain_url
112
+ Paperclip.interpolates(:asset_host) do |attachment, style|
113
+ "#{attachment.path(style).gsub(%r{^/}, "")}"
114
+ end unless Paperclip::Interpolations.respond_to? :asset_host
95
115
  end
96
116
 
97
117
  def expiring_url(time = 3600, style_name = default_style)
98
- AWS::S3::S3Object.url_for(path(style_name), bucket_name, :expires_in => time, :use_ssl => (s3_protocol == 'https'))
118
+ AWS::S3::S3Object.url_for(path(style_name), bucket_name, :expires_in => time, :use_ssl => (s3_protocol(style_name) == 'https'))
99
119
  end
100
120
 
101
121
  def bucket_name
102
122
  @bucket
103
123
  end
104
124
 
125
+ def s3_host_name
126
+ @s3_host_name || "s3.amazonaws.com"
127
+ end
128
+
129
+ def set_permissions permissions
130
+ if permissions.is_a?(Hash)
131
+ permissions[:default] = permissions[:default] || :public_read
132
+ else
133
+ permissions = { :default => permissions || :public_read }
134
+ end
135
+ permissions
136
+ end
137
+
105
138
  def s3_host_alias
106
139
  @s3_host_alias
107
140
  end
108
141
 
109
142
  def parse_credentials creds
110
143
  creds = find_credentials(creds).stringify_keys
111
- (creds[Rails.env] || creds).symbolize_keys
144
+ env = Object.const_defined?(:Rails) ? Rails.env : nil
145
+ (creds[env] || creds).symbolize_keys
112
146
  end
113
147
 
114
148
  def exists?(style = default_style)
@@ -119,8 +153,12 @@ module Paperclip
119
153
  end
120
154
  end
121
155
 
122
- def s3_protocol
123
- @s3_protocol
156
+ def s3_protocol(style)
157
+ if @s3_protocol.is_a?(Proc)
158
+ @s3_protocol.call(style)
159
+ else
160
+ @s3_protocol
161
+ end
124
162
  end
125
163
 
126
164
  # Returns representation of the data of the file assigned to the given
@@ -148,8 +186,8 @@ module Paperclip
148
186
  AWS::S3::S3Object.store(path(style),
149
187
  file,
150
188
  bucket_name,
151
- {:content_type => instance_read(:content_type),
152
- :access => @s3_permissions,
189
+ {:content_type => file.content_type.to_s.strip,
190
+ :access => (@s3_permissions[style] || @s3_permissions[:default]),
153
191
  }.merge(@s3_headers))
154
192
  rescue AWS::S3::NoSuchBucket => e
155
193
  create_bucket