svilen

svilen

Author of Concurrent Data Processing in Elixir

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!

Showing Posts 1 to 6

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.

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:

benwilson512

benwilson512

Author of Craft GraphQL APIs in Elixir with Absinthe

Why is this not great? Creating 1000 maps is still orders of magnitude faster than hitting the database to get 1000 users in the first place.

svilen

svilen OP

Author of Concurrent Data Processing in Elixir

Thanks @wmnnd and @OvermindDL1, great advice! I totally forgot about virtual fields, that would be a clever way to make use of them; I wasn’t aware of Poison’s Encoder protocol at all. I found this article which has a short example that might be helpful to others:

@benwilson512 I think I didn’t phrase this correctly. I’m always hitting the database, so what I meant was that instead of serializing the results coming from the DB, I was doing another round of operations to create the Maps and then serialize them. I’m not sure about the performance implications, but less operations == better, in my mind at least.

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.

svilen

svilen OP

Author of Concurrent Data Processing in Elixir

“What if” — sounds like swapping premature optimization for overengineering :wink:

I think you’ve raised some valid points, but everyone’s use case is different. I don’t envisage rendering models in different ways — I intend to keep the JSON representation of my models consistent — so implementing the Encoder protocol would be perfectly fine. If I do have an edge case where I need to render a “lightweight” version of a model, then I can always fall back to constructing and returning a map from the View.

— All posts loaded —

Where Next? Top

Trending in Questions Top

stjefim
Hello! Suppose you are building workflow (order / task / payment) processing system with the following requirements: Each workflow con...
New
Blokh
Hey guys, I’ve got a huge CSV ( around 10 GB ) that needs to be processed hourly Do you guys have any suggestions what is the best prac...
New
roeland
Kia ora, We have been using elixir-google-api to connect to Google Drive. However, with the updates to Tesla due to CVEs this is now bro...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
Onor.io
I have what I’ve heard referred to as a “lookup table” in my database. This is a way of assigning codes to common values. One common lo...
New
jaybe78
Hello, I’m developing a online persistent chat system (what’s app) like using elixir/dynamodb/aws for a mobile app(flutter). The diffic...
New
Trolleger
What approach to take when sending live updates to “random” users Hi! I have a question, I have a little chat app, and when I create a DM...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
mcass19
ExRatatui lets you cook up rich terminal UIs in Elixir, powered by Rust’s ratatui via Rustler NIFs. Build interactive terminal applicatio...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews