module Paperclip::Storage::S3

Amazon's S3 file hosting service is a scalable, easy place to store files for distribution. You can find out more about it at aws.amazon.com/s3

To use Paperclip with S3, include the aws-sdk-s3 gem in your Gemfile:

gem 'aws-sdk-s3'

There are a few S3-specific options for has_attached_file:

Public Class Methods

extended(base) click to toggle source
# File lib/paperclip/storage/s3.rb, line 124
def self.extended(base)
  begin
    require "aws-sdk-s3"
  rescue LoadError => e
    e.message << " (You may need to install the aws-sdk-s3 gem)"
    raise e
  end

  base.instance_eval do
    @s3_options     = @options[:s3_options] || {}
    @s3_permissions = set_permissions(@options[:s3_permissions])
    @s3_protocol    = @options[:s3_protocol] || ""
    @s3_metadata = @options[:s3_metadata] || {}
    @s3_headers = {}
    merge_s3_headers(@options[:s3_headers], @s3_headers, @s3_metadata)

    @s3_storage_class = set_storage_class(@options[:s3_storage_class])

    @s3_server_side_encryption = "AES256"
    @s3_server_side_encryption = false if @options[:s3_server_side_encryption].blank?
    @s3_server_side_encryption = @options[:s3_server_side_encryption] if @s3_server_side_encryption

    unless @options[:url].to_s.match(/\A:s3.*url\z/) || @options[:url] == ":asset_host"
      @options[:path] = path_option.gsub(/:url/, @options[:url]).sub(/\A:rails_root\/public\/system/, "")
      @options[:url]  = ":s3_path_url"
    end
    @options[:url] = @options[:url].inspect if @options[:url].is_a?(Symbol)

    @http_proxy = @options[:http_proxy] || nil

    @use_accelerate_endpoint = @options[:use_accelerate_endpoint]
  end

  unless Paperclip::Interpolations.respond_to? :s3_alias_url
    Paperclip.interpolates(:s3_alias_url) do |attachment, style|
      protocol = attachment.s3_protocol(style, true)
      host = attachment.s3_host_alias
      path = attachment.path(style).
             split("/")[attachment.s3_prefixes_in_alias..-1].
             join("/").
             sub(%r{\A/}, "")
      "#{protocol}//#{host}/#{path}"
    end
  end
  unless Paperclip::Interpolations.respond_to? :s3_path_url
    Paperclip.interpolates(:s3_path_url) do |attachment, style|
      "#{attachment.s3_protocol(style, true)}//#{attachment.s3_host_name}/#{attachment.bucket_name}/#{attachment.path(style).sub(%r{\A/}, '')}"
    end
  end
  unless Paperclip::Interpolations.respond_to? :s3_domain_url
    Paperclip.interpolates(:s3_domain_url) do |attachment, style|
      "#{attachment.s3_protocol(style, true)}//#{attachment.bucket_name}.#{attachment.s3_host_name}/#{attachment.path(style).sub(%r{\A/}, '')}"
    end
  end
  unless Paperclip::Interpolations.respond_to? :asset_host
    Paperclip.interpolates(:asset_host) do |attachment, style|
      attachment.path(style).sub(%r{\A/}, "").to_s
    end
  end
end

Public Instance Methods

bucket_name() click to toggle source
# File lib/paperclip/storage/s3.rb, line 231
def bucket_name
  @bucket = @options[:bucket] || s3_credentials[:bucket]
  @bucket = @bucket.call(self) if @bucket.respond_to?(:call)
  @bucket || raise(ArgumentError, "missing required :bucket option")
end
copy_to_local_file(style, local_dest_path) click to toggle source
# File lib/paperclip/storage/s3.rb, line 417
def copy_to_local_file(style, local_dest_path)
  log("copying #{path(style)} to local file #{local_dest_path}")
  s3_object(style).download_file(local_dest_path)
rescue Aws::Errors::ServiceError => e
  warn("#{e} - cannot copy #{path(style)} to local file #{local_dest_path}")
  false
