The goal is to return the following format
{"b": {"f1":"foo", "gibberish":"1"}}
given these two Ecto schemas:
defmodule A do
use Ecto.Schema
schema "a" do
embeds_one :b, B
end
end
defmodule B do
use Ecto.Schema
@primary_key false
embedded_schema do
field :f1, :binary
field :f2, Ecto.Enum,
values: [human_readable1: "1", human_readable2: "2"],
source: :gibberish,
embed_as: :dumped
end
end
In other words, how to keep the (more readable) field :f2
and its human_readable1
etc. values
in the codebase, while also be able to return the underlying JSONB Postgres record? In the example above, it contains a column :gibberish
, with raw values "1"
etc.
Iāve come up with 2 approaches.
Approach 1, by (partially) implementing Access
behaviour:
# First add Access implementation to both `A` and `B` module:
def fetch(struct, key) do
{:ok, Map.get(struct, key)}
end
# Then in API controller:
def show(conn, _) do
a = Repo.get(A, some_id)
|> Map.from_struct()
|> Map.drop([:__meta__, :id]))
b = Map.get(a, :b)
|> Map.from_struct()
|> Map.drop([:f2])
|> Map.put(:gibberish, Ecto.Enum.mappings(B, :f2)[get_in(a, [:b, :f2])])
json(conn, %{ b: b })
end
Approach 2, by implementing Jason.Encoder
protocol:
# First add Jason.Encoder implementation to `B` module:
defimpl Jason.Encoder do
def encode(struct, opts) do
Jason.Encode.map(
struct
|> Map.from_struct()
|> Map.drop([:f2])
|> Map.put(:gibberish, Ecto.Enum.mappings(B, :f2)[Map.get(struct, :f2)]),
opts
)
end
end
# Then in API controller:
def show(conn, _) do
json(conn, Repo.get(A, some_id)
|> Map.from_struct()
|> Map.drop([:__meta__, :id]))
json(conn, a)
end
Is there a more elegant approach #3? Or is this as good as it gets? Iād of course go for approach #1, since there can only be one implementation of a protocol per struct.