zachdaniel
AshStorage - Attachment and file management for Ash Framework
Introducing AshStorage! Attachment and file management that slots directly into your resources ![]()
I had hoped to get this to a releasable state before sharing. It’s not quite there yet but I’m announcing it anyway as I know the Ash community is waiting.
It isn’t on hex yet, but I’ve gotten quite a bit of the roadmap done and its in a decent place to accept contributions from the community, and for beta testers to give it a shot.
Examples:
defmodule MyApp.Post do
use Ash.Resource,
extensions: [AshStorage]
storage do
service {AshStorage.Service.S3, bucket: "my-app-uploads"}
blob_resource MyApp.StorageBlob
attachment_resource MyApp.StorageAttachment
has_one_attached :cover_image do
analyzer MyApp.ImageDimensions,
write_attributes: [width: :image_width, height: :image_height]
variant :thumbnail, {MyApp.Resize, width: 200, height: 200}, generate: :eager
variant :hero, {MyApp.Resize, width: 1200, height: 630}, generate: :oban
variant :webp, {MyApp.Convert, format: :webp}
end
has_many_attached :documents do
analyzer MyApp.FileInfo
analyzer MyApp.PdfPageCount, analyze: :oban
variant :preview, MyApp.PdfThumbnail, generate: :oban
end
end
attributes do
uuid_primary_key :id
attribute :title, :string, allow_nil?: false, public?: true
attribute :image_width, :integer, public?: true
attribute :image_height, :integer, public?: true
end
actions do
create :create do
accept [:title]
argument :cover_image, Ash.Type.File, allow_nil?: true
change {AshStorage.Changes.HandleFileArgument,
argument: :cover_image, attachment: :cover_image}
end
end
end
defmodule MyApp.StorageBlob do
use Ash.Resource,
extensions: [AshStorage.BlobResource, AshOban]
postgres do
table "storage_blobs"
repo MyApp.Repo
end
oban do
triggers do
trigger :run_pending_analyzers do
action :run_pending_analyzers
read_action :read
where expr(pending_analyzers == true)
scheduler_cron("* * * * *")
end
trigger :run_pending_variants do
action :run_pending_variants
read_action :read
where expr(pending_variants == true)
scheduler_cron("* * * * *")
end
trigger :purge_blob do
action :purge_blob
read_action :read
where expr(pending_purge == true)
scheduler_cron("* * * * *")
end
end
end
attributes do
uuid_primary_key :id
end
end
# Create with file — dimensions written to parent, thumbnail generated instantly
post = Ash.create!(Post, %{title: "Hello", cover_image: upload})
post.image_width #=> 1920
# Variant URLs — thumbnail ready, hero generating in background
post = Ash.load!(post, [:cover_image_thumbnail_url, :cover_image_hero_url])
post.cover_image_thumbnail_url #=> "https://my-app-uploads.s3.amazonaws.com/..."
# On-demand — webp variant generated on first request
post = Ash.load!(post, :cover_image_webp_url)
Most Liked
hwuethrich
FYI, I just managed to get caching (client- and server-side) working with plug_http_cache:
{:plug_http_cache, "~> 0.4.0"},
{:http_cache, "~> 0.4.0"},
{:plug_cache_control, "~> 1.1"},
{:http_cache_store_disk, "~> 0.3.0"} # or http_cache_store_memory
config.ex:
config :http_cache_store_disk,
cache_dir: "priv/cache",
memory_limit: 256 * 1024 * 1024
Note: Had to use fixed memory_limit because my Mac is running low on memory, and the store rejects if memory >70% by default.
In my router.ex:
pipeline :cache do
plug PlugHTTPCache, store: :http_cache_store_disk
plug PlugCacheControl, directives: [:public, max_age: {24, :hour}]
end
scope "/" do
pipe_through [:browser, :cache]
forward "/storage", AshStorage.Plug.Proxy,
service:
{AshStoragePGLO.Service,
lo_resource: AshStorageTest.Gallery.StorageLO, base_url: "/storage"}
end
Ideally, the proxy should set etag/date headers based on the blob meta data (maybe using plug_http_validator).
Could also be useful when using proxy with S3 and other external services.
pierrelegall
I was working on a non-Ash Elixir package to manage S3 use for dev env (stored in the filesystem) and test env (stored in an ETS). See: GitHub - pierrelegall/ps3: S3-compatible storage plug for Elixir/Phoenix applications designed for dev & test environments. · GitHub
Thank you to solve this the Ash way!
I’ll be happy to beta test it as soon as possible ![]()
munksgaard
Really cool! Any plans/ideas for something like ash_storage_phoenix for making handling files in forms easier, or what’s the plan there?
Popular in Announcing
Other popular topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









