zachdaniel

zachdaniel

Creator of Ash

AshStorage - Attachment and file management for Ash Framework

Introducing AshStorage! Attachment and file management that slots directly into your resources :smiling_face_with_sunglasses:

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)

https://github.com/ash-project/ash_storage

Most Liked

hwuethrich

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

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 :slight_smile:

munksgaard

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?

Where Next?

Popular in Announcing Top

mathieuprog
Hello :waving_hand: Allow me to introduce you to Tz, an alternative time zone database support to Tzdata. Why another library? First a...
New
ityonemo
Currently just starting out on a new mini-project - getting zig NIFs to run in elixir. https://github.com/ityonemo/zigler The idea here...
New
gabrielpoca
Hello everyone! I want to share with you something that I’m really proud of: https://stillstatic.io/ Still is a static site builder for...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43806 214
New
blatyo
The best overview for how things are tied together is this presentation. Modules and functions are pretty well documented at this point, ...
New
kip
Image is an image processing library for Elixir. It is based upon the fabulous vix library that provides a libvips wrapper for Elixir. I...
622 19010 194
New
markmark206
simple_feature_flags is a tiny package that lets you turn features on or off based on which environment (e.g. localhost, staging, product...
New
alisinabh
Hey everyone i’ve developed a library for Jalaali calendar for elixir which supports converting Gregorian dates to Jalaali and vice vers...
New
mattludwigs
Grizzly is a library for working with Z-Wave devices. Z-Wave is a low-frequency radio protocol for controlling smart home devices on a me...
New
trisolaran
Hi! :waving_hand: I would like to present LiveSelect, a little library that I wrote to easily add a dynamic selection input to your LV f...
198 10978 107
New

Other popular topics Top

malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
Lily
In templates/appointment/index.html.eex: <%= for appointment <- @appointments do %> <tr> <td><%= appoi...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
saif
Hello everyone, Long time lurker first time poster here. I’ve recently begun working on Elixir full-time again! :raised_hands: It’s been...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New

We're in Beta

About us Mission Statement