All Projects → mathieuprog → Polymorphic_embed

mathieuprog / Polymorphic_embed

Licence: apache-2.0
Polymorphic embeds in Ecto

Programming Languages

elixir
2628 projects

Projects that are alternatives of or similar to Polymorphic embed

ecto nested changeset
Helpers for manipulating nested Ecto changesets
Stars: ✭ 23 (-71.25%)
Mutual labels:  phoenix, ecto
Wallaby
Concurrent browser tests with elixir
Stars: ✭ 1,143 (+1328.75%)
Mutual labels:  ecto, phoenix
guardian trackable
A Guardian hook to track user sign ins.
Stars: ✭ 25 (-68.75%)
Mutual labels:  phoenix, ecto
querie
Compose Ecto query from the client side
Stars: ✭ 20 (-75%)
Mutual labels:  phoenix, ecto
Paper trail
Track and record all the changes in your database with Ecto. Revert back to anytime in history.
Stars: ✭ 380 (+375%)
Mutual labels:  ecto, phoenix
ecto profiler
Project for Ecto DB profiling
Stars: ✭ 16 (-80%)
Mutual labels:  phoenix, ecto
pretty print formatter
Pretty Print Formatter for Elixir Logger module -- Colorize Ecto's SQL ouput 🖌️
Stars: ✭ 22 (-72.5%)
Mutual labels:  phoenix, ecto
Params
Easy parameters validation/casting with Ecto.Schema, akin to Rails' strong parameters.
Stars: ✭ 239 (+198.75%)
Mutual labels:  ecto, phoenix
Machinery
State machine thin layer for structs (+ GUI for Phoenix apps)
Stars: ✭ 367 (+358.75%)
Mutual labels:  ecto, phoenix
one plus n detector
Elixir library to help you detect 1+n queries in applications using Ecto
Stars: ✭ 20 (-75%)
Mutual labels:  phoenix, ecto
phoenix pagination
Simple pagination for Ecto and Phoenix that uses plain EEx templates.
Stars: ✭ 20 (-75%)
Mutual labels:  phoenix, ecto
Query
Query adds tools to aid the use of Ecto in web settings.
Stars: ✭ 23 (-71.25%)
Mutual labels:  ecto, phoenix
algoliax
Algolia integration to elixir application
Stars: ✭ 38 (-52.5%)
Mutual labels:  phoenix, ecto
contextual
🌈 Generate your Ecto contexts using this macro and eliminate boilerplate
Stars: ✭ 18 (-77.5%)
Mutual labels:  phoenix, ecto
query builder
Compose Ecto queries without effort
Stars: ✭ 56 (-30%)
Mutual labels:  phoenix, ecto
ex sieve
Implement dynamic filtering and sorting API for Ecto queries
Stars: ✭ 37 (-53.75%)
Mutual labels:  phoenix, ecto
Phoenix Ecto Encryption Example
🔐 A detailed example for how to encrypt data in a Phoenix (Elixir) App before inserting into a database using Ecto Types
Stars: ✭ 166 (+107.5%)
Mutual labels:  ecto, phoenix
Formex
A better form library for Phoenix
Stars: ✭ 206 (+157.5%)
Mutual labels:  ecto, phoenix
ecto generator
Generate Ecto schemas from existing database in Phoenix - Elixir
Stars: ✭ 20 (-75%)
Mutual labels:  phoenix, ecto
Kaffy
Powerfully simple admin package for phoenix applications
Stars: ✭ 617 (+671.25%)
Mutual labels:  ecto, phoenix

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

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 that this library provides.

Find the schema code and explanations below.

defmodule MyApp.Reminder do
  use Ecto.Schema
  import Ecto.Changeset
  import PolymorphicEmbed, only: [cast_polymorphic_embed: 3]

  schema "reminders" do
    field :date, :utc_datetime
    field :text, :string

    field :channel, PolymorphicEmbed,
      types: [
        sms: MyApp.Channel.SMS,
        email: [module: MyApp.Channel.Email, identify_by_fields: [:address, :confirmed]]
      ],
      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

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.

PolymorphicEmbed Ecto type

The :types option for the PolymorphicEmbed 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:

[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").

[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:

field :contexts, {:array, PolymorphicEmbed},
  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 whether to raise or add a changeset error if the embed's type cannot be inferred. Possible values are :raise and :changeset_error. By 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

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 a polymorphic_embed_inputs_for/4 function.

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 %>

polymorphic_embed_inputs_for/4 also renders a hidden input for the "__type__" field.

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

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

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

def deps do
  [
    {:polymorphic_embed, "~> 1.3.4"}
  ]
end

HexDocs

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

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].