Files in memory, in a store that belongs to a process. It's meant for tests.
setup do
Fil.Adapter.Memory.checkout()
end
test "stores the avatar" do
disk = Fil.disk(adapter: Fil.Adapter.Memory, root: "uploads")
assert {:ok, _} = Fil.write(disk, "avatars/1.png", "png")
endStores
checkout/0 creates a store owned by the calling process, usually the test. The store is an ETS table, so it's gone
when the owner exits. Every test starts with an empty store and nothing needs cleaning up, with async: true too.
Every operation uses the store of the process that runs it:
- the store the process checked out itself
- a store it was allowed into with
allow/2 - the store of a process in its
$callers.Tasksets$callers, and so doesPhoenix.LiveViewTestfor the LiveView processes it starts, so both find the test's store withoutallow/2
An operation in a process without a store raises, because that's a missing checkout/0 or allow/2 in the test and
not a storage condition.
The disk itself only holds the root, so it can be built anywhere (in config/test.exs, for example), and every disk
built in the test sees the same store.
Behaviour
- directories exist only as prefixes, like on an object store. There are no empty directories, and a stat on a
directory returns
%Fil.Stat{type: :directory}with every other fieldnil. - writes are atomic, and
if_exists: :erroruses:ets.insert_new/2, so its check is atomic too. stat/3sets:etagto the MD5 of the content in hex (the ETag S3 returns for a single-part upload), and:content_typeto thecontent_type:the write stored.- checksums work as on S3: a write with
checksum:stores the checksum of the content,stat/3with the same algorithm returns it (andnilfor another algorithm or a file written without one), andverify_checksum: trueon a read compares it with the content. A copy keeps the checksum. - a memory store has no URLs. Attach
Fil.Plugin.URLfor public and signed URLs, andFil.Plugserves them, in a test throughPhoenix.ConnTesttoo.
Options
:root(String.t/0) - The prefix every path is resolved against, inside the store. Two disks on the same store see each other's files where their roots overlap, the same as two local disks on one filesystem. The default value is".".
Errors
:reason is the POSIX atom Fil.Adapter.Local would return in the same situation.
| Situation | Fil error | :reason |
|---|---|---|
| a missing file | Fil.NotFoundError | :enoent |
| an exclusive create finding the file already there | Fil.AlreadyExistsError | :eexist |
verify_checksum: true and content that doesn't match | Fil.ChecksumMismatchError | :checksum_mismatch |
Summary
Types
@type t() :: %Fil.Adapter.Memory{prefix: String.t()}
Functions
Lets allowed use the store of owner.
Both can be a pid or a registered name. owner must have a store, from checkout/0 or from an earlier allow/2.
Processes that owner starts as Tasks don't need this.
setup do
Fil.Adapter.Memory.checkout()
Fil.Adapter.Memory.allow(self(), MyApp.Thumbnailer)
end
@spec checkout() :: :ok
Creates a store owned by the calling process.
Returns :ok, so it can be the whole setup block. Calling it again in the same process keeps the existing store.