improvement: update to latest ash, prepare for ash-2.0

This commit is contained in:
Zach Daniel 2022-08-23 14:49:09 -04:00
parent 5723a449a7
commit 0d1d5b5465
13 changed files with 100 additions and 25 deletions

View file

@ -0,0 +1,15 @@
# Working With Phoenix
The AshPhoenix plugin adds lots of helpers for working with Phoenix Liveview (and regular views).
{{mix_dep:ash_phoenix}}
## Whats in the box?
- {{link:ash_phoenix:module:AshPhoenix.Form}} - A form data structure for using resource actions with phoenix forms
- {{link:ash_phoenix:module:AshPhoenix.Form.Auto}} - Tools to automatically determine nested form structures based on calls `manage_relationship` for an action.
- {{link:ash_phoenix:module:AshPhoenix.FilterForm}} - A form data structure for building filter statements
- {{link:ash_phoenix:module:AshPhoenix.LiveView}} - Helpers for querying data and integrating changes
- {{link:ash_phoenix:module:AshPhoenix.SubdomainPlug}} - A plug to determine a tenant using subdomains for multitenancy
- {{link:ash_phoenix:module:AshPhoenix.FormData.Error}} - A protocol to allow errors to be rendered in forms
- `Phoenix.HTML.Safe` implementations for `Ash.CiString` and `Ash.NotLoaded`

View file

