AE
Ash Elixirβ€’3y ago
axdc

Nested form error - using create when I (think I) want update?

I have a nested form that has been working wonderfully to edit a Site and its related resources, and now I'm attempting to manage the Users who will be permissioned to use the Site as well. When I click to do my add_form for Users, I get a crash with:
[error] GenServer #PID<0.25850.0> terminating
** (AshPhoenix.Form.NoActionConfigured) Attempted to add a form at path: [:edit_users], but no `create_action` was configured.
[error] GenServer #PID<0.25850.0> terminating
** (AshPhoenix.Form.NoActionConfigured) Attempted to add a form at path: [:edit_users], but no `create_action` was configured.
The relationship is many_to_many:
many_to_many :users, MyApp.Accounts.User do
api MyApp.Accounts
through MyApp.Sites.SitesUsers
source_attribute :id
source_attribute_on_join_resource :site_id
destination_attribute :id
destination_attribute_on_join_resource :user_id
end
many_to_many :users, MyApp.Accounts.User do
api MyApp.Accounts
through MyApp.Sites.SitesUsers
source_attribute :id
source_attribute_on_join_resource :site_id
destination_attribute :id
destination_attribute_on_join_resource :user_id
end
In my action I have:
change manage_relationship(:edit_users, :users,
on_lookup: :relate,
on_no_match: :ignore,
on_missing: :unrelate,
on_match: :ignore
)
change manage_relationship(:edit_users, :users,
on_lookup: :relate,
on_no_match: :ignore,
on_missing: :unrelate,
on_match: :ignore
)
Because you should only be able to select from the existing users in a select widget and attach them to the Site, not create new users from this form (they require passwords and all that, they have their own separate form for creating). Similar to the tags example I found here, except without creating new ones. But it looks (based on my current understanding of the error) like the form helper is expecting there to be a create action involved. Am I misunderstanding some component of the system? The form is made like this:
form =
AshPhoenix.Form.for_update(site, :update_existing_site,
api: MyApp.Sites,
forms: [auto?: true],
actor: socket.assigns.current_user
)
|> to_form
form =
AshPhoenix.Form.for_update(site, :update_existing_site,
api: MyApp.Sites,
forms: [auto?: true],
actor: socket.assigns.current_user
)
|> to_form
Perhaps forms: auto? is getting confused somehow? Have I incompletely specified my resources so as to guide it?
48 Replies
ZachDaniel
ZachDanielβ€’3y ago
Can I see how you are adding the form? Like your call to AshPhoenix.Form.add_form
axdc
axdcOPβ€’3y ago
def handle_event("add_form", %{"path" => path}, socket) do
form = AshPhoenix.Form.add_form(socket.assigns.form, path)
{:noreply, assign(socket, form: form)}
end
def handle_event("add_form", %{"path" => path}, socket) do
form = AshPhoenix.Form.add_form(socket.assigns.form, path)
{:noreply, assign(socket, form: form)}
end
The path used is "form[edit_users]"
ZachDaniel
ZachDanielβ€’3y ago
Try this: form = AshPhoenix.Form.add_form(socket.assigns.form, path, data: the_user_you_want_to_edit, type: :update)
axdc
axdcOPβ€’3y ago
okay so the users are actually getting added from a livecomponent message so that looks like this
@impl true
def handle_info({MyAppWeb.TypeaheadComponent, {:typeahead_selection, email}}, socket) do
form =
AshPhoenix.Form.add_form(socket.assigns.form, "form[edit_users]",
params: %{"email" => email}
)

socket = socket |> assign(form: form)

{:noreply, socket}
end
@impl true
def handle_info({MyAppWeb.TypeaheadComponent, {:typeahead_selection, email}}, socket) do
form =
AshPhoenix.Form.add_form(socket.assigns.form, "form[edit_users]",
params: %{"email" => email}
)

socket = socket |> assign(form: form)

