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!