Skip to content

Commit dab7918

Browse files
committed
Add resource metadata to Ash.NotLoaded struct
1 parent 9f2e2ca commit dab7918

File tree

5 files changed

+187
-17
lines changed

5 files changed

+187
-17
lines changed

lib/ash/actions/helpers.ex

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1018,7 +1018,8 @@ defmodule Ash.Actions.Helpers do
10181018
end
10191019
end)
10201020
|> Map.new(fn attribute ->
1021-
{attribute.name, %Ash.NotLoaded{field: attribute.name, type: :attribute}}
1021+
{attribute.name,
1022+
%Ash.NotLoaded{field: attribute.name, type: :attribute, resource: resource}}
10221023
end)
10231024
end
10241025

lib/ash/not_loaded.ex

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,36 @@
44

55
defmodule Ash.NotLoaded do
66
@moduledoc "Used when a field hasn't been loaded or selected."
7-
defstruct [:field, :type]
7+
defstruct [:field, :type, :resource]
88

99
@type t :: %__MODULE__{
1010
field: atom,
11-
type: :relationship | :calculation | :aggregate | :attribute
11+
type: :relationship | :calculation | :aggregate | :attribute,
12+
resource: module | nil
1213
}
1314

1415
defimpl Inspect do
1516
import Inspect.Algebra
1617

1718
def inspect(not_loaded, opts) do
18-
concat([
19+
custom_options = Map.get(opts, :custom_options, [])
20+
in_resource? = Keyword.get(custom_options, :in_resource?, false)
21+
show_resource? = not is_nil(not_loaded.resource) and !in_resource?
22+
23+
container_doc(
1924
"#Ash.NotLoaded<",
20-
to_doc(not_loaded.type, opts),
21-
", field: ",
22-
to_doc(not_loaded.field, opts),
23-
">"
24-
])
25+
[
26+
to_doc(not_loaded.type, opts),
27+
concat("field: ", to_doc(not_loaded.field, opts)),
28+
or_empty(concat("resource: ", to_doc(not_loaded.resource, opts)), show_resource?)
29+
],
30+
">",
31+
opts,
32+
fn doc, _opts -> doc end
33+
)
2534
end
35+
36+
defp or_empty(value, true), do: value
37+
defp or_empty(_, false), do: empty()
2638
end
2739
end

lib/ash/resource.ex

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,15 @@ defmodule Ash.Resource do
232232
fields = @show_fields -- subtract_fields
233233

234234
fun = fn field, opts ->
235+
custom_options = Map.get(opts, :custom_options, [])
236+
237+
opts =
238+
Map.put(
239+
opts,
240+
:custom_options,
241+
Keyword.put(custom_options, :in_resource?, true)
242+
)
243+
235244
Inspect.List.keyword({field, Map.get(record, field)}, opts)
236245
end
237246

@@ -618,8 +627,15 @@ defmodule Ash.Resource do
618627
Map.get(record, resource_calculation.name)
619628
else
620629
case Map.fetch(record.calculations, resource_calculation.name) do
621-
{:ok, value} -> value
622-
:error -> %Ash.NotLoaded{type: :calculation, field: resource_calculation.name}
630+
{:ok, value} ->
631+
value
632+
633+
:error ->
634+
%Ash.NotLoaded{
635+
type: :calculation,
636+
field: resource_calculation.name,
637+
resource: record.__struct__
638+
}
623639
end
624640
end
625641

lib/ash/resource/schema.ex

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,12 @@ defmodule Ash.Schema do
8787
Module.put_attribute(
8888
__MODULE__,
8989
:ash_struct_fields,
90-
{relationship.name, %Ash.NotLoaded{type: :relationship, field: relationship.name}}
90+
{relationship.name,
91+
%Ash.NotLoaded{
92+
type: :relationship,
93+
field: relationship.name,
94+
resource: __MODULE__
95+
}}
9196
)
9297
end
9398

@@ -110,7 +115,8 @@ defmodule Ash.Schema do
110115
Module.put_attribute(
111116
__MODULE__,
112117
:ash_struct_fields,
113-
{aggregate.name, %Ash.NotLoaded{type: :aggregate, field: aggregate.name}}
118+
{aggregate.name,
119+
%Ash.NotLoaded{type: :aggregate, field: aggregate.name, resource: __MODULE__}}
114120
)
115121
end
116122

@@ -140,7 +146,8 @@ defmodule Ash.Schema do
140146
Module.put_attribute(
141147
__MODULE__,
142148
:ash_struct_fields,
143-
{calculation.name, %Ash.NotLoaded{type: :calculation, field: calculation.name}}
149+
{calculation.name,
150+
%Ash.NotLoaded{type: :calculation, field: calculation.name, resource: __MODULE__}}
144151
)
145152
end
146153

@@ -258,7 +265,12 @@ defmodule Ash.Schema do
258265
Module.put_attribute(
259266
__MODULE__,
260267
:ash_struct_fields,
261-
{relationship.name, %Ash.NotLoaded{type: :relationship, field: relationship.name}}
268+
{relationship.name,
269+
%Ash.NotLoaded{
270+
type: :relationship,
271+
field: relationship.name,
272+
resource: __MODULE__
273+
}}
262274
)
263275
end
264276

