Blog Post: Polymorphic embeds in Ecto

Recently I had to ensure semi-arbitrary data for an embedded schema could be validated and easily mapped in Phoenix forms. I didn’t need to store this data in the database. After tinkering with it for a bit polymorphic embeds was the solution.

5 Likes

Hi Dan,

Can’t thank you enough for this great write-up!

It became very useful as I was doing some funny things with [polymorphic_embed]( polymorphic_embed | Hex ), such as:

  • highlighting changed form fields by comparing field.form.data.<fieldname> with field.value
  • building a collaborative editing system relying on changesets (stored in DB)

image

In both case, this was impossible with polymorphic_embed because it defines a new Ecto type that does not fully behave like regular embeds (and cannot, because of limitations in Ecto), hence numerous bug reports on this library such as [this one]( Dan Schultzer: Polymorphic embeds in Ecto ).

But you cracked it with this trick:

    # We must modify the type so Ecto will handle this field as an embed.
    changeset = %{changeset | types: Map.put(changeset.types, name, {:embed, relation})}

Now, may I ask if you took a look at polymorphic_embed before writing this blog post, and if so why you didn’t use it in the first place?

I’m curious because, if as I suppose correctly and the author of polymorphic_embed didn’t know about le trick, then I guess it could be rewritten by deleting 80% of the code and eliminating 80% of the bugs :thinking:

Thinking out loud, but this library became unmaintained (it seems), is widely used and is riddled with subtle discrepancies compared with regular Ecto embeds. For those who would need it, a PR with “some” fixes here.

PS : might be a typo here:

    + {:parameterized, {__MODULE__, relation}} = Map.fetch!(types, name)
    - {:parameterized, __MODULE__, relation} = Map.fetch!(types, name)

Cheers!

1 Like

I think the typo is actually from Ecto API change, I’m updating it thanks!

Yeah, I did look at the library but it doesn’t really solve polymorphic embeds with Ecto and neither does my small version here. The actual production version I have is more involved so it can handle some normalization, prepare changes, etc. But at the end of the day, embeds have some sharp corners and it only gets worse when you lose the guarantee with the struct which Ecto is built upon. There are a lot of assumptions with data structures in Ecto so I think the only way for polymorphic embeds to work properly is that Ecto needs to be changed in how embeds works. It’s been a while so I can’t remember the exact details, but if you go through the code path of how embeds are dumped/loaded you’ll see that embeds are treated separately from other data and it makes things tricky.

At least having all of this logic in your codebase you can adjust it for your exact needs and work around the sharp corners yourself. You often don’t need everything that the embed type need to be able to do.

1 Like