

Polymorphic embeds for Ecto — Polymorphic Embed v3.0.3

 try {

 var settings = JSON.parse(localStorage.getItem('ex\_doc:settings') || '{}');

 if (settings.theme === 'dark' ||

 ((settings.theme === 'system' || settings.theme == null) &&

 window.matchMedia('(prefers-color-scheme: dark)').matches)

 ) {

 document.body.classList.add('dark')

 }

 } catch (error) { }

 

Settings

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/README.md#L1 "View Source")

Polymorphic embeds for Ecto

===============================================================================================================================================

`polymorphic_embed` brings support for polymorphic/dynamic embedded schemas in Ecto.

Ecto's `embeds_one` and `embeds_many` macros require a specific schema module to be specified. This library removes this restriction by

**dynamically** determining which schema to use, based on data to be stored (from a form or API) and retrieved (from the

data source).

[usage](#usage)

 Usage

-----------------------

### 

[enable-polymorphism](#enable-polymorphism)

 Enable polymorphism

Let's say we want a schema `Reminder` representing a reminder for an event, that can be sent either by email or SMS.

We create the `Email` and `SMS` embedded schemas containing the fields that are specific for each of those communication

channels.

The `Reminder` schema can then contain a `:channel` field that will either hold an `Email` or `SMS` struct, by setting

its type to the custom type [`PolymorphicEmbed`](PolymorphicEmbed.html) that this library provides.

Find the schema code and explanations below.

```

defmodule MyApp.Reminder do

 use Ecto.Schema

 import Ecto.Changeset

 import PolymorphicEmbed

 schema "reminders" do

 field :date, :utc\_datetime

 field :text, :string

 polymorphic\_embeds\_one :channel,

 types: [

 sms: MyApp.Channel.SMS,

 email: MyApp.Channel.Email

 ],

 on\_type\_not\_found: :raise,

 on\_replace: :update

 end

 def changeset(struct, values) do

 struct

 |> cast(values, [:date, :text])

 |> cast\_polymorphic\_embed(:channel, required: true)

 |> validate\_required(:date)

 end

end

```

```

defmodule MyApp.Channel.Email do

 use Ecto.Schema

 import Ecto.Changeset

 @primary\_key false

 embedded\_schema do

 field :address, :string

 field :confirmed, :boolean

 end

 def changeset(email, params) do

 email

 |> cast(params, ~w(address confirmed)a)

 |> validate\_required(:address)

 |> validate\_length(:address, min: 4)

 end

end

```

```

defmodule MyApp.Channel.SMS do

 use Ecto.Schema

 @primary\_key false

 embedded\_schema do

 field :number, :string

 end

end

```

In your migration file, you may use the type `:map` for both `polymorphic_embeds_one/2` and `polymorphic_embeds_many/2` fields.

```

add(:channel, :map)

```

[It is not recommended](https://hexdocs.pm/ecto/3.8.4/Ecto.Schema.html#embeds_many/3) to use `{:array, :map}` for a list of embeds.

### 

[cast\_polymorphic\_embed-3](#cast_polymorphic_embed-3)

`cast_polymorphic_embed/3`

`cast_polymorphic_embed/3` must be called to cast the polymorphic embed's parameters.

#### Options

* `:required` – if the embed is a required field.

* `:with` – allows you to specify a custom changeset. Either pass an MFA or a function:

```

changeset

|> cast\_polymorphic\_embed(:channel,

 with: [

 sms: {SMS, :custom\_changeset, ["hello"]},

 email: &Email.custom\_changeset/2

 ]

)

```

### 

[polymorphicembed-ecto-type](#polymorphicembed-ecto-type)

 PolymorphicEmbed Ecto type

The `:types` option for the [`PolymorphicEmbed`](PolymorphicEmbed.html) custom type contains a keyword list mapping an atom representing the type

(in this example `:email` and `:sms`) with the corresponding embedded schema module.

There are two strategies to detect the right embedded schema to use:

1.

```

[sms: MyApp.Channel.SMS]

```

When receiving parameters to be casted (e.g. from a form), we expect a `"__type__"` (or `:__type__`) parameter

containing the type of channel (`"email"` or `"sms"`).

2.

```

[email: [

 module: MyApp.Channel.Email,

 identify\_by\_fields: [:address, :confirmed]]]

```

Here we specify how the type can be determined based on the presence of given fields. In this example, if the data

contains `:address` and `:confirmed` parameters (or their string version), the type is `:email`. A `"__type__"`

parameter is then no longer required.

Note that you may still include a `__type__` parameter that will take precedence over this strategy (this could still be

useful if you need to store incomplete data, which might not allow identifying the type).

#### List of polymorphic embeds

Lists of polymorphic embeds are also supported:

```

polymorphic\_embeds\_many :contexts,

 types: [

 location: MyApp.Context.Location,

 age: MyApp.Context.Age,

 device: MyApp.Context.Device

 ],

 on\_type\_not\_found: :raise,

 on\_replace: :delete

```

#### Options

* `:types` – discussed above.

* `:type_field` – specify a custom type field. Defaults to `:__type__`.

* `:on_type_not_found` – specify what to do if the embed's type cannot be inferred.

Possible values are

	+ `:raise`: raise an error

	+ `:changeset_error`: add a changeset error

	+ `:nilify`: replace the data by `nil`; only for single (non-list) embeds

	+ `:ignore`: ignore the data; only for lists of embedsBy default, a changeset error "is invalid" is added.

* `:on_replace` – mandatory option that can only be set to `:update` for a single embed and `:delete` for a list of

embeds (we force a value as the default value of this option for `embeds_one` and `embeds_many` is `:raise`).

### 

[displaying-form-inputs-and-errors-in-phoenix-templates](#displaying-form-inputs-and-errors-in-phoenix-templates)

 Displaying form inputs and errors in Phoenix templates

The library comes with a form helper in order to build form inputs for polymorphic embeds and display changeset errors.

In the entrypoint defining your web interface (`lib/your_app_web.ex` file), add the following import:

```

def view do

 quote do

 # imports and stuff

 import PolymorphicEmbed.HTML.Form

 end

end

```

This provides you with the `polymorphic_embed_inputs_for/3` and `polymorphic_embed_inputs_for/4` functions.

Here is an example form using the imported function:

```

<%= inputs\_for f, :reminders, fn reminder\_form -> %>

 <%= polymorphic\_embed\_inputs\_for reminder\_form, :channel, :sms, fn sms\_form -> %>

 <div class="sms-inputs">

 <label>Number<label>

 <%= text\_input sms\_form, :number %>

 <div class="error">

 <%= error\_tag sms\_form, :number %>

 </div>

 </div>

 <% end %>

<% end %>

```

When using `polymorphic_embed_inputs_for/4`, you have to manually specify the type. When the embed is `nil`, empty fields will be displayed.

`polymorphic_embed_inputs_for/3` doesn't require the type to be specified. When the embed is `nil`, no fields are displayed.

They both render a hidden input for the `"__type__"` field.

### 

[displaying-form-inputs-and-errors-in-liveview](#displaying-form-inputs-and-errors-in-liveview)

 Displaying form inputs and errors in LiveView

You may use `polymorphic_embed_inputs_for/2` when working with LiveView.

```

<.form

 let={f}

 for={@changeset}

 id="reminder-form"

 phx-change="validate"

 phx-submit="save"

>

 <%= for channel\_form <- polymorphic\_embed\_inputs\_for f, :channel do %>

 <%= hidden\_inputs\_for(channel\_form) %>

 <%= case get\_polymorphic\_type(channel\_form, Reminder, :channel) do %>

 <% :sms -> %>

 <%= label channel\_form, :number %>

 <%= text\_input channel\_form, :number %>

 <% :email -> %>

 <%= label channel\_form, :email %>

 <%= text\_input channel\_form, :email %>

 <% end %>

</.form>

```

Using this function, you have to render the necessary hidden inputs manually as shown above.

### 

[get-the-type-of-a-polymorphic-embed](#get-the-type-of-a-polymorphic-embed)

 Get the type of a polymorphic embed

Sometimes you need to serialize the polymorphic embed and, once in the front-end, need to distinguish them.

`get_polymorphic_type/3` returns the type of the polymorphic embed:

```

PolymorphicEmbed.get\_polymorphic\_type(Reminder, :channel, SMS) == :sms

```

### 

[traverse\_errors-2](#traverse_errors-2)

`traverse_errors/2`

The function `Ecto.changeset.traverse_errors/2` won't include the errors of polymorphic embeds. You may instead use [`PolymorphicEmbed.traverse_errors/2`](PolymorphicEmbed.html#traverse_errors/2) when working with polymorphic embeds.

[features](#features)

 Features

--------------------------------

* Detect which types to use for the data being `cast`-ed, based on fields present in the data (no need for a *type* field in the data)

* Run changeset validations when a `changeset/2` function is present (when absent, the library will introspect the fields to cast)

* Support for nested polymorphic embeds

* Support for nested `embeds_one`/`embeds_many` embeds

* Display form inputs for polymorphic embeds in Phoenix templates

* Tests to ensure code quality

[installation](#installation)

 Installation

--------------------------------------------

Add `polymorphic_embed` for Elixir as a dependency in your `mix.exs` file:

```

def deps do

 [

 {:polymorphic\_embed, "~> 3.0.3"}

 ]

end

```

[hexdocs](#hexdocs)

 HexDocs

-----------------------------

HexDocs documentation can be found at <https://hexdocs.pm/polymorphic_embed>.

[← Previous Page

 

API Reference](api-reference.html)





PolymorphicEmbed — Polymorphic Embed v3.0.3

 try {

 var settings = JSON.parse(localStorage.getItem('ex\_doc:settings') || '{}');

 if (settings.theme === 'dark' ||

 ((settings.theme === 'system' || settings.theme == null) &&

 window.matchMedia('(prefers-color-scheme: dark)').matches)

 ) {

 document.body.classList.add('dark')

 }

 } catch (error) { }

 

Settings

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed.ex#L1 "View Source")

PolymorphicEmbed

(Polymorphic Embed v3.0.3)

==============================================================================================================================================================================

[Link to this section](#summary)

 Summary

==========================================

[Functions](#functions)

------------------------

[cast\_polymorphic\_embed(changeset, field, cast\_options \\ [])](#cast_polymorphic_embed/3)

[do\_load(data, loader, map)](#do_load/3)

[get\_polymorphic\_module(schema, field, type\_or\_data)](#get_polymorphic_module/3)

[get\_polymorphic\_type(schema, field, module\_or\_struct)](#get_polymorphic_type/3)

[polymorphic\_embeds\_many(field\_name, opts)](#polymorphic_embeds_many/2)

[polymorphic\_embeds\_one(field\_name, opts)](#polymorphic_embeds_one/2)

[traverse\_errors(changeset, msg\_func)](#traverse_errors/2)

[types(schema, field)](#types/2)

Returns the possible types for a given schema and field

[Link to this section](#functions)

Functions

=============================================

[Link to this function](#cast_polymorphic_embed/3 "Link to this function")

cast\_polymorphic\_embed(changeset, field, cast\_options \\ [])

===============================================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed.ex#L58 "View Source")

[Link to this function](#do_load/3 "Link to this function")

do\_load(data, loader, map)

===========================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed.ex#L277 "View Source")

[Link to this function](#get_polymorphic_module/3 "Link to this function")

get\_polymorphic\_module(schema, field, type\_or\_data)

=======================================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed.ex#L318 "View Source")

[Link to this function](#get_polymorphic_type/3 "Link to this function")

get\_polymorphic\_type(schema, field, module\_or\_struct)

=========================================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed.ex#L353 "View Source")

[Link to this macro](#polymorphic_embeds_many/2 "Link to this macro")

polymorphic\_embeds\_many(field\_name, opts)

============================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed.ex#L10 "View Source")

(macro)

[Link to this macro](#polymorphic_embeds_one/2 "Link to this macro")

polymorphic\_embeds\_one(field\_name, opts)

===========================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed.ex#L4 "View Source")

(macro)

[Link to this function](#traverse_errors/2 "Link to this function")

traverse\_errors(changeset, msg\_func)

======================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed.ex#L425 "View Source")

[Link to this function](#types/2 "Link to this function")

types(schema, field)

====================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed.ex#L373 "View Source")

Returns the possible types for a given schema and field

you can call [`types/2`](#types/2) like this:

```

PolymorphicEmbed.types(MySchema, :contexts)

#=> [:location, :age, :device]

```





PolymorphicEmbed.HTML.Form — Polymorphic Embed v3.0.3

 try {

 var settings = JSON.parse(localStorage.getItem('ex\_doc:settings') || '{}');

 if (settings.theme === 'dark' ||

 ((settings.theme === 'system' || settings.theme == null) &&

 window.matchMedia('(prefers-color-scheme: dark)').matches)

 ) {

 document.body.classList.add('dark')

 }

 } catch (error) { }

 

Settings

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed/html/form.ex#L2 "View Source")

PolymorphicEmbed.HTML.Form

(Polymorphic Embed v3.0.3)

==================================================================================================================================================================================================

[Link to this section](#summary)

 Summary

==========================================

[Functions](#functions)

------------------------

[get\_polymorphic\_type(form, schema, field)](#get_polymorphic_type/3)

Returns the polymorphic type of the given field in the given form data.

[polymorphic\_embed\_inputs\_for(form, field)](#polymorphic_embed_inputs_for/2)

Generates a new form builder without an anonymous function.

[polymorphic\_embed\_inputs\_for(form, field, fun)](#polymorphic_embed_inputs_for/3)

Like [`polymorphic_embed_inputs_for/4`](#polymorphic_embed_inputs_for/4), but determines the type from the

form data.

[polymorphic\_embed\_inputs\_for(form, field, type, fun)](#polymorphic_embed_inputs_for/4)

[to\_form(source\_changeset, form, field, type, options)](#to_form/5)

[Link to this section](#functions)

Functions

=============================================

[Link to this function](#get_polymorphic_type/3 "Link to this function")

get\_polymorphic\_type(form, schema, field)

===========================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed/html/form.ex#L9 "View Source")

Returns the polymorphic type of the given field in the given form data.

[Link to this function](#polymorphic_embed_inputs_for/2 "Link to this function")

polymorphic\_embed\_inputs\_for(form, field)

============================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed/html/form.ex#L63 "View Source")

Generates a new form builder without an anonymous function.

Similarly to [`Phoenix.HTML.Form.inputs_for/3`](https://hexdocs.pm/phoenix_html/3.2.0/Phoenix.HTML.Form.html#inputs_for/3), this function exists for

integration with `Phoenix.LiveView`.

Unlike [`polymorphic_embed_inputs_for/4`](#polymorphic_embed_inputs_for/4), this function does not generate

hidden inputs.

[example](#polymorphic_embed_inputs_for/2-example)

 Example

------------------------------------------------------------

```

<.form

 let={f}

 for={@changeset}

 id="reminder-form"

 phx-change="validate"

 phx-submit="save"

>

 <%= for channel\_form <- polymorphic\_embed\_inputs\_for f, :channel do %>

 <%= hidden\_inputs\_for(channel\_form) %>

 <%= case get\_polymorphic\_type(channel\_form, Reminder, :channel) do %>

 <% :sms -> %>

 <%= label channel\_form, :number %>

 <%= text\_input channel\_form, :number %>

 <% :email -> %>

 <%= label channel\_form, :email %>

 <%= text\_input channel\_form, :email %>

 <% end %>

</.form>

```

[Link to this function](#polymorphic_embed_inputs_for/3 "Link to this function")

polymorphic\_embed\_inputs\_for(form, field, fun)

=================================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed/html/form.ex#L100 "View Source")

Like [`polymorphic_embed_inputs_for/4`](#polymorphic_embed_inputs_for/4), but determines the type from the

form data.

[example](#polymorphic_embed_inputs_for/3-example)

 Example

------------------------------------------------------------

```

<%= inputs\_for f, :reminders, fn reminder\_form -> %>

 <%= polymorphic\_embed\_inputs\_for reminder\_form, :channel, fn channel\_form -> %>

 <%= case get\_polymorphic\_type(channel\_form, Reminder, :channel) do %>

 <% :sms -> %>

 <%= label poly\_form, :number %>

 <%= text\_input poly\_form, :number %>

 <% :email -> %>

 <%= label poly\_form, :email %>

 <%= text\_input poly\_form, :email %>

 <% end %>

 <% end %>

<% end %>

```

While [`polymorphic_embed_inputs_for/4`](#polymorphic_embed_inputs_for/4) renders empty fields if the data is

`nil`, this function does not. Instead, you can initialize your changeset

to render an empty fieldset:

```

changeset = reminder\_changeset(

 %Reminder{},

 %{"channel" => %{"\_\_type\_\_" => "sms"}}

)

```

[Link to this function](#polymorphic_embed_inputs_for/4 "Link to this function")

polymorphic\_embed\_inputs\_for(form, field, type, fun)

=======================================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed/html/form.ex#L114 "View Source")

[Link to this function](#to_form/5 "Link to this function")

to\_form(source\_changeset, form, field, type, options)

=======================================================

[View Source](https://github.com/mathieuprog/polymorphic_embed/blob/v3.0.3/lib/polymorphic_embed/html/form.ex#L126 "View Source")

