AE
Ash Elixir•3y ago
Jason

Nested form with filtered subform elements

Let's say there is a nested form with Tweet as the main and Comment as the sub. The requirement is to only show and make editable the comments entered by the current user in the subform. I'm hoping to accomplish this without using CSS tricks of hiding other users' comments. By loading filtered records into the form using the code below, I was able to make the form look like what I want, but when I submit the form, comments previously made by other users (apparently, filtered out from the form) seem to be removed. Is this the expected behavior of a nested form? Is there a relatively easy way to make it save the change without removing other users' comments?
def tweet_load(current_user) do
comments=
Comment
|> Ash.Query.filter(user_id == ^current_user.id)

tweets=
Tweet
|> Ash.Query.sort(row_order: :asc)
|> Ash.Query.load([
comments: comments
])
def tweet_load(current_user) do
comments=
Comment
|> Ash.Query.filter(user_id == ^current_user.id)

tweets=
Tweet
|> Ash.Query.sort(row_order: :asc)
|> Ash.Query.load([
comments: comments
])
7 Replies
Jason
JasonOP•3y ago
I'm guessing one way to do this is by defining a clever create/update action to handle the comment_form, but wanted to ask here first, in case I'm missing something easier.
ZachDaniel
ZachDaniel•3y ago
🤔 there are ways to do this, but it might be a bit complex no matter how you cut it. What I would suggest is essentially "rolling your own" by using the ignore?: true option to manage_relationship
change manage_relationship(:name, ...., ignore?: true)
change manage_relationship(:name, ...., ignore?: true)
What that does is communicate to things like AshPhoenix.Form that you manage that relationship in a similar way w/ similar semantics, but you'll handle the inputs yourself Then:
change fn changeset, %{actor: actor} ->
Ash.Changeset.before_action(changeset, fn changeset ->
inputs = changeset.arguments[:arg_name] || []
ids = inputs |> Enum.map(&(&1[:id] || &1["id"])) |> Enum.reject(&is_nil/1)
# use some manual ecto for bulk delete since we don't have bulk delete yet
removed_comments =
from row in Related,
where: user_id == ^actor.id,
where: id not in ids

MyApp.Repo.delete_all(removed_comments)

Enum.each(inputs, fn input ->
# upsert related resources by id
end)
end)
end
change fn changeset, %{actor: actor} ->
Ash.Changeset.before_action(changeset, fn changeset ->
inputs = changeset.arguments[:arg_name] || []
ids = inputs |> Enum.map(&(&1[:id] || &1["id"])) |> Enum.reject(&is_nil/1)
# use some manual ecto for bulk delete since we don't have bulk delete yet
removed_comments =
from row in Related,
where: user_id == ^actor.id,
where: id not in ids

MyApp.Repo.delete_all(removed_comments)

Enum.each(inputs, fn input ->
# upsert related resources by id
end)
end)
end
Jason
JasonOP•3y ago
Thank you!! Tried this code for changing attribute of the form input manually with the value from the changeset attribute. When the form is validated the first inspect message is printed, but neither message is printed when the form is submitted, which leads me to believe this update action isn't being triggered upon form submission. What could I be missing?
defmodule RollOwnUpdate do
use Ash.Resource.Change

def change(changeset, opts, %{actor: actor}) do
IO.inspect("before before_action ################") ## This prints

Ash.Changeset.before_action(changeset, fn changeset ->
IO.inspect(changeset.argument[:comment], ## This doesn't.
label: "inside before_action ###########"
)

comment = MyApp.Comment.get_by_id!(changeset.data.id)
changeset = comment |> Ash.Changeset.new()

Ash.Changeset.change_attribute(
changeset,
:comment,
Ash.Changeset.get_attribute(changeset, :comment)
)
|> IO.inspect(label: "changeset in update ###########")
|> MyApp.Comment.update()
end)
end
end
defmodule RollOwnUpdate do
use Ash.Resource.Change

def change(changeset, opts, %{actor: actor}) do
IO.inspect("before before_action ################") ## This prints

Ash.Changeset.before_action(changeset, fn changeset ->
IO.inspect(changeset.argument[:comment], ## This doesn't.
label: "inside before_action ###########"
)

comment = MyApp.Comment.get_by_id!(changeset.data.id)
changeset = comment |> Ash.Changeset.new()

Ash.Changeset.change_attribute(
changeset,
:comment,
Ash.Changeset.get_attribute(changeset, :comment)
)
|> IO.inspect(label: "changeset in update ###########")
|> MyApp.Comment.update()
end)
end
end
ignore? true was added to the update action of Tweet resource. like this.
argument :comments, {:array, :map}
change manage_relationship(:comments, type: :direct_control, ignore?: true)
argument :comments, {:array, :map}
change manage_relationship(:comments, type: :direct_control, ignore?: true)
update :update_current_user_comment_only do
IO.inspect("####update entered4######") ## This doesn't print

change RollOwnUpdate
end
update :update_current_user_comment_only do
IO.inspect("####update entered4######") ## This doesn't print

change RollOwnUpdate
end
Here is the form definition in the mount function of the liveview.
forms: [
tweets: [
resource: Tweet,
data: tweets,
create_action: :create,
update_action: :update,
type: :list,
forms: [
comments: [
resource: Comment,
data: & &1.comments,
create_action: :create,
update_action: :update_current_user_comment_only,
type: :list
]
]
forms: [
tweets: [
resource: Tweet,
data: tweets,
create_action: :create,
update_action: :update,
type: :list,
forms: [
comments: [
resource: Comment,
data: & &1.comments,
create_action: :create,
update_action: :update_current_user_comment_only,
type: :list
]
]
ZachDaniel
ZachDaniel•3y ago
could you have a different error happening somewhere? The before action hooks are only called on valid changesets.
Jason
JasonOP•3y ago
The fact that the inspect message in the update action doesn't print makes me think that something is off in the way I trigger the action when the form is submitted.
ZachDaniel
ZachDaniel•3y ago
Oh, thats not how that works really that update code block is evaluated once at compile time so you'll never see that
Jason
JasonOP•3y ago
my bad.. yeah, I shouldn't have expected the inspect line in the update action to print.

Did you find this page helpful?