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:
- saving files (
#save
) - deleting files (
#delete
) - opening files (
#open
) - verifying that files exist (
#exist?
) - retrieving file sizes (
#size
) - retrieving file URLs (
#url
)
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)