Supabase Auth Testing: Don't Forget Your Plugs and LiveView Hooks

The elixir supabase library is community developed (Thank you!), but not quite as easy to work with as the javascript or Python variants. I hit a tricky issue today. I wanted to test some of my live views. In my routerI have pipeline :browser do plug :accepts, ["html"] plug :fetch_session plug :fetch_live_flash plug :put_root_layout, html: {DreamersdashWeb.Layouts, :root} plug :protect_from_forgery plug :put_secure_browser_headers plug :fetch_current_user plug :fetch_current_profile end pipeline :admin_browser do plug :browser plug :require_admin_profile end pipeline :photo_manager_browser do plug :browser plug :require_photo_manager_profile end # ... more scope "/", DreamersdashWeb do pipe_through :photo_manager_browser live_session :authenticated_photo_manager, on_mount: [ {DreamersdashWeb.Auth, :mount_current_user}, {DreamersdashWeb.Auth, :ensure_authenticated}, {DreamersdashWeb.Auth, :load_current_profile}, {DreamersdashWeb.Auth, :require_photo_manager_profile} ] do live "/admin/photos", PhotoReviewLive.Index, :index end end Then In my Dreamersdash.Auth module I have: ...

June 30, 2025 · 2 min · 321 words

Elixir Case Clause Errors from Changed Function Returns

Hit a tricky debugging scenario today that cost me more time than I’d like to admit. I accidentally changed a function’s return value from {:ok, atom} to just atom. Elsewhere in the code, I was calling that function with: with {:ok, atom} <- function() do # ... end After the change, this stopped working because there was no :ok tuple to match against. The confusing part? The error that showed up in the logs was a “missing case clause error” rather than something more obvious like a pattern match failure. ...

June 19, 2025 · 1 min · 160 words

Ecto's `preload` with joins uses a single query!

TIL that when you explicitly join associations in Ecto and use preload, it executes just ONE query: # ✅ Single query - uses the joined data! from(rt in RaceTeam, left_join: r in assoc(rt, :race), left_join: cl in assoc(rt, :clues), where: rt.id == ^team_id, preload: [race: r, clues: cl] ) |> Repo.one() I always thought preload meant multiple queries, but that’s only true without explicit joins: # ❌ Multiple queries RaceTeam |> preload([:race, :clues]) |> Repo.one() The joined version handles the SQL row duplication problem automatically, grouping everything into the proper nested structure. No more N+1 queries AND no manual grouping needed! 🚀 ...

May 28, 2025 · 1 min · 102 words

Ecto changeset retrieval methods

When working with Ecto.changesets I have a hard time remembering when I should use get_field/3, get_change/3, fetch_field/2 and fetch_change/2 Here’s my summary: the get_* methods are used when want to be able to provide a default value the fetch_* methods return a tuple with the source of the data (or :ok) OR :error the *_field methods will search either the existing data or the changes the *_change methods will search only the changes for the key Short doc snippets for each of the methods ...

November 5, 2024 · 1 min · 165 words

Elixir Update in

The update_in function and Access module of Elixir are extremely powerful. Here’s a snippet I just wrote in Jupyteach: def strip_jupyteach_metadata_from_ipynb_map(ipynb) do ipynb |> update_in(["metadata"], &Map.drop(&1, ["jupyteach"])) |> update_in(["cells", Access.all(), "metadata"], &Map.drop(&1, ["jupyteach"])) end ipynb is a map that follows the Jupyter notebook spec To support some features I jupyteach, I add custom cell metadata to the cells so when we import back in to Jupyteach we know where we are. Some of these metadata entries are sensitive and should be stripped out when exporting for public use or even for student use (we store solutions in the metadata!) ...

October 25, 2024 · 1 min · 161 words