{:noreply, socket}
end
adding type: :update to that after params changes the error to:
[error] GenServer #PID<0.1878.0> terminating
** (AshPhoenix.Form.NoActionConfigured) The `data` key was configured for [:edit_users], but no `update_action` was configured. Please configure one.
[error] GenServer #PID<0.1878.0> terminating
** (AshPhoenix.Form.NoActionConfigured) The `data` key was configured for [:edit_users], but no `update_action` was configured. Please configure one.
Should this all be pointing at the join resource instead of at the user?
ZachDaniel
ZachDanielβ€’3y ago
oh, so are the users just getting added like "connected" to the form? You can try type: :read
axdc
axdcOPβ€’3y ago
The users should be related if they already exist, and I suppose there should be an error of some sort if someone somehow manages to attach a user to the form that doesn't already exist (they're all in a select widget of preexisting users). This form updates Site resources like Domains and Configuration, but the Users section is purely for relating already existing ones.
ZachDaniel
ZachDanielβ€’3y ago
Then yeah type: :read should be what you want πŸ™‚
axdc
axdcOPβ€’3y ago
Ok, yes, it appears that type: :read allows the form to be successfully added to the page, unlike before, nice. I'm not fully understanding why a read type action is used here, is there a section in the guide that might explain that? I know that reads sometimes have to be used in interesting ways until bulk actions are supported, right? Something similar? Now it's saying
[warning] Unhandled error in form submission for MyApp.Accounts.User.read

This error was unhandled because it did not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Invalid.NoSuchResource) No such resource MyApp.Sites.SitesUsers
[warning] Unhandled error in form submission for MyApp.Accounts.User.read

This error was unhandled because it did not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Invalid.NoSuchResource) No such resource MyApp.Sites.SitesUsers
MyApp.Sites.SitesUsers exists, is in the registry, etc, so troubleshooting that now..
ZachDaniel
ZachDanielβ€’3y ago
πŸ€” The reason for using read is because the form itself is not to modify a resource, but to look one up That error is strange if the resource is definitely in the registry. Could you have passed in the wrong value for api when creating the form?
axdc
axdcOPβ€’3y ago
Sites are in one api, Users are in another. Could that be causing trouble?
ZachDaniel
ZachDanielβ€’3y ago
Potentially, yeah In your relationship, if you cross api boundaries, you need to set the api option i.e
belongs_to :user, MyApp.Accounts.User do
api MyApp.Accounts
end
belongs_to :user, MyApp.Accounts.User do
api MyApp.Accounts
end
In your registry, do you have the resource validations extension?
use Ash.Registry, extensions: [Ash.Registry.ResourceValidations]
use Ash.Registry, extensions: [Ash.Registry.ResourceValidations]
axdc
axdcOPβ€’3y ago
relationships do
# https://discord.com/channels/711271361523351632/1074712810505908254

many_to_many :users, MyApp.Accounts.User do
api MyApp.Accounts #<- like this?
through MyApp.Sites.SitesUsers
source_attribute :id
source_attribute_on_join_resource :site_id
destination_attribute :id
destination_attribute_on_join_resource :user_id
end
...
end
relationships do
# https://discord.com/channels/711271361523351632/1074712810505908254

many_to_many :users, MyApp.Accounts.User do
api MyApp.Accounts #<- like this?
through MyApp.Sites.SitesUsers
source_attribute :id
source_attribute_on_join_resource :site_id
destination_attribute :id
destination_attribute_on_join_resource :user_id
end
...
end
Yes, both registries have that extension at the top
ZachDaniel
ZachDanielβ€’3y ago
oh, this is interesting its likely because of the through relationship
axdc
axdcOPβ€’3y ago
have I munted the relationship :<
ZachDaniel
ZachDanielβ€’3y ago
Nah, I don't think so I think this is actually an oversight try this:
has_many :sites_users, MyApp.Accounts.SiteUsers do
...
end

many_to_many :users, MyApp.Accounts.User do
api MyApp.Accounts #<- like this?
through MyApp.Sites.SitesUsers
join_relationship :sites_users
source_attribute :id
source_attribute_on_join_resource :site_id
destination_attribute :id
destination_attribute_on_join_resource :user_id
end
has_many :sites_users, MyApp.Accounts.SiteUsers do
...
end

