Embed a struct in a virtual field

I have a schema that has metrics that are not stored in the DB but computed later.

schema entity do
  ...
  field :metrics, {:array, :map}, virtual: true, default: nil)
end

The metric is a struct like this:

%Metric{start: date_time, stop: date_time, error_rate: float}

Can I somehow indicate in the schema that metrics will always have those three fields?
I like exploring codebase by its data structures.
If I stumble upon something like {:array, :map} I can’t see what struct is that. The compiler also doesn’t check if I use correct keys when I access metrics because they are free form maps.
Unfortunately virtual: true does not work with has_many or embeds_many. Defining custom type with all the logic of casting to DB format also seems like overkill. Do you have a better way of embedding structs as virtual fields in the schema?

3 Likes

If the type is only used virtually, you might get away with defining a custom type where actually load and dump are error-throwing no-ops, and only cast is implemented in a sensible way (like calling struct!/2).

1 Like

I have a project that use embeds_many

Hope it will help

alias Connect.Campaign.Starting

schema "campaigns" do
    field :description, :string

    embeds_many :startings, Starting

    timestamps()
  end

defmodule Connect.Campaign.Starting do
  use Ecto.Schema
  import Ecto.Changeset
  alias Connect.Campaign.Starting

  embedded_schema do
    field :can_run, :boolean, default: true
    field :day, :integer, default: 0
    field :end_at, :time, default: ~T[20:00:00]
    field :start_at, :time, default: ~T[08:00:00]
  end

end

Adjust it to your needs

1 Like

Thank you for your input.
Unfortunately, that solution persists :startings in the DB. I’d like not to persist it but virtual: true does not mix with embeds_many.

1 Like

If I’m not mistaken, you can still use an embedded_schema without saving it to the database. Just don’t cast the field nor make it required when you generate changesets.

I had a similar problem this week, you might want to take a look at this topic:

3 Likes

Hi Tomasz,

You probably solved this a long time ago.
In my projects I deal with exactly your problem a lot.
In such cases I use virtual (embedded) models for user interaction (forms).
These virtual models are never persisted so they can implement embeds_many.
Once the virtual model (changeset) is valid and it’s time to persist it’s contents, then I transform the virtual model to the actual model which may implement {:array, :map}.

2 Likes