svilen

svilen

Author of Concurrent Data Processing in Elixir

JSON-encode modified Ecto Schema

As far as I know, the recommended (and easiest) way to JSON-encode your Ecto Schema in Phoenix is using the Poison.Encoder like so:

defmodule MyApp.Web.User do
  use MyApp.Web, :model
  @derive {Poison.Encoder, only: [:first_name, :last_name, :email]}
  
  schema "users" do
    field :first_name, :string
    field :last_name, :string
    field :email, :string
    # ...
    timestamps()
  end
end

That works great as it allows me to simply return the User schema in a show.json Phoenix.View, for example, and then Poison would pick it up and serialize it.

However, I’m not sure what’s the best way to go if you want to do something extra, before returning the schema from the View.

Let’s say the user has a last_login field that I’d like to convert from UTC to a specific timezone. The actual timezone string identifier is not part of the User schema, so it needs to be fetched separately. Also I would like to add a field that’s not part of the original User schema. Right now, I’m constructing a new Map and returning it from the Phoenix.View, like so:

defmodule MyApp.Web.UserView do
  use MyApp.Web, :view

  def render("show.json", %{....}) do
    # ...
    tz = organization.timezone
    %{
      first_name: user.first_name,
      last_name: user.last_name,
      org_name: organization.name,
      last_login: Timex.Timezone.convert(user.last_login, tz)
    }
  end
end

That seems to work, but I’m not very keen on this approach as it is a lot of duplicated code; also if I want to return a list of 1000 users, this will construct 1000 maps just for encoding, which is not great.

Is there a better way to do this?

P.S. Forum moderators: I’m not sure if this topic belongs to the Phoenix, Ecto or a different category, so feel free to move it if it makes sense!

Most Liked

wmnnd

wmnnd

You could make use of Ecto’s virtual fields in order to add this key to your Schema struct and then have it encoded without creating a whole new Map.
Virtual fields on Ecto Schemas can be set and read but Ecto will not try to persist them to your database.

And since Ecto Schemas are really just normal Elixir structs, you can also simply update the value of your last_login field with the formatted string without it having any impact on your stored data.

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Premature optimization comes with its own set of issues though. Suppose you go for the poison encoder route, to avoid creating some intermediate data. You’ve now tied your model to one very specific representation of JSON. What if you want an additional API endpoint where you serialize users differently? You’re out of luck, there can only be one implementation of a protocol per struct. There’s more on this in a nice blog post here: JSON Views in Phoenix | RokkinCat

Functional programming generally produces a lot of intermediate data, but since this is known, the BEAM (and other functional platforms) are built around handling this well. Immutable data means that very little actually needs to be copied, because you can point to a lot of the same data you were already pointing too since you know it won’t ever change. Garbage collectors are written to efficiently handle this kind of workload, and so on.

At the end of the day, you don’t want to make architectural decisions about how you relate your view layer to your modeling layer on the basis of what might gain you a couple of microseconds within a request that is already at a minimum gonna be several milliseconds.

OvermindDL1

OvermindDL1

Or if you really only want it in the json and not elsewhere then just make your own poison protocol to serialize out your schema. It is very simple to do and you can see how to do it via the docs or source. :slight_smile:

Where Next?

Popular in Questions Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
vac
Hi, I’m quite new in Elixir and I’m trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and I...
New
fireproofsocks
Forgive me if this is obvious, but how does one delete a database record WITHOUT selecting it first? Ecto.Repo — Ecto v3.14.0 has exampl...
New
hariharasudhan94
lets say i have a sample like a = 20; b = 10; if (a > b) do {:ok, "a"} end if (a < b) do {:ok, b} end if (a == b) do {:ok, "equa...
New
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
srinivasu
How to handle excepions in elixir? Suppose i have A, B, C ,D, E modules. and each module has get() function. A.get() method will call t...
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

Other popular topics Top

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
gshaw
What is the idiomatic way of matching for not nil in Elixir? E.g., First way: defp halt_if_not_signed_in(conn, signed_in_account) when...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
New
pmjoe
I have a relationship of love and hate with Elixir. Lots of things are just absolutely right, but there are some things that are kind of ...
New
vonH
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
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
freewebwithme
Using vs code and installed ElixirLS: support and debugger. And I got an error popped up on start up says Failed to run ‘elixir’ comma...
New
klo
Got a question about when to concat vs. prepending items to list then reversing to achieve appending. So i know lists boil down to [1 | ...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

We're in Beta

About us Mission Statement