Nested form error - using create when I (think I) want update?
axdc:
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.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
endIn my action I have:
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
Perhaps
forms: auto? is getting confused somehow? Have I incompletely specified my resources so as to guide it?
ZachDaniel:
Can I see how you are adding the form?
ZachDaniel:
Like your call to
AshPhoenix.Form.add_form
axdc:
def handle_event("add_form", %{"path" => path}, socket) do
form = AshPhoenix.Form.add_form(socket.assigns.form, path)
{:noreply, assign(socket, form: form)}
end
axdc:
The path used is
"form[edit_users]"
ZachDaniel:
Try this:
form = AshPhoenix.Form.add_form(socket.assigns.form, path, data: the_user_you_want_to_edit, type: :update)
axdc:
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
axdc:
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.
axdc:
Should this all be pointing at the join resource instead of at the user?
ZachDaniel:
oh, so are the users just getting added like “connected” to the form?
ZachDaniel:
You can try
type: :read
axdc:
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:
Then yeah
type: :read should be what you want 🙂
axdc:
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.SitesUsersMyApp.Sites.SitesUsers exists, is in the registry, etc, so troubleshooting that now..
ZachDaniel:
🤔
ZachDaniel:
The reason for using read is because the form itself is not to modify a resource, but to look one up
ZachDaniel:
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:
Sites are in one api, Users are in another. Could that be causing trouble?
ZachDaniel:
Potentially, yeah
ZachDaniel:
In your relationship, if you cross api boundaries, you need to set the
api option
ZachDaniel:
i.e
belongs_to :user, MyApp.Accounts.User do
api MyApp.Accounts
end
ZachDaniel:
In your registry, do you have the resource validations extension?
use Ash.Registry, extensions: [Ash.Registry.ResourceValidations]
axdc:
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
axdc:
Yes, both registries have that extension at the top
ZachDaniel:
oh, this is interesting
ZachDaniel:
its likely because of the
through relationship
axdc:
have I munted the relationship :<
ZachDaniel:
Nah, I don’t think so
ZachDaniel:
I think this is actually an oversight
ZachDaniel:
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
ZachDaniel:
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
ZachDaniel:
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
ZachDaniel:
😆
ZachDaniel:
okay, so it might work in the interim if you do the example above but explicitly setting the api to the parent api
ZachDaniel:
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
ZachDaniel:
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
ZachDaniel:
if its not explicitly configured
ZachDaniel:
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:
{: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.SitesUsersI don’t get that error if I do
has_many :sites_users, MyApp.Sites.SitesUsers do
api MyApp.Sites
endBut 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:
When you inspect errors how are you doing it?
axdc:
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:
try
IO.inspect(AshPhoenix.Form.errors(form, for_path: :all)
axdc:
%{}
axdc:
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:
Oh so you’re still getting that issue?
ZachDaniel:
[warning] Unhandled error in form submission for Panacea.Accounts.User.readThat one?
axdc:
Yes, despite being on ash main
ZachDaniel:
Okay, does the warning include a stack trace?
ZachDaniel:
Does everything work if you manually specify the api, like you did here? https://discord.com/channels/711271361523351632/1099572167638794290/1100330595042734170
axdc:
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 46msThe page does not redirect or crash
axdc:
I’m currently manually specifying the api like that in the Site resource: https://gitlab.com/avoh-labs/panacea/-/blob/main/lib/panacea/sites/resources/site.ex#L136
ZachDaniel:
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:
🙇 please let me know if there’s anything I can provide to track down what’s going on, thank you!!
ZachDaniel:
just to confirm
SitesUsers is part of the
Sites registry
ZachDaniel:
you didn’t move it or anything when debugging?
axdc:
I have not moved it since its creation, it has always been under the Sites api
axdc:
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:
yeah, okay now that I’m at my laptop, this is all much clearer
ZachDaniel:
I have a plan that will simplify all of this 🙂
ZachDaniel:
okay, try ash main 🙂
ZachDaniel:
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:
compiling 🙂
axdc:
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 143msI 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
axdc:
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:
Did you do
mix deps.update ash to update the lock to the latest commit?
ZachDaniel:
That’s the only way to make it use the latest version of a git dependency
axdc:
COMPILE ERROR OKAY
axdc:
i didn’t realize there was any version tracking going on under the hood beyond specifying
origin/main 😂 i’ll remember that.
axdc:
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
ZachDaniel:
ah, I messed up the error message
ZachDaniel:
That should say
many_to_many relationship
:the_many_to_many_name
axdc:
I’m not sure what it’s saying, in the error the the through resource is the same as the destination resource?
axdc:
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:
How are you defining the join relationship currently?
ZachDaniel:
Do you have a
has_many relationship set up for the
many_to_many ?
ZachDaniel:
or are you letting it do it automatically?
axdc:
The Site has the relationships defined https://gitlab.com/avoh-labs/panacea/-/blob/main/lib/panacea/sites/resources/site.ex#L136
through a join resource called SitesUsers https://gitlab.com/avoh-labs/panacea/-/blob/main/lib/panacea/sites/resources/sites_users.ex#L21
axdc:
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:
got it
ZachDaniel:
So you haven’t actually connected the relationships
ZachDaniel:
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
ZachDaniel:
you want to add
join_relationship :sites_users
axdc:
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:
You probably need to load the relationship on the data
ZachDaniel:
before creating your form
axdc:
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
ZachDaniel:
I don’t think so
ZachDaniel:
You’re using “inputs for” to loop over each nested form right?
axdc:
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.inspect ing there shows a blank users list. Could it be a policies thing?
ZachDaniel:
yes, it definitely could be 🙂
ZachDaniel:
Try passing
authorize?: false when loading
ZachDaniel:
if you get the full list then its a policies thing
axdc:
Yes that was it! confirmed policies issue