@@ -281,7 +293,8 @@ defmodule Ash.Schema do
281293
Module.put_attribute(
282294
__MODULE__,
283295
:ash_struct_fields,
284-
{aggregate.name, %Ash.NotLoaded{type: :aggregate, field: aggregate.name}}
296+
{aggregate.name,
297+
%Ash.NotLoaded{type: :aggregate, field: aggregate.name, resource: __MODULE__}}
285298
)
286299
end
287300

@@ -311,7 +324,8 @@ defmodule Ash.Schema do
311324
Module.put_attribute(
312325
__MODULE__,
313326
:ash_struct_fields,
314-
{calculation.name, %Ash.NotLoaded{type: :calculation, field: calculation.name}}
327+
{calculation.name,
328+
%Ash.NotLoaded{type: :calculation, field: calculation.name, resource: __MODULE__}}
315329
)
316330
end
317331

test/not_loaded_test.exs

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule Ash.Test.NotLoadedTest do
6+
@moduledoc false
7+
use ExUnit.Case, async: true
8+
9+
defmodule Domain do
10+
@moduledoc false
11+
use Ash.Domain
12+
13+
resources do
14+
allow_unregistered? true
15+
end
16+
end
17+
18+
defmodule Post do
19+
@moduledoc false
20+
use Ash.Resource,
21+
domain: Domain,
22+
data_layer: Ash.DataLayer.Ets
23+
24+
ets do
25+
private?(true)
26+
end
27+
28+
actions do
29+
default_accept :*
30+
defaults [:read, :destroy, create: :*, update: :*]
31+
end
32+
33+
attributes do
34+
uuid_primary_key :id
35+
attribute :title, :string, public?: true
36+
attribute :body, :string, public?: true
37+
end
38+
39+
relationships do
40+
belongs_to :author, __MODULE__, public?: true
41+
end
42+
43+
calculations do
44+
calculate :title_upcase, :string, expr(string_upcase(title)) do
45+
public? true
46+
end
47+
end
48+
49+
aggregates do
50+
count :count_of_related, :author, public?: true
51+
end
52+
end
53+
54+
describe "struct" do
55+
test "resource field defaults to nil" do
56+
not_loaded = %Ash.NotLoaded{type: :relationship, field: :author}
57+
assert not_loaded.resource == nil
58+
end
59+
60+
test "resource field can be set" do
61+
not_loaded = %Ash.NotLoaded{type: :relationship, field: :author, resource: Post}
62+
assert not_loaded.resource == Post
63+
end
64+
end
65+
66+
describe "resource in struct defaults" do
67+
test "relationship fields have resource set in struct default" do
68+
post = struct(Post)
69+
assert %Ash.NotLoaded{resource: Post, type: :relationship} = post.author
70+
end
71+
72+
test "aggregate fields have resource set in struct default" do
73+
post = struct(Post)
74+
assert %Ash.NotLoaded{resource: Post, type: :aggregate} = post.count_of_related
75+
end
76+
77+
test "calculation fields have resource set in struct default" do
78+
post = struct(Post)
79+
assert %Ash.NotLoaded{resource: Post, type: :calculation} = post.title_upcase
80+
end
81+
end
82+
83+
describe "inspect" do
84+
test "shows resource when inspected standalone" do
85+
not_loaded = %Ash.NotLoaded{type: :relationship, field: :author, resource: Post}
86+
result = inspect(not_loaded)
87+
assert result =~ "resource:"
88+
assert result =~ "Post"
89+
end
90+
91+
test "omits resource when resource is nil" do
92+
not_loaded = %Ash.NotLoaded{type: :relationship, field: :author}
93+
result = inspect(not_loaded)
94+
refute result =~ "resource:"
95+
end
96+
97+
test "hides resource inside a record inspect" do
98+
post = struct(Post)
99+
result = inspect(post)
100+
assert result =~ "NotLoaded"
101+
refute result =~ "resource:"
102+
end
103+
104+
test "hides resource when in_resource? custom option is set" do
105+
not_loaded = %Ash.NotLoaded{type: :relationship, field: :author, resource: Post}
106+
opts = %Inspect.Opts{custom_options: [in_resource?: true]}
107+
108+
result =
109+
Inspect.Algebra.format(Inspect.inspect(not_loaded, opts), 80)
110+
|> IO.iodata_to_binary()
111+
112+
refute result =~ "resource:"
113+
end
114+
115+
test "shows resource when in_resource? is not set" do
116+
not_loaded = %Ash.NotLoaded{type: :relationship, field: :author, resource: Post}
117+
opts = %Inspect.Opts{}
118+
119+
result =
120+
Inspect.Algebra.format(Inspect.inspect(not_loaded, opts), 80)
121+
|> IO.iodata_to_binary()
122+
123+
assert result =~ "resource:"
124+
assert result =~ "Post"
125+
end
126+
end
127+
end

0 commit comments

Comments
 (0)