many_to_many :users, MyApp.Accounts.User do
api MyApp.Accounts #<- like this?
through MyApp.Sites.SitesUsers
join_relationship :sites_users
source_attribute :id
source_attribute_on_join_resource :site_id
destination_attribute :id
destination_attribute_on_join_resource :user_id
end
I'm not sure that will work. I think the basic issue is that we're assuming that both the join relationship and the destination are in the same api yeah that probably won't work. Somewhere where we are managing relationships we are making a bad assumption internally. Need to figure out how to handle this properly. You might be the first person who made a many_to_many across api boundaries πŸ˜† okay, so it might work in the interim if you do the example above but explicitly setting the api to the parent api
has_many :sites_users, MyApp.Accounts.SiteUsers do
api MyApp.Sites
end

many_to_many :users, MyApp.Accounts.User do
api MyApp.Accounts
through MyApp.Sites.SitesUsers
join_relationship :sites_users
source_attribute :id
source_attribute_on_join_resource :site_id
destination_attribute :id
destination_attribute_on_join_resource :user_id
end
has_many :sites_users, MyApp.Accounts.SiteUsers do
api MyApp.Sites
end

many_to_many :users, MyApp.Accounts.User do
api MyApp.Accounts
through MyApp.Sites.SitesUsers
join_relationship :sites_users
source_attribute :id
source_attribute_on_join_resource :site_id
destination_attribute :id
destination_attribute_on_join_resource :user_id
end
I'm making a potential fix to ash core that will have the join relationship check either the destination api or the source api for the resource in question if its not explicitly configured okay, I'm actually on vacation at the moment so I don't have much time to dedicate to it, but I believe I've just fixed the issue in mind, if you wouldn't mind trying the main branch of ash
axdc
axdcOPβ€’3y ago
{:ash, github: "ash-project/ash", branch: "main", override: true},
{:ash, github: "ash-project/ash", branch: "main", override: true},
delete build folder, mix clean --all, mix deps.clean --all, mix deps.get, mix compile Same error on form submit
[warning] Unhandled error in form submission for Panacea.Accounts.User.read

This error was unhandled because it did not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Invalid.NoSuchResource) No such resource Panacea.Sites.SitesUsers
[warning] Unhandled error in form submission for Panacea.Accounts.User.read

This error was unhandled because it did not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Invalid.NoSuchResource) No such resource Panacea.Sites.SitesUsers
I don't get that error if I do
has_many :sites_users, MyApp.Sites.SitesUsers do
api MyApp.Sites
end
has_many :sites_users, MyApp.Sites.SitesUsers do
api MyApp.Sites
end
But the form does not redirect and no data is written. I don't see any errors when IO.inspecting the form at this point
ZachDaniel
ZachDanielβ€’3y ago
When you inspect errors how are you doing it?
axdc
axdcOPβ€’3y ago
def handle_event("save", %{"form" => form}, socket) do
form = AshPhoenix.Form.validate(socket.assigns.form, form)

case AshPhoenix.Form.submit(form) do
{:ok, site} ->
{:noreply,
socket
|> put_flash(:info, "Site updated successfully")
|> push_navigate(to: ~p"/commander/sites/#{site}")}

{:error, form} ->
IO.inspect(form) # <- here
{:noreply, assign(socket, form: form)}
end
end
def handle_event("save", %{"form" => form}, socket) do
form = AshPhoenix.Form.validate(socket.assigns.form, form)

case AshPhoenix.Form.submit(form) do
{:ok, site} ->
{:noreply,
socket
|> put_flash(:info, "Site updated successfully")
|> push_navigate(to: ~p"/commander/sites/#{site}")}

