Skip to content

Commit a684963

Browse files
authored
fix: pass actor to Ash.Query.for_read for aggregate and sort
2 parents b1ff9ed + 1c1eb85 commit a684963

3 files changed

Lines changed: 134 additions & 81 deletions

File tree

lib/aggregate.ex

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -969,41 +969,11 @@ defmodule AshSql.Aggregate do
969969
{:cont, {:ok, [aggregate | aggregates]}}
970970

971971
aggregate, {:ok, aggregates} ->
972-
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)
973-
974-
read_action =
975-
aggregate.read_action || Ash.Resource.Info.primary_action!(related, :read).name
976-
977-
with %{valid?: true} = aggregate_query <- Ash.Query.for_read(related, read_action),
978-
%{valid?: true} = aggregate_query <-
979-
Ash.Query.build(aggregate_query, filter: aggregate.filter, sort: aggregate.sort) do
980-
Ash.Query.Aggregate.new(
981-
resource,
982-
aggregate.name,
983-
aggregate.kind,
984-
path: aggregate.relationship_path,
985-
query: aggregate_query,
986-
field: aggregate.field,
987-
default: aggregate.default,
988-
filterable?: aggregate.filterable?,
989-
type: aggregate.type,
990-
sortable?: aggregate.filterable?,
991-
include_nil?: aggregate.include_nil?,
992-
constraints: aggregate.constraints,
993-
implementation: aggregate.implementation,
994-
uniq?: aggregate.uniq?,
995-
read_action:
996-
aggregate.read_action ||
997-
Ash.Resource.Info.primary_action!(
998-
Ash.Resource.Info.related(resource, aggregate.relationship_path),
999-
:read
1000-
).name,
1001-
authorize?: aggregate.authorize?
1002-
)
1003-
else
1004-
%{errors: errors} ->
1005-
{:error, errors}
1006-
end
972+
resource
973+
|> resource_aggregate_to_aggregate(aggregate,
974+
actor: private_context[:actor],
975+
tenant: private_context[:tenant]
976+
)
1007977
|> case do
1008978
{:ok, aggregate} ->
1009979
aggregate =
@@ -1027,6 +997,44 @@ defmodule AshSql.Aggregate do
1027997
end)
1028998
end
1029999

