zimt28

zimt28

Ecto: Shared schema definition/ model alias

In my application I want to have a single entities table, which stores type a and type b entities. type a and type b share the exact same fields, I just set a type field can be "type_a" or "type_b".

For some reasons I don’t want to work with %Entity{} structs, but %TypeA{} and %TypeB{} structs, and I’m now wondering what might be the best way to do this.

In the end I’m looking for a way to create “aliases” for my %Entity{} struct. Here’s what I’ve tried so far:

1. Just define the schema multiple times

defmodule App.Entities.Entity do
  # ...

  schema "entities" do
    field :title, :string
    field :type, :string
    has_one :address, Address
  end

  @doc false
  def changeset(%Entity{} = entity, attrs) do
    entity
    |> cast(attrs, [:title, :type])
    |> validate_required([:title, :type])
  end
end

defmodule App.Entities.TypeA/B do
  # ...

  schema "entities" do
    field :title, :string
    field :type, :string
    has_one :address, Address, foreign_key: :entity_id
  end

  @doc false
  def changeset(%TypeA/B{} = entity, attrs) do
    entity
    |> cast(attrs, [:title, :type])
    |> validate_required([:title, :type])
    |> put_change(:type, "type_a/b")
  end
end

This works fine, but I don’t like the repetitiveness of this approach.

2. Use a separate schema module

defmodule App.Entities.EntitySchema do
  defmacro __using__(_) do
    quote do
      # ...

      schema "entities" do
        field :title, :string
        field :type, :string
        has_one :address, Address, foreign_key: :entity_id
      end
    end
  end
end

defmodule App.Entities.Entity do
  use App.Entities.EntitySchema

  @doc false
  def changeset(%Entity{} = entity, attrs) do
    entity
    |> cast(attrs, [:title, :type])
    |> validate_required([:title, :type])
  end
end

defmodule App.Entities.TypeA/B do
  use App.Entities.EntitySchema

  @doc false
  def changeset(%TypeA/B{} = entity, attrs) do
    entity
    |> Map.put(:__struct__, Entity)
    |> Entity.changeset(attrs)
    |> put_change(:type, "type_a/b")
  end
end

I prefer this approach, but it still seems to be a bit overkill for a “simple alias”.

3. Define structs

I have not tried this so far but it might be possible to use regular Elixir structs:

defmodule App.Entities.TypeA/B do
  defstruct [:id, :title, :type, :address, :created_at, :updated_at]
end

defmodule App.Entities.Entity do
  # ...

  schema "entities" do
    field :title, :string
    field :type, :string
    has_one :address, Address
  end

  @doc false
  def changeset(%TypeA/B{} = entity, attrs) do
    entity
    |> Map.put(:__struct__, Entity)
    |> changeset(attrs)
    |> put_change(:type, "type_a/b")
  end
  
  def changeset(%Entity{} = entity, attrs) do
    entity
    |> cast(attrs, [:title, :type])
    |> validate_required([:title, :type])
  end
end

This seems to be a clean approach as my %TypeA/B{} structs are pure data containers,
but I’m not sure if I would lose some Ecto functionality, say preloads etc.?

Alternatives, Feedback?

How would you solve this problem and why? Maybe there’s a better approach that I haven’t thought of yet :slight_smile:

First Post!

zimt28

zimt28

I’m still not sure how I want to do it, but no. 3 – defining structs – is also possible:

defmodule App.Entities.TypeA/B do
  @entity App.Entities.Entity.__struct__()

  defstruct @entity
            |> Map.keys()
            |> Enum.reduce([], fn key, acc ->
              val = Map.get(@entity, key)
              Keyword.put_new(acc, key, val)
            end)

  def __schema__(key) do
    App.Entities.Entity.__schema__(key)
  end

  def __schema__(key, arg) do
    App.Entities.Entity.__schema__(key, arg)
  end

  def __changeset__ do
    App.Entities.Entity.__changeset__()
  end
end

I’ve moved this into a separate module so I can just do

defmodule App.Entities.TypeA/B do
  use App.Alias, for: App.Entities.Entity
end

Where Next?

Popular in Questions Top

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
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
chokchit
** (DBConnection.ConnectionError) connection not available and request was dropped from queue after 2733ms. You can configure how long re...
New
shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
shijith.k
I am trying to start a new phoenix project with elixir 1.9, but mix phx.new does not work. It says that ** (Mix) The task "phx.new" could...
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
tduccuong
Hi, is there any work on GUI with Elixir, that is similar to Electron/Javascript? My idea is to bundle Phoenix and BEAM into a single se...
New
marius95
Hello everyone, I try to use an Javascript Event Handler in my root.html.leex file. Therefore I created a function in the app.js file: ...
New
JorisKok
I have a server on AWS, and was running a load test using artillery. When looking at the Phoenix dashboard I see the Ports going to 100% ...
New

Other popular topics Top

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
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
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
PeterCarter
There are pre-rolled solutions for other frameworks that do work. However, Phoenix does not seem to have these. Have people had good expe...
New
greenz1
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
msaraiva
Surface is an experimental library built on top of Phoenix LiveView and its new LiveComponent API that aims to provide a more declarative...
564 43806 214
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New

We're in Beta

About us Mission Statement