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: An example of nested forms, nested changesets, and error handling 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… Add LiveView and example · baldwindavid/crit@4d1688a · GitHub

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… crit/lib/crit_web/live/animal/edit_live.ex at 4d1688aa7edaae180621a6ed01394224c874b28b · baldwindavid/crit · GitHub

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

Jayshua
I recently came across the javascript library htmx. It reminded me a lot of liveview so I thought the community here might be interested....
New
thojanssens1
It would be nice to be able to define a redirect from one route to another from the router.ex file. E.g.: redirect "/", UserController, ...
New
sashaafm
Piggy backing a bit on @dvcrn topic BEAM optimization for functions with static return type?, I’ve been trying to understand in a deeper ...
New
AstonJ
Are there any Elixir or Erlang libraries that help with this? I’ve been thinking how streaming services like twitch have exploded recentl...
New
crabonature
I’m still quite new to Elixir. As I understand we got in Elixir “multi guards” as convention to simplify one large guard with or’s?: de...
New
fireproofsocks
I’ve been working on an Elixir project that has required a lot of scripting. I usually reach for Elixir because I like it more (and in th...
New
rms.mrcs
A couple of days ago I was discussing with a friend about different approaches to write microservices. He said that if he was going to w...
New
jsonify
So, is Heroku the only free option for hosting Phoenix/Elixir at this point? I’m not ready to commit to paying monthly and was wondering ...
New
pdgonzalez872
If this has been asked here before, please point me to where it was asked as I didn’t find it when I searched the forum. Maybe a mailing ...
New
kostonstyle
Hi all How can I compare haskell with elixir, included tools, webservices, ect. Thanks
New

Other popular topics Top

New
sorentwo
Hello! tl;dr Announcing Oban, an Ecto based job processing library with a focus on reliability and historical observability. After spen...
985 42920 311
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
Qqwy
Original source of discussion: This topic on the Pragmatic Programmers’ Functional Web Development with Elixir, OTP, and Phoenix forum. ...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New
joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
New
marick
I had some trouble figuring out how to make many-to-many associations work. Once I got it working, I wrote a blog post. Because I’m a nov...
New
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

We're in Beta

About us Mission Statement