Instantly share code, notes, and snippets.

Embed
What would you like to do?
# S3 backend for paperclip plugin. Copy the file to:
# +config/initializers/+ directory
#
# Example configuration for CNAME bucket:
#
# has_attached_file :image,
# :s3_host_alias => "bucket.domain.tld",
# :url => ":s3_alias_url",
# :styles => {
# :medium => "300x300>",
# :thumb => "100x100>"
# },
# :storage => :s3,
# :s3_credentials => {
# :access_key_id => "...",
# :secret_access_key => "..."
# },
# :bucket => "bucket.domain.tld",
# :path => ":attachment/:id/:style.:extension"
module Paperclip
module Storage
module S3
def self.extended(base)
begin
require "s3"
rescue LoadError => e
e.message << " (You may need to install the s3 gem)"
raise e
end
base.instance_eval do
@s3_credentials = parse_credentials(@options[:s3_credentials])
@bucket_name = @options[:bucket] || @s3_credentials[:bucket]
@bucket_name = @bucket_name.call(self) if @bucket_name.is_a?(Proc)
@s3_options = @options[:s3_options] || {}
@s3_permissions = @options[:s3_permissions] || :public_read
@s3_protocol = @options[:s3_protocol] || (@s3_permissions == :public_read ? "http" : "https")
@s3_headers = @options[:s3_headers] || {}
@s3_host_alias = @options[:s3_host_alias]
@url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
@service = ::S3::Service.new(@s3_options.merge(:access_key_id => @s3_credentials[:access_key_id],
:secret_access_key => @s3_credentials[:secret_access_key],
:use_ssl => @s3_protocol == "https"))
@bucket = @service.buckets.build(@bucket_name)
end
Paperclip.interpolates(:s3_alias_url) do |attachment, style|
"#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
end
Paperclip.interpolates(:s3_path_url) do |attachment, style|
"#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
end
Paperclip.interpolates(:s3_domain_url) do |attachment, style|
"#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
end
end
def bucket
@bucket
end
def bucket_name
@bucket.name
end
def s3_host_alias
@s3_host_alias
end
def expiring_url(style_name = default_style, time = 3600)
bucket.objects.build(path(style_name)).temporary_url(Time.current + time)
end
def parse_credentials(creds)
creds = find_credentials(creds).stringify_keys
(creds[RAILS_ENV] || creds).symbolize_keys
end
def exists?(style = default_style)
if original_filename
bucket.objects.build(path(style)).exists?
else
false
end
end
def s3_protocol
@s3_protocol
end
# Returns representation of the data of the file assigned to the given
# style, in the format most representative of the current storage.
def to_file(style = default_style)
return @queued_for_write[style] if @queued_for_write[style]
begin
file = Tempfile.new(path(style))
file.binmode if file.respond_to?(:binmode)
file.write(bucket.objects.find(path(style)).content)
file.rewind
rescue ::S3::Error::NoSuchKey
file.close if file.respond_to?(:close)
file = nil
end
file
end
def flush_writes #:nodoc:
@queued_for_write.each do |style, file|
begin
log("saving #{path(style)}")
object = bucket.objects.build(path(style))
object.content = file.read
object.acl = @s3_permisions
object.content_type = instance_read(:content_type)
object.content_disposition = @s3_headers[:content_disposition]
object.content_encoding = @s3_headers[:content_encoding]
object.save
rescue ::S3::Error::ResponseError => e
raise
end
end
@queued_for_write = {}
end
def flush_deletes #:nodoc:
@queued_for_delete.each do |path|
begin
log("deleting #{path}")
bucket.objects.find(path).destroy
rescue ::S3::Error::ResponseError
# Ignore this.
end
end
@queued_for_delete = []
end
def find_credentials(creds)
case creds
when File
YAML::load(ERB.new(File.read(creds.path)).result)
when String
YAML::load(ERB.new(File.read(creds)).result)
when Hash
creds
else
raise ArgumentError, "Credentials are not a path, file, or hash."
end
end
private :find_credentials
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment