AshDispatch.Naming (AshDispatch v0.5.0)

View Source

Central module for all AshDispatch naming conventions.

This module is the single source of truth for deriving names from resources, events, and channels. All other modules should delegate to these functions to ensure consistent naming across the library.

Resource & Event Naming

Derive identifiers and module names from Ash resources:

iex> Naming.event_id(MyApp.Orders.ProductOrder, :created)
"product_order.created"

iex> Naming.domain_name(MyApp.Orders.ProductOrder)
"orders"

iex> Naming.resource_name(MyApp.Orders.ProductOrder)
"product_order"

iex> Naming.data_key(MyApp.Orders.ProductOrder)
:product_order

iex> Naming.event_module(MyApp.Orders.ProductOrder, "orders", :created)
MyApp.Orders.Events.Created.Event

Channel Naming (Filenames & Labels)

Handles the logic for combining transport, audience, and variant into:

  • Filenames (e.g., email.user.html, email.admin.summary.txt)
  • Display labels (e.g., "email (user)", "email (admin, summary)")

Deduplication Rule

When variant equals audience (e.g., audience: :admin, variant: "admin"), the variant is omitted to avoid redundant names like "email.admin.admin.html".

Examples

iex> Naming.filename("email", :user, nil, "html")
"email.user.html"

iex> Naming.filename("email", :admin, "admin", "html")
"email.admin.html"  # variant omitted (matches audience)

iex> Naming.filename("email", :admin, "summary", "html")
"email.admin.summary.html"

iex> Naming.label(:email, :user, nil)
"email (user)"

iex> Naming.label(:email, :admin, "admin")
"email (admin)"  # variant omitted (matches audience)

iex> Naming.label(:email, :admin, "summary")
"email (admin, summary)"

Summary

Functions

Extract the app module name from a resource.

Derive the data_key from a resource module.

Extract the domain name from a resource module.

Generate an event ID from a resource module and event name.

Derive the expected event module name from resource and event info.

Derive the event module from a resource (auto-deriving domain).

Derive the event module from otp_app and domain/event names.

Build a filename from channel properties.

Check if variant should be included (not redundant with audience).

Build a display label from channel properties.

Derive the module directory path from a module name.

Extract the resource name from a resource module.

Derive the template directory path from an event module.

Get the effective variant parts to display/include.

Derive the wire-name from an event_id by splitting on . and taking the last segment. Used by the broadcast transport (and any future transport that needs a Phoenix-channel-friendly event name).

Functions

app_module(resource)

@spec app_module(module()) :: String.t() | nil

Extract the app module name from a resource.

Takes the first module segment.

Examples

iex> Naming.app_module(MyApp.Orders.ProductOrder)
"MyApp"

data_key(resource)

@spec data_key(module()) :: atom() | nil

Derive the data_key from a resource module.

Converts the resource name to a snake_case atom, used as the key in context.data for storing the primary resource.

Examples

iex> Naming.data_key(MyApp.Orders.ProductOrder)
:product_order

iex> Naming.data_key(MyApp.Accounts.User)
:user

domain_name(resource)

@spec domain_name(module()) :: String.t() | nil

Extract the domain name from a resource module.

Takes the second-to-last module segment and converts to snake_case.

Examples

iex> Naming.domain_name(MyApp.Orders.ProductOrder)
"orders"

iex> Naming.domain_name(MyApp.Accounts.User)
"accounts"

event_id(resource, event_name)

@spec event_id(module(), atom()) :: String.t()

Generate an event ID from a resource module and event name.

Uses the resource name (last module segment) to avoid collisions when multiple resources in the same domain have common event names.

Examples

iex> Naming.event_id(MyApp.Orders.ProductOrder, :created)
"product_order.created"

iex> Naming.event_id(MyApp.Accounts.User, :password_reset)
"user.password_reset"

event_module(resource, domain_name, event_name)