{:error, form} ->
IO.inspect(form) # <- here
{:noreply, assign(socket, form: form)}
end
end
ZachDaniel
ZachDanielβ€’3y ago
try IO.inspect(AshPhoenix.Form.errors(form, for_path: :all)
axdc
axdcOPβ€’3y ago
%{}
axdc
axdcOPβ€’3y ago
I /believe/ I've got this right according to the ash-hq docs, but neither specifying :all nor specifying the specific form path reveals anything: https://gitlab.com/avoh-labs/panacea/-/blob/main/lib/panacea_web/live/commander/sites_live/edit.ex#L87 That means there are no validation errors in the form itself, right? Just the existing No Such Resource warning when it attempts to save, which seems to have something to do with acting across api boundaries
ZachDaniel
ZachDanielβ€’3y ago
Oh so you're still getting that issue?
[warning] Unhandled error in form submission for Panacea.Accounts.User.read
[warning] Unhandled error in form submission for Panacea.Accounts.User.read
That one?
axdc
axdcOPβ€’3y ago
Yes, despite being on ash main
ZachDaniel
ZachDanielβ€’3y ago
Okay, does the warning include a stack trace? Does everything work if you manually specify the api, like you did here? https://discord.com/channels/711271361523351632/1099572167638794290/1100330595042734170
axdc
axdcOPβ€’3y ago
All I get are a ton of teal debug messages for the ash/ecto transactions, and then on form submit the yellow [warning]
[debug] QUERY OK db=0.2ms
rollback []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1615
[warning] Unhandled error in form submission for Panacea.Accounts.User.read

This error was unhandled because it did not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Invalid.NoSuchResource) No such resource Panacea.Sites.SitesUsers

%{}
[debug] Replied in 46ms

[debug] QUERY OK db=0.2ms
rollback []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1615
[warning] Unhandled error in form submission for Panacea.Accounts.User.read

This error was unhandled because it did not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Invalid.NoSuchResource) No such resource Panacea.Sites.SitesUsers

%{}
[debug] Replied in 46ms

The page does not redirect or crash
ZachDaniel
ZachDanielβ€’3y ago
Okay. I’ll be back at my laptop in a bit and will build a proper reproduction and finish this once and for all πŸ™‚
axdc
axdcOPβ€’3y ago
πŸ™‡ please let me know if there's anything I can provide to track down what's going on, thank you!!
ZachDaniel
ZachDanielβ€’3y ago
just to confirm SitesUsers is part of the Sites registry you didn't move it or anything when debugging?
axdc
axdcOPβ€’3y ago
I have not moved it since its creation, it has always been under the Sites api the Accounts api is pretty much wholesale the one from the auth documentation, then I tried to tie it into Sites with a cross-api many-to-many following the tags examples i found in this discord, and the join relationship is specified under Sites
ZachDaniel
ZachDanielβ€’3y ago
yeah, okay now that I'm at my laptop, this is all much clearer I have a plan that will simplify all of this πŸ™‚ okay, try ash main πŸ™‚ What it should do is give you compile time error messages if its misconfigured, and in your case should tell you to define the join relationship (although I think you already have, so this might just fix the issue)
axdc
axdcOPβ€’3y ago
compiling πŸ™‚ I'm still seeing the same message on submit if I add a user:
[debug] QUERY OK db=0.7ms
rollback []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1615
[warning] Unhandled error in form submission for Panacea.Accounts.User.read

This error was unhandled because it did not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Invalid.NoSuchResource) No such resource Panacea.Sites.SitesUsers

%{}
[debug] Replied in 143ms
[debug] QUERY OK db=0.7ms
rollback []
↳ anonymous fn/3 in Ash.Changeset.with_hooks/3, at: lib/ash/changeset/changeset.ex:1615
[warning] Unhandled error in form submission for Panacea.Accounts.User.read

This error was unhandled because it did not implement the `AshPhoenix.FormData.Error` protocol.

** (Ash.Error.Invalid.NoSuchResource) No such resource Panacea.Sites.SitesUsers

