123456789_123456789_123456789_123456789_123456789_

Class: ActiveStorage::Service

Relationships & Source Files
Namespace Children
Classes:
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Inherits: Object
Defined in: activestorage/lib/active_storage/service.rb

Overview

=======================

Abstract class serving as an interface for concrete services.

The available services are:

  • Disk, to manage attachments saved directly on the hard drive.
  • GCS, to manage attachments through Google Cloud Storage.
  • S3, to manage attachments through Amazon S3.
  • Mirror, to be able to use several services to manage attachments.

Inside a Rails application, you can set-up your services through the generated config/storage.yml file and reference one of the aforementioned constant under the service key. For example:

local:
  service: Disk
  root: <%= Rails.root.join("storage") %>

You can checkout the service's constructor to know which keys are required.

Then, in your application's configuration, you can specify the service to use like this:

config.active_storage.service = :local

If you are using Active Storage outside of a Ruby on Rails application, you can configure the service to use like this:

{ActiveStorage::Blob.service} = {ActiveStorage::Service.configure}(
  :local,
  { local: {service: "Disk",  root: Pathname("/tmp/foo/storage") } }
)

Class Method Summary

::ActiveSupport::Autoload - Extended

Instance Attribute Summary

Instance Method Summary

Class Method Details

.build(configurator:, name:, service: nil, **service_config)

This method is for internal use only.

Override in subclasses that stitch together multiple services and hence need to build additional services using the configurator.

Passes the configurator and all of the service's config as keyword args.

See MirrorService for an example.

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 69

def build(configurator:, name:, service: nil, **service_config) # :nodoc:
  new(**service_config).tap do |service_instance|
    service_instance.name = name
  end
end

.configure(service_name, configurations)

Configure an Active Storage service by name from a set of configurations, typically loaded from a YAML file. The Active Storage engine uses this to set the global Active Storage service when the app boots.

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 59

def configure(service_name, configurations)
  Configurator.build(service_name, configurations)
end

Instance Attribute Details

#name (rw)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 53

attr_accessor :name

#public? ⇒ Boolean (readonly)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 155

def public?
  @public
end

Instance Method Details

#checksum_implementation

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 180

def checksum_implementation(**)
  OpenSSL::Digest::MD5
end

#compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})

Concatenate multiple files into a single "composed" file.

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 104

def compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
  raise NotImplementedError
end

#compute_checksum(io, **options)

Raises:

  • (ArgumentError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 163

def compute_checksum(io, **options)
  raise ArgumentError, "io must be rewindable" unless io.respond_to?(:rewind)

  # Defer to Digest class's file implementation if File or base64digest if no chunk_size
  return checksum_implementation(**options).file(io).base64digest if io.is_a?(File)
  return checksum_implementation(**options).base64digest(io.read) if default_chunk_size.to_i == 0

  checksum_implementation(**options).new.tap do |checksum|
    read_buffer = "".b
    while io.read(default_chunk_size, read_buffer)
      checksum << read_buffer
    end

    io.rewind
  end.base64digest
end

#content_disposition_with(type: "inline", filename:) (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 217

def content_disposition_with(type: "inline", filename:)
  disposition = (type.to_s.presence_in(%w( attachment inline )) || "inline")
  ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename.sanitized)
end

#custom_metadata_headers(metadata) (private)

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 197

def ()
  raise NotImplementedError
end

#default_chunk_size (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 185

def default_chunk_size
  5.megabytes
end

#delete(key)

Delete the file at the key.

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 109

def delete(key)
  raise NotImplementedError
end

#delete_prefixed(prefix)

Delete files at keys starting with the prefix.

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 114

def delete_prefixed(prefix)
  raise NotImplementedError
end

#download(key)

Return the content of the file at the key.

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 90

def download(key)
  raise NotImplementedError
end

#download_and_verify_tempfile(key, checksum: nil, verify: true, name: "ActiveStorage-", tmpdir: nil, &block) (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 222

