-
Notifications
You must be signed in to change notification settings - Fork 22
Howto: Adding a new storage engine
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?
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
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
The CarrierWave::Uploader::Configuration::ClassMethods#add_config
method defines new instance methods on the CarrierWave::Uploader::Base
class.
class CarrierWave::Uploader::Base
add_config :active_record_tablename
end
@uploader.active_record_tablename = 'my_table'
@uploader.active_record_tablename # => 'my_table'
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
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
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
.
Your retrieve!
method should return a File
or CarrierWave::SanitizedFile
.
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
.
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.).
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
.
The uploader proxies certain method calls through to the storage engine
using file.respond_to? :method_name
. For example, #url()
.