View Source EctoDiscriminator
motivation
Motivation
This small library was built to support table-per-hierarchy inheritance pattern (TPH) popular in Microsoft's Entity
Framework.
TPH uses a single table to store the data for all types in the hierarchy, and a discriminator column is used to identify
which type each row represents.
It is similar to Polymorphic Embed with few key differences.
Thanks to this library, those entities can be fully separated structs, which brings many simplifications during
inserting and querying.
You can also add any extra fields that will exist only in one struct (for example virtual ones or relationships).
inheritance-huh
Inheritance, huh?!
It may seem not reasonable to introduce concept of inheritance to the Ecto (and Elixir itself), but for some cases I
think it's better to have it instead of repeated code.
Inheritance is natural for our environment, everything comes from some more general being and shares its capabilities.
Without it, you're left just with pattern matching, and it's enough for most cases, but not all of them.
Quick example:
mug and cup, they look almost the same, but cup can have reference to a saucer.
You could preload this reference for both and just remember that for mugs it's always empty, but this generates extra
load on the DB (which doesn't know what you know) and forces you to explain for any new person to the project why this
will be missing for mug
So yea, this library introduces a concept of inheritance to your code.
installation
Installation
The package can be installed by adding ecto_discriminator
to your list of dependencies in mix.exs
:
def deps do
[
{:ecto_discriminator, "~> 0.2.0"}
]
end
usage
Usage
schema
Schema
It has been built to mimic Ecto.Schema
as much as possible. That said, the only changes to do in base schema are:
- Replace Schema module
defmodule EctoDiscriminator.SomeTable do
- use Ecto.Schema
+ use EctoDiscriminator.Schema
- Define discriminator column
schema "some_table" do
+ field :type, EctoDiscriminator.DiscriminatorType
You can also mark the primary key as a discriminator
+ @primary_key {:type, EctoDiscriminator.DiscriminatorType, []}
schema "some_table" do
Then you can add some diverged schemas
defmodule EctoDiscriminator.SomeTable.Foo do
use EctoDiscriminator.Schema
schema EctoDiscriminator.SomeTable do
embeds_one :content, EctoDiscriminator.SomeTable.BarContent
end
end
Library will do the rest. Querying for diverged schema automatically adds filter to SQL.
changeset
Changeset
To reduce repetitive usage of cast
with a list of common fields for diverged schemas you can call cast_base(params)
to automatically apply changeset from base schema.
This function won't be available if base schema doesn't have any changeset/2
function
Some may find it useful to insert diverged schema directly from the base (by specifying discriminator value in changeset
params).
This is doable by calling diverged_changeset
function on base schema.
examples
Examples
Refer to the documentation of modules for some examples.
You can also browse test
directory for some example setup.
known-limitations
Known limitations
- When using keywords for constructing queries:
from(s in SomeTable)
thewhere
condition won't be automatically applied.
This is because Ecto handlesfrom
macro in a way that skipsEcto.Queryable
protocol. - It's not possible to obtain correct mapping with something like
Repo.all(BaseSchema)
. You have to execute separate query for each diverged type and then concat results (this was tested and seems to be the fastest solution). - Dialyzer may add some warnings regarding "missing callback information". Maybe it can be solved somehow.