def download_and_verify_tempfile(key, checksum: nil, verify: true, name: "ActiveStorage-", tmpdir: nil, &block)
  tempfile = Tempfile.new(name, tmpdir, binmode: true)
  download(key) { |chunk| tempfile.write(chunk) }
  tempfile.flush
  tempfile.rewind

  verify_integrity_of(tempfile, checksum: checksum) if verify
  if block_given?
    begin
      yield tempfile
    ensure
      tempfile.close!
    end
  else
    tempfile
  end
end

#download_chunk(key, range)

Return the partial content in the byte range of the file at the key.

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 95

def download_chunk(key, range)
  raise NotImplementedError
end

#exist?(key) ⇒ Boolean

Return true if a file exists at the key.

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 119

def exist?(key)
  raise NotImplementedError
end

#headers_for_direct_upload(key, filename:, content_type:, content_length:, checksum:, custom_metadata: {})

Returns a ::Hash of headers for #url_for_direct_upload requests.

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 151

def headers_for_direct_upload(key, filename:, content_type:, content_length:, checksum:, custom_metadata: {})
  {}
end

#inspect

This method is for internal use only.
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 159

def inspect # :nodoc:
  "#<#{self.class}#{name.present? ? " name=#{name.inspect}" : ""}>"
end

#instrument(operation, payload = {}, &block) (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 206

def instrument(operation, payload = {}, &block)
  ActiveSupport::Notifications.instrument(
    "service_#{operation}.active_storage",
    payload.merge(service: service_name), &block)
end

#open(*args, **options, &block)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 99

def open(*args, **options, &block)
  download_and_verify_tempfile(*args, **options, &block)
end

#private_url(key, expires_in:, filename:, disposition:, content_type:) (private)

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 189

def private_url(key, expires_in:, filename:, disposition:, content_type:, **)
  raise NotImplementedError
end

#public_url(key) (private)

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 193

def public_url(key, **)
  raise NotImplementedError
end

#service_name (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 212

def service_name
  # ActiveStorage::Service::DiskService => Disk
  self.class.name.split("::").third.remove("Service")
end

#update_metadata(key, **metadata)

Update metadata for the file identified by key in the service.

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 83

def (key, **)
  instrument :, key: key, ** do
     key, **
  end
end

#update_metadata_for(key, **metadata) (private)

Override in subclasses only if the service needs to store specific metadata that has to be updated upon identification.

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 203

def (key, **)
end

#upload(key, io, checksum: nil, **options)

Upload the io to the key specified. If a checksum is provided, the service will ensure a match when the upload has completed or raise an IntegrityError.

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 78

def upload(key, io, checksum: nil, **options)
  raise NotImplementedError
end

#url(key, **options)

Returns the URL for the file at the key. This returns a permanent URL for public files, and returns a short-lived URL for private files. For private files you can provide the disposition (:inline or :attachment), filename, and content_type that you wish the file to be served with on request. Additionally, you can also provide the amount of seconds the URL will be valid for, specified in expires_in.

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 127

def url(key, **options)
  instrument :url, key: key do |payload|
    generated_url =
      if public?
        public_url(key, **options)
      else
        private_url(key, **options)
      end

    payload[:url] = generated_url

    generated_url
  end
end

#url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, custom_metadata: {})

Returns a signed, temporary URL that a direct upload file can be PUT to on the key. The URL will be valid for the amount of seconds specified in expires_in. You must also provide the content_type, content_length, and checksum of the file that will be uploaded. All these attributes will be validated by the service upon upload.

Raises:

  • (NotImplementedError)
[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 146

def url_for_direct_upload(key, expires_in:, content_type:, content_length:, checksum:, custom_metadata: {})
  raise NotImplementedError
end

#verify_integrity_of(file, checksum:) (private)

[ GitHub ]

  
# File 'activestorage/lib/active_storage/service.rb', line 240

def verify_integrity_of(file, checksum:)
  actual_checksum = compute_checksum(file)
  unless actual_checksum == checksum
    raise ActiveStorage::IntegrityError, "Checksum verification failed expecting #{checksum}, but downloaded file having #{actual_checksum}"
  end
end