@ -5,24 +5,6 @@ defmodule AshPhoenix do
These will be deprecated at some point, once the work on `AshPhoenix.Form` is complete. These will be deprecated at some point, once the work on `AshPhoenix.Form` is complete.
""" """
require Logger
def hide_errors(%Ash.Changeset{} = changeset) do
Ash.Changeset.put_context(changeset, :private, %{ash_phoenix: %{hide_errors: true}})
end
def hide_errors(%Ash.Query{} = query) do
Ash.Query.put_context(query, :private, %{ash_phoenix: %{hide_errors: true}})
end
def hiding_errors?(%Ash.Changeset{} = changeset) do
changeset.context[:private][:ash_phoenix][:hide_errors] == true
end
def hiding_errors?(%Ash.Query{} = query) do
query.context[:private][:ash_phoenix][:hide_errors] == true
end
@doc false @doc false
def replace_vars(message, vars) do def replace_vars(message, vars) do
Enum.reduce(vars || [], message, fn {key, value}, acc -> Enum.reduce(vars || [], message, fn {key, value}, acc ->

View file

@ -1,9 +1,11 @@
defmodule AshPhoenix.Form.Auto do defmodule AshPhoenix.Form.Auto do
@moduledoc """ @moduledoc """
A (slightly) experimental tool to automatically generate available nested forms based on a resource and action. A tool to automatically generate available nested forms based on a resource and action.
To use this, specify `forms: [auto?: true]` when creating the form. To use this, specify `forms: [auto?: true]` when creating the form.
Keep in mind, you can always specify these manually when creating a form by simply specifying the `forms` option.
There are two things that this builds forms for: There are two things that this builds forms for:
1. Attributes/arguments who's type is an embedded resource. 1. Attributes/arguments who's type is an embedded resource.
@ -25,7 +27,7 @@ defmodule AshPhoenix.Form.Auto do
<%= text_input comment_form, :on_create_field %> <%= text_input comment_form, :on_create_field %>
<% else %> <% else %>
<%= text_input comment_form, :text %> <%= text_input comment_form, :text %>
<%= text_input comment_form, :on_create_field %> <%= text_input comment_form, :on_update_field %>
<% end %> <% end %>
<button phx-click="remove_form" phx-value-path="<%= comment_form.name %>">Add Comment</button> <button phx-click="remove_form" phx-value-path="<%= comment_form.name %>">Add Comment</button>

View file

@ -332,7 +332,6 @@ defmodule AshPhoenix.Form do
] ]
] ]
@doc false
defp validate_opts_with_extra_keys(opts, schema) do defp validate_opts_with_extra_keys(opts, schema) do
keys = Keyword.keys(schema) keys = Keyword.keys(schema)
@ -1513,6 +1512,13 @@ defmodule AshPhoenix.Form do
] ]
] ]
@doc """
Updates the form at the provided path using the given function.
Marks all forms along the path as touched by default. To prevent it, provide `mark_as_touched?: false`.
This can be useful if you have a button that should modify a nested form in some way, for example.
"""
@spec update_form(t(), list(atom | integer) | String.t(), (t() -> t())) :: t() @spec update_form(t(), list(atom | integer) | String.t(), (t() -> t())) :: t()
def update_form(form, path, func, opts \\ []) do def update_form(form, path, func, opts \\ []) do
opts = Spark.OptionsHelpers.validate!(opts, @update_form_opts) opts = Spark.OptionsHelpers.validate!(opts, @update_form_opts)
@ -1573,6 +1579,9 @@ defmodule AshPhoenix.Form do
end end
end end
@doc """
Returns true if a given form path exists in the form
"""
@spec has_form?(t(), list(atom | integer) | String.t()) :: boolean @spec has_form?(t(), list(atom | integer) | String.t()) :: boolean
def has_form?(form, path) do def has_form?(form, path) do
not is_nil(get_form(form, path)) not is_nil(get_form(form, path))
@ -1581,6 +1590,9 @@ defmodule AshPhoenix.Form do
false false
end end
@doc """
Gets the form at the specified path
"""
@spec get_form(t(), list(atom | integer) | String.t()) :: t() | nil @spec get_form(t(), list(atom | integer) | String.t()) :: t() | nil
def get_form(form, path) do def get_form(form, path) do
path = path =
@ -1764,6 +1776,7 @@ defmodule AshPhoenix.Form do
end) end)
end end
@doc false
@spec errors_for(t(), list(atom | integer) | String.t(), type :: :simple | :raw | :plaintext) :: @spec errors_for(t(), list(atom | integer) | String.t(), type :: :simple | :raw | :plaintext) ::
[{atom, {String.t(), Keyword.t()}}] | [String.t()] | map | nil [{atom, {String.t(), Keyword.t()}}] | [String.t()] | map | nil
@deprecated "Use errors/2 instead" @deprecated "Use errors/2 instead"
@ -2153,6 +2166,8 @@ defmodule AshPhoenix.Form do
```elixir ```elixir
Enum.reduce(removed_form_paths, form, &AshPhoenix.Form.remove_form(&2, &1)) Enum.reduce(removed_form_paths, form, &AshPhoenix.Form.remove_form(&2, &1))
``` ```
#{Spark.OptionsHelpers.docs(@remove_form_opts)}
""" """
def remove_form(form, path, opts \\ []) do def remove_form(form, path, opts \\ []) do
opts = Spark.OptionsHelpers.validate!(opts, @remove_form_opts) opts = Spark.OptionsHelpers.validate!(opts, @remove_form_opts)
@ -2231,6 +2246,7 @@ defmodule AshPhoenix.Form do
end) end)
end end
@doc false
def arguments_changed?(form) do def arguments_changed?(form) do
changeset = form.source changeset = form.source
@ -2292,6 +2308,9 @@ defmodule AshPhoenix.Form do
defp apply_or_return(value, nil, _type, _), do: value defp apply_or_return(value, nil, _type, _), do: value
defp apply_or_return(value, function, type, _), do: function.(value, type) defp apply_or_return(value, function, type, _), do: function.(value, type)
@doc """
Returns the hidden fields for a form as a keyword list
"""
def hidden_fields(form) do def hidden_fields(form) do
hidden = hidden =
if form.type in [:read, :update, :destroy] && form.data do if form.type in [:read, :update, :destroy] && form.data do

View file

@ -1,4 +1,5 @@
defmodule AshPhoenix.Form.InvalidPath do defmodule AshPhoenix.Form.InvalidPath do
@moduledoc "Raised when an invalid path is used to find, update or remove a form"
defexception [:path] defexception [:path]
def exception(opts) do def exception(opts) do

View file

@ -1,4 +1,5 @@
defmodule AshPhoenix.Form.NoActionConfigured do defmodule AshPhoenix.Form.NoActionConfigured do
@moduledoc "Raised when a form action should happen but no action of the appropriate type has been configured"
defexception [:action, :path] defexception [:action, :path]
def exception(opts) do def exception(opts) do

View file

@ -1,4 +1,5 @@
defmodule AshPhoenix.Form.NoDataLoaded do defmodule AshPhoenix.Form.NoDataLoaded do
@moduledoc "Raised when a data needed to be used but the required data was not loaded"
defexception [:path] defexception [:path]
def exception(opts) do def exception(opts) do

View file

