jdumont

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.

Showing Posts 1 to 10

LostKobrakai

LostKobrakai

phoenix_html does work completely without ecto (the integration of ecto comes with the phoenix_ecto package) so you can always set the method you need manually:

from_for form_data, action, [method: :put], fn f -> […] end

jdumont

jdumont OP

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

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 Kitchen context!) 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 the form_for tag?

More generally, what are the best practices when you want to decouple your data model from your UI?

Thanks in advance!

josevalim

josevalim

Creator of Elixir

@hourliert setting method: :put is 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

hourliert

That is awesome!! Thanks for the prompt answer. I was exactly looking for something like this Ecto.put_meta(embedded_schema, state: :loaded). I tried to create and update the __meta__ field on my embedded schema myself in the first place :sweat_smile: (totally new to Elixir and Phoenix).

I will have a look at the ebook you linked.
Thank you very much again!!!

hourliert

hourliert

Hey @josevalim,
I tried your suggestion and I was expecting put_meta to 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_meta won’t match if I am calling it with an embedded_schema.

Error:

** (FunctionClauseError) no function clause matching in Ecto.put_meta/2

    The following arguments were given to Ecto.put_meta/2:

        # 1
        %MyRecipeBox.Kitchen.RecipeCreation{
          cost: nil,
          difficulty: nil,
          id: nil,
          ingredients: [],
          name: nil,
          steps: []
        }

        # 2
        [state: :loaded]

    Attempted function clauses (showing 1 out of 1):

        def put_meta(%{__meta__: meta} = struct, opts)

    (ecto) lib/ecto.ex:560: Ecto.put_meta/2

Did I miss something?

Thanks in advance :folded_hands:

josevalim

josevalim

Creator of Elixir

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

hourliert

Oh noo. This solution looked elegant! :joy:
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

josevalim

Creator of Elixir

No, you don’t have to. You can completely work with them forgetting completely that Ecto.Repo exists.

hourliert

hourliert

Oh nice, I think I was confused by the doc.

It makes total sense. Once again, big thanks!

Where Next? Top

Trending in Questions Top

Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews