I’m struggling with the proper way to handle nested associations in a Phoenix form when I need to manually manipulate form data.
Let me explain,
The structure of my data look like this
%Order{
configuration: %Configuration{
panel_groups: []
}
}
I’m building a UI where you can select the number of panels you want, then you can group and ungroup panels. For example, when a user select panels and clicks a “group panels” button, I want to take the selected panels and group them.
First i select that i want 4 panels:
%Order{
configuration: %Configuration{
panel_groups: [
%PanelGroup{image_id: some_id, panels: [%Panel{}]},
%PanelGroup{image_id: some_id, panels: [%Panel{}]},
%PanelGroup{image_id: some_id, panels: [%Panel{}]},
%PanelGroup{image_id: some_id, panels: [%Panel{}]}
]
}
}
Then i group some panels together:
%Order{
configuration: %Configuration{
panel_groups: [
%PanelGroup{image_id: some_id, panels: [%Panel{}, %Panel{}]},
%PanelGroup{image_id: some_id, panels: [%Panel{}, %Panel{}]}
]
}
}
I used get_assoc
and put_assoc
everywhere to get and update the data in my form. Here is a pseudo example of grouping panels together.
%{source: changeset} = form
configuration = Changeset.get_assoc(form.source, :configuration)
current_panel_groups = Changeset.get_assoc(configuration, :panel_groups)
new_panel_groups = function_that_rebuild_the_panel_groups()
custom_configuration = Changeset.put_assoc(configuration, new_panel_groups)
changeset = Changeset.put_assoc(changeset, configuration)
to_form(changeset, action: :update)
This works fine when creating a new order, but I started facing issues when adding the possibility to update an existing order:
- Somewhere in my page i need to display the panels. I made a function that takes the form and return the panel_groups using
get_assoc
, In the new form i didn’t had problem, but when editing an existing order, when I useget_assoc
after aput_assoc
, I get both the old panel groups (withaction: :replace
) and the new ones (withaction: :insert
). As a quick workaround i decided to filter out the:replace
ones, but this feels wrong and naive. - This doesn’t happen in a new form but when editing, If I try to perform a second
put_assoc
operation (e.g., ungroup panels, then group different ones), I get this error
cannot replace related %PanelGroup{} This typically happens when you are calling put_assoc/put_embed with the results of a previous put_assoc/put_embed/cast_assoc/cast_embed operation, which is not supported. You must call such operations only once per embed/assoc, in order for Ecto to track changes efficiently
So i’m starting to understand that put_assoc
is maybe not the right choice here. What would be the best way to manipulate form data manually?
PS: The reason i’m using a form and not just a state using a map where i can manipulate freely my data is that the panels stuff is just a small part of my form and i didn’t want to have multiple state/source-of-truth and also i need validation