alexcastano

alexcastano

Polymorphic embed in Ecto

Hello,

I want to store a structured raw input in a JSONB column. This raw input is already in an embedded schema because it has been previously validated. However, the raw input can be of different type, some of the types being quite complex, as they have deeply nested structures.

When I had only 4 different types, I had a column for each one and I handled the polymorphism by myself:

schema "table" do
  ...
  embeds_one type_one, TypeOne
  embeds_one type_two, TypeTwo
  ...
end

But now, I want to add a lot more types, and I don’t think is efficient to have 30 different columns. So I would like to store the information in the same column, but when loading the record using a type field, it would load one structure or another.

I’m trying to use Ecto.Type to do this job, but I find myself doing a lot of work that was previously handled by Ecto:

First, I have to @derive {Jason.Encoder, only: [...]} in a lot of schemas. It feels wrong because I’m forced to use this protocol to store this information in one way. If I want to use the same protocol for another use, for example to encode in a JSON API, I won’t be able to do it in the future.

Also, I’m creating a lot of functions to load the raw JSON in the structures, basically using cast and cast_embed with all the stored fields.

So, I don’t know if I’m missing something, but I’d like to use functionalities that are already in Ecto with embeds_one to load, dump, embed_as, etc. my custom Ecto.Type. Do you think it would be possible?

Do you think is a good solution in general terms? Any advice?

Most Liked

mathieuprog

mathieuprog

I published a library that brings support for polymorphic embeds :point_down:

Eiji

Eiji

@alexcastano Of course it’s possible, but ecto itself does not helps in complex polymorphic use cases, so you would need to write it yourself.

First of all you would need to have your JSON like:

{"data": …, "type": "your_type_name"}

For it you would need to create a custom Ecto.Type. For each callback you need to check type field and based on it you would need to create n number of modules which also implements Ecto.Type.

Let’s say:

defmodule MyApp.EctoTypes.MainJSON do
  use Ecto.Type

  def ecto_callback(%{data: data, type: type}) do
    with {:ok, result} <- type |> find_module() |> :apply(:ecto_callback, [data]) do
      %{data: result, type: type}
    end
  end

  defp find_module("first_type"), do: MyApp.EctoTypes.FirstType
  defp find_module("second_type"), do: MyApp.EctoTypes.SecondType
  defp find_module("third_type"), do: MyApp.EctoTypes.ThirdType
end

defmodule MyApp.EctoTypes.FirstType do
  # no need to use Ecto.Type here
  # as this those "sub types" would be used only for Kernel.apply/3
  # in order to simply separate code for each type

  def ecto_callback(data) do
    # …
  end
end

In such way you can simply write a code for each type. You only need to implement each Ecto.Type required callback (cast, dump and load if I remember correctly).

If you do this all you need to do (like migrations) is exactly the same as with embeds_many or embeds_one. I wrote all from memory, but it should work even with arrays.

defmodule MyApp.MyContext.MySchema do
  alias MyApp.EctoTypes.MainJSON

  schema "table_name" do
    …
    field(:field_name, {:array, MainJSON})
    …
  end
end

The only difference is that you no longer need to call cast_embed and related functions as everything would be handled in cast.

al2o3cr

al2o3cr

Have you looked at https://github.com/greenboxal/ecto_poly ? I haven’t tried it yet, but it seems pretty close to what you’re looking for.

EDIT: looks like Ecto 3 compatibility is still in-flight https://github.com/greenboxal/ecto_poly/pull/1

tme_317

tme_317

I had a similar issue and couldn’t figure it out so I am just storing compressed ETF in a bytea database field that way I can store arbitrary highly nested structs/embedded_schemas in a single database field.

It works using a custom Ecto type that dumps using :erlang.term_to_binary and loads using :erlang.binary_to_term

Of course it’s not JSON so you can’t do PgSQL queries against it but I don’t need to do that in my use case.

tme_317

tme_317

I just found this issue/conversation that seems exactly what you are trying to do… maybe this helps?

Where Next?

Popular in Questions Top

Tee
can someone please explain to me how Enum.reduce works with maps
New
_russellb
I want to try my hand at web scraping. What tools/libraries do I need to use. I’m hoping to turn this into something professional so don’...
New
ycv005
I have followed this StackOverflow post to install the specific version of Erlang. And When I am running mix ecto.setup then getting fol...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
Kagamiiiii
Student &amp; New to elixir. Nice language. I want to convert a english character, e.g. “a”, which is stored in a variable, to it’s asci...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
gonzofish
I’m currently trying to understand how to join three tables using Ecto. All the examples I’ve seen use 2, so maybe I’m just missing somet...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

lessless
I believe there are people here who are dealing with CSV files import on the daily basis, and since Excel is a really popular tool there ...
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
SoCreat
i’m a new one to elixir which editor can i use vs code? or atom? Thanks! :smiley:
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
polypush135
As many of you may have realized by now (sorry for all the posts here) I’ve been working on a db problem where I’m trying to aggregate a ...
New
chrismccord
As promised, the first release candidate of Phoenix 1.3.0 is out! This release focuses on code generators with improved project structure...
New
danschultzer
None of the current solutions worked well for me, so I went ahead and built a user management system from scratch. This project took far...
548 27727 240
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
magnetic
Hey :wave:t3: Elixir community, I’ve been learning Elixir, and working on some side projects. My editor of choice is VSCode, and althoug...
New

We're in Beta

About us Mission Statement