jdumont
Hello all!
Yet again, I’ve run across a problem with Ecto: it seems to be my nemesis!
In this instance, I’m trying to build an admin area for a travel blog. The application allows a user to submit Posts, and Places. These are in two separate contexts (Content.Post & Locations.Place). The desired outcome is that when a user is submitting a Post they associate a Place through a Location.Visit.
In an effort to provide a nice editing experience, I’ve created another context called Admin which uses an “embedded_schema” for a Admin.Post model, which combines the required fields into one model.
This is to avoid using Phoenix’s nested forms, and also to leverage Ecto.Multi.
All of the above works correctly for creation of new Posts & Places. However, I can’t seem to use the same model/form to update them.
I’ve written a query to load the data from the various tables and load it back into an %Admin.Post{} struct, which I pass to the form, along with a changeset. When I try to submit updates, Phoenix tries to use a POST route rather than a PUT or a PATCH. I understand this is because the changeset doesn’t have a :loaded state because I built it manually (from it’s constituent models), so it infers that this should be a creation, not an update.
I’ve only been able to find examples of inserts with an embedded_schema (use case is usually something like “registration”, which obviously doesn’t require updates). Is something like this possible with “embedded_schemas”?
Now, the real problem here is that I just don’t seem to be able to get my head around associations with Ecto properly. I understand them well enough, and can build them so that they work perfectly well in iex. I’ve done this for multiple approaches: nested data structures where cast_assoc is used, a get_or_insert approach that uses put_assoc as explained in “What’s new in Ecto”, and another using embedded_schema and Ecto.Multi.
Where all of these fall apart for me is when I come to use them through Phoenix. The only approach that I’ve been able to get working is with nested forms and cast_assoc.
This has been an issue for me in every Elixir/Phoenix project that I’ve started over the last 18 months, with all of them bar one falling by the wayside. No matter how simple I make the problem, as soon as there’s a non-trivial association going on, I hit a wall.
I’ve hunted high and low for advice and guidance on this. Most tutorials or examples are necessarily simple, and don’t really try to solve the same problems I’m encountering. “What’s new in Ecto” has helped me learn a lot about Ecto, but I still haven’t got to a stage where I’m comfortable with it.
The best example I’ve found of the types of associations I’m trying to model is in the Changelog codebase, but from what I can tell, their approach uses nested forms, cast_assoc and a sprinkling of JS to achieve the desired outcome of managing other models (hosts and sponsors in their case) whilst adding an episode. I’m not adverse to this approach at all, but in the spirit of learning and understanding, I’m keen to see what an approach that uses “embedded_schemas” would look like.
Am I just being a bit dim with all this? Given the things I’ve worked on previously, I don’t think I should be struggling with this the way that I am. However, the fact is that I just can’t get my head around it, and almost would prefer to just write plain SQL. That would of course leave me without Ecto’s brilliant integration with Phoenix and assistance with storing & querying data, but at this stage, I don’t know what else I can do/read to understand Ecto properly.
Does anyone know of an example, or a book, or a video course that might help me understand this?
Cheers, Jamie.
Trending in Questions
Other Trending Topics
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #blog-post
- #elixirconf-us
- #elixir-ls
- #ai
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #hex
- #security
- #metaprogramming










Showing Posts 1 to 10- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
LostKobrakai
phoenix_htmldoes work completely without ecto (the integration of ecto comes with thephoenix_ectopackage) so you can always set the method you need manually:from_for form_data, action, [method: :put], fn f -> […] endjdumont
Brilliant thank you! I knew there would be a way of doing this, but was trying to tackle it from the Ecto side by forcibly modifying the changesets state or action.
Changing that lets me make the update to the embedded_schema through the form, and some of the subsequent data does indeed update, but certain models/tables don’t.
I think I’ve tied myself up in knots and am fighting Ecto the entire way. Perhaps I should go back to the drawing board and work out what I’m trying to accomplish, and whether I’m making things more complex than they need to be?
hourliert
Hi @jdumont,
I am kind of having the same issue here. I am building a recipe management app which support recipe versioning. The thing is that my recipe_version schema is immutable, so when a user edits a recipe, it is implicit that he edits the last recipe version but submitting the form will actually create a new version. All the logic (inside my
Kitchencontext!) is working perfectly.My data model is as simple as:
Recipe has many RecipeVersion has many {Ingredients, Steps}
I am trying to build a form where the user wouldn’t see the RecipeVersion at all. He would simply edit the Recipe name, the ingredients and steps in place.
I have created an embedded_schema that represents the form parameter but I am running into the same confusion as you.
I was wondering if you found something relevant in your case? Did you simply choose to use the
[method: :put]on theform_fortag?More generally, what are the best practices when you want to decouple your data model from your UI?
Thanks in advance!
josevalim
@hourliert setting
method: :putis completely fine but you can also do this:Ecto.put_meta(embedded_schema, state: :loaded)before wrapping the schema in a changeset and therefore Phoenix<->Ecto will consider it as a PUT. That may be semantically better.But generally speaking, decoupling your forms (UI representation) from your DB representation is a very good practice. So you are going in the right direction. I also recommend then What’s new in Ecto 2 ebook. It covers this and a couple of other things. The ebook is for 2.0 but almost all of it works just fine on 3.0.
hourliert
That is awesome!! Thanks for the prompt answer. I was exactly looking for something like this
(totally new to Elixir and Phoenix).
Ecto.put_meta(embedded_schema, state: :loaded). I tried to create and update the__meta__field on my embedded schema myself in the first placeI will have a look at the ebook you linked.
Thank you very much again!!!
hourliert
Hey @josevalim,
I tried your suggestion and I was expecting
put_metato create the meta field on the embedded schema since according to the doc: (Ecto.Schema — Ecto v3.14.0) embedded_schemas don’t have a meta field, but it didn’t seem to work.Also looking at the code source of Ecto: ecto/lib/ecto.ex at master · elixir-ecto/ecto · GitHub,
put_metawon’t match if I am calling it with an embedded_schema.Error:
Did I miss something?
Thanks in advance
josevalim
Oh, yes, I forgot that embedded do not have a meta field. So yes. In this case, you either need to make it not embdded or pass the method. Sorry for wrong advice!
hourliert
Oh noo. This solution looked elegant!
In the case I am making this schema not embedded, that would mean that I HAVE to persist it somewhere? I would be back to my initial problem: coupling the database model to the UI right?
I am gonna pass the method to the form for now. If I found something more satisfying, I’ll update this post (or write it somewhere else).
Thanks a lot for the help. Really appreciate it.
josevalim
No, you don’t have to. You can completely work with them forgetting completely that Ecto.Repo exists.
hourliert
Oh nice, I think I was confused by the doc.
It makes total sense. Once again, big thanks!