Packages
ash
3.16.0
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/domain/info/diagram.ex
# SPDX-FileCopyrightText: 2019 ash contributors <https://github.com/ash-project/ash/graphs/contributors>
#
# SPDX-License-Identifier: MIT
defmodule Ash.Domain.Info.Diagram do
@moduledoc """
Generate Mermaid diagrams from a specified domain.
## Limitations
We can't easily model Ash relationships with Mermaid diagrams
because they are unidirectional and could be asymmetric.
Mermaid assumes symmetrical, bidirectional relationships.
If we try to model all unidirectional relationships as separate
lines in the diagram it gets very hard to read very quickly.
"""
@indent " "
@show_private? false
@argument_print_limit 4
@default_opts indent: @indent, show_private?: @show_private?
defp resource_name(resource, opts) do
if opts[:name] == :full do
inspect(resource)
else
resource
|> Ash.Resource.Info.short_name()
|> to_string()
|> Macro.camelize()
end
end
defp domain_name(domain, opts) do
if opts[:name] == :full do
inspect(domain)
else
domain
|> Ash.Domain.Info.short_name()
|> to_string()
|> Macro.camelize()
end
end
defp short_module(module, opts) do
if opts[:name] == :full do
inspect(module)
else
module
|> Module.split()
|> List.last()
end
end
defp normalise_relationships(domain) do
for resource <- Ash.Domain.Info.resources(domain) do
for relationship <- Ash.Resource.Info.relationships(resource) do
[
relationship.source,
relationship.destination
]
|> Enum.sort()
|> List.to_tuple()
end
end
|> Enum.flat_map(& &1)
|> Enum.uniq()
|> Enum.sort()
end
defp aggregate_type(_resource, %{kind: :custom, type: type}, opts) do
short_type(type, opts)
end
defp aggregate_type(resource, aggregate, opts) do
{:ok, type, _constraints} = Ash.Query.Aggregate.aggregate_type(resource, aggregate)
short_type(type, opts)
end
# default to one to one to just show connection
defp rel_type, do: "||--||"
defp short_type({:array, t}, opts), do: "ArrayOf#{short_type(t, opts)}"
defp short_type(t, opts), do: short_module(t, opts)
@doc """
Generates a Mermaid Entity Relationship Diagram for a given domain or list of domains.
Shows only public attributes, calculations, aggregates and actions.
Shows a one-to-one line for relationships as enumerating all unidirectional
relationships is far too noisy.
"""
def mermaid_er_diagram(domains, opts \\ @default_opts) do
"""
erDiagram
#{domains |> List.wrap() |> Enum.map(&mermaid_er_domain_section(&1, opts))}\
"""
end
defp mermaid_er_domain_section(domain, opts) do
indent = opts[:indent] || @indent
show_private? = Access.get(opts, :show_private?, @show_private?)
resources =
for resource <- Ash.Domain.Info.resources(domain) do
{attrs, calcs, aggs} =
if show_private? do
{
Ash.Resource.Info.attributes(resource),
Ash.Resource.Info.calculations(resource),
Ash.Resource.Info.aggregates(resource)
}
else
{
Ash.Resource.Info.public_attributes(resource),
Ash.Resource.Info.public_calculations(resource),
Ash.Resource.Info.public_aggregates(resource)
}
end
contents =
[
join_template(attrs, indent, &"#{short_type(&1.type, [])} #{&1.name}"),
join_template(calcs, indent, &"#{short_type(&1.type, [])} #{&1.name}"),
join_template(aggs, indent, &"#{aggregate_type(resource, &1, [])} #{&1.name}")
]
|> Enum.reject(&(&1 == ""))
|> Enum.join("\n")
"""
#{indent}#{inspect(resource_name(resource, opts))} {
#{contents}
#{indent}}
"""
end
|> Enum.join()
relationships =
for {src, dest} <- normalise_relationships(domain) do
~s(#{indent}#{inspect(resource_name(src, opts))} #{rel_type()} #{inspect(resource_name(dest, opts))} : "")
end
|> Enum.join("\n")
"""
#{resources}
#{relationships}
"""
end
defp class_short_type({:array, t}, opts), do: "#{short_module(t, opts)}[]"
defp class_short_type(t, opts), do: short_module(t, opts)
defp join_template(list, indent, template_fn) do
Enum.map_join(list, "\n", fn item -> "#{indent}#{indent}#{template_fn.(item)}" end)
end
defp list_arguments_and_attributes(resource, action, opts) do
arguments = action.arguments
attributes =
case action do
%{type: :action} -> []
%{type: :read} -> []
%{type: :destroy, soft?: false} -> []
%{accept: accept} -> accept |> Enum.map(&Ash.Resource.Info.attribute(resource, &1))
end
list_arguments(Enum.uniq_by(arguments ++ attributes, & &1.name), opts)
end
defp list_arguments(arguments, opts) when length(arguments) > @argument_print_limit do
{displayed_args, _rest} = Enum.split(arguments, @argument_print_limit)
list_arguments(displayed_args, opts) <> ", ..."
end
defp list_arguments(arguments, opts) do
arguments
|> Enum.map_join(", ", &"#{class_short_type(&1.type, opts)} #{&1.name}")
end
@doc """
Generates a Mermaid Class Diagram for a given domain.
Shows only public attributes, calculations, aggregates and actions.
Shows a connecting line for relationships with the type of relationship
indicated in the attribute list.
"""
def mermaid_class_diagram(domain, opts \\ @default_opts)
def mermaid_class_diagram(domains, opts) when is_list(domains) do
indent = opts[:indent] || @indent
show_private? = Access.get(opts, :show_private?, @show_private?)
resources =
for domain <- domains, resource <- Ash.Domain.Info.resources(domain) do
actions = Ash.Resource.Info.actions(resource)
{attrs, calcs, aggs, relationships} =
if show_private? do
{
Ash.Resource.Info.attributes(resource),
Ash.Resource.Info.calculations(resource),
Ash.Resource.Info.aggregates(resource),
Ash.Resource.Info.relationships(resource)
}
else
{Ash.Resource.Info.public_attributes(resource),
Ash.Resource.Info.public_calculations(resource),
Ash.Resource.Info.public_aggregates(resource),
Ash.Resource.Info.public_relationships(resource)}
end
contents =
[
join_template(attrs, indent, &"#{class_short_type(&1.type, opts)} #{&1.name}"),
join_template(calcs, indent, &"#{class_short_type(&1.type, opts)} #{&1.name}"),
join_template(aggs, indent, &"#{aggregate_type(resource, &1, opts)} #{&1.name}"),
join_template(
relationships,
indent,
&"#{resource_name(&1.destination, opts)}#{if &1.cardinality == :many, do: "[]", else: ""} #{&1.name}"
),
join_template(
actions,
indent,
&"#{&1.name}(#{list_arguments_and_attributes(resource, &1, opts)})"
)
]
|> Enum.reject(&(&1 == ""))
|> Enum.join("\n")
"""
#{indent}class #{resource_name(resource, opts)} {
#{indent}#{indent}Domain: #{domain_name(domain, opts)}#{source_link(resource, indent, opts)}
#{contents}
#{indent}}
"""
end
|> Enum.join()
relationships =
for domain <- domains,
{src, dest} <- normalise_relationships(domain) do
~s(#{indent}#{resource_name(src, opts)} -- #{resource_name(dest, opts)})
end
|> Enum.join("\n")
domains =
Enum.map_join(domains, "\n", fn domain ->
~s(#{indent}#{domain_name(domain, opts)})
end)
"""
classDiagram
#{domains}
#{resources}
#{relationships}
"""
end
def mermaid_class_diagram(domain, opts) do
indent = opts[:indent] || @indent
show_private? = Access.get(opts, :show_private?, @show_private?)
resources =
for resource <- Ash.Domain.Info.resources(domain) do
actions = Ash.Resource.Info.actions(resource)
{attrs, calcs, aggs, relationships} =
if show_private? do
{
Ash.Resource.Info.attributes(resource),
Ash.Resource.Info.calculations(resource),
Ash.Resource.Info.aggregates(resource),
Ash.Resource.Info.relationships(resource)
}
else
{Ash.Resource.Info.public_attributes(resource),
Ash.Resource.Info.public_calculations(resource),
Ash.Resource.Info.public_aggregates(resource),
Ash.Resource.Info.public_relationships(resource)}
end
contents =
[
join_template(attrs, indent, &"#{class_short_type(&1.type, opts)} #{&1.name}"),
join_template(calcs, indent, &"#{class_short_type(&1.type, opts)} #{&1.name}"),
join_template(aggs, indent, &"#{aggregate_type(resource, &1, opts)} #{&1.name}"),
join_template(
relationships,
indent,
&"#{resource_name(&1.destination, opts)}#{if &1.cardinality == :many, do: "[]", else: ""} #{&1.name}"
),
join_template(
actions,
indent,
&"#{&1.name}(#{list_arguments_and_attributes(resource, &1, opts)})"
)
]
|> Enum.reject(&(&1 == ""))
|> Enum.join("\n")
"""
#{indent}class #{resource_name(resource, opts)} {
#{contents}
#{indent}}
"""
end
|> Enum.join()
relationships =
for {src, dest} <- normalise_relationships(domain) do
~s(#{indent}#{resource_name(src, opts)} -- #{resource_name(dest, opts)})
end
|> Enum.join("\n")
"""
classDiagram
#{resources}
#{relationships}
"""
end
defp source_link(module, indent, opts) do
if opts[:include_source_links?] do
module.module_info(:compile)[:source]
|> List.to_string()
|> Path.relative_to_cwd()
|> then(&"\n#{indent}#{indent}Source: #{&1}\n")
end
rescue
_ ->
nil
end
end