end
create_bucket() click to toggle source
# File lib/paperclip/storage/s3.rb, line 351
def create_bucket
  s3_interface.bucket(bucket_name).create
end
exists?(style = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 320
def exists?(style = default_style)
  if original_filename
    s3_object(style).exists?
  else
    false
  end
rescue Aws::Errors::ServiceError => e
  false
end
expiring_url(time = 3600, style_name = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 185
def expiring_url(time = 3600, style_name = default_style)
  if path(style_name)
    base_options = { expires_in: time }
    s3_object(style_name).presigned_url(
      :get,
      base_options.merge(s3_url_options)
    ).to_s
  else
    url(style_name)
  end
end
http_proxy_host() click to toggle source
# File lib/paperclip/storage/s3.rb, line 288
def http_proxy_host
  using_http_proxy? ? @http_proxy[:host] : nil
end
http_proxy_password() click to toggle source
# File lib/paperclip/storage/s3.rb, line 300
def http_proxy_password
  using_http_proxy? ? @http_proxy[:password] : nil
end
http_proxy_port() click to toggle source
# File lib/paperclip/storage/s3.rb, line 292
def http_proxy_port
  using_http_proxy? ? @http_proxy[:port] : nil
end
http_proxy_user() click to toggle source
# File lib/paperclip/storage/s3.rb, line 296
def http_proxy_user
  using_http_proxy? ? @http_proxy[:user] : nil
end
obtain_s3_instance_for(options) click to toggle source
# File lib/paperclip/storage/s3.rb, line 263
def obtain_s3_instance_for(options)
  instances = (Thread.current[:paperclip_s3_instances] ||= {})
  instances[options] ||= ::Aws::S3::Resource.new(options)
end
parse_credentials(creds) click to toggle source
# File lib/paperclip/storage/s3.rb, line 314
def parse_credentials(creds)
  creds = creds.respond_to?(:call) ? creds.call(self) : creds
  creds = find_credentials(creds).stringify_keys
  (creds[RailsEnvironment.get] || creds).symbolize_keys
end
s3_bucket() click to toggle source
# File lib/paperclip/storage/s3.rb, line 268
def s3_bucket
  @s3_bucket ||= s3_interface.bucket(bucket_name)
end
s3_credentials() click to toggle source
# File lib/paperclip/storage/s3.rb, line 197
def s3_credentials
  @s3_credentials ||= parse_credentials(@options[:s3_credentials])
end
s3_host_alias() click to toggle source
# File lib/paperclip/storage/s3.rb, line 215
def s3_host_alias
  @s3_host_alias = @options[:s3_host_alias]
  @s3_host_alias = @s3_host_alias.call(self) if @s3_host_alias.respond_to?(:call)
  @s3_host_alias
end
s3_host_name() click to toggle source
# File lib/paperclip/storage/s3.rb, line 201
def s3_host_name
  host_name = @options[:s3_host_name]
  host_name = host_name.call(self) if host_name.is_a?(Proc)

  host_name || s3_credentials[:s3_host_name] || "s3.amazonaws.com"
end
s3_interface() click to toggle source
# File lib/paperclip/storage/s3.rb, line 237
def s3_interface
  @s3_interface ||= begin
    config = { region: s3_region }

    if using_http_proxy?

      proxy_opts = { host: http_proxy_host }
      proxy_opts[:port] = http_proxy_port if http_proxy_port
      if http_proxy_user
        userinfo = http_proxy_user.to_s
        userinfo += ":#{http_proxy_password}" if http_proxy_password
        proxy_opts[:userinfo] = userinfo
      end
      config[:proxy_uri] = URI::HTTP.build(proxy_opts)
    end

    config[:use_accelerate_endpoint] = use_accelerate_endpoint?

    [:access_key_id, :secret_access_key, :credential_provider, :credentials].each do |opt|
      config[opt] = s3_credentials[opt] if s3_credentials[opt]
    end

    obtain_s3_instance_for(config.merge(@s3_options))
  end
end
s3_object(style_name = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 276
def s3_object(style_name = default_style)
  s3_bucket.object style_name_as_path(style_name)
end
s3_permissions(style = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 330
def s3_permissions(style = default_style)
  s3_permissions = @s3_permissions[style] || @s3_permissions[:default]
  s3_permissions = s3_permissions.call(self, style) if s3_permissions.respond_to?(:call)
  s3_permissions
end
s3_prefixes_in_alias() click to toggle source
# File lib/paperclip/storage/s3.rb, line 221
def s3_prefixes_in_alias
  @s3_prefixes_in_alias ||= @options[:s3_prefixes_in_alias].to_i
end
s3_protocol(style = default_style, with_colon = false) click to toggle source
# File lib/paperclip/storage/s3.rb, line 340
def s3_protocol(style = default_style, with_colon = false)
  protocol = @s3_protocol
  protocol = protocol.call(style, self) if protocol.respond_to?(:call)

  if with_colon && !protocol.empty?
    "#{protocol}:"
  else
    protocol.to_s
  end
end
s3_region() click to toggle source
# File lib/paperclip/storage/s3.rb, line 208
def s3_region
  region = @options[:s3_region]
  region = region.call(self) if region.is_a?(Proc)

  region || s3_credentials[:s3_region]
end
s3_storage_class(style = default_style) click to toggle source
# File lib/paperclip/storage/s3.rb, line 336
def s3_storage_class(style = default_style)
  @s3_storage_class[style] || @s3_storage_class[:default]
end
s3_url_options() click to toggle source
# File lib/paperclip/storage/s3.rb, line 225
def s3_url_options
  s3_url_options = @options[:s3_url_options] || {}
  s3_url_options = s3_url_options.call(instance) if s3_url_options.respond_to?(:call)
  s3_url_options
end
set_permissions(permissions) click to toggle source
# File lib/paperclip/storage/s3.rb, line 304
def set_permissions(permissions)
  permissions = { default: permissions } unless permissions.respond_to?(:merge)
  permissions.merge default: (permissions[:default] || :"public-read")
end
set_storage_class(storage_class) click to toggle source
# File lib/paperclip/storage/s3.rb, line 309
def set_storage_class(storage_class)
  storage_class = { default: storage_class } unless storage_class.respond_to?(:merge)
  storage_class
end
style_name_as_path(style_name) click to toggle source
# File lib/paperclip/storage/s3.rb, line 272
def style_name_as_path(style_name)
  path(style_name).sub(%r{\A/}, "")
end
use_accelerate_endpoint?() click to toggle source
# File lib/paperclip/storage/s3.rb, line 280
def use_accelerate_endpoint?
  !!@use_accelerate_endpoint
end
using_http_proxy?() click to toggle source
# File lib/paperclip/storage/s3.rb, line 284
def using_http_proxy?
  !!@http_proxy
end

Private Instance Methods

find_credentials(creds) click to toggle source
# File lib/paperclip/storage/s3.rb, line 427
def find_credentials(creds)
  case creds
  when File
    YAML::safe_load(ERB.new(File.read(creds.path)).result, [], [], true)
  when String, Pathname
    YAML::safe_load(ERB.new(File.read(creds)).result, [], [], true)
  when Hash
    creds
  when NilClass
    {}
  else
    raise ArgumentError, "Credentials given are not a path, file, proc, or hash."
  end
end
merge_s3_headers(http_headers, s3_headers, s3_metadata) click to toggle source
# File lib/paperclip/storage/s3.rb, line 446
def merge_s3_headers(http_headers, s3_headers, s3_metadata)
  return if http_headers.nil?

  http_headers = http_headers.call(instance) if http_headers.respond_to?(:call)
  http_headers.inject({}) do |_headers, (name, value)|
    case name.to_s
    when /\Ax-amz-meta-(.*)/i
      s3_metadata[$1.downcase] = value
    else
      s3_headers[name.to_s.downcase.sub(/\Ax-amz-/, "").tr("-", "_").to_sym] = value
    end
  end
end
use_secure_protocol?(style_name) click to toggle source
# File lib/paperclip/storage/s3.rb, line 442
def use_secure_protocol?(style_name)
  s3_protocol(style_name) == "https"
end