ash-project / ash_phoenix Goto Github PK
View Code? Open in Web Editor NEWUtilities for integrating Ash and Phoenix
Home Page: https://hexdocs.pm/ash_phoenix
License: MIT License
Utilities for integrating Ash and Phoenix
Home Page: https://hexdocs.pm/ash_phoenix
License: MIT License
Minor DX improvement? I'm going to be doing a lot of this:
def handle_event("submit", %{"form" => params}, socket) do
form = AshPhoenix.Form.validate(socket.assigns.form, params)
case AshPhoenix.Form.submit(form, Crm.Accounts) do
{:ok, _} ->
...
end
end
when i could:
def handle_event("submit", %{"form" => params}, socket) do
case AshPhoenix.Form.validate_and_submit(socket.assigns.form, params, Crm.Accounts) do
{:ok, _} ->
...
end
end
I can't think of a reason why I'd need to do something with the validated form before passing to submit? But if i do, i still can. Unless I'm doing it wrong in the first place? Of course i could implement my own helper
Describe the bug
I noticed it while testing stuff in ash_admin but I believe the root cause is here.
When destroying a record via ash_admin, the following error is raised:
** (UndefinedFunctionError) function :ok.resource/0 is undefined (module :ok is not available)
:ok.resource()
(ash 1.52.0-rc.8) lib/ash/api/api.ex:987: Ash.Api.update/3
(my_app 0.1.0) lib/my_app/ash/api.ex:1: MyApp.Api.update/2
(ash_phoenix 0.7.2-rc.0) lib/ash_phoenix/form/form.ex:1160: AshPhoenix.Form.with_changeset/2
(ash_phoenix 0.7.2-rc.0) lib/ash_phoenix/form/form.ex:1076: AshPhoenix.Form.submit/2
(ash_admin 0.4.4) lib/ash_admin/components/resource/form.ex:1083: AshAdmin.Components.Resource.Form.handle_event/3
Looking at that line of ash_phoenix, it looks like it's trying to call an update function after a destroy? Which was introduced as part of this commit:
50f6a10#diff-9cf68247dcbdd870091032f2ed3c7f1ac6ee87d1dfb2051658c4e61cab2fe3efR1074
Should this line have been added?
To Reproduce
I'm not sure how to trigger it outside ash_admin. But within ash_admin, triggering a Destroy action will cause it.
Expected behavior
The action does actually delete the record, but the LV crashes and so the app is left on a white screen - this shouldn't happen, it should go back to the default read action or schema or something with a flash message. Whatever it did before the bug was introduced!
** Runtime
Minor DX improvement, when providing the wrong API to submit, currently getting:
** (BadMapError) expected a map, got: nil
(ash_phoenix 0.4.23-rc.1) lib/ash_phoenix/form/form.ex:698: AshPhoenix.Form.submit/3
(crm 0.1.0) lib/crm_web/live/profile_live.ex:83: CrmWeb.ProfileLive.handle_event/3
As per Discord discussion:
Is your feature request related to a problem? Please describe.
I've noticed that AshPhoenix.Form
persists data if there were errors, which makes sense, but sometimes it could be sensitive data like passwords. I'd like to scrub them from the form struct.
Describe the solution you'd like
Perhaps drop values which marked as sensetive?: true
.
Or maybe add clear_value(form, list_of_fields_to_clear)
function for this.
Describe alternatives you've considered
Drop those values manually from the form struct.
Is your feature request related to a problem? Please describe.
As discussed earlier. If we -
The nested records might not be in the same order as on the original form, thus re-rendering the page would have the nested records re-ordering and shifting on the page.
Even if we sort forms in the view, with something like
Enum.sort_by(inputs_for(@form, :tool_specifications), & Form.value(&1, :inserted_at))
Nested records may have been created at the same time, so the inserted_at
timestamps will be the same and thus fall back to the database default ordering. Correct sorting of timestamps also needs an extra argument like {:asc, DateTime}
to be passed to Enum.sort_by/3
, which will then fail when new nested records are built (because the timestamps will be not be set).
And if a field in the nested form is focussed at the time of save - Phoenix won't change the value of any focussed field, even though the value underlying it may change. So eg.
(That may be more of an edge case for my project as we have keyboard shortcuts for saving, instead of a submit button)
Describe the solution you'd like
A way to take a new form, and an old form, and tell the new form to use the order of nested records from the old form.
Express the feature either with a change to resource syntax, or with a change to the resource interface
Possibly something like:
Form.for_update(element, :update, api: MyApp.Api, forms: [auto?: true]
|> Form.reorder_nested(previous_form)
That would then iterate over the nested data and make sure ordering in the new form matches the old form.
Its been pointed out that we don't have a "Getting Started with LiveView", and given that most (or many) of our users are using LiveView this would likely be a very helpful piece of material :)
hey,
very interesting project
is there a phx 1.7 branch I can PoC against for AshPhoenix
I've upgraded my own project to phoenix 1.7rc0 (which has a bunch of new stuff)
See test case in #20
C&P from discord:
whenever we remove a form, we just track the path we're removing e.g
%Form{
removed_paths: [
[:comments, 0, :post],
[:post, :comments, 0]
]
}
and then when we create a form we just traverse all the forms and their data and remove those things
Yeah, thats what we'll have to do
dblack — Today at 16:19
uh, say i remove a form, then add a form, then remove a form... will that all fall over as the new form might get blown away on validate by the list of removed_paths.
ZachDaniel — Today at 16:19
No, that should be fine
dblack — Today at 16:20
cool
ZachDaniel — Today at 16:20
because when we handle these removed_paths, we're only going to remove the data that backs them
like if you had data like post: [comment, comment, comment]
if you remove [:post, :comments, 0]
we also get the data for that form, and remove the 0th thing
and thats effectively "permanent"
Describe the bug
I'm creating a toy application for tracking weight lifting workouts. I have a WeightType resource that I want to create a LiveView for. When running the Ash Phoenix LiveView generator I'm getting the following error and having trouble tracking down where it comes from so I can fix it. Help?
mix ash_phoenix.gen.live Mitochex.Workouts Mitochex.Workouts.WeightType
Would you like to name your actor? For example: `current_user`. If you choose no, we will not add any actor logic. [Yn] n
Please provide a plural_name. For example the plural of tweet is tweets.
You can press enter to abort, and then configure one on the resource, for example:
resource do
plural_name "tweets"
end
> weight_types
** (SyntaxError) lib/mitochex_web/live/weight_type_live/index.ex:80:87: unexpected token: }
HINT: the "(" on line 80 is missing terminator ")"
|
80 | |> assign(:weight_type, Mitochex.Workouts.get!(Mitochex.Workouts.WeightType, id)})
| ^
(elixir 1.15.5) lib/code.ex:945: Code.format_string!/2
(mix 1.15.5) lib/mix/tasks/format.ex:600: Mix.Tasks.Format.elixir_format/2
(ash_phoenix 1.2.17) lib/ash_phoenix/gen/live.ex:162: AshPhoenix.Gen.Live.write_formatted_template/5
(ash_phoenix 1.2.17) lib/ash_phoenix/gen/live.ex:97: AshPhoenix.Gen.Live.generate/3
(mix 1.15.5) lib/mix/task.ex:447: anonymous fn/3 in Mix.Task.run_task/5
(mix 1.15.5) lib/mix/cli.ex:92: Mix.CLI.run_task/2
/Users/cro/.local/share/rtx/installs/elixir/1.15.5-otp-26/bin/mix:2: (file)
To Reproduce
mix ash_phoenix.gen.live against an existing resource
Expected behavior
Generator should produce code without the syntax error.
** Runtime
{:ash, "~> 2.14"},
{:ash_postgres, "~> 1.3.6"},
{:ash_phoenix, "~> 1.2"},
{:ash_authentication, "~> 3.11"},
{:ash_authentication_phoenix, "~> 1.7"}
Describe the bug
The generator creates a multiple select input for atom attributes with the constraint one_of
To Reproduce
When you have an attribute like:
attribute :status, :atom do
constraints one_of: [:available, :reserved, :sold]
end
The generator mix ash_phoenix.gen.live Api Resource
will generate a select input with the attribute multiple
set which will make the field validation fail.
Expected behavior
I expect the generator to generate a select input without the attribute multiple
set.
Runtime
Additional context
none
This example appears on ash-hq:
ash_phoenix/lib/ash_phoenix/form/form.ex
Lines 109 to 126 in 1e5f344
Note that the action parameter is missing. Is it supposed to be?
Describe the bug
Thanks for adding the validate_opts to these two functions. They're not working though!
To Reproduce
Setting validate_opts like:
form = AshPhoenix.Form.add_form(form, :nested, validate_opts: [errors: false])
is hiding errors on the outer form, but not for the :nested
form
Expected behavior
both the outer and nested forms to have errors hidden/not populated on the form, when setting validate_opts: [errors: false]
** Runtime
Additional context
Add any other context about the problem here.
Describe the bug
mix ash_phoenix.gen.live doesn't add fields to show.ex, but they are added to form_component.ex and index.ex
To Reproduce
mix ash_phoenix.gen.live AshActivityPlanner.Planner AshActivityPlanner.Planner.Participant
in
https://github.com/jarlah/ash-activity-planner
(which will ofc now overwrite the existing live view
Expected behavior
Fields should also be added to show.ex, as I have modified it after running the generator in https://github.com/jarlah/ash-activity-planner/blob/main/lib/ash_activity_planner_web/live/participant_live/show.ex#L18
Additional context
Forum discussion:
https://elixirforum.com/t/how-to-convert-mix-gen-live-to-ash-compatible-live-view/59785
Describe the bug
Required version of latest ash phoenix release 1.3.2
requires ash version with (upstream fixed) bug that ensures that "If the aggregated resource has a default preparation that will load other resources, the aggregation will fail". See Ash issue #806
To Reproduce
Have following in mix.exs's deps, and try to load multiple aggregates, follow Ash issue #806 to reproduce.
{:ash, "~> 2.16.1"},
{:ash_phoenix, "~> 1.3.2"},
Expected behavior
A clear and concise description of what you expected to happen.
Runtime
1.15.7
25
darwin
2.16.1
1.3.2
We discussed in discord (https://discord.com/channels/711271361523351632/799097774523547669/853717356865519648) that 0.4.19 appears to cause a regression in the add_to_path
logic
You can easily see the failing test from my private repo https://github.com/axelson/gen_tracker/tree/broken-ash-phoenix-0.4.19 (note the tag)
by running the seed script and then visiting an item (i.e. http://localhost:4000/item/1) and pressing "add track"
FilterForm.to_filter
was renamed some time ago to to_filter_expression
but it's raising sibling to_filter!
didn't get renamed at the same time. Can I rename to_filter!
to to_filter_expression!
and deprecate to_filter
with a message saying use to_filter_expression!
?
Describe the bug
Submitting a Form by AshPhoenix.Form.submit
doesn't apply a change
from defined action. The change
actions sets the tenant
automatically. Committing the change using the Ash.Changeset |> MyApp.Api.create()
and passing params from form works. I have to pass the tenant
explicitly for the AshPhoenix.Form.submit
to work.
To Reproduce
I have a resource with roughly that structure:
defmodule MyApp.Api.SomeResource do
use MyApp.Api.SomeResource
actions do
create :create do
primary? true
change MyApp.Api.Changes.SetTenantFromActor
end
end
attributes do
...attributes
end
multitenancy do
strategy :attribute
attribute :organization_id
end
identities do
identity :organization_some_resource, :name
end
policies do
policy action(:create) do
forbid_unless MyApp.Api.Checks.IsAllowedTenant
...some other checks
end
end
end
With SetTenantFromActor
being:
defmodule MyApp.Api.Changes.SetTenantFromActor do
@moduledoc false
use Ash.Resource.Change
@impl true
def change(%Ash.Changeset{tenant: nil} = changeset, _opts, %{
actor: %Actor{metadata: %{organization_id: organization_id}}
}) do
Ash.Changeset.set_tenant(changeset, organization_id)
end
def change(changeset, _opts, _context), do: changeset
end
I have a AshPhoenix.Form created using AshPhoenix.Form.for_create(MyApp.Api.SomeResource, :create, api: MyApp.Api)
and the following code handling the form changes/submit.
def handle_event("change", %{"form" => params}, socket) do
form = AshPhoenix.Form.validate(socket.assigns.form, params)
{:noreply, assign(socket, :form, form)}
end
def handle_event("submit", %{"form" => params}, socket) do
case AshPhoenix.Form.submit(socket.assigns.form,
params: params,
api_opts: [
actor: socket.assigns.actor,
]
) do
{:ok, _resource} ->
{:noreply, socket}
{:error, _error} ->
{:noreply, put_flash(socket, :error, "Error")}
end
end
Submitting the form returns the {:error, form}
tuple, with form having no errors, but the errors?
attribute being set to true
.
Passing the tenant explicitly to the api_opts
of AshPhoenix.Form.submit
fixes the issue.
Expected behavior
The AshPhoenix.Form.submit
should apply a change
from create
action.
** Runtime
Describe the bug
"- Philosophy Guide"
results in a broken link
Expected behavior
"- Philosophy Guide"
leads to (probably?) the right document
Proposal
Allow to avoid the :api
option on form creation if an Api is define on the resource.
Describe the solution you'd like
Resource
|> Form.for_action(:create, api: App.Api)
|> Form.validate(%{name: "name"})
|> Form.submit()
Could be simplified to:
Resource
|> Form.for_action(:create)
|> Form.validate(%{name: "name"})
|> Form.submit()
Describe the bug
A result of AshPhoenix.Form.for_update/3 with resource that has has_one relationship is not valid.
This might be not a bug, but I couldn't find the cause in the document.
post = %Post{
id: Ash.UUID.generate(),
title: "title",
author: %Author{id: Ash.UUID.generate(), name: "name"}
}
form =
post
|> AshPhoenix.Form.for_update(:edit, forms: [auto?: true])
|> to_form()
form.source.valid? # <== false
To Reproduce
https://gist.github.com/nallwhy/9460c228d77bd469b13d7740614a2882
Expected behavior
form.source.valid?
is true
.
Runtime
Describe the bug
when inspecting the Phoenix.Html.Form
{inspect form.source.forms[:nested] }
if I haven't added any forms with add_form, it gives []
but, if i add_form, then remove form, it is nil
Expected behavior
A clear and concise description of what you expected to happen.
** Runtime
Additional context
Add any other context about the problem here.
Description
ash_postgres
v1.3.2
require as dependency {:ash, "~> v2.6.1"}
, which is too specific.
To Reproduce
Try to require ash_postgres
v1.3.2
with ash
v2.19.14
it will fail to satisfied dependencies.
Expected behavior
ash_postgres
v1.3.2
should be compatible with ash
v2.19.14
.
Runtime
v2.19.14
When submitting a form outside of LiveView Phoenix will populate unfilled form values with ""
. Even if the given resource has default values after calling AshPhoenix.Form.submit
what ends up being created is resource with attributes with the value of ""
. Furthermore if allow_nil? false
is set then the validation will also fail. This leads to a situation where default values need to be manually set before validating and submitting the form even though the attribute has a default value set.
Example Controller
def new(conn, %{"new_list" => params}) do
Todoish.Entries.List
|> AshPhoenix.Form.for_create(:create,
api: Todoish.Entries,
transform_params: fn params, _ ->
# Manually set default
params =
if params["title"] in ["", nil] do
Map.put(params, "title", "A Todoish List")
else
params
end
params =
# Manually set default
if params["description"] in ["", nil] do
Map.put(params, "description", "Add items to get started!")
else
params
end
Map.put(params, "url_id", Nanoid.generate())
end
)
|> AshPhoenix.Form.validate(params)
|> AshPhoenix.Form.submit()
|> case do
{:ok, result} ->
IO.inspect(result)
redirect(conn, to: "/#{result.url_id}")
{:error, form} ->
IO.inspect(form)
conn
|> put_flash(:error, "Uh-oh! Something went wrong. Please try again!")
|> render("index.html", form: form)
end
end
Expected behavior
It would be ideal if there was a way to submit a form such that the default values of the attribute itself can be used instead of needing to manually check and fill them prior to submitting/validating the form.
Describe the bug
My app is crashing when I am trying to submit a nested form.
To Reproduce
Create a nested form. In my case, I have a cart with cart items where I would want to change the quantity of the cart items:
form =
cart
|> Form.for_update(:update,
api: Api,
forms: [
items: [
type: :list,
data: cart.items,
resource: CartItem,
create_action: :create,
update_action: :update
]
]
)
|> to_form()
Now submitting the data from this form is where it crashes. The params look like like this when the form changes:
Form.submit(form, params: %{
"items" => %{
"0" => %{
"_form_type" => "update",
"_persistent_id" => "0",
"id" => "018d045e-8413-7dfe-b619-b468328242b3",
"quantity" => "10"
},
"1" => %{
"_form_type" => "update",
"_persistent_id" => "1",
"id" => "018d0e93-3019-75da-948b-8c9ebe60bfdf",
"quantity" => "20"
}
}
})
The result is the app crashing with this error:
[error] GenServer #PID<0.1482.0> terminating
** (FunctionClauseError) no function clause matching in Puppy.Orders.CartItem.cast_input/2
(puppy 0.1.0) lib/puppy/orders/resources/cart_item.ex:2: Puppy.Orders.CartItem.cast_input(%{"_form_type" => "update", "_persistent_id" => "1", "id" => "018d0e93-3019-75da-948b-8c9ebe60bfd
f", "quantity" => "20"}, [])
(ash 2.18.1) lib/ash/type/type.ex:576: Ash.Type.cast_input/3
(puppy 0.1.0) deps/ash/lib/ash/type/type.ex:1115: anonymous fn/3 in Puppy.Orders.CartItem.cast_input_array/2
(elixir 1.16.0) lib/enum.ex:4842: Enumerable.List.reduce/3
(elixir 1.16.0) lib/enum.ex:2582: Enum.reduce_while/3
(ash 2.18.1) lib/ash/type/helpers.ex:10: Ash.Type.Helpers.cast_input/5
(ash 2.18.1) lib/ash/changeset/changeset.ex:3784: Ash.Changeset.do_set_argument/3
(stdlib 5.2) maps.erl:416: :maps.fold_1/4
(ash 2.18.1) lib/ash/changeset/changeset.ex:1262: Ash.Changeset.handle_params/4
(ash 2.18.1) lib/ash/changeset/changeset.ex:1196: Ash.Changeset.do_for_action/4
(ash_phoenix 1.2.26) lib/ash_phoenix/form/form.ex:1096: AshPhoenix.Form.validate/3
(ash_phoenix 1.2.26) lib/ash_phoenix/form/form.ex:1644: AshPhoenix.Form.submit/2
(ash_phoenix 1.2.26) lib/ash_phoenix/form/form.ex:1629: AshPhoenix.Form.submit/2
(puppy 0.1.0) lib/puppy_web/pages/cart_live.ex:63: PuppyWeb.CartLive.handle_event/3
(phoenix_live_view 0.20.3) lib/phoenix_live_view/channel.ex:497: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
(telemetry 1.2.1) /home/tore/workspace/puppy/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
(phoenix_live_view 0.20.3) lib/phoenix_live_view/channel.ex:250: Phoenix.LiveView.Channel.handle_info/2
(stdlib 5.2) gen_server.erl:1095: :gen_server.try_handle_info/3
(stdlib 5.2) gen_server.erl:1183: :gen_server.handle_msg/6
(stdlib 5.2) proc_lib.erl:251: :proc_lib.wake_up/3
Expected behavior
Either that the form will be submitted or an error that is pointing out what I am doing wrong.
Similar error also happening when adding a form
Form.add_form(form, [:items])
crashes with a similar error:
[error] #PID<0.567.0> running Phoenix.Endpoint.SyncCodeReloadPlug (connection #PID<0.566.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: GET /cart
** (exit) an exception was raised:
** (FunctionClauseError) no function clause matching in Puppy.Orders.CartItem.cast_input/2
(puppy 0.1.0) lib/puppy/orders/resources/cart_item.ex:2: Puppy.Orders.CartItem.cast_input(%{"_form_type" => "create"}, [])
(ash 2.18.1) lib/ash/type/type.ex:576: Ash.Type.cast_input/3
(puppy 0.1.0) deps/ash/lib/ash/type/type.ex:1115: anonymous fn/3 in Puppy.Orders.CartItem.cast_input_array/2
(elixir 1.16.0) lib/enum.ex:4842: Enumerable.List.reduce/3
(elixir 1.16.0) lib/enum.ex:2582: Enum.reduce_while/3
(ash 2.18.1) lib/ash/type/helpers.ex:10: Ash.Type.Helpers.cast_input/5
(ash 2.18.1) lib/ash/changeset/changeset.ex:3784: Ash.Changeset.do_set_argument/3
(stdlib 5.2) maps.erl:416: :maps.fold_1/4
(ash 2.18.1) lib/ash/changeset/changeset.ex:1262: Ash.Changeset.handle_params/4
(ash 2.18.1) lib/ash/changeset/changeset.ex:1196: Ash.Changeset.do_for_action/4
(ash_phoenix 1.2.26) lib/ash_phoenix/form/form.ex:1096: AshPhoenix.Form.validate/3
(puppy 0.1.0) lib/puppy_web/pages/cart_live.ex:29: PuppyWeb.CartLive.mount/3
(phoenix_live_view 0.20.3) lib/phoenix_live_view/utils.ex:354: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
(telemetry 1.2.1) /home/tore/workspace/puppy/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
(phoenix_live_view 0.20.3) lib/phoenix_live_view/static.ex:281: Phoenix.LiveView.Static.call_mount_and_handle_params!/5
(phoenix_live_view 0.20.3) lib/phoenix_live_view/static.ex:116: Phoenix.LiveView.Static.render/3
(phoenix_live_view 0.20.3) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
(phoenix 1.7.10) lib/phoenix/router.ex:432: Phoenix.Router.__call__/5
(puppy 0.1.0) lib/puppy_web/endpoint.ex:1: PuppyWeb.Endpoint.plug_builder_call/2
(puppy 0.1.0) deps/plug/lib/plug/debugger.ex:136: PuppyWeb.Endpoint."call (overridable 3)"/2
Runtime
Additional context
Add any other context about the problem here.
The cart has a relationship to cart items:
relationships do
has_many :items, CartItem
end
Update action should also update the cart item:
update :update do
primary? true
argument :items, {:array, CartItem}
change manage_relationship(:items, type: :append_and_remove)
end
Describe the bug
I'm creating login where I try to mix phx.gen.auth
and example_with_auth. I hit this bug which prevents me from rendering forms.
To Reproduce
Create
This throws error above:
<.form let={f} for={@changeset} action={Routes.accounts_user_registration_path(@conn, :create)}>
eex equivalent: <%= form_for @changeset, Routes.accounts_user_registration_path(@conn, :create), fn f -> %>
When changing changeset
for :user
form works but is pretty much useless.
<.form let={f} for={:user} action={Routes.accounts_user_registration_path(@conn, :create)}>
eex equivalent: <%= form_for :user, Routes.accounts_user_registration_path(@conn, :create), fn f -> %>
Both heex and eex cause this bug.
Expected behavior
Form to be rendered into HTML.
** Runtime
I tried also latest master of ash and ash_phoenix with the same result.
Additional context
Maybe I'm using too complex forms here but I doubt it. My code is basically the same as user_registration_controller.ex and new.html.eex.
If you need access to code you can look on my GitLab. Prepare for mid upgrade mess on a small toy project with no tests. Just try to register. Oh and use env variables from .envrc.tmpl
to get it up and running.
Url to trigger bug: http://localhost:4000/accounts/users/register
As you can tell I wasn't expecting someone else to use that code for anything. But I'm happy to test any changes that might help solve the issue.
Because your app depends on ash_phoenix ~> 1.2 which depends on phoenix_html ~> 2.14 or ~> 3.0, phoenix_html ~> 2.14 or ~> 3.0 is required.
Describe the bug
Validations are a bit broken - validation occurs, but errors are not appearing on the fields
To Reproduce
Create a resource with:
attribute :note, :string do
allow_nil?(false)
default("HELLO")
end
and a form
AshPhoenix.Form.for_create(Activity, :create,
as: "activity",
api: Projects
)
Open the form, clear the "HELLO" value from the note, validate, validation occurs (error on the form), but no error message on the field. type something in the box, form error goes away, clear it again and error message does appear on the field this time...
Expected behavior
Error appears on the field
** Runtime
Additional context
Add any other context about the problem here.
Describe the bug
The form for a resource action that accepts an array of UUIDs for a many-many relationship, crashes before even rendering the form! :(
To Reproduce
Main resource (Element):
relationships do
many_to_many :competencies, MyApp.Competency,
source_attribute_on_join_resource: :element_id,
destination_attribute_on_join_resource: :competency_id,
through: MyApp.ElementCompetency
end
actions do
create :create do
primary? true
argument :competency_ids, {:array, :uuid}
change manage_relationship(:competency_ids, :competencies, type: :append_and_remove)
end
Join table (ElementCompetency):
relationships do
belongs_to :element, MyApp.Element
belongs_to :competency, MyApp.Competency
end
actions do
defaults [:create, :read, :update, :destroy]
end
Many-many relation (Competency):
relationships do
many_to_many :elements, MyApp.Element,
source_attribute_on_join_resource: :competency_id,
destination_attribute_on_join_resource: :element_id,
through: MyApp.ElementCompetency
end
actions do
defaults [:create, :read, :update, :destroy]
end
Rendering the form for the Element create
action raises the following error -
** (exit) an exception was raised:
** (CaseClauseError) no case clause matching: [{:join, :destroy, []}]
(ash_phoenix 1.3.4) lib/ash_phoenix/form/auto.ex:613: AshPhoenix.Form.Auto.add_destroy_action/4
(ash_phoenix 1.3.4) lib/ash_phoenix/form/auto.ex:382: anonymous fn/4 in AshPhoenix.Form.Auto.related/3
(ash_phoenix 1.3.4) lib/ash_phoenix/form/form.ex:3224: anonymous fn/1 in AshPhoenix.Form.update_opts/3
(elixir 1.15.4) lib/enum.ex:1693: Enum."-map/2-lists^map/1-1-"/2
(elixir 1.15.4) lib/keyword.ex:1107: Keyword.update!/4
(elixir 1.15.4) lib/keyword.ex:1111: Keyword.update!/4
(ash_phoenix 1.3.4) lib/ash_phoenix/form/form.ex:441: AshPhoenix.Form.for_create/3
(ash_admin 0.10.7) lib/ash_admin/components/resource/form.ex:1620: AshAdmin.Components.Resource.Form.assign_form/1
(ash_admin 0.10.7) lib/ash_admin/components/resource/form.ex:37: AshAdmin.Components.Resource.Form.update/2
(phoenix_live_view 0.20.14) lib/phoenix_live_view/utils.ex:498: Phoenix.LiveView.Utils.maybe_call_update!/3
(elixir 1.15.4) lib/enum.ex:1693: Enum."-map/2-lists^map/1-1-"/2
(phoenix_live_view 0.20.14) lib/phoenix_live_view/diff.ex:685: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
(telemetry 1.2.1) /Users/rebecca/Projects/work/my_client/my_app/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
(phoenix_live_view 0.20.14) lib/phoenix_live_view/diff.ex:680: anonymous fn/4 in Phoenix.LiveView.Diff.render_pending_components/6
(stdlib 5.0.2) maps.erl:416: :maps.fold_1/4
(phoenix_live_view 0.20.14) lib/phoenix_live_view/diff.ex:639: Phoenix.LiveView.Diff.render_pending_components/6
(phoenix_live_view 0.20.14) lib/phoenix_live_view/diff.ex:143: Phoenix.LiveView.Diff.render/3
(phoenix_live_view 0.20.14) lib/phoenix_live_view/static.ex:249: Phoenix.LiveView.Static.to_rendered_content_tag/4
(phoenix_live_view 0.20.14) lib/phoenix_live_view/static.ex:132: Phoenix.LiveView.Static.render/3
(phoenix_live_view 0.20.14) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
Runtime
Describe the bug
Building the documentation by running mix doc
fails because in mix.exs
file a non-existing "logos/small-logo.png"
To Reproduce
Just checkout the repo and build docs.
Expected behavior
The doc creation should produce the result in the docs
folder.
** Runtime
I can PR a fix which deletes the logo or can fix it if I get the logo which is intended to be used.
Is your feature request related to a problem? Please describe.
When integrating a library like SortableJS for drag and drop reordering of records in nested forms, it's currently super-cumbersome to properly reorder them within an AshPhoenix form so they don't re-render in their old order.
Describe the solution you'd like
Our current setup has SortableJS set up to send an LV event with the item paths in the desired order, eg. def handle_event("reposition", %{"items" => ["form[steps][1]", "form[steps][0]"]}, socket)
. Each resource (in this case Step) has a position
field to determine the order it should appear in.
We can get the new position
value for each record with Enum.with_index
, so it would be awesome to be able to pass that in to a new helper function within AshPhoenix to do the setting of the values and re-ordering in one fell swoop.
It could be something like
def handle_event("reposition", %{"items" => paths}, socket) do
order_field = :position
new_positions = Enum.with_index(paths)
form =
socket.assigns.form
|> AshPhoenix.reorder_nested(new_positions, order_field) # new helper?
# ... rest
Describe alternatives you've considered
Currently we've (well, with your help) got it done inline manually, with some gnarly code that looks like the following:
updates =
paths
|> Enum.with_index()
|> Enum.map(fn {path, index} -> %{form_path: path, position: index} end)
socket =
Enum.reduce(updates, socket, fn %{form_path: path, position: position}, socket ->
update_nested_form_value(socket, path, "position", position)
end)
|> update(:form, fn form ->
Map.update!(form, :forms, fn forms ->
Map.update!(forms, :steps, fn nested ->
Enum.sort_by(nested, fn nested_form ->
Ash.Changeset.get_attribute(nested_form.source, :position)
end)
|> Enum.map(fn nested_form ->
position = AshPhoenix.Form.value(nested_form, :position)
%{
nested_form
| name: form.name <> "[#{key}][#{position}]",
id: form.id <> "_#{key}_#{position}"
}
end)
end)
end)
end)
where update_nested_form_value
is defined as:
defp update_nested_form_value(socket, form_path, field_name, value) do
updated_form =
AshPhoenix.Form.update_form(socket.assigns.form, form_path, fn form ->
updated_params = Map.put(AshPhoenix.Form.params(form), field_name, value)
AshPhoenix.Form.validate(form, updated_params)
end)
assign(socket, form: updated_form)
end
Which works but it's not pretty by any means. Plus it would need to be copied and pasted and rewritten if we had a more deeply nested form.
It seems like a reasonable candidate for having a helper for it in AshPhoenix IMO!
This guide can really help contextualize how to use Ash, and would be a great reference. We could start with running some phoenix generators, and then show how the same thing would be accomplished with Ash.
Describe the bug
Form validation on an "for_update" form crashes with
** (Decimal.Error) : number parsing syntax: "x"
when passing a value for a decimal field which cannot be parsed to decimal (e.g. "x")
To Reproduce
error is triggered in call to Comp.not_equal? in AshPhoenix.Form.attributes_changed?/1
you can trigger the error standalone like this:
Comp.not_equal?(Elixir.Decimal.new("1.5"),"x")
Expected behavior
technically it is correct for Comp to fail when comparing apples and oranges.
But in this case i'd like attributes_changed?/1 to return true, because a change of attribute is present, although an invalid one.
the invalid format can then be catched later on and presents an "invalid value" message in the UI.
i solved it in my project by redefining Comparable.Comparable.Type.BitString.To.Decimal so that
comparing a decimal with a non-decimal always returns :gt
lib/my_app/my_decimal_comp.ex:
import Ash.Type.Comparable
defcomparable left :: Decimal, right :: BitString do
case Decimal.cast(right) do
:error -> :gt
_ -> Decimal.compare(left, Ash.Type.Decimal.new(right))
end
end
** Runtime **
As per discord 👍🏾
Describe the bug
AshPhoenix.Form.value
returns the string
value of the field instead of the boolean
type.
When the form is changed and the value of the field is the same as the initial.
To Reproduce
:enabled
set to false
functionAshPhoenix.Form.value
return correctly returns false
.true
AshPhoenix.Form.value
return correctly returns true
.false
AshPhoenix.Form.value
return returns "false"
.defmodule HelloWeb.Post do
use Ash.Resource
actions do
defaults([:create, :read, :update, :destroy])
end
attributes do
uuid_primary_key(:id)
attribute(:enabled, :boolean)
end
end
defmodule HelloWeb.PostsLive do
use HelloWeb, :live_view
alias HelloWeb.Post
def render(assigns) do
~H"""
<.form :let={f} for={@form} phx-change="change">
<.input type="checkbox" field={f[:enabled]} />
<%= inspect(AshPhoenix.Form.value(@form, :enabled)) %>
</.form>
"""
end
def mount(_params, _session, socket) do
socket =
assign(socket,
form:
AshPhoenix.Form.for_update(%Post{enabled: false}, :update)
|> to_form()
)
{:ok, socket}
end
def handle_event("change", %{"form" => params}, socket) do
form =
AshPhoenix.Form.validate(
socket.assigns.form,
Map.merge(socket.assigns.form.params, params)
)
IO.inspect(AshPhoenix.Form.value(form, :enabled), label: "enabled")
{:noreply, assign(socket, :form, form)}
end
end
Expected behavior
Return the same value for the field not depending on whether the form was edited or is untouched.
Runtime
Describe the bug
A clear and concise description of what the bug is. If you are not sure if the bug is related to ash
or an extension, log it with ash and we will move it.
This may be a user error rather than a bug, but I'm porting an issue here per Zach's guidance.
The user info set into the process with Ash.set_actor
is only accessible from the main form's (create/update) actions, but not from its sub-forms'. In the sub-forms' action, actor
returns nil.
To Reproduce
A minimal set of resource definitions and calls that can reproduce the bug.
Run Ash.set_actor(current_user)
in a liveview's mount function and check the value of actor
in the create action of a sub-form to observe its value being nil.
Expected behavior
A clear and concise description of what you expected to happen.
actor
should return the user info set by Ash.set_actor across different nested forms.
** Runtime
Additional context
Add any other context about the problem here.
Setting actor as follows, without using Ash.set_actor in the mount function, causes a nil actor value in every form's action including the main form's. Again, this may be a user error, but mentioning it here in case this problem is somehow related to the main issue reported above.
AshPhoenix.Form.for_update(post, :create,
api: MyAPI,
actor: current_user,
forms: [
comments: [
resources: Comment,
...
Describe the bug
When setting up a keep_live
it's currently impossible to access the callback result for setting up any subscriptions.
To Reproduce
A minimal set of resource definitions and calls that can reproduce the bug.
socket
|> keep_live(
:merge_request,
fn socket ->
Nxy.Workspace.MergeRequest.get_by_id!(params["id"])
end,
subscribe: [
"merge_request:updated:#{socket.assigns.merge_request.project_id}" # <-- unable to access the project_id here
]
)
Expected behavior
subscription
should could be a callback that get's the result of the previous callback.
Potentially strange usages of "foo" in form_for:
ash_phoenix/lib/ash_phoenix/form/form.ex
Line 1335 in 09f6737
ash_phoenix/lib/ash_phoenix/form/form.ex
Line 1502 in 09f6737
Not necessarily a bug but may need clarification
Describe the bug
I noticed this when I was badly copying-pasting actions in a resource - I had defined
# should be update :update, oops
create :update do
accept list_of_fields
end
My form instantiation code looked like:
Form.for_update(album, :update, api: MyApp.Music)
Which worked when rendering the form, but then on change gave a validation error on any fields that were allow_nil? false
.
I tested inverting it as well - having an update :create
and calling for_create
, and it seemed to do a very similar thing.
Expected behavior
I would expect the for_update(record, :update, api: whatever)
call to raise an error if there is no update
action named :update
.
It could be very clever and say something like "No update action found named :update
, but did find a create action with the same name. Did you mean for_create
?".
Runtime
Describe the bug
I have an action that given invalid input will generate 2 errors. But when given the same input in an AshPhoenix.Form, the same invalid input will generate each error twice.
I wrote the following test to duplicate the issue in my app.
test "should not duplicate error messages", %{
environment: environment,
feature: feature,
owner: owner
} do
# This assertation passes. There are 2 errors: key is required, value is required
assert {:error,
%Ash.Error.Invalid{
errors: [
%Ash.Error.Changes.Required{
field: :key,
type: :attribute,
resource: MyApp.Environments.FeatureVariant.String,
changeset: nil,
query: nil,
error_context: [],
vars: [],
path: [:variant],
class: :invalid
},
%Ash.Error.Changes.Required{
field: :value,
type: :attribute,
resource: MyApp.Environments.FeatureVariant.String,
changeset: nil,
query: nil,
error_context: [],
vars: [],
path: [:variant],
class: :invalid
}
]
}} =
MyApp.Environments.Feature.add_variant(
feature,
%{
variant: %{
description: "",
enabled: "true",
key: "",
metadata: "{}",
type: "string",
value: ""
}
},
actor: owner,
tenant: environment.organization_id
)
# This should be the same as above, except going AshPhoenix.Form, but there are now 4 errors.
assert {:error,
%{
source: %{
forms: %{
variant: %{
# submit_errors does not match.
# I expect 2 errors, but there are 4. Each of the 2 errors is duplicated.
submit_errors: [value: {"is required", []}, key: {"is required", []}]
}
}
}
}} =
AshPhoenix.Form.for_update(feature, :add_variant,
api: MyApp.Environments,
as: "feature",
forms: [
auto?: true
],
actor: owner,
tenant: environment.organization_id
)
|> Phoenix.Component.to_form()
|> AshPhoenix.Form.add_form(:variant, params: %{"type" => "string"})
|> AshPhoenix.Form.submit(
params: %{
"variant" => %{
"_form_type" => "create",
"_persistent_id" => "0",
"_touched" => "_form_type,_touched,type",
"description" => "",
"enabled" => "true",
"key" => "",
"metadata" => "{}",
"type" => "string",
"value" => ""
}
}
)
end
To Reproduce
I can reliably reproduce this in my app. I don't have an easy way to replicate it outside of my app though.
Expected behavior
The errors should not duplicate
** Runtime
I'm on the latest Ash Versions.
Additional context
This form has an embedded form for an argument that is an embedded resource (but not a relationship). That resource is also a union of models.
Describe the bug
I ran mix ash_phoenix.gen.live
and got a syntax error on the generated code.
mix ash_phoenix.gen.live Red.Api Red.Api.Attempt
Compiling 1 file (.ex)
warning: the Inspect protocol has already been consolidated, an implementation for Red.Api.User has no effect. If you want to implement protocols after compilation or during tests, check the "Consolidation" section in the Protocol module documentation
lib/red/api/resources/user.ex:1
warning: the Inspect protocol has already been consolidated, an implementation for Red.Api.User has no effect. If you want to implement protocols after compilation or during tests, check the "Consolidation" section in the Protocol module documentation
lib/red/api/resources/user.ex:1
Would you like to name your actor? For example: `current_user`. If you choose no, we will not add any actor logic. [Yn] y
What would you like to name it? For example: `current_user` current_user
Please provide a plural_name. For example the plural of tweet is tweets.
You can press enter to abort, and then configure one on the resource, for example:
resource do
plural_name "tweets"
end
> attempts
Primary destroy action not found, and a destroy action not supplied. Would you like to create one? [Yn] n
** (SyntaxError) lib/red_web/live/attempt_live/index.ex:80:96: unexpected token: }
HINT: the "(" on line 80 is missing terminator ")"
|
80 | |> assign(:attempt, Red.Api.get!(Red.Api.Attempt, id, actor: socket.assigns.current_user)})
| ^
(elixir 1.15.4) lib/code.ex:945: Code.format_string!/2
(mix 1.15.4) lib/mix/tasks/format.ex:597: Mix.Tasks.Format.elixir_format/2
(ash_phoenix 1.2.17) lib/ash_phoenix/gen/live.ex:162: AshPhoenix.Gen.Live.write_formatted_template/5
(ash_phoenix 1.2.17) lib/ash_phoenix/gen/live.ex:97: AshPhoenix.Gen.Live.generate/3
(mix 1.15.4) lib/mix/task.ex:447: anonymous fn/3 in Mix.Task.run_task/5
(mix 1.15.4) lib/mix/cli.ex:92: Mix.CLI.run_task/2
/Users/dewet/.asdf/installs/elixir/1.15.4-otp-26/bin/mix:2: (file)
To Reproduce
I have a very minimal starter app here https://github.com/dewetblomerus/red
Expected behavior
It should generate code
Runtime
Additional context
Even if I did something wrong, there should never be a syntax error in generated code.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.