mirror of
https://github.com/ash-project/ash_phoenix.git
synced 2024-09-19 23:02:48 +12:00
improvement: update to latest ash, prepare for ash-2.0
This commit is contained in:
parent
5723a449a7
commit
0d1d5b5465
13 changed files with 100 additions and 25 deletions
15
documentation/topics/working-with-phoenix.md
Normal file
15
documentation/topics/working-with-phoenix.md
Normal 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`
|
|
@ -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 ->
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
37
lib/doc_index.ex
Normal 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
|
5
mix.exs
5
mix.exs
|
@ -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"},
|
||||||
|
|
Loading…
Reference in a new issue