@spec event_module(module(), String.t() | nil, atom()) :: module()

Derive the expected event module name from resource and event info.

Convention: {App}.{Domain}.Events.{EventName}.Event

Examples

iex> Naming.event_module(MyApp.Orders.ProductOrder, "orders", :created)
MyApp.Orders.Events.Created.Event

iex> Naming.event_module(MyApp.Accounts.User, "accounts", :password_reset)
MyApp.Accounts.Events.PasswordReset.Event

event_module_for_resource(resource, event_name)

@spec event_module_for_resource(module(), atom()) :: module()

Derive the event module from a resource (auto-deriving domain).

Convenience function that combines domain_name/1 and event_module/3.

Examples

iex> Naming.event_module_for_resource(MyApp.Orders.ProductOrder, :created)
MyApp.Orders.Events.Created.Event

event_module_from_otp_app(otp_app, domain_name, event_name)

@spec event_module_from_otp_app(atom(), atom() | String.t(), atom()) :: module()

Derive the event module from otp_app and domain/event names.

Used when the app prefix should come from otp_app rather than a resource module. This is the preferred approach for generated modules since they should live in the user's app namespace.

Examples

iex> Naming.event_module_from_otp_app(:my_app, :orders, :created)
MyApp.Orders.Events.Created.Event

iex> Naming.event_module_from_otp_app(:acme_support, :tickets, :assigned)
AcmeSupport.Tickets.Events.Assigned.Event

filename(base, audience, variant, extension)

Build a filename from channel properties.

Parameters

  • base - Base name, typically the transport (e.g., "email")
  • audience - Target audience atom (e.g., :user, :admin)
  • variant - Optional variant string or atom (e.g., "summary", :admin)
  • extension - File extension without dot (e.g., "html", "txt")

Returns

Filename string like "email.user.html" or "email.admin.summary.txt"

include_variant?(audience, variant)

Check if variant should be included (not redundant with audience).

Returns true if variant is present AND different from audience.

label(transport, audience, variant)

Build a display label from channel properties.

Parameters

  • transport - Transport type atom (e.g., :email, :sms)
  • audience - Target audience atom (e.g., :user, :admin)
  • variant - Optional variant string or atom

Returns

Label string like "email (user)" or "email (admin, summary)"

module_directory(module)

@spec module_directory(module()) :: String.t()

Derive the module directory path from a module name.

Used for locating template files relative to the event module.

Examples

iex> Naming.module_directory(MyApp.Orders.Events.Created.Event)
"lib/my_app/orders/events/created"

resource_name(resource)

@spec resource_name(module()) :: String.t() | nil

Extract the resource name from a resource module.

Takes the last module segment and converts to snake_case.

Examples

iex> Naming.resource_name(MyApp.Orders.ProductOrder)
"product_order"

iex> Naming.resource_name(MyApp.Accounts.User)
"user"

template_directory(module)

@spec template_directory(module()) :: String.t()

Derive the template directory path from an event module.

Examples

iex> Naming.template_directory(MyApp.Orders.Events.Created.Event)
"lib/my_app/orders/events/created/templates"

variant_parts(audience, variant)

Get the effective variant parts to display/include.

Returns a list of parts after audience, excluding redundant variant.

wire_event_name(event_id)

@spec wire_event_name(String.t() | atom()) :: String.t()

Derive the wire-name from an event_id by splitting on . and taking the last segment. Used by the broadcast transport (and any future transport that needs a Phoenix-channel-friendly event name).

Per-event override is available via AshDispatch.Event.wire_event_name/0 (defaults to this string transform). F15 — review-deep 2026-05-15.

Examples

iex> AshDispatch.Naming.wire_event_name("pipeline_events.chat_chunk")
"chat_chunk"

iex> AshDispatch.Naming.wire_event_name(:report_resolved)
"report_resolved"

iex> AshDispatch.Naming.wire_event_name("orders.created")
"created"