From b6c5aa5402d1daa281c1e76a69a9a8e7b3f25824 Mon Sep 17 00:00:00 2001 From: JohnDoneth Date: Tue, 18 Feb 2025 01:12:00 -0500 Subject: [PATCH] improvement: return a single record from Ash.read when the action has get?: true --- lib/ash.ex | 2 +- lib/ash/actions/read/read.ex | 17 +++++++++++++++-- lib/ash/changeset/changeset.ex | 3 +++ lib/ash/helpers.ex | 12 ++++++++++++ test/actions/read_test.exs | 21 +++++++++++++++++++++ 5 files changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/ash.ex b/lib/ash.ex index 9937c5fc6..18437e085 100644 --- a/lib/ash.ex +++ b/lib/ash.ex @@ -2191,7 +2191,7 @@ defmodule Ash do defp do_read_one(query, action, opts) do query |> Ash.Actions.Read.unpaginated_read(action, opts) - |> Ash.Helpers.unwrap_one() + |> Ash.Helpers.unwrap_one_if(!action.get?) |> case do {:ok, nil} -> if opts[:not_found_error?] do diff --git a/lib/ash/actions/read/read.ex b/lib/ash/actions/read/read.ex index de48f8e13..3e64057d5 100644 --- a/lib/ash/actions/read/read.ex +++ b/lib/ash/actions/read/read.ex @@ -20,8 +20,9 @@ defmodule Ash.Actions.Read do end @spec run(Ash.Query.t(), Ash.Resource.Actions.action(), Keyword.t()) :: - {:ok, Ash.Page.page() | list(Ash.Resource.record())} - | {:ok, Ash.Page.page() | list(Ash.Resource.record()), Ash.Query.t()} + {:ok, Ash.Page.page() | Ash.Resource.record() | list(Ash.Resource.record())} + | {:ok, Ash.Page.page() | Ash.Resource.record() | list(Ash.Resource.record()), + Ash.Query.t()} | {:error, term} def run(query, action, opts \\ []) @@ -373,6 +374,7 @@ defmodule Ash.Actions.Read do query, opts ) + |> unwrap_if_get(query.action) |> add_query(query, opts) else {:error, %Ash.Query{errors: errors} = query} -> @@ -1839,6 +1841,17 @@ defmodule Ash.Actions.Read do end end + defp unwrap_if_get(result, action) do + if action && action.get? do + case result do + [] -> nil + [record | _] -> record + end + else + result + end + end + @doc false def add_page(data, action, count, sort, original_query, opts) do cond do diff --git a/lib/ash/changeset/changeset.ex b/lib/ash/changeset/changeset.ex index 6c7c39220..3bb0256d2 100644 --- a/lib/ash/changeset/changeset.ex +++ b/lib/ash/changeset/changeset.ex @@ -2246,6 +2246,9 @@ defmodule Ash.Changeset do {:ok, nil} -> changeset + {:ok, []} -> + changeset + {:ok, _} -> error = Ash.Error.Changes.InvalidChanges.exception( diff --git a/lib/ash/helpers.ex b/lib/ash/helpers.ex index 238249913..ba2643939 100644 --- a/lib/ash/helpers.ex +++ b/lib/ash/helpers.ex @@ -298,6 +298,14 @@ defmodule Ash.Helpers do end end + def unwrap_one_if(result, condition) do + if condition do + unwrap_one(result) + else + result + end + end + def unwrap_one({:error, error}) do {:error, error} end @@ -339,6 +347,10 @@ defmodule Ash.Helpers do {:error, error} end + def unwrap_one(struct) when is_struct(struct) do + {:ok, struct} + end + def resource_from_data!(data, query, opts) do if opts[:resource] do opts[:resource] diff --git a/test/actions/read_test.exs b/test/actions/read_test.exs index 898faf296..576d7a048 100644 --- a/test/actions/read_test.exs +++ b/test/actions/read_test.exs @@ -81,10 +81,12 @@ defmodule Ash.Test.Actions.ReadTest do end read :get_by_id do + get?(true) get_by(:id) end read :get_by_id_and_uuid do + get?(true) get_by([:id, :uuid]) end end @@ -401,6 +403,25 @@ defmodule Ash.Test.Actions.ReadTest do |> Ash.Query.offset(1) |> Ash.read() end + + test "with get?: true actions, it returns the record instead of a list" do + post = + Post + |> Ash.Changeset.for_create(:create, %{title: "test", contents: "yeet"}) + |> Ash.create!() + + assert {:ok, %Post{}} = + Post + |> Ash.Query.for_read(:get_by_id, %{id: post.id}) + |> Ash.read() + end + + test "with get?: true actions, it returns nil for missing records" do + assert {:ok, nil} = + Post + |> Ash.Query.for_read(:get_by_id, %{id: Ash.UUID.generate()}) + |> Ash.read() + end end describe "Ash.read!/2" do