Nested form example
While applying the example on this page to my project, https://ash-hq.org/docs/module/ash_phoenix/latest/ashphoenix-form, I ran into this error.
Can you please give me some pointers?
<my code in TweetEdit>
<lib/ashphoenix/form/form.ex>
``
import AshPhoenix.FormData.Helpers
@doc "Calls the corresponding
for*` function depending on the action type"
def for_action(resource_or_data, action, opts) do
{resource, data} =
case resource_or_data do
module when is_atom(resource_or_data) -> {module, module.struct()}
%resource{} = data -> {resource, data}
end
ash_phoenix lib/ash_phoenix/form/form.ex:379 AshPhoenix.Form.for_action/3
ash_phoenix lib/ash_phoenix/form/form.ex:3480 AshPhoenix.Form.handle_form_without_params/13
elixir lib/enum.ex:2468 Enum."-reduce/3-lists^foldl/2-0-"/3
ash_phoenix lib/ash_phoenix/form/form.ex:538 AshPhoenix.Form.for_update/3
lib/tweet_web/live/tweet_live/tweet_edit.ex:21 TweetLive.TweetEdit.mount/3
```Ash HQ
Guide: Getting Started With Ash And Phoenix
Read the "Getting Started With Ash And Phoenix" guide on Ash HQ
22 Replies
Add
type: :list
to the configuration of items
Looks like that isn’t shown in the examples.Thank you! Now I'm getting
Invalid or non-existent path
errors when I click on the add buttons.
It's possible I'm not understanding the example code in the said documentation.
In the leex template below,
if I click the Add Tweet button, I get Invalid or non-existent path: []
for the Add Item button, I get Invalid or non-existent path: [:items, 0]
<leex>
Ah, yeah so that’s not how you’d add a form. That form’s name includes the index.
You’d want something like
phx-click=add-item
And then in the add item handler you’d use add_form(form, :items)
For top level forms it’s pretty simple. But for multiply nested forms you’d do parent_form.name <> “[items]”
Thank you! I was able to get the form validation to work such that a new item entered by
add_item
was included in the form
's params
. Form submission, though it returned an ok
tuple, didn't persist the new item in the database, and I realized I need to add manage_relationship
to the parent (Tweet) resource's update
action, which I did like this.
<tweet.ex>
```
update :update do
...
argument :items, {:array, :map}
change manage_relationship(:items, type: :update)
relationships do
has_many :items, MyApp.Tweets.Item
end
```
Then it gave me this error as soon as the app started. What am I missing?
```
** (EXIT from #PID<0.104.0>) an exception was raised:
** (Spark.Error.DslError) [MyApp.Tweets.tweet]
actions -> update -> update -> change -> manage_relationship -> items:
The following error was raised when validating options provided to manage_relationship.
** (FunctionClauseError) no function clause matching in Ash.Changeset.manage_relationship_opts/1
(ash 2.6.10) lib/ash/changeset/changeset.ex:2010: Ash.Changeset.manage_relationship_opts(:update)
(ash 2.6.10) lib/ash/resource/transformers/validate_manage_relationship_opts.ex:68: anonymous fn/3 in Ash.Resource.Transformers.ValidateManagedRelationshipOpts.transform/1
(elixir 1.14.1) lib/enum.ex:975: Enum."-each/2-lists^foreach/1-0-"/2
(ash 2.6.10) lib/ash/resource/transformers/validate_manage_relationship_opts.ex:19: Ash.Resource.Transformers.ValidateManagedRelationshipOpts.transform/1
(spark 0.4.5) lib/spark/dsl/extension.ex:563: anonymous fn/4 in Spark.Dsl.Extension.run_transformers/4
(elixir 1.14.1) lib/enum.ex:4751: Enumerable.List.reduce/3
(elixir 1.14.1) lib/enum.ex:2514: Enum.reduce_while/3
(elixir 1.14.1) lib/enum.ex:975: Enum."-each/2-lists^foreach/1-0-"/2
```
The
form definition in the
mount` function looks like this.
:update
is not a valid type
What do you want to happen with the given items
? Should it essentially replace the relationship in its entirety? deleting any thing that is missing, adding new things, and updating any currently related things?
If so, then you want type: :direct_control
Thank you! That did the trick. 🙂
I have a follow up question. When a nested form is validated,
Ash.Changeset.before_action
idoesn't seem to behave the way I expected.
In the above example, Tweet's update action triggers Item's update action through change manage_relationship(:items, type: direct_control)
.
Item's update
action happens to have a custom change
module like this
which is defined as
When item_count
is changed in a nested form, this RequireValidItemCount
is not validated. Interestingly, print 1
is printed, but not print 2
which means the validation flow reachesRequireValidItemCount
but the Ash.Changeset.before_action
block doesn't run.
If I update Item
directly like item |> Ash.Changeset.for_update ...
RequireValidItemCount
does its job just fine. How do I make Ash.Changeset.before_action
work through manage_relationship
?Before action hooks are not called until the actual action is invoked (i.e the form is submitted) as that is their purpose(to delay things until the action lifecycle).
Got it. Thanks. Is there an example of custom validation?
In that case, you should be able to do
validate compare(:item_count, greater_than_or_equal_to: 4)
You can put that in an individual action:
or for all actions
Yeah, I tried it and it worked perfectly. I just wanted to know how to write the same using customer module in case it comes in handy.
Ah, gotcha
GitHub
ash/builtins.ex at v2.6.20 · ash-project/ash
A declarative and extensible framework for building Elixir applications. - ash/builtins.ex at v2.6.20 · ash-project/ash
So all of the built in validations are technically custom validations
They are just provided with convenient function names like
compare/1
So the
compare/2
validation's implementation is here: https://github.com/ash-project/ash/blob/v2.6.20/lib/ash/resource/validation/compare.exGitHub
ash/compare.ex at v2.6.20 · ash-project/ash
A declarative and extensible framework for building Elixir applications. - ash/compare.ex at v2.6.20 · ash-project/ash
Naturally, though, the built in ones are a bit more involved because they cover all kinds of cases.
I see. And to use it in a resource is something like this? Apparently this doesn't seem to work.
RequireValidItemCount
needs to be an Ash.Resource.Validation
as well
and then you can also return the message from the validation directly {:error, "Item count must be greater than 0"}
Okay. Still digesting the
compare
example for that. one sec.
I thought this would work as a minimalistic example, but it doesn't. What am I missing?
I'm guessing invalid attribute error
is needed in the error tuple?That looks like it should work just fine really
When you say it doesn't work, why not?
Do you mean its not showing up in your form?
No error message in the form.
Right.
SO yes an invalid attribute error would also have done it
Yay! It's working. Thank you!!