diff --git a/lib/ash/actions/read.ex b/lib/ash/actions/read.ex index 9502c495..75c6a96f 100644 --- a/lib/ash/actions/read.ex +++ b/lib/ash/actions/read.ex @@ -281,6 +281,8 @@ defmodule Ash.Actions.Read do ) end + query = add_calculation_context(query, actor, authorize?, tenant, tracer) + query = %{ query | api: api, @@ -424,6 +426,77 @@ defmodule Ash.Actions.Read do [fetch, process] end + defp add_calculation_context(query, actor, authorize?, tenant, tracer) do + query = + if query.calculations do + %{ + query + | calculations: + Map.new(query.calculations, fn {name, calc} -> + {name, + %{ + calc + | context: + Map.merge( + %{actor: actor, authorize?: authorize?, tenant: tenant, tracer: tracer}, + calc.context + ) + }} + end) + } + end + + if query.filter do + %{ + query + | filter: add_calc_context_to_filter(query.filter, actor, authorize?, tenant, tracer) + } + else + query + end + end + + defp add_calc_context_to_filter(filter, actor, authorize?, tenant, tracer) do + Ash.Filter.map(filter, fn + %Ash.Query.Parent{} = parent -> + %{ + parent + | expr: add_calc_context_to_filter(parent.expr, actor, authorize?, tenant, tracer) + } + + %Ash.Query.Exists{} = exists -> + %{ + exists + | expr: add_calc_context_to_filter(exists.expr, actor, authorize?, tenant, tracer) + } + + %Ash.Query.Ref{attribute: %Ash.Resource.Calculation{}} = ref -> + raise Ash.Error.Framework.AssumptionFailed, + message: "Unhandled calculation in filter statement #{inspect(ref)}" + + %Ash.Query.Ref{attribute: %Ash.Query.Calculation{} = calc} = ref -> + %{ + ref + | attribute: %{ + calc + | context: + Map.merge( + %{ + actor: actor, + authorize?: authorize?, + tenant: tenant, + tracer: tracer + }, + calc.context + ) + } + } + + other -> + other + end) + end + defp unwrap_for_get({:ok, [value | _]}, true, _, _resource), do: {:ok, value} defp unwrap_for_get({:ok, []}, true, true, resource), diff --git a/test/calculation_test.exs b/test/calculation_test.exs index a0ec300a..32e52683 100644 --- a/test/calculation_test.exs +++ b/test/calculation_test.exs @@ -25,6 +25,21 @@ defmodule Ash.Test.CalculationTest do end end + defmodule NameWithUsersName do + # Don't do this kind of thing in real life + use Ash.Calculation + + def load(_, _, _) do + [:full_name] + end + + def calculate(records, _opts, %{actor: actor}) do + Enum.map(records, fn record -> + record.full_name <> " " <> actor.first_name <> " " <> actor.last_name + end) + end + end + defmodule ConcatWithLoad do # An example concatenation calculation, that accepts the delimiter as an argument use Ash.Calculation @@ -160,6 +175,7 @@ defmodule Ash.Test.CalculationTest do calculate :best_friends_name, :string, BestFriendsName calculate :names_of_best_friends_of_me, :string, NamesOfBestFriendsOfMe + calculate :name_with_users_name, :string, NameWithUsersName calculate :conditional_full_name, :string, @@ -258,6 +274,14 @@ defmodule Ash.Test.CalculationTest do assert %{slug: "zach daniel123"} = Api.load!(user, [:slug]) end + test "calculations can access the actor", %{user1: user1, user2: user2} do + assert %{ + name_with_users_name: "zach daniel brian cranston" + } = + user1 + |> Api.load!(:name_with_users_name, actor: user2) + end + test "it loads anything specified by the load callback" do full_names = User