AshDiagram behaviour (AshDiagram v0.1.2)
View SourceAshDiagram is an Elixir library for generating beautiful, interactive diagrams to visualize your Ash Framework applications. Generate Entity Relationship diagrams, Class diagrams, C4 Architecture diagrams, and Policy flowcharts directly from your Ash resources and domains.
Features
- 🔗 Entity Relationship Diagrams - Visualize relationships between your Ash resources
- 📦 Class Diagrams - Show the structure of your resources with attributes and relationships
- 🏗️ C4 Architecture Diagrams - Display system architecture at different abstraction levels
- 🔐 Policy Diagrams - Understand authorization flows with flowchart representations
- 🎨 Multiple Output Formats - Render to PNG, SVG, or Mermaid markdown
- ⚡ Clarity Integration - Works seamlessly with the Clarity introspection framework
- 🔄 Automatic Generation - Generate diagrams at application, domain, and resource levels
Supported Diagram Types
Entity Relationship Diagrams
Generate ER diagrams showing your resources and their relationships:
- Resource entities with attributes
- Relationship cardinalities
- Foreign key relationships
Class Diagrams
Create UML-style class diagrams of your resources:
- Resource attributes and types
- Methods (actions)
- Inheritance and composition relationships
C4 Architecture Diagrams
Visualize system architecture using the C4 model:
- Context diagrams showing system boundaries
- Container diagrams showing high-level technology choices
- Component diagrams showing internal structure
Policy Flowcharts
Understand authorization logic through flowchart diagrams:
- Policy conditions and rules
- Decision trees and access controls
- Authorization flow visualization
Installation
Add ash_diagram
to your list of dependencies in mix.exs
:
def deps do
[
{:ash_diagram, "~> 0.1.0"}
]
end
For rendering capabilities, you'll also want to include optional dependencies:
def deps do
[
{:ash_diagram, "~> 0.1.0"},
{:ex_cmd, "~> 0.15.0"}, # For CLI rendering
{:req, "~> 0.5.15"} # For Mermaid.ink rendering
]
end
Usage
Basic Usage
Generate and render diagrams programmatically:
# Generate an Entity Relationship diagram for a domain
diagram = AshDiagram.Data.EntityRelationship.for_domains([MyApp.Accounts])
mermaid_code = AshDiagram.compose(diagram)
# Render to PNG
png_data = AshDiagram.render(diagram, format: :png)
File.write!("diagram.png", png_data)
Integration with Clarity
AshDiagram includes a clarity
introspector that automatically generates diagrams for your Ash applications.
Renderer Configuration
AshDiagram automatically detects and uses available renderers. You can configure a specific renderer in your application config:
# config/config.exs
config :ash_diagram, :renderer, AshDiagram.Renderer.CLI
# or
config :ash_diagram, :renderer, AshDiagram.Renderer.MermaidInk
Built-in Renderers
CLI Renderer (requires ex_cmd
and mmdc
command in PATH):
- Uses local Mermaid CLI installation
- Supports PNG, SVG, and PDF formats
- Best for server environments with Node.js installed
Mermaid.ink Renderer (requires req
):
- Uses the online Mermaid.ink service
- Supports SVG and PNG formats
- Best for development and when CLI tools aren't available
Custom Renderers
You can implement custom renderers by implementing the AshDiagram.Renderer
behaviour:
defmodule MyApp.CustomRenderer do
@behaviour AshDiagram.Renderer
@impl true
def supported?(), do: true
@impl true
def render(diagram, options) do
# Your custom rendering logic
end
end
# Configure it
config :ash_diagram, :renderer, MyApp.CustomRenderer
Diagram Extensions
You can extend generated diagrams with custom data by implementing the AshDiagram.Data.Extension
behaviour. This allows you to add additional elements to any diagram type.
defmodule MyApp.DiagramExtension do
@behaviour AshDiagram.Data.Extension
use Spark.Dsl.Extension
@impl AshDiagram.Data.Extension
def supports?(AshDiagram.Data.Architecture), do: true
def supports?(_creator), do: false
@impl AshDiagram.Data.Extension
def extend_diagram(AshDiagram.Data.Architecture, %AshDiagram.C4{} = diagram) do
# Add custom element to C4 architecture diagrams
custom_element = %AshDiagram.C4.Element{
type: :system,
external?: true,
alias: "external_system",
label: "External System"
}
%{diagram | entries: [custom_element | diagram.entries]}
end
end
Extension Discovery: Extensions are discovered by adding them to your Ash domains and resources:
# Add to domain
defmodule MyApp.Accounts do
use Ash.Domain,
extensions: [MyApp.DiagramExtension]
resources do
resource MyApp.Accounts.User
end
end
# Add to specific resources
defmodule MyApp.Accounts.User do
use Ash.Resource,
domain: MyApp.Accounts,
extensions: [MyApp.DiagramExtension]
# ... resource definition
end
Extensions are automatically collected from all domains and resources when generating diagrams.
Examples
Entity Relationship Diagram
alias AshDiagram.Data.EntityRelationship
# Generate for specific domains
diagram = EntityRelationship.for_domains([MyApp.Accounts, MyApp.Blog])
# Generate for entire application
diagram = EntityRelationship.for_applications([:my_app])
# Render as PNG
png_data = AshDiagram.render(diagram, format: :png)
Class Diagram
alias AshDiagram.Data.Class
# Generate class diagram
diagram = Class.for_domains([MyApp.Accounts])
svg_data = AshDiagram.render(diagram, format: :svg)
Architecture Diagram
alias AshDiagram.Data.Architecture
# Generate C4 architecture diagram
diagram = Architecture.for_applications([:my_app])
markdown = AshDiagram.compose_markdown(diagram)
Policy Diagram
alias AshDiagram.Data.Policy
# Generate policy flowchart for a resource
diagram = Policy.for_resource(MyApp.Accounts.User)
mermaid = AshDiagram.compose(diagram)
Optional Dependencies
ex_cmd ~> 0.15.0
- Required for CLI renderingreq ~> 0.5.15
- Required for Mermaid.ink renderingclarity ~> 0.1.2
- For automatic diagram generation integration
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Links
Example Diagrams
Examples taken from the tunez
starter
app of the Ash Framework
book.
Class
classDiagram
class `Tunez.Music.Album`["Music.Album"] {
+UUID id
+String name
+Integer year_released
+?String cover_image_url
+read() : read~Music.Album~
+destroy() : destroy~Music.Album~
+create(?Map[] tracks) : create~Music.Album~
+update(?Map[] tracks) : update~Music.Album~
}
class `Tunez.Music.Artist`["Music.Artist"] {
+UUID id
+String name
+?String[] previous_names
+?String biography
+UtcDatetimeUsec inserted_at
+UtcDatetimeUsec updated_at
+Boolean followed_by_me
+unknown album_count
+unknown latest_album_year_released
+unknown follower_count
+read() : read~Music.Artist~
+create() : create~Music.Artist~
+search(?CiString query) : read~Music.Artist~
+update() : update~Music.Artist~
+destroy() : destroy~Music.Artist~
}
class `Tunez.Music.ArtistFollower`["Music.ArtistFollower"] {
+read() : read~Music.ArtistFollower~
+for_artist(UUID artist_id) : read~Music.ArtistFollower~
+create() : create~Music.ArtistFollower~
+destroy(UUID artist_id) : destroy~Music.ArtistFollower~
}
class `Tunez.Music.Track`["Music.Track"] {
+UUID id
+String name
+Integer number
+String duration
+destroy() : destroy~Music.Track~
+read() : read~Music.Track~
+create(String duration) : create~Music.Track~
+update(String duration) : update~Music.Track~
}
`Tunez.Music.Album` "*" o--* "0..1" `Tunez.Music.Track`
`Tunez.Music.Album` "0..1" *--o "*" `Tunez.Music.Artist`
Entity Relationship
erDiagram
"Tunez.Music.Album"["Music.Album"] {
UUID id
String name
Integer year_released
String﹖ cover_image_url
}
"Tunez.Music.Artist"["Music.Artist"] {
UUID id
String name
String[]﹖ previous_names
String﹖ biography
UtcDatetimeUsec inserted_at
UtcDatetimeUsec updated_at
Boolean followed_by_me
unknown album_count
unknown latest_album_year_released
unknown follower_count
}
"Tunez.Music.ArtistFollower"["Music.ArtistFollower"] {
}
"Tunez.Music.Track"["Music.Track"] {
UUID id
String name
Integer number
String duration
}
"Tunez.Music.Album" }o--o| "Tunez.Music.Track" : ""
"Tunez.Music.Album" |o--o{ "Tunez.Music.Artist" : ""
C4 Architecture
C4Context
System_Boundary("beam", "BEAM") {
System_Boundary("tunez", "tunez Application") {
System_Boundary("tunez_accounts", "Accounts") {
System("tunez_accounts_token", "Accounts.Token", "Resource with 12 actions, 0 relationships")
System("tunez_accounts_user", "Accounts.User", "Resource with 14 actions, 2 relationships")
System("tunez_accounts_notification", "Accounts.Notification", "Resource with 4 actions, 2 relationships")
}
System_Boundary("tunez_music", "Music") {
System("tunez_music_artist", "Music.Artist", "Resource with 5 actions, 5 relationships")
System("tunez_music_album", "Music.Album", "Resource with 4 actions, 5 relationships")
System("tunez_music_track", "Music.Track", "Resource with 4 actions, 1 relationships")
System("tunez_music_artist_follower", "Music.ArtistFollower", "Resource with 4 actions, 2 relationships")
}
}
System_Boundary("ash_postgres", "ash_postgres Application") {
SystemDb("", "", "")
}
}
Rel("tunez_music_album", "tunez_music_track", "tracks", "has_many relationship")
Rel("tunez_music_artist", "tunez_music_album", "albums", "has_many relationship")
Rel("tunez_accounts_token", "", "uses", "Stores data")
Rel("tunez_accounts_user", "", "uses", "Stores data")
Rel("tunez_accounts_notification", "", "uses", "Stores data")
Rel("tunez_music_artist", "", "uses", "Stores data")
Rel("tunez_music_album", "", "uses", "Stores data")
Rel("tunez_music_track", "", "uses", "Stores data")
Rel("tunez_music_artist_follower", "", "uses", "Stores data")
Resource Policy
---
title: "Policy Flow: Tunez.Music.Album"
---
flowchart TD
start((Policy Evaluation Start))
subgraph at_least_one_policy [at least one policy applies]
at_least_one_policy_check{"actor.role == :admin or action.type == :read or action == :create or action.type in [:update, :destroy]"}
end
0_conditions{"actor.role == :admin"}
0_checks_0{"always true"}
1_conditions{"action.type == :read"}
1_checks_0{"always true"}
2_conditions{"action == :create"}
2_checks_0{"actor.role == :editor"}
3_conditions{"action.type in [:update, :destroy]"}
3_checks_0{"can_manage_album?"}
subgraph results [Results]
authorized((Authorized))
forbidden((Forbidden))
end
0_conditions -->|True| 0_checks_0
0_conditions -->|False| 1_conditions
0_checks_0 -->|True| authorized
0_checks_0 -->|False| 1_conditions
1_conditions -->|True| 1_checks_0
1_conditions -->|False| 2_conditions
1_checks_0 -->|True| 2_conditions
1_checks_0 -->|False| forbidden
2_conditions -->|True| 2_checks_0
2_conditions -->|False| 3_conditions
2_checks_0 -->|True| 3_conditions
2_checks_0 -->|False| forbidden
3_conditions -->|True| 3_checks_0
3_conditions -->|False| authorized
3_checks_0 -->|True| authorized
3_checks_0 -->|False| forbidden
start --> at_least_one_policy_check
at_least_one_policy_check -->|False| forbidden
at_least_one_policy_check -->|True| 0_conditions
classDef authorized fill:#e8f5e8,stroke:#4CAF50,stroke-width:2px
classDef forbidden fill:#ffebee,stroke:#f44336,stroke-width:2px
classDef condition fill:#e3f2fd,stroke:#2196F3
class authorized authorized
class forbidden forbidden
class start condition
License
Copyright 2025 Alembic Pty Ltd
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Summary
Types
Module implementing the AshDiagram.Renderer
behaviour.
Diagram struct where the Struct Module is the implementation module.
Callbacks
Compose the Mermaid diagram from the given diagram
data structure.
Functions
Compose the Mermaid diagram as Markdown from the given diagram
data structure.
Compose the Mermaid diagram as Markdown from the given diagram
data structure.
Render the Mermaid diagram from the given diagram
data structure.
Types
@type implementation() :: module()
Module implementing the AshDiagram.Renderer
behaviour.
See t/1
.
Diagram struct where the Struct Module is the implementation module.
Callbacks
Functions
Compose the Mermaid diagram as Markdown from the given diagram
data structure.
Compose the Mermaid diagram as Markdown from the given diagram
data structure.
@spec render(diagram :: t(), options :: AshDiagram.Renderer.options()) :: iodata()
Render the Mermaid diagram from the given diagram
data structure.