marick

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:

  1. 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.
  2. 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.
  3. 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

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

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

josevalim

Creator of Elixir

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. :slight_smile:

Where Next?

Popular in Discussions Top

Other popular topics Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41454 115
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
joeerl
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
malloryerik
Hi, this is for people who, like me, have had some friction using .html.heex templates in VSCode. The solution seems to be, in a hyphena...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
hariharasudhan94
Lets say i have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => "XX...
New

We're in Beta

About us Mission Statement