Skip to main content
Version: 0.4

Create custom file storages

Marten uses a file storage mechanism to perform file operations like saving files, deleting files, generating URLs, ... This file storages mechanism allows to save files in different backends by leveraging a standardized API. You can leverage this capability to implement custom file storages (which you can then use for assets or as part of file model fields).

Basic file storage implementation

File storages are implemented as subclasses of the Marten::Core::Storage::Base abstract class. As such, they must implement a set of mandatory methods which provide the following functionalities:

Note that you can fully customize how file storage objects are initialized.

For example, a custom-made "file system" storage (that reads and writes files in a specific folder of the local file system) could be implemented as follows:

require "file_utils"

class FileSystem < Marten::Core::Storage::Base
def initialize(@root : String, @base_url : String)
end

def delete(filepath : String) : Nil
File.delete(path(filepath))
rescue File::NotFoundError
raise Marten::Core::Storage::Errors::FileNotFound.new("File '#{filepath}' cannot be found")
end

def exists?(filepath : String) : Bool
File.exists?(path(filepath))
end

def open(filepath : String) : IO
File.open(path(filepath), mode: "rb")
rescue File::NotFoundError
raise Marten::Core::Storage::Errors::FileNotFound.new("File '#{filepath}' cannot be found")
end

def size(filepath : String) : Int64
File.size(path(filepath))
end

def url(filepath : String) : String
File.join(base_url, URI.encode_path(filepath))
end

def write(filepath : String, content : IO) : Nil
new_path = path(filepath)

FileUtils.mkdir_p(Path[new_path].dirname)

File.open(new_path, "wb") do |new_file|
IO.copy(content, new_file)
end
end

private getter root
private getter base_url

private def path(filepath)
File.join(root, filepath)
end
end

Using custom file storages

You have many options when it comes to using your custom file storage classes, and those depend on what you are trying to do:

  • if you want to use a custom storage for assets, then you will likely want to assign an instance of your custom storage class to the assets.storage setting (see Assets storage to learn more about assets storages specifically)
  • if you want to use a custom storage for all your file model fields, then you will likely want to assign an instance of your custom storage class to the media_files.storage setting (see File storages to learn more about file storages specifically)