Skip to content
This repository has been archived by the owner on May 6, 2021. It is now read-only.

Howto: Adding a new storage engine

Chris Roos edited this page Oct 12, 2017 · 5 revisions

High-level overview

CarrierWave uses an "uploader" to handle the storage and retrieval of files to the storage engine. By default, the uploader is CarrierWave::Uploader, but you can extend or replace it as required. The uploader interacts with the ORM to handle any logic, such as validations or processing.

CarrierWave calls CarrierWave::Uploader::Base#store!(<CarrierWave::SanitizedFile>) and CarrierWave::Uploader::Base#retrieve!(identifier). Both methods should return an object which responds to methods from CarrierWave::SanitizedFile.

The return value of CarrierWave::SanitizedFile#identifier will be stored in the column upon which the uploader is mounted. Note CarrierWave::SanitizedFile#identifer is aliased to CarrierWave::SanitizedFile#filename, thus by default the identifier is the filename.

CarrierWave::Uploader::Base#retrieve!(identifier) is called with the value of the uploader column, e.g. the identifer which was written when the file was stored.

FIXME: How are duplicate files handled by existing engines?

Details

The uploader defines column-named methods as well, e.g. for a column named image, image_identifier, write_image_identifier.

FIXME: What are these for? write_image_identifier is called from a before_save hook.

class Article < AR::Base
  mount_uploader :image, ArticleImageUploader
  ...
end

@article.image_identifier

Configuration

A storage engine is specified by either

  • the user-implemented sub-class of CarrierWave::Uploader::Base:
class ImageUploader < CarrierWave::Uploader::Base
  storage :my_engine
end

or

  • the CarrierWave configuration block (perhaps provided in a Rails initializer, e.g. config/initializers/carrierwave.rb):
CarrierWave.configuration do |config|
  config.storage :my_engine
end

Accessing configuration

The CarrierWave::Uploader::Configuration::ClassMethods#add_config method defines new instance methods on the CarrierWave::Uploader::Base class.

Example

class CarrierWave::Uploader::Base
  add_config :active_record_tablename
end

@uploader.active_record_tablename = 'my_table'
@uploader.active_record_tablename # => 'my_table'

Implementation

Before beginning, read the Rdoc for the storage method: https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/uploader/configuration.rb#L70

The new engine must provide store!(CarrierWave::SanitizedFile) and retrieve!(identifier) methods, see the built-in Fog and File engines for examples.

The new engine should be added to CarrierWave::Uploader::Base.storage_engines, how to do this without altering CarrierWave core? https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/uploader/configuration.rb#L110

Required methods: store!(CarrierWave::SanitizedFile) and retrieve!(identifier)

These methods should be implemented in a subclass of Abstract. It can be helpful to define your own File class, which will be used by your engine Abstract subclass (see the Fog engine).

module CarrierWave
  module Storage

    class MyEngine < Abstract

      # From Abstract
      # attr_reader :uploader

      # def initialize(uploader)
      #   @uploader = uploader
      # end

      # def identifier
      #  uploader.filename
      # end

      #################
      # Abstract provides stubs we must implement.
      # 
      # Create and save a file instance to your engine.
      def store!(file)
        # File.create(file)
      end

      # Load and return a file instance from your engine.
      def retrieve!(identifier)
        # File.find(file)
      end

      # Subclass or duck-type CarrierWave::SanitizedFile ; responsible for storing the file to your engine.
      class File

        # Initialize as required.
        def initialize() ; end

        # Duck-type methods for CarrierWave::SanitizedFile. 
        def content_type ; end
        def url          ; end
        def read         ; end
        def size         ; end
        def delete       ; end
        def exists?      ; end
        # Others... ?

      end # File

    end # MyEngine
  end # Storage
end # CarrierWave

Saving the file to your engine: store!(CarrierWave::SanitizedFile)

As a subclass of Abstract, your engine has access to the Uploader by calling the attribute reader MyEngine#uploader (using the ivar @uploader directly is bad practice).

Use the methods on the passed CarrierWave::SanitizedFile to store the file in your engine appropriately.

Your store! method should return a File or CarrierWave::SanitizedFile.

Returning a file from your engine: retrieve!(engine_key)

Your retrieve! method should return a File or CarrierWave::SanitizedFile.

The returned file.

Base your returned file on the CarrierWave::SanitizedFile for the convenience of CarrierWave users. https://github.com/jnicklas/carrierwave/blob/master/lib/carrierwave/sanitized_file.rb

You could do this by subclassing CarrierWave::SanitizedFile, and override methods as required; or provide most methods, such that your MyEngine::File class duck-types CarrierWave::SanitizedFile.

Example: ActiveRecord engine

Distinction

This is the storage of file data; not the filename. CarrierWave will still store the name using your ORM and the attribute upon which you mounted your uploader (ex. mount_uploader :article, ArticleUploader).

Therefore, when CarrierWave is configured to use ActiveRecord as it's ORM (default), it will use ActiveRecord "twice" - once to store the filename in your model, and again for file data storage (using it's own table).

This is implementation is debatable. A configuration option could be added to modify CarrierWave::Uploader behaviour to add the required columns to the model table (binary blob, content-type, etc.).

Configuration

CarrierWave.configure do |config|
  config.active_record_tablename = 'carrier_wave_files' # Default.

  # Let us ':limit => X' on the data column.  But, punt and let the user do this in a model validation?
  # config.upload_max_size = X.megabytes
end

At runtime, the configuration settings are available on your uploader as methods, e.g. @uploader.active_record_tablename.

Uploader and Storage Engine interaction

The uploader proxies certain method calls through to the storage engine using file.respond_to? :method_name. For example, #url().