%{}
[debug] Replied in 143ms
I deleted the build directory, deleted dependencies, recompiled, it says it pulled ash
mix deps.get
* Getting ash (https://github.com/ash-project/ash.git - origin/main)
remote: Enumerating objects: 24290, done.
remote: Counting objects: 100% (2038/2038), done.
remote: Compressing objects: 100% (990/990), done.
remote: Total 24290 (delta 1077), reused 1967 (delta 1035), pack-reused 22252
Resolving Hex dependencies...
Resolution completed in 0.774s
mix deps.get
* Getting ash (https://github.com/ash-project/ash.git - origin/main)
remote: Enumerating objects: 24290, done.
remote: Counting objects: 100% (2038/2038), done.
remote: Compressing objects: 100% (990/990), done.
remote: Total 24290 (delta 1077), reused 1967 (delta 1035), pack-reused 22252
Resolving Hex dependencies...
Resolution completed in 0.774s
Your commit looks like documentation changed as well so I'm reading back through that trying to see what I have set up incorrectly
ZachDaniel
ZachDanielβ€’3y ago
Did you do mix deps.update ash to update the lock to the latest commit? That’s the only way to make it use the latest version of a git dependency
axdc
axdcOPβ€’3y ago
COMPILE ERROR OKAY i didn't realize there was any version tracking going on under the hood beyond specifying origin/main πŸ˜‚ i'll remember that. beautiful error! working through this now
Compiling 20 files (.ex)
** (EXIT from #PID<0.95.0>) an exception was raised:
** (RuntimeError) Resource `Panacea.Sites.SitesUsers` is not accepted by api `Panacea.Accounts` for autogenerated join relationship: `:users_join_assoc`

Relationship was generated by the `many_to_many` relationship `:users_join_assoc`

If the `through` resource `Panacea.Sites.SitesUsers` is not accepted by the same
api as the destination resource `Panacea.Sites.SitesUsers`,
then you must define that relationship manually. To define it manually, add the following to your
relationships:

has_many :users_join_assoc, Panacea.Sites.SitesUsers do
# configure the relationship attributes
...
end

You can use a name other than `:users_join_assoc`, but if you do, make sure to
add that to `:users_join_assoc`, i.e

many_to_many :users_join_assoc, Panacea.Sites.SitesUsers do
...
join_relationship_name :your_new_name
end
Compiling 20 files (.ex)
** (EXIT from #PID<0.95.0>) an exception was raised:
** (RuntimeError) Resource `Panacea.Sites.SitesUsers` is not accepted by api `Panacea.Accounts` for autogenerated join relationship: `:users_join_assoc`

Relationship was generated by the `many_to_many` relationship `:users_join_assoc`

If the `through` resource `Panacea.Sites.SitesUsers` is not accepted by the same
api as the destination resource `Panacea.Sites.SitesUsers`,
then you must define that relationship manually. To define it manually, add the following to your
relationships:

has_many :users_join_assoc, Panacea.Sites.SitesUsers do
# configure the relationship attributes
...
end

You can use a name other than `:users_join_assoc`, but if you do, make sure to
add that to `:users_join_assoc`, i.e

many_to_many :users_join_assoc, Panacea.Sites.SitesUsers do
...
join_relationship_name :your_new_name
end
ZachDaniel
ZachDanielβ€’3y ago
ah, I messed up the error message That should say many_to_many relationship :the_many_to_many_name
axdc
axdcOPβ€’3y ago
I'm not sure what it's saying, in the error the the through resource is the same as the destination resource? or because the join resource is not accepted by the accounts api, do I have to define something under the accounts api to make it accept it?
ZachDaniel
ZachDanielβ€’3y ago
How are you defining the join relationship currently? Do you have a has_many relationship set up for the many_to_many? or are you letting it do it automatically?
axdc
axdcOPβ€’3y ago
Something wasn't working with the relationship initially, so I added that join resource based on some examples I found here to do with posts and tags, i think I have the link as a comment in there still. My understanding was that it's supposed to kind of automatically intuit what's going on but in my case it needed an explicit join resource
ZachDaniel
ZachDanielβ€’3y ago
got it So you haven't actually connected the relationships
has_many :sites_users, Panacea.Sites.SitesUsers do
api Panacea.Sites
end

# https://discord.com/channels/711271361523351632/1074712810505908254

many_to_many :users, Panacea.Accounts.User do
api(Panacea.Accounts)
through(Panacea.Sites.SitesUsers)
source_attribute(:id)
source_attribute_on_join_resource(:site_id)
destination_attribute(:id)
destination_attribute_on_join_resource(:user_id)
end
has_many :sites_users, Panacea.Sites.SitesUsers do
api Panacea.Sites
end

# https://discord.com/channels/711271361523351632/1074712810505908254

many_to_many :users, Panacea.Accounts.User do
api(Panacea.Accounts)
through(Panacea.Sites.SitesUsers)
source_attribute(:id)
source_attribute_on_join_resource(:site_id)
destination_attribute(:id)
destination_attribute_on_join_resource(:user_id)
end
you want to add join_relationship :sites_users
axdc
axdcOPβ€’3y ago
got it 🀦 With that addition I can successfully persist users to the database and they show up from queries in iex. just gotta figure out what I need to do to show the existing ones in the form
ZachDaniel
ZachDanielβ€’3y ago
You probably need to load the relationship on the data before creating your form
axdc
axdcOPβ€’3y ago
It's being loaded along with the other working resources in handle_params on the edit action. Am I right to suspect the form might need some manual work? edit.ex
@impl true
def handle_params(%{"id" => id}, _, socket) do
site =
Site.get_by_id!(id, actor: socket.assigns.current_user)
|> Panacea.Sites.load!([:domains, :configuration, :profiles, :users])

form =
AshPhoenix.Form.for_update(site, :update_existing_site,
api: Panacea.Sites,
forms: [auto?: true],
actor: socket.assigns.current_user
)
|> to_form

all_users_options =
for user <- Panacea.Accounts.User.read_all!(actor: socket.assigns.current_user) do
%{
name: Ash.CiString.to_comparable_string(user.email),
value: Ash.CiString.to_comparable_string(user.email)
}
end

socket =
socket
|> apply_title(socket.assigns.live_action)
|> assign(
:site,
site
)
|> assign(form: form)
|> assign(all_users_options: all_users_options)

{:noreply, socket}
end
@impl true
def handle_params(%{"id" => id}, _, socket) do
site =
Site.get_by_id!(id, actor: socket.assigns.current_user)
|> Panacea.Sites.load!([:domains, :configuration, :profiles, :users])

form =
AshPhoenix.Form.for_update(site, :update_existing_site,
api: Panacea.Sites,
forms: [auto?: true],
actor: socket.assigns.current_user
)
|> to_form

all_users_options =
for user <- Panacea.Accounts.User.read_all!(actor: socket.assigns.current_user) do
%{
name: Ash.CiString.to_comparable_string(user.email),
value: Ash.CiString.to_comparable_string(user.email)
}
end

socket =
socket
|> apply_title(socket.assigns.live_action)
|> assign(
:site,
site
)
|> assign(form: form)
|> assign(all_users_options: all_users_options)

{:noreply, socket}
end
ZachDaniel
ZachDanielβ€’3y ago
I don't think so You're using "inputs for" to loop over each nested form right?
axdc
axdcOPβ€’3y ago
Yes, inputs_for here: https://gitlab.com/avoh-labs/panacea/-/blob/main/lib/panacea_web/live/commander/sites_live/edit.html.heex#L23 Loading here: https://gitlab.com/avoh-labs/panacea/-/blob/main/lib/panacea_web/live/commander/sites_live/edit.html.heex#L23 Loading with the same code works in iex but not in show.ex or edit.ex, IO.inspecting there shows a blank users list. Could it be a policies thing?
ZachDaniel
ZachDanielβ€’3y ago
yes, it definitely could be πŸ™‚ Try passing authorize?: false when loading if you get the full list then its a policies thing
axdc
axdcOPβ€’3y ago
Yes that was it! confirmed policies issue

Did you find this page helpful?