improvement: move functions to more conventional places

improvement: move ash.formatter task to `spark.formatter`
This commit is contained in:
Zach Daniel 2022-08-15 16:02:22 -04:00
parent 6c79519b6c
commit 04744f395f
37 changed files with 561 additions and 317 deletions

View file

@ -11,7 +11,7 @@
## ...or adjusted (e.g. use one-line formatter for more compact credo output)
# {:credo, "mix credo --format oneline"},
{:check_formatter, command: "mix ash.formatter --check"},
{:check_formatter, command: "mix spark.formatter --check"},
# TODO: upgrade to the new version of ex_check that should do this on the right elixir version
# {:unused_deps, command: "mix deps.unlock --check-unused"}

View file

@ -1,6 +1,4 @@
# THIS FILE IS AUTOGENERATED USING `mix ash.formatter`
# DONT MODIFY IT BY HAND
locals_without_parens = [
spark_locals_without_parens = [
accept: 1,
access_type: 1,
action: 1,
@ -29,6 +27,7 @@ locals_without_parens = [
before_action?: 1,
belongs_to: 2,
belongs_to: 3,
broadcast_type: 1,
bypass: 1,
bypass: 2,
calculate: 3,
@ -189,8 +188,8 @@ locals_without_parens = [
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"],
locals_without_parens: locals_without_parens,
locals_without_parens: spark_locals_without_parens,
export: [
locals_without_parens: locals_without_parens
locals_without_parens: spark_locals_without_parens
]
]

View file

@ -44,7 +44,7 @@ defmodule Ash.Actions.Create do
actor: actor,
authorize?: authorize?,
notification_metadata: opts[:notification_metadata],
timeout: opts[:timeout] || changeset.timeout || Ash.Api.timeout(api),
timeout: opts[:timeout] || changeset.timeout || Ash.Api.Info.timeout(api),
return_notifications?: opts[:return_notifications?],
transaction?: Keyword.get(opts, :transaction?, true)
)
@ -177,7 +177,7 @@ defmodule Ash.Actions.Create do
changeset = %{
changeset
| timeout: timeout || changeset.timeout || Ash.Api.timeout(api)
| timeout: timeout || changeset.timeout || Ash.Api.Info.timeout(api)
}
tenant =

View file

@ -57,7 +57,7 @@ defmodule Ash.Actions.Destroy do
return_notifications?: opts[:return_notifications?],
notification_metadata: opts[:notification_metadata],
authorize?: authorize?,
timeout: opts[:timeout] || changeset.timeout || Ash.Api.timeout(api),
timeout: opts[:timeout] || changeset.timeout || Ash.Api.Info.timeout(api),
transaction?: true
)
|> case do
@ -231,7 +231,7 @@ defmodule Ash.Actions.Destroy do
case Ash.DataLayer.destroy(resource, changeset) do
:ok ->
{:ok,
Ash.Resource.Info.set_meta(record, %Ecto.Schema.Metadata{
Ash.Resource.set_meta(record, %Ecto.Schema.Metadata{
state: :deleted,
schema: resource
})}

View file

@ -85,7 +85,7 @@ defmodule Ash.Actions.Helpers do
end
if api do
if !Keyword.has_key?(opts, :actor) && Ash.Api.require_actor?(api) do
if !Keyword.has_key?(opts, :actor) && Ash.Api.Info.require_actor?(api) do
raise Ash.Error.Forbidden.ApiRequiresActor, api: api
end
@ -112,7 +112,7 @@ defmodule Ash.Actions.Helpers do
end
if api do
case Ash.Api.authorize(api) do
case Ash.Api.Info.authorize(api) do
:always ->
Keyword.put(opts, :authorize?, true)
@ -232,7 +232,7 @@ defmodule Ash.Actions.Helpers do
|> Enum.reduce(result, fn key, record ->
Map.put(record, key, nil)
end)
|> Ash.Resource.Info.put_metadata(:selected, select)
|> Ash.Resource.put_metadata(:selected, select)
end
def attributes_to_select(%{select: nil, resource: resource}) do

View file

@ -511,7 +511,7 @@ defmodule Ash.Actions.Load do
end
defp lazy_load_or(data, lazy?, relationship, api, related_query, request_opts, func) do
if lazy? && Ash.Resource.Info.loaded?(data, relationship) do
if lazy? && Ash.Resource.loaded?(data, relationship) do
pkey = Ash.Resource.Info.primary_key(related_query.resource)
data

View file

@ -174,11 +174,11 @@ defmodule Ash.Actions.Read do
case load do
{key, _value} ->
calculation_or_aggregate?(resource, key) &&
Ash.Resource.Info.loaded?(resource, key)
Ash.Resource.loaded?(resource, key)
key ->
calculation_or_aggregate?(resource, key) &&
Ash.Resource.Info.loaded?(resource, key)
Ash.Resource.loaded?(resource, key)
end
end)
@ -237,7 +237,7 @@ defmodule Ash.Actions.Read do
query = %{
query
| api: api,
timeout: timeout || query.timeout || Ash.Api.timeout(api)
timeout: timeout || query.timeout || Ash.Api.Info.timeout(api)
}
query =
@ -469,7 +469,7 @@ defmodule Ash.Actions.Read do
opts
|> Keyword.take([:verbose?, :actor, :authorize?, :timeout])
|> Keyword.put(:transaction?, action.transaction? || opts[:transaction?])
|> Keyword.put(:default_timeout, Ash.Api.timeout(api))
|> Keyword.put(:default_timeout, Ash.Api.Info.timeout(api))
|> Keyword.put(:resource, resource)
|> Keyword.put(:name, "#{inspect(resource)}.#{action.name}")
end
@ -1325,7 +1325,7 @@ defmodule Ash.Actions.Read do
loads
|> List.wrap()
|> Enum.reject(fn load ->
Ash.Resource.Info.loaded?(results, load)
Ash.Resource.loaded?(results, load)
end)
end

View file

@ -38,8 +38,8 @@ defmodule Ash.Actions.Update do
notification_metadata: opts[:notification_metadata],
return_notifications?: opts[:return_notifications?],
authorize?: authorize?,
timeout: opts[:timeout] || changeset.timeout || Ash.Api.timeout(api),
default_timeout: Ash.Api.timeout(api),
timeout: opts[:timeout] || changeset.timeout || Ash.Api.Info.timeout(api),
default_timeout: Ash.Api.Info.timeout(api),
transaction?: Keyword.get(opts, :transaction?, true)
)
|> case do
@ -226,7 +226,7 @@ defmodule Ash.Actions.Update do
else
changeset = %{
changeset
| timeout: timeout || changeset.timeout || Ash.Api.timeout(changeset.api)
| timeout: timeout || changeset.timeout || Ash.Api.Info.timeout(changeset.api)
}
changeset =

View file

@ -34,8 +34,7 @@ defmodule Ash.Api do
The functions documented here can be used to call any action on any resource in the Api.
For example, `MyApi.read(Myresource, [...])`.
Additionally, you can define a `code_interface` on each resource to be exposed in the Api module.
See the resource DSL documentation for more.
Additionally, you can define a `code_interface` on each resource. See the code interface guide for more.
"""
use Spark.Dsl, default_extensions: [extensions: [Ash.Api.Dsl]]
@ -52,8 +51,6 @@ defmodule Ash.Api do
alias Ash.Error.Query.NotFound
alias Spark.Dsl.Extension
require Ash.Query
@dialyzer {:nowarn_function, unwrap_or_raise!: 3}
@ -221,7 +218,7 @@ defmodule Ash.Api do
type: :boolean,
default: true,
doc:
"Wether or not an error should be returned or raised when the record is not found. If set to false, `nil` will be returned."
"Whether or not an error should be returned or raised when the record is not found. If set to false, `nil` will be returned."
],
load: [
type: :any,
@ -532,99 +529,43 @@ defmodule Ash.Api do
@callback reload(record :: Ash.Resource.record()) ::
{:ok, Ash.Resource.record()} | {:error, term}
@doc false
def handle_opts(_) do
quote do
@behaviour Ash.Api
end
end
@doc false
def handle_before_compile(_) do
quote do
use Ash.Api.Interface
end
end
def resource(api, resource) do
cond do
allow_unregistered?(api) ->
if Spark.Dsl.is?(resource, Ash.Resource) do
resource
else
nil
end
@deprecated "use Ash.Api.Info.resource/2 instead"
defdelegate resource(api, resource), to: Ash.Api.Info
Ash.Resource.Info.embedded?(resource) ->
resource
@deprecated "use Ash.Api.Info.resources/1 instead"
defdelegate resources(api), to: Ash.Api.Info
true ->
api
|> resources()
|> Enum.find(&(&1 == resource))
end
|> case do
nil ->
if allowed?(allow(api), resource) do
{:ok, resource}
else
{:error, NoSuchResource.exception(resource: resource)}
end
@deprecated "use Ash.Api.Info.registry/1 instead"
defdelegate registry(api), to: Ash.Api.Info
resource ->
{:ok, resource}
end
end
@deprecated "use Ash.Api.Info.allow/1 instead"
defdelegate allow(api), to: Ash.Api.Info
@spec allowed?(mfa | nil, module()) :: boolean
defp allowed?({m, f, a}, resource) do
apply(m, f, List.wrap(a) ++ [resource])
end
@deprecated "use Ash.Api.Info.timeout/1 instead"
defdelegate timeout(api), to: Ash.Api.Info
defp allowed?(_, _), do: false
@deprecated "use Ash.Api.Info.require_actor?/1 instead"
defdelegate require_actor?(api), to: Ash.Api.Info
@doc """
Gets the resources of an Api module. DO NOT USE AT COMPILE TIME.
@deprecated "use Ash.Api.Info.authorize/1 instead"
defdelegate authorize(api), to: Ash.Api.Info
If you need the resource list at compile time, you will need to introduce a compile time
dependency on all of the resources, and therefore should use the registry directly. `Registry |> Ash.Registry.entries()`.
"""
@spec resources(Ash.Api.t()) :: list(Ash.Resource.t())
def resources(api) do
if registry = registry(api) do
Ash.Registry.entries(registry)
else
[]
end
end
@spec registry(Ash.Api.t()) :: atom | nil
def registry(api) do
Extension.get_opt(api, [:resources], :registry, nil, true)
end
@spec allow(Ash.Api.t()) :: mfa() | nil
def allow(api) do
Extension.get_opt(api, [:resources], :allow, nil, true)
end
@spec timeout(Ash.Api.t()) :: nil | :infinity | integer()
def timeout(api) do
Extension.get_opt(api, [:execution], :timeout, 30_000, true)
end
@spec require_actor?(Ash.Api.t()) :: boolean
def require_actor?(api) do
Extension.get_opt(api, [:authorization], :require_actor?, false, true)
end
@spec authorize(Ash.Api.t()) :: :when_requested | :always | :by_default
def authorize(api) do
Extension.get_opt(api, [:authorization], :authorize, :when_requested, true)
end
@spec allow_unregistered?(Ash.Api.t()) :: atom | nil
def allow_unregistered?(api) do
Extension.get_opt(api, [:resources], :allow_unregistered?, nil)
end
@deprecated "use Ash.Api.Info.allow_unregistered?/1 instead"
defdelegate allow_unregistered?(api), to: Ash.Api.Info
@doc false
@spec get!(Ash.Api.t(), Ash.Resource.t(), term(), Keyword.t()) ::
@ -642,7 +583,7 @@ defmodule Ash.Api do
{:ok, Ash.Resource.record()} | {:error, term}
def get(api, resource, id, opts) do
with {:ok, opts} <- Spark.OptionsHelpers.validate(opts, @get_opts_schema),
{:ok, resource} <- Ash.Api.resource(api, resource),
{:ok, resource} <- Ash.Api.Info.resource(api, resource),
{:ok, filter} <- Ash.Filter.get_filter(resource, id) do
query =
resource
@ -711,6 +652,7 @@ defmodule Ash.Api do
end
end
@doc false
def page!(api, keyset, request) do
{_, opts} = keyset.rerun
@ -719,6 +661,7 @@ defmodule Ash.Api do
|> unwrap_or_raise!(opts[:stacktraces?])
end
@doc false
def page(_, %Ash.Page.Keyset{results: []} = page, :next) do
{:ok, page}
end
@ -993,7 +936,7 @@ defmodule Ash.Api do
| {:error, term}
def create(api, changeset, opts) do
with {:ok, opts} <- Spark.OptionsHelpers.validate(opts, @create_opts_schema),
{:ok, resource} <- Ash.Api.resource(api, changeset.resource),
{:ok, resource} <- Ash.Api.Info.resource(api, changeset.resource),
{:ok, action} <- get_action(resource, opts, :create, changeset.action) do
Create.run(api, changeset, action, opts)
end
@ -1015,7 +958,7 @@ defmodule Ash.Api do
| {:error, term}
def update(api, changeset, opts) do
with {:ok, opts} <- Spark.OptionsHelpers.validate(opts, @update_opts_schema),
{:ok, resource} <- Ash.Api.resource(api, changeset.resource),
{:ok, resource} <- Ash.Api.Info.resource(api, changeset.resource),
{:ok, action} <- get_action(resource, opts, :update, changeset.action) do
Update.run(api, changeset, action, opts)
end
@ -1048,7 +991,7 @@ defmodule Ash.Api do
| {:error, term}
def destroy(api, %Ash.Changeset{resource: resource} = changeset, opts) do
with {:ok, opts} <- Spark.OptionsHelpers.validate(opts, @destroy_opts_schema),
{:ok, resource} <- Ash.Api.resource(api, resource),
{:ok, resource} <- Ash.Api.Info.resource(api, resource),
{:ok, action} <- get_action(resource, opts, :destroy, changeset.action) do
Destroy.run(api, changeset, action, opts)
end

100
lib/ash/api/info.ex Normal file
View file

@ -0,0 +1,100 @@
defmodule Ash.Api.Info do
@moduledoc "Introspection tools for Ash.Api"
alias Ash.Error.Invalid.NoSuchResource
alias Spark.Dsl.Extension
@doc """
Gets the resources of an Api module. DO NOT USE AT COMPILE TIME.
If you need the resource list at compile time, you will need to introduce a compile time
dependency on all of the resources, and therefore should use the registry directly. `Registry |> Ash.Registry.Info.entries()`.
"""
@spec resources(Ash.Api.t()) :: list(Ash.Resource.t())
def resources(api) do
if registry = registry(api) do
Ash.Registry.Info.entries(registry)
else
[]
end
end
@doc "The resource registry for an api"
@spec registry(Ash.Api.t()) :: atom | nil
def registry(api) do
Extension.get_opt(api, [:resources], :registry, nil, true)
end
@doc "The allow MFA for an api"
@spec allow(Ash.Api.t()) :: mfa() | nil
def allow(api) do
Extension.get_opt(api, [:resources], :allow, nil, true)
end
@doc "The execution timeout for an api"
@spec timeout(Ash.Api.t()) :: nil | :infinity | integer()
def timeout(api) do
Extension.get_opt(api, [:execution], :timeout, 30_000, true)
end
@doc "Whether or not the actor is always required for an api"
@spec require_actor?(Ash.Api.t()) :: boolean
def require_actor?(api) do
Extension.get_opt(api, [:authorization], :require_actor?, false, true)
end
@doc "When authorization should happen for a given api"
@spec authorize(Ash.Api.t()) :: :when_requested | :always | :by_default
def authorize(api) do
Extension.get_opt(api, [:authorization], :authorize, :when_requested, true)
end
@doc "Whether or not the api allows unregistered resources to be used with it"
@spec allow_unregistered?(Ash.Api.t()) :: atom | nil
def allow_unregistered?(api) do
Extension.get_opt(api, [:resources], :allow_unregistered?, nil)
end
@doc """
Returns `{:ok, resource}` if the resource can be used by the api, or `{:error, error}`.
"""
@spec resource(Ash.Api.t(), Ash.Resource.t()) ::
{:ok, Ash.Resource.t()} | {:error, Ash.Error.t()}
def resource(api, resource) do
cond do
allow_unregistered?(api) ->
if Spark.Dsl.is?(resource, Ash.Resource) do
resource
else
nil
end
Ash.Resource.Info.embedded?(resource) ->
resource
true ->
api
|> resources()
|> Enum.find(&(&1 == resource))
end
|> case do
nil ->
if allowed?(allow(api), resource) do
{:ok, resource}
else
{:error, NoSuchResource.exception(resource: resource)}
end
resource ->
{:ok, resource}
end
end
@spec allowed?(mfa | nil, module()) :: boolean
defp allowed?({m, f, a}, resource) do
apply(m, f, List.wrap(a) ++ [resource])
end
defp allowed?(_, _), do: false
end

View file

@ -1849,12 +1849,12 @@ defmodule Ash.Changeset do
post1 =
changeset
|> Api.create!()
|> Ash.Resource.Info.put_metadata(:join_keys, %{type: "a"})
|> Ash.Resource.put_metadata(:join_keys, %{type: "a"})
post1 =
changeset2
|> Api.create!()
|> Ash.Resource.Info.put_metadata(:join_keys, %{type: "b"})
|> Ash.Resource.put_metadata(:join_keys, %{type: "b"})
author = Api.create!(author_changeset)

View file

@ -40,17 +40,12 @@ defmodule Ash.DataLayer.Ets do
transformers: [Ash.DataLayer.Transformers.RequirePreCheckWith]
alias Ash.Actions.Sort
alias Spark.Dsl.Extension
@spec private?(Ash.Resource.t()) :: boolean
def private?(resource) do
Extension.get_opt(resource, [:ets], :private?, false, true)
end
@deprecated "use Ash.DataLayer.Ets.Info.private?/1 instead"
defdelegate private?(resource), to: Ash.DataLayer.Ets.Info
@spec table(Ash.Resource.t()) :: boolean
def table(resource) do
Extension.get_opt(resource, [:ets], :table, resource, true) || resource
end
@deprecated "use Ash.DataLayer.Ets.Info.table/1 instead"
defdelegate table(resource), to: Ash.DataLayer.Ets.Info
defmodule Query do
@moduledoc false
@ -165,6 +160,7 @@ defmodule Ash.DataLayer.Ets do
end
end
@doc false
@impl true
def can?(resource, :async_engine) do
not private?(resource)
@ -198,6 +194,7 @@ defmodule Ash.DataLayer.Ets do
def can?(_, {:sort, _}), do: true
def can?(_, _), do: false
@doc false
@impl true
def resource_to_query(resource, api) do
%Query{
@ -206,21 +203,26 @@ defmodule Ash.DataLayer.Ets do
}
end
@doc false
@impl true
def limit(query, offset, _), do: {:ok, %{query | limit: offset}}
@doc false
@impl true
def offset(query, offset, _), do: {:ok, %{query | offset: offset}}
@doc false
@impl true
def add_calculation(query, calculation, _, _),
do: {:ok, %{query | calculations: [calculation | query.calculations]}}
@doc false
@impl true
def set_tenant(_resource, query, tenant) do
{:ok, %{query | tenant: tenant}}
end
@doc false
@impl true
def filter(query, filter, _resource) do
if query.filter do
@ -230,11 +232,13 @@ defmodule Ash.DataLayer.Ets do
end
end
@doc false
@impl true
def sort(query, sort, _resource) do
{:ok, %{query | sort: sort}}
end
@doc false
@impl true
def run_aggregate_query(%{api: api} = query, aggregates, resource) do
case run_query(query, resource) do
@ -260,6 +264,7 @@ defmodule Ash.DataLayer.Ets do
end
end
@doc false
@impl true
def run_query(
%Query{
@ -420,6 +425,7 @@ defmodule Ash.DataLayer.Ets do
Ash.Filter.Runtime.filter_matches(api, records, filter)
end
@doc false
@impl true
def upsert(resource, changeset, keys) do
keys = keys || Ash.Resource.Info.primary_key(resource)
@ -459,6 +465,7 @@ defmodule Ash.DataLayer.Ets do
end
end
@doc false
@impl true
def create(resource, changeset) do
pkey =
@ -525,6 +532,7 @@ defmodule Ash.DataLayer.Ets do
end)
end
@doc false
@impl true
def destroy(resource, %{data: record} = changeset) do
do_destroy(resource, record, changeset.tenant)
@ -541,6 +549,7 @@ defmodule Ash.DataLayer.Ets do
end
end
@doc false
@impl true
def update(resource, changeset) do
pkey = pkey_map(resource, changeset.data)

View file

@ -0,0 +1,17 @@
defmodule Ash.DataLayer.Ets.Info do
@moduledoc "Introspection helpers for the Ets data layer"
alias Spark.Dsl.Extension
@doc "Whether or not the ets table for the resource should be private"
@spec private?(Ash.Resource.t()) :: boolean
def private?(resource) do
Extension.get_opt(resource, [:ets], :private?, false, true)
end
@doc "The ets table name for a resource"
@spec table(Ash.Resource.t()) :: boolean
def table(resource) do
Extension.get_opt(resource, [:ets], :table, resource, true) || resource
end
end

View file

@ -0,0 +1,10 @@
defmodule Ash.DataLayer.Mnesia.Info do
@moduledoc "Introspection helpers for Ash.DataLayer.Mnesia"
alias Spark.Dsl.Extension
@doc "The mnesia table for a resource"
def table(resource) do
Extension.get_opt(resource, [:ets], :private?, resource, true)
end
end

View file

@ -37,9 +37,11 @@ defmodule Ash.DataLayer.Mnesia do
transformers: [Ash.DataLayer.Transformers.RequirePreCheckWith]
alias Ash.Actions.Sort
alias Spark.Dsl.Extension
alias :mnesia, as: Mnesia
@doc """
Creates the table for each mnesia resource in an api
"""
def start(api) do
Mnesia.create_schema([node()])
Mnesia.start()
@ -47,14 +49,13 @@ defmodule Ash.DataLayer.Mnesia do
Code.ensure_compiled(api)
api
|> Ash.Api.resources()
|> Enum.each(fn resource ->
resource |> table() |> Mnesia.create_table(attributes: [:_pkey, :val])
|> Ash.Api.Info.resources()
|> Enum.flat_map(fn resource ->
resource
|> Ash.DataLayer.Mnesia.Info.table()
|> List.wrap()
end)
end
def table(resource) do
Extension.get_opt(resource, [:ets], :private?, resource, true)
|> Enum.each(&Mnesia.create_table(&1, attributes: [:_pkey, :val]))
end
defmodule Query do
@ -62,6 +63,10 @@ defmodule Ash.DataLayer.Mnesia do
defstruct [:api, :resource, :filter, :limit, :sort, relationships: %{}, offset: 0]
end
@deprecated "use Ash.DataLayer.Mnesia.Info.table/1 instead"
defdelegate table(resource), to: Ash.DataLayer.Mnesia.Info
@doc false
@impl true
def can?(_, :async_engine), do: true
def can?(_, :composite_primary_key), do: true
@ -82,7 +87,7 @@ defmodule Ash.DataLayer.Mnesia do
# if someone needs to use these both and *actually* needs real joins for private
# ets resources then we can talk about making this only happen in ash tests
not (Ash.DataLayer.data_layer(resource) == Ash.DataLayer.Ets &&
Ash.DataLayer.Ets.private?(resource))
Ash.DataLayer.Ets.Info.private?(resource))
end
def can?(_, {:filter_expr, _}), do: true
@ -91,6 +96,7 @@ defmodule Ash.DataLayer.Mnesia do
def can?(_, _), do: false
@doc false
@impl true
def resource_to_query(resource, api) do
%Query{
@ -99,15 +105,19 @@ defmodule Ash.DataLayer.Mnesia do
}
end
@doc false
@impl true
def in_transaction?(_), do: Mnesia.is_transaction()
@doc false
@impl true
def limit(query, offset, _), do: {:ok, %{query | limit: offset}}
@doc false
@impl true
def offset(query, offset, _), do: {:ok, %{query | offset: offset}}
@doc false
@impl true
def filter(query, filter, _resource) do
if query.filter do
@ -117,11 +127,13 @@ defmodule Ash.DataLayer.Mnesia do
end
end
@doc false
@impl true
def sort(query, sort, _resource) do
{:ok, %{query | sort: sort}}
end
@doc false
@impl true
def run_query(
%Query{
@ -177,6 +189,7 @@ defmodule Ash.DataLayer.Mnesia do
end
end
@doc false
@impl true
def create(resource, changeset) do
{:ok, record} = Ash.Changeset.apply_attributes(changeset)
@ -209,6 +222,7 @@ defmodule Ash.DataLayer.Mnesia do
end
end
@doc false
@impl true
def destroy(resource, %{data: record}) do
pkey =
@ -227,6 +241,7 @@ defmodule Ash.DataLayer.Mnesia do
end
end
@doc false
@impl true
def update(resource, changeset) do
pkey = pkey_list(resource, changeset.data)
@ -297,6 +312,7 @@ defmodule Ash.DataLayer.Mnesia do
end
end
@doc false
@impl true
def upsert(resource, changeset, keys) do
keys = keys || Ash.Resource.Info.primary_key(resource)
@ -336,6 +352,7 @@ defmodule Ash.DataLayer.Mnesia do
end
end
@doc false
@impl true
def transaction(_, func, _timeout) do
case Mnesia.transaction(func) do
@ -350,6 +367,7 @@ defmodule Ash.DataLayer.Mnesia do
end
end
@doc false
@impl true
@spec rollback(term, term) :: no_return
def rollback(_, value) do

View file

@ -8,8 +8,7 @@ defmodule Ash.DataLayer.Simple do
use Spark.Dsl.Extension, transformers: [], sections: []
def show_in_docs?, do: false
@doc false
def can?(_, :create), do: true
def can?(_, :update), do: true
def can?(_, :destroy), do: true
@ -35,10 +34,12 @@ defmodule Ash.DataLayer.Simple do
Ash.Query.put_context(query, :data, data)
end
@doc false
def resource_to_query(resource, api) do
%Query{data: [], resource: resource, api: api}
end
@doc false
def run_query(%{data_set?: false}, resource) do
{:error,
Ash.Error.SimpleDataLayer.NoDataProvided.exception(
@ -61,20 +62,25 @@ defmodule Ash.DataLayer.Simple do
end)}
end
@doc false
def limit(query, limit, _) do
{:ok, %{query | limit: limit}}
end
@doc false
def set_tenant(_, query, _), do: {:ok, query}
@doc false
def filter(query, filter, _resource) do
{:ok, %{query | filter: filter}}
end
@doc false
def sort(query, sort, _resource) do
{:ok, %{query | sort: sort}}
end
@doc false
def set_context(_resource, query, context) do
case Map.fetch(context, :data) do
{:ok, value} ->
@ -85,14 +91,17 @@ defmodule Ash.DataLayer.Simple do
end
end
@doc false
def create(_resource, changeset) do
Ash.Changeset.apply_attributes(changeset)
end
@doc false
def update(_resource, changeset) do
Ash.Changeset.apply_attributes(changeset)
end
@doc false
def destroy(_resource, _changeset) do
:ok
end

View file

@ -32,7 +32,7 @@ defmodule Ash.Filter.Runtime do
|> Enum.map(& &1.relationship_path)
|> Enum.reject(&(&1 == []))
|> Enum.uniq()
|> Enum.reject(&Ash.Resource.Info.loaded?(records, &1))
|> Enum.reject(&Ash.Resource.loaded?(records, &1))
|> Enum.map(&path_to_load/1)
|> case do
[] ->
@ -81,7 +81,7 @@ defmodule Ash.Filter.Runtime do
|> Enum.uniq()
relationship_paths
|> Enum.reject(&Ash.Resource.Info.loaded?(record, &1))
|> Enum.reject(&Ash.Resource.loaded?(record, &1))
|> case do
[] ->
{:ok,

View file

@ -0,0 +1,28 @@
defmodule Ash.Notifier.PubSub.Info do
@moduledoc "Introspection helpers for Ash.Notifier.PubSub"
@doc "The list of publications for a resource"
def publications(resource) do
Spark.Dsl.Extension.get_entities(resource, [:pub_sub])
end
@doc "The pubsub module for a resource"
def module(resource) do
Spark.Dsl.Extension.get_opt(resource, [:pub_sub], :module, nil)
end
@doc "The topic prefix for a resource"
def prefix(resource) do
Spark.Dsl.Extension.get_opt(resource, [:pub_sub], :prefix, nil)
end
@doc "The pubsub name for a resource"
def name(resource) do
Spark.Dsl.Extension.get_opt(resource, [:pub_sub], :name, nil)
end
@doc "The broadcast type for aresource"
def broadcast_type(resource) do
Spark.Dsl.Extension.get_opt(resource, [:pub_sub], :broadcast_type, nil)
end
end

View file

@ -112,6 +112,14 @@ defmodule Ash.Notifier.PubSub do
doc:
"A prefix for all pubsub messages, e.g `users`. A message with `created` would be published as `users:created`"
],
broadcast_type: [
type: {:one_of, [:notification, :phoenix_broadcast, :broadcast]},
default: :notification,
doc: """
What shape the event payloads will be in. `:notification` just sends the notification, `phoenix_broadcast` sends a `%Phoenix.Socket.Broadcast{}`, and `:broadcast`
sends `%{topic: <topic>, event: <event>, notification: <notification>}`
"""
],
name: [
type: :atom,
doc: """
@ -120,9 +128,6 @@ defmodule Ash.Notifier.PubSub do
If you are simply using your `Endpoint` module for pubsub then this is unnecessary. If you want to use
a custom pub started with something like `{Phoenix.PubSub, name: MyName}`, then you can provide `MyName` to
here.
If this option is provided, we assume we are working with a `Phoenix.PubSub` and not a `Phoenix.Endpoint`, so
the payload is sent as a `%Phoenix.Socket.Broadcast{}` if that module is available.
"""
]
]
@ -136,22 +141,19 @@ defmodule Ash.Notifier.PubSub do
use Spark.Dsl.Extension, sections: @sections
def publications(resource) do
Spark.Dsl.Extension.get_entities(resource, [:pub_sub])
end
@deprecated "use Ash.Notifier.PubSub.Info.publications/1 instead"
defdelegate publications(resource), to: Ash.Notifier.PubSub.Info
def module(resource) do
Spark.Dsl.Extension.get_opt(resource, [:pub_sub], :module, nil)
end
@deprecated "use Ash.Notifier.PubSub.Info.module/1 instead"
defdelegate module(resource), to: Ash.Notifier.PubSub.Info
def prefix(resource) do
Spark.Dsl.Extension.get_opt(resource, [:pub_sub], :prefix, nil)
end
@deprecated "use Ash.Notifier.PubSub.Info.prefix/1 instead"
defdelegate prefix(resource), to: Ash.Notifier.PubSub.Info
def name(resource) do
Spark.Dsl.Extension.get_opt(resource, [:pub_sub], :name, nil)
end
@deprecated "use Ash.Notifier.PubSub.Info.name/1 instead"
defdelegate name(resource), to: Ash.Notifier.PubSub.Info
@doc false
def notify(%Ash.Notifier.Notification{resource: resource} = notification) do
resource
|> publications()
@ -182,7 +184,7 @@ defmodule Ash.Notifier.PubSub do
args =
case name(notification.resource) do
nil ->
[prefixed_topic, event, notification]
[prefixed_topic, event, to_payload(topic, event, notification)]
pub_sub ->
payload = to_payload(topic, event, notification)
@ -205,19 +207,41 @@ defmodule Ash.Notifier.PubSub do
if Code.ensure_loaded?(Phoenix.Socket.Broadcast) do
def to_payload(topic, event, notification) do
%Phoenix.Socket.Broadcast{
topic: topic,
event: event,
payload: notification
}
case Ash.Notifier.PubSub.Info.broadcast_type(notification.resource) do
:phoenix_broadcast ->
%Phoenix.Socket.Broadcast{
topic: topic,
event: event,
payload: notification
}
:broadcast ->
%{
topic: topic,
event: event,
payload: notification
}
:notification ->
notification
end
end
else
def to_payload(topic, event, notification) do
%{
topic: topic,
event: event,
payload: notification
}
case Ash.Notifier.PubSub.Info.broadcast_type(notification.resource) do
:phoenix_broadcast ->
raise "A resource was configured with `broadcast_type :phoenix_broadcast` but `Phoenix.Socket.Broadcast` was not compiled."
:broadcast ->
%{
topic: topic,
event: event,
payload: notification
}
:notification ->
notification
end
end
end

View file

@ -49,8 +49,4 @@ defmodule Ash.Registry.Dsl do
"""
use Spark.Dsl.Extension, sections: @sections, transformers: @transformers
def warn_on_empty?(registry) do
Extension.get_opt(registry, [:entries], :warn_on_empty?, false, true)
end
end

View file

@ -12,7 +12,7 @@ defmodule Ash.Registry.ResourceValidations.Transformers.EnsureNoEmbeds do
@impl true
def transform(registry, dsl) do
registry
|> Ash.Registry.entries()
|> Ash.Registry.Info.entries()
|> Enum.filter(&Ash.Resource.Info.embedded?/1)
|> case do
[] ->

View file

@ -12,7 +12,7 @@ defmodule Ash.Registry.ResourceValidations.Transformers.EnsureResourcesCompiled
@impl true
def transform(registry, dsl) do
registry
|> Ash.Registry.entries()
|> Ash.Registry.Info.entries()
|> Enum.map(fn resource ->
try do
# This is to get the compiler to ensure that the resource is compiled

View file

@ -13,7 +13,7 @@ defmodule Ash.Registry.ResourceValidations.Transformers.ValidateRelatedResourceI
@impl true
def transform(module, dsl) do
resources = Ash.Registry.entries(module)
resources = Ash.Registry.Info.entries(module)
resources
|> Enum.flat_map(&get_all_related_resources(&1, resources))

23
lib/ash/registry/info.ex Normal file
View file

@ -0,0 +1,23 @@
defmodule Ash.Registry.Info do
@moduledoc "Introspection helpers for `Ash.Registry`"
alias Spark.Dsl.Extension
@doc "Wether or not the registry will warn if it has no entries"
@spec warn_on_empty?(Ash.Registry.t()) :: boolean
def warn_on_empty?(registry) do
Extension.get_opt(registry, [:entries], :warn_on_empty?, true, true)
end
@doc "The list of entries in the registry"
@spec entries(Ash.Registry.t()) :: list(module)
def entries(registry) do
case registry |> Extension.get_entities([:entries]) |> Enum.map(& &1.entry) do
[] ->
registry |> Extension.get_entities([:resources]) |> Enum.map(& &1.resource)
other ->
other
end
end
end

View file

@ -23,25 +23,9 @@ defmodule Ash.Registry do
use Spark.Dsl, default_extensions: [extensions: [Ash.Registry.Dsl]]
alias Spark.Dsl.Extension
@deprecated "use Ash.Registry.Info.warn_on_empty?/1 instead"
defdelegate warn_on_empty?(registry), to: Ash.Registry.Info
@spec entries(t()) :: list(module)
def entries(registry) do
case registry |> Extension.get_entities([:entries]) |> Enum.map(& &1.entry) do
[] ->
registry |> Extension.get_entities([:resources]) |> Enum.map(& &1.resource)
other ->
other
end
end
@spec warn_on_empty?(t()) :: boolean
def warn_on_empty?(registry) do
Extension.get_opt(registry, [:entries], :warn_on_empty?, true, true)
end
@spec api_or_api_and_registry(Ash.Api.t() | {Ash.Api.t(), t()}) :: {t(), t()}
def api_or_api_and_registry({api, registry}), do: {api, registry}
def api_or_api_and_registry(api), do: {api, api}
@deprecated "use Ash.Registry.Info.entries/1 instead"
defdelegate entries(registry), to: Ash.Registry.Info
end

View file

@ -3,8 +3,8 @@ defmodule Ash.Registry.Transformers.WarnOnEmpty do
use Spark.Dsl.Transformer
def transform(registry, dsl) do
if Ash.Registry.warn_on_empty?(registry) do
case Ash.Registry.entries(registry) do
if Ash.Registry.Info.warn_on_empty?(registry) do
case Ash.Registry.Info.entries(registry) do
[] ->
{:warn, dsl, "#{inspect(registry)} has no entries."}

View file

@ -19,6 +19,7 @@ defmodule Ash.Resource do
extensions: [Ash.Resource.Dsl]
]
@doc false
def init(opts) do
if opts[:data_layer] == :embedded do
{:ok,
@ -30,6 +31,7 @@ defmodule Ash.Resource do
end
end
@doc false
def handle_opts(opts) do
quote bind_quoted: [embedded?: opts[:embedded?]] do
if embedded? do
@ -63,6 +65,7 @@ defmodule Ash.Resource do
end
end
@doc false
def handle_before_compile(_opts) do
quote do
require Ash.Schema
@ -151,4 +154,116 @@ defmodule Ash.Resource do
end
end
end
@spec set_metadata(Ash.Resource.record(), map) :: Ash.Resource.record()
def set_metadata(record, map) do
%{record | __metadata__: Ash.Helpers.deep_merge_maps(record.__metadata__, map)}
end
@doc false
def set_meta(%{__meta__: _} = struct, meta) do
%{struct | __meta__: meta}
end
def set_meta(struct, _), do: struct
@spec put_metadata(Ash.Resource.record(), atom, term) :: Ash.Resource.record()
def put_metadata(record, key, term) do
set_metadata(record, %{key => term})
end
@doc "Sets a list of loaded key or paths to a key back to their original unloaded stated"
@spec unload_many(
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page(),
list(atom) | list(list(atom))
) ::
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page()
def unload_many(data, paths) do
Enum.reduce(paths, data, &unload(&2, &1))
end
@doc "Sets a loaded key or path to a key back to its original unloaded stated"
@spec unload(
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page(),
atom | list(atom)
) ::
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page()
def unload(nil, _), do: nil
def unload(%struct{results: results} = page, path)
when struct in [Ash.Page.Keyset, Ash.Page.Offset] do
%{page | results: unload(results, path)}
end
def unload(records, path) when is_list(records) do
Enum.map(records, &unload(&1, path))
end
def unload(record, [path]) do
unload(record, path)
end
def unload(record, [key | rest]) do
Map.update!(record, key, &unload(&1, rest))
end
def unload(%struct{} = record, key) when is_atom(key) do
Map.put(record, key, Map.get(struct.__struct__(), key))
end
def unload(other, _), do: other
@doc "Returns true if the load or path to load has been loaded"
@spec loaded?(
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page(),
atom | list(atom)
) ::
boolean
def loaded?(nil, _), do: true
def loaded?(%page{results: results}, path) when page in [Ash.Page.Keyset, Ash.Page.Offset] do
loaded?(results, path)
end
def loaded?(records, path) when not is_list(path) do
loaded?(records, [path])
end
def loaded?(records, path) when is_list(records) do
Enum.all?(records, &loaded?(&1, path))
end
def loaded?(%Ash.NotLoaded{}, _), do: false
def loaded?(_, []), do: true
def loaded?(record, [key | rest]) do
record
|> Map.get(key)
|> loaded?(rest)
end
@spec get_metadata(Ash.Resource.record(), atom | list(atom)) :: term
def get_metadata(record, key_or_path) do
get_in(record.__metadata__ || %{}, List.wrap(key_or_path))
end
@spec selected?(Ash.Resource.record(), atom) :: boolean
def selected?(%resource{} = record, field) do
case get_metadata(record, :selected) do
nil ->
attribute = Ash.Resource.Info.attribute(resource, field)
attribute && (!attribute.private? || attribute.primary_key?)
select ->
if field in select do
true
else
attribute = Ash.Resource.Info.attribute(resource, field)
attribute && attribute.primary_key?
end
end
end
end

View file

@ -3,23 +3,27 @@ defmodule Ash.Resource.Info do
alias Spark.Dsl.Extension
@spec set_metadata(Ash.Resource.record(), map) :: Ash.Resource.record()
def set_metadata(record, map) do
%{record | __metadata__: Ash.Helpers.deep_merge_maps(record.__metadata__, map)}
end
@deprecated "Use `Ash.Resource.set_metadata/2` instead"
defdelegate set_metadata(record, map), to: Ash.Resource
@doc false
def set_meta(%{__meta__: _} = struct, meta) do
%{struct | __meta__: meta}
end
@deprecated "Use `Ash.Resource.put_metadata/3` instead"
defdelegate put_metadata(record, key, term), to: Ash.Resource
def set_meta(struct, _), do: struct
@deprecated "Use `Ash.Resource.unload_many/2` instead"
defdelegate unload_many(record, loads), to: Ash.Resource
@spec put_metadata(Ash.Resource.record(), atom, term) :: Ash.Resource.record()
def put_metadata(record, key, term) do
set_metadata(record, %{key => term})
end
@deprecated "Use `Ash.Resource.unload/2` instead"
defdelegate unload(record, key_or_path), to: Ash.Resource
@deprecated "Use `Ash.Resource.get_metadata/2` instead"
defdelegate get_metadata(record, key_or_path), to: Ash.Resource
@deprecated "Use `Ash.Resource.selected?/2` instead"
defdelegate selected?(record, field), to: Ash.Resource
@doc """
Retrieves a relationship path from the resource related by path, to the provided resource.
"""
def reverse_relationship(resource, path, acc \\ [])
def reverse_relationship(_, [], acc),
@ -66,136 +70,49 @@ defmodule Ash.Resource.Info do
is_nil(rel.context)
end
@doc "Sets a list of loaded key or paths to a key back to their original unloaded stated"
@spec unload_many(
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page(),
list(atom) | list(list(atom))
) ::
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page()
def unload_many(data, paths) do
Enum.reduce(paths, data, &unload(&2, &1))
end
@doc "Sets a loaded key or path to a key back to its original unloaded stated"
@spec unload(
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page(),
atom | list(atom)
) ::
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page()
def unload(nil, _), do: nil
def unload(%struct{results: results} = page, path)
when struct in [Ash.Page.Keyset, Ash.Page.Offset] do
%{page | results: unload(results, path)}
end
def unload(records, path) when is_list(records) do
Enum.map(records, &unload(&1, path))
end
def unload(record, [path]) do
unload(record, path)
end
def unload(record, [key | rest]) do
Map.update!(record, key, &unload(&1, rest))
end
def unload(%struct{} = record, key) when is_atom(key) do
Map.put(record, key, Map.get(struct.__struct__(), key))
end
def unload(other, _), do: other
@doc "Returns true if the load or path to load has been loaded"
@spec loaded?(
nil | list(Ash.Resource.record()) | Ash.Resource.record() | Ash.Page.page(),
atom | list(atom)
) ::
boolean
def loaded?(nil, _), do: true
def loaded?(%page{results: results}, path) when page in [Ash.Page.Keyset, Ash.Page.Offset] do
loaded?(results, path)
end
def loaded?(records, path) when not is_list(path) do
loaded?(records, [path])
end
def loaded?(records, path) when is_list(records) do
Enum.all?(records, &loaded?(&1, path))
end
def loaded?(%Ash.NotLoaded{}, _), do: false
def loaded?(_, []), do: true
def loaded?(record, [key | rest]) do
record
|> Map.get(key)
|> loaded?(rest)
end
@spec get_metadata(Ash.Resource.record(), atom | list(atom)) :: term
def get_metadata(record, key_or_path) do
get_in(record.__metadata__ || %{}, List.wrap(key_or_path))
end
@spec selected?(Ash.Resource.record(), atom) :: boolean
def selected?(%resource{} = record, field) do
case get_metadata(record, :selected) do
nil ->
attribute = Ash.Resource.Info.attribute(resource, field)
attribute && (!attribute.private? || attribute.primary_key?)
select ->
if field in select do
true
else
attribute = Ash.Resource.Info.attribute(resource, field)
attribute && attribute.primary_key?
end
end
end
@doc """
The list of code interface definitions.
"""
@spec interfaces(Ash.Resource.t()) :: [Ash.Resource.Interface.t()]
def interfaces(resource) do
Extension.get_entities(resource, [:code_interface])
end
@spec define_interface_in_resource?(Ash.Resource.t()) :: boolean
def define_interface_in_resource?(resource) do
!!Extension.get_opt(resource, [:code_interface], :define_for, false)
end
@doc """
The Api to define the interface for, when defining it in the resource
"""
@spec define_interface_for(Ash.Resource.t()) :: atom | nil
def define_interface_for(resource) do
Extension.get_opt(resource, [:code_interface], :define_for, nil)
end
@spec extensions(Ash.Resource.t()) :: [module]
def extensions(resource) do
Extension.get_persisted(resource, :extensions, [])
end
@doc """
Whether or not the resource is an embedded resource
"""
@spec embedded?(Ash.Resource.t()) :: boolean
def embedded?(resource) do
Extension.get_persisted(resource, :embedded?, false)
end
@doc """
The description of the resource
"""
@spec description(Ash.Resource.t()) :: String.t() | nil
def description(resource) do
Extension.get_opt(resource, [:resource], :description, "no description")
end
@doc """
The base filter of the resource
"""
@spec base_filter(Ash.Resource.t()) :: term
def base_filter(resource) do
Extension.get_opt(resource, [:resource], :base_filter, nil)
end
@doc """
The default context of the resource
"""
@spec default_context(Ash.Resource.t()) :: term
def default_context(resource) do
Extension.get_opt(resource, [:resource], :default_context, nil)
@ -227,6 +144,7 @@ defmodule Ash.Resource.Info do
Extension.get_persisted(resource, :notifiers, [])
end
@doc "A list of all validations for the resource for a given action type"
@spec validations(Ash.Resource.t(), :create | :update | :destroy) :: [
Ash.Resource.Validation.t()
]
@ -242,6 +160,7 @@ defmodule Ash.Resource.Info do
Extension.get_entities(resource, [:validations])
end
@doc "A list of all changes for the resource for a given action type"
@spec changes(Ash.Resource.t(), :create | :update | :destroy) ::
list(
Ash.Resource.Validation.t()
@ -344,17 +263,19 @@ defmodule Ash.Resource.Info do
|> Enum.find(&(&1.name == relationship_name && !&1.private?))
end
@doc "Get the multitenancy strategy for a resource"
@doc "The multitenancy strategy for a resource"
@spec multitenancy_strategy(Ash.Resource.t()) :: :context | :attribute | nil
def multitenancy_strategy(resource) do
Spark.Dsl.Extension.get_opt(resource, [:multitenancy], :strategy, nil)
end
@doc "The multitenancy attribute for a resource"
@spec multitenancy_attribute(Ash.Resource.t()) :: atom | nil
def multitenancy_attribute(resource) do
Spark.Dsl.Extension.get_opt(resource, [:multitenancy], :attribute, nil)
end
@doc "The function to parse the tenant from the attribute"
@spec multitenancy_parse_attribute(Ash.Resource.t()) :: {atom, atom, list(any)}
def multitenancy_parse_attribute(resource) do
Spark.Dsl.Extension.get_opt(
@ -368,16 +289,19 @@ defmodule Ash.Resource.Info do
@doc false
def _identity(x), do: x
@doc "The MFA to parse the tenant from the attribute"
@spec multitenancy_global?(Ash.Resource.t()) :: atom | nil
def multitenancy_global?(resource) do
Spark.Dsl.Extension.get_opt(resource, [:multitenancy], :global?, nil)
end
@doc "The source attribute for multitenancy"
@spec multitenancy_source(Ash.Resource.t()) :: atom | nil
def multitenancy_source(resource) do
Spark.Dsl.Extension.get_opt(resource, [:multitenancy], :source, nil)
end
@doc "The template for creating the tenant name"
@spec multitenancy_template(Ash.Resource.t()) :: atom | nil
def multitenancy_template(resource) do
Spark.Dsl.Extension.get_opt(resource, [:multitenancy], :template, nil)

View file

@ -8,7 +8,7 @@ defmodule Ash.Resource.Interface do
defmacro __using__(_) do
quote bind_quoted: [], generated: true do
if Ash.Resource.Info.define_interface_in_resource?(__MODULE__) do
if Ash.Resource.Info.define_interface_for(__MODULE__) do
require Ash.CodeInterface
Ash.CodeInterface.define_interface(

View file

@ -76,7 +76,7 @@ defmodule Ash.Resource.Relationships.SharedOptions do
type: :boolean,
default: true,
doc: """
Wether or not the relationship may be managed.
Whether or not the relationship may be managed.
""",
links: []
],
@ -117,7 +117,7 @@ defmodule Ash.Resource.Relationships.SharedOptions do
default: false,
links: [],
doc: """
Wether or not related values may exist for this relationship at creation.
Whether or not related values may exist for this relationship at creation.
"""
],
violation_message: [

View file

@ -201,7 +201,7 @@ defmodule Ash.MixProject do
# Run "mix help deps" to learn about dependencies.
defp deps do
[
{:spark, "~> 0.1 and >= 0.1.6"},
{:spark, "~> 0.1 and >= 0.1.7"},
{:ecto, "~> 3.7"},
{:ets, "~> 0.8.0"},
{:decimal, "~> 2.0"},
@ -228,8 +228,8 @@ defmodule Ash.MixProject do
[
sobelow: "sobelow --skip",
credo: "credo --strict",
"ash.formatter":
"ash.formatter --extensions Ash.Resource.Dsl,Ash.Api.Dsl,Ash.Flow.Dsl,Ash.Registry.Dsl,Ash.DataLayer.Ets,Ash.DataLayer.Mnesia,Ash.Notifier.PubSub,Ash.Policy.Authorizer"
"spark.formatter":
"spark.formatter --extensions Ash.Resource.Dsl,Ash.Api.Dsl,Ash.Flow.Dsl,Ash.Registry.Dsl,Ash.DataLayer.Ets,Ash.DataLayer.Mnesia,Ash.Notifier.PubSub,Ash.Policy.Authorizer"
]
end
end

View file

@ -35,7 +35,7 @@
"providers": {:hex, :providers, "1.8.1", "70b4197869514344a8a60e2b2a4ef41ca03def43cfb1712ecf076a0f3c62f083", [:rebar3], [{:getopt, "1.0.1", [hex: :getopt, repo: "hexpm", optional: false]}], "hexpm", "e45745ade9c476a9a469ea0840e418ab19360dc44f01a233304e118a44486ba0"},
"sobelow": {:hex, :sobelow, "0.11.1", "23438964486f8112b41e743bbfd402da3e5b296fdc9eacab29914b79c48916dd", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "9897363a7eff96f4809304a90aad819e2ad5e5d24db547af502885146746a53c"},
"sourceror": {:hex, :sourceror, "0.11.1", "1b80efe84330beefb6b3da95b75c1e1cdefe9dc785bf4c5064fae251a8af615c", [:mix], [], "hexpm", "22b6828ee5572f6cec75cc6357f3ca6c730a02954cef0302c428b3dba31e5e74"},
"spark": {:hex, :spark, "0.1.6", "74659fdee3b68d7f8e78e3b479e9fe3cc9f4454de8bbcdc52f8808b86a7483c2", [:mix], [{:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.11.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "877d460958d990784f2e2caa7a0d2dc2b50a8e251b0692caef3c2715c20648d9"},
"spark": {:hex, :spark, "0.1.7", "1691e87a4aa08ea4b04751d20308c0d077d90d1e20eff2f5e0d2d4b5a552f23e", [:mix], [{:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:sourceror, "~> 0.11.1", [hex: :sourceror, repo: "hexpm", optional: false]}], "hexpm", "e2447df8f717fef3888f4b8fa727b6a878f5165cf55aa97d342488ae37052353"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
"stream_data": {:hex, :stream_data, "0.5.0", "b27641e58941685c75b353577dc602c9d2c12292dd84babf506c2033cd97893e", [:mix], [], "hexpm", "012bd2eec069ada4db3411f9115ccafa38540a3c78c4c0349f151fc761b9e271"},
"telemetry": {:hex, :telemetry, "1.1.0", "a589817034a27eab11144ad24d5c0f9fab1f58173274b1e9bae7074af9cbee51", [:rebar3], [], "hexpm", "b727b2a1f75614774cff2d7565b64d0dfa5bd52ba517f16543e6fc7efcc0df48"},

View file

@ -19,6 +19,51 @@ These should all be straight forward enough to do a simple find and replace in y
- `destination_field_on_join_table` -> `destination_attribute_on_join_resource`
- `no_fields?` -> `no_attributes?`
## DSL changes
A new option has been added to the pub_sub notifier. If you are using it with phoenix, and you want it to publish a `%Phoenix.Socket.Broadcast{}` struct (which is what it used to do if you specified the `name` option with pub sub), then you'll need to set `broadcast_type :phoenix_broadcast`
## Function Changes
The following functions have been moved from `Ash.Resource.Info` to `Ash.Resource`. The old functions still exist, but will warn as deprecated.
- `set_metadata/2`
- `put_metadata/3`
- `unload_many/2`
- `unload/2`
- `get_metadata/2`
- `selected?/2`
The following functions have been moved from `Ash.Api` to `Ash.Api.Info`. The old functions still exist, but will warn as deprecated.
- `resource/2`
- `resources/1`
- `registry/1`
- `allow/1`
- `timeout/1`
- `require_actor?/1`
- `authorize/1`
- `allow_unregistered?/1`
The following functions have been moved from `Ash.Notifier.PubSub` to `Ash.Notifier.PubSub.Info`. The old functions still exist, but will warn as deprecated.
- `publications/1`
- `module/1`
- `prefix/1`
- `name/1`
The following functions have been moved. The old functions still exist, but will warn as deprecated.
- `Ash.DataLayer.Ets.private?/1` -> `Ash.DataLayer.Ets.Info.private?/1`
- `Ash.DataLayer.Ets.table/1` -> `Ash.DataLayer.Ets.Info.table/1`
- `Ash.DataLayer.Mnesia.table/1` -> `Ash.DataLayer.Mnesia.table/1`
- `Ash.Registry.warn_on_empty?/1` -> `Ash.Registry.Info.warn_on_empty?/1`
- `Ash.Registry.entries/1` -> `Ash.Registry.Info.entries/1`
The following functions have been moved:
- `Ash.Resource.extensions/1` -> `Spark.extensions/1`
## Upgrading to 1.53
### Default actions

View file

@ -29,7 +29,7 @@ have a condition like `actor_attribute_equals(:admin, true)`.
If both apply (i.e an admin is using a read action), then both policies must pass.
A policy can produce one of three results: `:forbidden`, `:authorized`, or `:unknown`. `:unknown` is treated
the same as a `:forbidden`.
A policy contains checks, which determine wether or not the policy passes for a given request.
A policy contains checks, which determine whether or not the policy passes for a given request.
#### Bypass

View file

@ -297,7 +297,7 @@ defmodule Ash.Test.Actions.LoadTest do
|> Api.read!(authorize?: true)
assert author
|> Ash.Resource.Info.unload([:posts, :author])
|> Ash.Resource.unload([:posts, :author])
|> Map.get(:posts)
|> Enum.all?(fn post ->
%Ash.NotLoaded{} = post.author

View file

@ -13,7 +13,7 @@ defmodule Ash.Test.Actions.ReadTest do
def prepare(query, _, _) do
Ash.Query.after_action(query, fn _query, authors ->
{:ok, Enum.map(authors, &Ash.Resource.Info.set_metadata(&1, %{prepared?: true}))}
{:ok, Enum.map(authors, &Ash.Resource.set_metadata(&1, %{prepared?: true}))}
end)
end
end

View file

@ -459,8 +459,8 @@ defmodule Ash.Test.Actions.UpdateTest do
post
|> new()
|> replace_relationship(:related_posts, [
Ash.Resource.Info.set_metadata(post2, %{join_keys: %{type: "a"}}),
Ash.Resource.Info.set_metadata(post3, %{join_keys: %{type: "b"}})
Ash.Resource.set_metadata(post2, %{join_keys: %{type: "a"}}),
Ash.Resource.set_metadata(post3, %{join_keys: %{type: "b"}})
])
|> Api.update!()
|> Api.load!(:related_posts_join_assoc)
@ -475,8 +475,8 @@ defmodule Ash.Test.Actions.UpdateTest do
|> replace_relationship(
:related_posts,
[
Ash.Resource.Info.set_metadata(post2, %{join_keys: %{type: "c"}}),
Ash.Resource.Info.set_metadata(post3, %{join_keys: %{type: "d"}})
Ash.Resource.set_metadata(post2, %{join_keys: %{type: "c"}}),
Ash.Resource.set_metadata(post3, %{join_keys: %{type: "d"}})
],
on_match: :update,
on_lookup: :relate