@ -1,4 +1,5 @@
defmodule AshPhoenix.Form.NoFormConfigured do defmodule AshPhoenix.Form.NoFormConfigured do
@moduledoc "Raised when attempting to refer to a form but no nested form with that name was configured.
defexception [:field, :available, :path] defexception [:field, :available, :path]
def exception(opts) do def exception(opts) do

View file

@ -1,4 +1,5 @@
defmodule AshPhoenix.Form.NoResourceConfigured do defmodule AshPhoenix.Form.NoResourceConfigured do
@moduledoc "Raised when a form needed to be constructed but the resource for that form could not be determined"
defexception [:path] defexception [:path]
def exception(opts) do def exception(opts) do

View file

@ -1,4 +1,14 @@
defprotocol AshPhoenix.FormData.Error do defprotocol AshPhoenix.FormData.Error do
@moduledoc """
A protocol for allowing errors to be rendered into a form.
To implement, define a `to_form_error/1` and return a single error or list of errors of the following shape:
`{:field_name, message, replacements}`
Replacements is a keyword list to allow for translations, by extracting out the constants like numbers from the message.
"""
def to_form_error(exception) def to_form_error(exception)
end end

View file

@ -74,6 +74,12 @@ defmodule AshPhoenix.LiveView do
Additionally, you'll need to define a `handle_info/2` callback for your liveview to receive any Additionally, you'll need to define a `handle_info/2` callback for your liveview to receive any
notifications, and pass that notification into `handle_live/3`. See `handle_live/3` for more. notifications, and pass that notification into `handle_live/3`. See `handle_live/3` for more.
## Important
The logic for handling events to keep data live is currently very limited. It will simply rerun the query
every time. To this end, you should feel free to intercept individual events and handle them yourself for
more optimized liveness.
## Pagination ## Pagination
To make paginated views convenient, as well as making it possible to keep those views live, Ash does not To make paginated views convenient, as well as making it possible to keep those views live, Ash does not
@ -89,7 +95,7 @@ defmodule AshPhoenix.LiveView do
## Options: ## Options:
#{NimbleOptions.docs(@opts)} #{NimbleOptions.docs(@opts)}
A great way to get readable millisecond values, you can use the functions in erlang's `:timer` module, A great way to get readable millisecond values is to use the functions in erlang's `:timer` module,
like `:timer.hours/1`, `:timer.minutes/1`, and `:timer.seconds/1` like `:timer.hours/1`, `:timer.minutes/1`, and `:timer.seconds/1`
#### refetch_interval #### refetch_interval

37
lib/doc_index.ex Normal file
View file

@ -0,0 +1,37 @@
defmodule AshPhoenix.DocIndex do
@moduledoc """
The doc index for ash_phoenix in ash-hq.org
"""
use Spark.DocIndex,
guides_from: [
"documentation/**/*.md"
]
def for_library, do: "ash_phoenix"
def extensions do
[]
end
def code_modules,
do: [
{"Phoenix Helpers",
[
AshPhoenix.Form,
AshPhoenix.Form.Auto,
AshPhoenix.FilterForm,
AshPhoenix.LiveView,
AshPhoenix.FormData.Error,
AshPhoenix.SubdomainPlug
],
{"Errors",
[
AshPhoenix.Form.InvalidPath,
AshPhoenix.Form.NoActionConfigured,
AshPhoenix.Form.NoDataLoaded,
AshPhoenix.Form.NoFormConfigured,
AshPhoenix.FOrm.NoResourceConfigured
]}}
]
end

View file

@ -5,7 +5,7 @@ defmodule AshPhoenix.MixProject do
Utilities for integrating Ash with Phoenix Utilities for integrating Ash with Phoenix
""" """
@version "0.7.7" @version "1.0.0-pre.0"
def project do def project do
[ [
@ -72,8 +72,7 @@ defmodule AshPhoenix.MixProject do
# Run "mix help deps" to learn about dependencies. # Run "mix help deps" to learn about dependencies.
defp deps do defp deps do
[ [
# {:ash, ash_version("~> 1.53 and >= 1.53.3")}, {:ash, ash_version("~> 2.0.0-pre.3")},
{:ash, github: "ash-project/ash", branch: "2.0"},
{:phoenix, "~> 1.5.6 or ~> 1.6.0"}, {:phoenix, "~> 1.5.6 or ~> 1.6.0"},
{:phoenix_html, "~> 2.14 or ~> 3.0"}, {:phoenix_html, "~> 2.14 or ~> 3.0"},
{:phoenix_live_view, "~> 0.15"}, {:phoenix_live_view, "~> 0.15"},