Packages
Event-driven notification system for Ash Framework with multiple transport types
Current section
Files
Jump to
Current section
Files
lib/setup.ex
defmodule AshDispatch.Setup do
@moduledoc """
Defines a DeliveryReceipt resource inside your Deliveries domain.
A shortcut for `AshDispatch.Resources.DeliveryReceipt.Base`: instead of
writing a resource file, `use` this in the domain module and it creates
`<Domain>.DeliveryReceipt` there — built on the same Base, with the domain
set to the module you are in.
## Usage
defmodule MyApp.Deliveries do
use AshDispatch.Setup,
repo: MyApp.Repo,
notification_resource: MyApp.Notifications.Notification,
user_resource: MyApp.Accounts.User
use Ash.Domain
resources do
resource MyApp.Deliveries.DeliveryReceipt
end
end
Then point AshDispatch at it:
config :ash_dispatch,
delivery_receipt_resource: MyApp.Deliveries.DeliveryReceipt
## Options
- `:repo` - (required) Ecto repo module
- `:notification_resource` - (required) your Notification resource
- `:user_resource` - adds a `belongs_to :user` relationship
- `:table`, `:notifiers` - as for `AshDispatch.Resources.DeliveryReceipt.Base`
- `:extensions` - Ash extensions to add. Defaults to `[AshTypescript.Resource]`
when ash_typescript is installed and `[]` otherwise. With
`AshTypescript.Resource` the receipt's TypeScript type is `"DeliveryReceipt"`.
For anything the options don't cover — your own policies, actions or
relationships — write the resource yourself on
`AshDispatch.Resources.DeliveryReceipt.Base`.
"""
defmacro __using__(opts) do
domain = __CALLER__.module
# The receipt is compiled as a module of its own, which does not see the
# aliases in scope here — so aliases in the options are resolved now,
# where they were written.
opts = Macro.prewalk(opts, &expand_alias(&1, __CALLER__))
for key <- [:repo, :notification_resource], not Keyword.has_key?(opts, key) do
raise ArgumentError,
"use AshDispatch.Setup requires #{inspect(key)} " <>
"(see the AshDispatch.Setup docs for an example)"
end
extensions = Keyword.get_lazy(opts, :extensions, &default_extensions/0)
base_opts = Keyword.merge(opts, domain: domain, extensions: extensions)
doc = "Delivery receipt resource, defined by `AshDispatch.Setup` in `#{inspect(domain)}`."
body =
quote do
@moduledoc unquote(doc)
use AshDispatch.Resources.DeliveryReceipt.Base, unquote(base_opts)
unquote(typescript_section(extensions))
end
# `Module.create/3` takes the module body as quoted code, so it must be
# escaped. Before 0.8.4 it was not: the body was spliced in as code and
# ran inside the *domain* module instead, so `use Ash.Domain` right after
# failed with "can be called only one time", and this macro had never
# produced a receipt.
quote do
Module.create(
unquote(Module.concat(domain, DeliveryReceipt)),
unquote(Macro.escape(body)),
Macro.Env.location(__ENV__)
)
end
end
# `ash_typescript` is optional (#31): the receipt is a TypeScript resource
# only in apps that have it. Evaluated at macro expansion, so it follows the
# consuming app's deps rather than ash_dispatch's.
defp default_extensions do
if Code.ensure_loaded?(AshTypescript.Resource), do: [AshTypescript.Resource], else: []
end
# `type_name` is required once the extension is present — ash_typescript's
# unique-type-name verifier calls `typescript_type_name!/1` on every
# TypeScript resource — and a generated module leaves nowhere else to set it.
defp typescript_section(extensions) do
if AshTypescript.Resource in extensions do
quote do
typescript do
type_name("DeliveryReceipt")
end
end
end
end
defp expand_alias({:__aliases__, _, _} = alias, env), do: Macro.expand(alias, env)
defp expand_alias(other, _env), do: other
end