Dynamic Embeds in Ecto

Thank you, the library looks nice. It does not fit out needs though as if JSONb column has type information it’s trivial to make it polymorphic without any external libraries (just pattern match on load in specific type).

Our cast is different, in the raw Ecto schema we have a type field and then payload field which should be casted depending on type field value. Which Ecto does not allow as you can’t access type value while casting/dumpin/loading payload field.

Here is our implementation: https://gist.github.com/AndrewDryga/72f2cdd366265afb4c0528935fa96927. It’s used in production and did not cause any issues yet. It’s very hacky though.

Notice that we explicitly call load_dynamic_embed/2 function every time schema is loaded from DB. We also dump everything to string-keyed map if changeset is valid to mimic behaviour of writing and reading from DB (so that you can compare user == inserted_user in test cases, otherwise string- and atom-keyed maps would be a problem).

If embedded changeset has an error it looks very much like nested schema was defined by using embeds_one/many`.

Why not just copy the type field into the payload? You could do that in the changeset, before calling cast of a custom type.

There are few reasons:

  • One is that in PostgreSQL using index on regular column and JSONb column at the same query is expensive (at least it was back when we designed embeds) - we do query data by type;
  • Back then, casting by using Ecto.Type was not flexible enough to cover our needs either;
  • For me, it just feels weird to have JSON field that holds information about itself, because type for us is strictly field even when payload is empty.

I suggested to copy the type field, so you end up with the exact same structure in the DB, no difference for querying, i.e. a type and payload fields in the schema, in addition to a type in the payload to enable polymorphism. That small logic for copying the type would be done in the changeset.

Resorting to non documented, private internals such as Ecto.Embedded struct and its fields (thus that might change and break your app) seems worse as a solution. Would you go for a minor weirdness, or a fragile hack?

Indeed I don’t know about the limitations you faced back in 2018, as I wasn’t doing Elixir at that time.
I see however that ecto_poly was released end 2017, so there were some possibilities.

1 Like