1000+
@doc false
1001+
def resource_aggregate_to_aggregate(resource, aggregate, opts \\ []) do
1002+
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)
1003+
1004+
read_action =
1005+
aggregate.read_action || Ash.Resource.Info.primary_action!(related, :read).name
1006+
1007+
with %{valid?: true} = aggregate_query <-
1008+
Ash.Query.for_read(related, read_action, %{},
1009+
actor: opts[:actor],
1010+
tenant: opts[:tenant]
1011+
),
1012+
%{valid?: true} = aggregate_query <-
1013+
Ash.Query.build(aggregate_query, filter: aggregate.filter, sort: aggregate.sort) do
1014+
Ash.Query.Aggregate.new(
1015+
resource,
1016+
aggregate.name,
1017+
aggregate.kind,
1018+
path: aggregate.relationship_path,
1019+
query: aggregate_query,
1020+
field: aggregate.field,
1021+
default: aggregate.default,
1022+
filterable?: aggregate.filterable?,
1023+
type: aggregate.type,
1024+
sortable?: aggregate.filterable?,
1025+
include_nil?: aggregate.include_nil?,
1026+
constraints: aggregate.constraints,
1027+
implementation: aggregate.implementation,
1028+
uniq?: aggregate.uniq?,
1029+
read_action: read_action,
1030+
authorize?: aggregate.authorize?
1031+
)
1032+
else
1033+
%{errors: errors} ->
1034+
{:error, errors}
1035+
end
1036+
end
1037+
10301038
defp add_first_join_aggregate(
10311039
query,
10321040
_resource,

lib/sort.ex

Lines changed: 9 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -43,52 +43,15 @@ defmodule AshSql.Sort do
4343
{calculation, val}
4444

4545
%Ash.Resource.Aggregate{} = aggregate ->
46-
related = Ash.Resource.Info.related(resource, aggregate.relationship_path)
47-
48-
read_action =
49-
aggregate.read_action ||
50-
Ash.Resource.Info.primary_action!(
51-
related,
52-
:read
53-
).name
54-
55-
with %{valid?: true} = aggregate_query <- Ash.Query.for_read(related, read_action),
56-
%{valid?: true} = aggregate_query <-
57-
Ash.Query.build(aggregate_query,
58-
filter: aggregate.filter,
59-
sort: aggregate.sort
60-
) do
61-
case Ash.Query.Aggregate.new(
62-
resource,
63-
aggregate.name,
64-
aggregate.kind,
65-
path: aggregate.relationship_path,
66-
query: aggregate_query,
67-
field: aggregate.field,
68-
default: aggregate.default,
69-
filterable?: aggregate.filterable?,
70-
type: aggregate.type,
71-
sortable?: aggregate.filterable?,
72-
include_nil?: aggregate.include_nil?,
73-
constraints: aggregate.constraints,
74-
implementation: aggregate.implementation,
75-
uniq?: aggregate.uniq?,
76-
read_action:
77-
aggregate.read_action ||
78-
Ash.Resource.Info.primary_action!(
79-
Ash.Resource.Info.related(resource, aggregate.relationship_path),
80-
:read
81-
).name,
82-
authorize?: aggregate.authorize?
83-
) do
84-
{:ok, agg} ->
85-
{agg, val}
86-
87-
{:error, error} ->
88-
raise Ash.Error.to_ash_error(error)
89-
end
90-
else
91-
%{errors: errors} -> raise Ash.Error.to_ash_error(errors)
46+
case AshSql.Aggregate.resource_aggregate_to_aggregate(resource, aggregate,
47+
actor: query.__ash_bindings__.context[:private][:actor],
48+
tenant: query.__ash_bindings__.context[:private][:tenant]
49+
) do
50+
{:ok, agg} ->
51+
{agg, val}
52+
53+
{:error, error} ->
54+
raise Ash.Error.to_ash_error(error)
9255
end
9356

9457
_ ->

test/aggregate_test.exs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# SPDX-FileCopyrightText: 2024 ash_sql contributors <https://github.com/ash-project/ash_sql/graphs/contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule AshSql.AggregateTest do
6+
use ExUnit.Case, async: true
7+
8+
defmodule Comment do
9+
use Ash.Resource, domain: AshSql.AggregateTest.Domain, data_layer: Ash.DataLayer.Ets
10+
11+
attributes do
12+
uuid_primary_key(:id)
13+
attribute(:post_id, :uuid)
14+
attribute(:score, :integer)
15+
end
16+
17+
actions do
18+
read :read_all do
19+
primary?(true)
20+
end
21+
end
22+
end
23+
24+
defmodule Post do
25+
use Ash.Resource, domain: AshSql.AggregateTest.Domain, data_layer: Ash.DataLayer.Ets
26+
27+
attributes do
28+
uuid_primary_key(:id)
29+
end
30+
31+
relationships do
32+
has_many(:comments, Comment, destination_attribute: :post_id)
33+
end
34+
35+
aggregates do
36+
max(:highest_score, :comments, :score)
37+
end
38+
39+
actions do
40+
defaults([:read])
41+
end
42+
end
43+
44+
defmodule Domain do
45+
use Ash.Domain, validate_config_inclusion?: false
46+
47+
authorization do
48+
require_actor?(true)
49+
end
50+
51+
resources do
52+
resource(Post)
53+
resource(Comment)
54+
end
55+
end
56+
57+
defp build(opts) do
58+
AshSql.Aggregate.resource_aggregate_to_aggregate(
59+
Post,
60+
Ash.Resource.Info.aggregate(Post, :highest_score),
61+
opts
62+
)
63+
end
64+
65+
describe "resource_aggregate_to_aggregate/3" do
66+
test "passes actor and tenant to the related read which may require an actor" do
67+
actor = %{id: Ash.UUID.generate()}
68+
69+
assert {:ok, aggregate} = build(actor: actor, tenant: "acme")
70+
71+
assert aggregate.query.context.private.actor == actor
72+
assert aggregate.query.tenant == "acme"
73+
end
74+
75+
test "reads the related resource through its primary read action" do
76+
assert {:ok, aggregate} = build(actor: %{id: Ash.UUID.generate()})
77+
78+
assert aggregate.query.resource == Comment
79+
assert aggregate.read_action == :read_all
80+
end
81+
end
82+
end

0 commit comments

Comments
 (0)