marick
How much are nested changesets used in practice?
TL;DR: Ecto has this nice feature where you can Repo.update a nested tree of changesets and Ecto figures out the appropriate SQL UPDATE and INSERT commands… or provides an error-annotated nested changeset.
I’ve been using that, and I keep encountering special cases. Am I doing something wrong? Do people use this feature or do they construct their own sequence of SQL commands?
Here’s the form in front of my recent code:
Submitting such a form requires (I think) a little massaging on the way into changeset-creation. Specifically, if the user didn’t attempt to fill in the “Add a gap” subform, that set of blank values has to be removed from the HTTP parameters. (They wouldn’t pass validation and, even if they did, they certainly shouldn’t be given to INSERT.)
But removing an empty “gap” subform has implications when there are errors elsewhere:
- If there’s an error in only the top-level form (the animal), the animal’s changeset will have no entries for any gaps. So, having earlier removed the empty gap from the HTTP parameters, we have to make sure to create a new one for the “please fix these errors” version of the form.
- If the user tried to create a new gap with validation failures, the user should not be shown an empty form for creating a new gap. The previous attempt to do that should appear, with the appropriate error annotations.
- But if the validation failure was not in the “create a new gap” subform but rather in an update to one of the already-existing gaps,
Changeset.get_change(:gaps)will have changesets for all of the existing gaps (the wrong one and all the others) - but we have to remember to provide the user with an empty form. (At least, that seems to me the most user-friendly course of action.)
The result is that I have code like this:
This level of special-case-ey-ness makes me think I must be doing something wrong.
Most Liked
marick
I’ve written up my solution here: https://www.crustofcode.com/an-example-of-nested-forms/ Possibly the most useful bit is I have a set of integration-style tests that can be used as a checklist for cases to cover.
baldwindavid
@marick - I built a slim example app using some of the language of your app. There is a single commit that shows the diff between mix phx.new and my additions at… https://github.com/baldwindavid/crit/commit/4d1688aa7edaae180621a6ed01394224c874b28b
I skipped a lot of stuff, but this might give you a basic idea of the mechanics of using LiveView for this kind of thing.
You should be able to create an animal via a regular controller create and then be dropped into a LiveView for editing. On that page you can update the animal name and create, update, and delete service gaps. All of these are updates to single resources via separate events and without a page update.
The liveview is mounted within the edit template, but it could also have been mounted from the controller or router.
The animal and service gap forms all validate upon interacting with inputs and save upon clicking a button. There are a lot of ways this can be done and rate limiting can easily be added. It would also be trivial to change the forms to save upon editing and removing the submit buttons.
Most of the action takes place in CritWeb.Animal.EditLive and hopefully you’ll see that a good bit of it is boring, yet simple, repetitive code. For simplicity, the service gaps are queried upon just about any event to keep them up to date. Rate limiting or only updating upon submission would cut down on queries. The only slightly complex thing is replacing a changeset from the list of service gap changesets in some cases. Maybe this is a hacky way to do it, but seems to work… https://github.com/baldwindavid/crit/blob/4d1688aa7edaae180621a6ed01394224c874b28b/lib/crit_web/live/animal/edit_live.ex#L155
If you have multiple people editing at the same time, you might want to introduce some pubsub that live updates via actions that others make, but that was a step further than I went here.
I’m no expert in LiveView, but it is already providing me with the blocks to build some interfaces that I probably would have not done before because of the amount of JS involved. I am also one of those people that tries to avoid frontend work. I didn’t write any JS here. The only JS you’ll see in the diff is straight out of the installation docs… https://hexdocs.pm/phoenix_live_view/installation.html
Does that help? Let me know if you have any issues getting it running.
josevalim
It doesn’t feel like what you are doing is wrong but it also feels what you are doing is all UI concerns - which would explain (at least to me) why Ecto doesn’t take care of it. But Ecto does provide the groundwork for handling those scenarios.
For example, for handling an empty form, you can check if all params are empty and retturn :ignore by the cast_assoc changeset function.
Your function also works great but I would keep it as part of the UI functionality - if you don’t already do it. If memory serves me right, the functions in Phoenix.HTML for working with nested associations does support a prepend option, exactly for use case like yours, but I am unsure if it does exactly what you did. 









