thiagomajesk

thiagomajesk

What is your preferred way of dealing with computed fields in Ecto?

Hi, I was wondering something for the past few weeks while working on multiple projects and I’d like to collect some feedback. Here’s the use case…

Very often, we’ll have the need to store computed fields after some sort of validation has happened. Those fields could be something simple as a user’s full name or something more complex like markdown content that needs to be parsed.

While working on different projects, I always came across the same uncertainty: Where would be the best place to put the computing logic. After thinking carefully about it, I had a different answer for each project I was working on. Here are two similar approaches with conceptual differences:

First example

The context module abstracts all inputs behind the attrs map and everything else is delegated to the schema:

# attrs = %{"name" => "john", "surname" => "doe"}
def create_user(attrs) do
  %User{}
  |> User.changeset(attrs)
  |> Repo.insert
end

The schema module takes care of holding the relevant logic and computing the field when the changeset is valid. The changeset validation is coupled to the computing logic (this will be important later):

def changeset(user, attrs) do
   user
   |> cast([:name, :surname])
   |> validate_required([:name, :surname])
   |> put_full_name()
end

def put_full_name(changeset) do
  if changeset.is_valid? do
    name = get_change(changeset, :name) 
    surname = get_change(changeset, :surname)
    put_change(changeset, :surname, name <> surname)
  else
    changeset
  end
end

Second example

In the context, user and “application” input are split. We take care of including the computed value after we have validated user input. The changeset validation is decoupled with the computing logic (this will be important later):

# content = "User x has just logged in!"
# attrs = %{"user_id" => 1 "user_full_name" => "john doe"}
def system_message(attrs, content) do
  %Message{}
  |> Message.changeset()
  |> Message.put_html(html)
  |> Repo.insert
end

The schema module only takes care of validating user input and it optionally exposes a way to deal with additional computed values:

def changeset(user, attrs) do
   user
   |> cast([:user_id, :user_full_name])
   |> validate_required([:user_id, :user_full_name])
end

# Depending on how complex or specific this is; 
# it could be left inside of the schema or kept in the context
def put_html(changeset, html) do
    html = Markdown.parse(content) 
    put_change(changeset, :html, html)
end

The obvious distinction is that the second example heavily depends on the context function to provide a “valid” way to insert a message while the first does not.

Both approaches have obvious advantages and disadvantages. For instance, if you have to interface with a lot of other modules to have a given value computed (perhaps an external API or service), it would probably be more reasonable to place the logic in the context to not “overload” the schema with too many responsibilities.

Considering that the resulting API is a direct correlation with the level of abstraction that we chose and given that contexts already represent some sort of public API for the application, what approach do you usually prefer in your projects?

First Post!

cmo

cmo

I tend to keep schema modules paper thin and purely functional. Generally only validation and query logic live there.

Storing the full name seems pointless in this example and can your markdown parse function not fail either? Sems like there should be a case or with in the second one, which would exclude it from the schema module in my mind.

Where Next?

Popular in Discussions Top

Qqwy
I would like to spark a discussion about the static access operator: .. For whom does not know: it is used in Elixir to access fields of...
New
pillaiindu
In django there is a cache framework backed by memcached. Rails also puts a lot of emphasis on caching, and even the idea of russian-doll...
New
acrolink
How does the two languages compare when it comes to server side application development? Any experiences or ideas? Thank you.
New
boundedvariable
I am going through the kafka architecture. All the features what the kafka is providing are already in Erlang. I would like hear your opi...
New
pdgonzalez872
If this has been asked here before, please point me to where it was asked as I didn’t find it when I searched the forum. Maybe a mailing ...
New
chulkilee
Here are the list of HTTP client libraries/wrappers, and some thoughts on HTTP client in general. I’d like to hear from others how they w...
New
chuck
Let me start by stating an assumption: Phoenix is a great approach to building REST APIs. There are many reasons for this, but I will ass...
New
lucaong
Hello Elixir and Nerves community, I have been working for a while on an open-source embedded key-value database for Elixir, that I call...
230 14027 124
New
Ankhers
Just a little information upfront. Generally speaking, if I feel like I need to either break a pipe chain or use an anonymous function in...
New
100phlecs
Are there any downsides, like perf issues, to putting all functional components in CoreComponents as long as you prefix it with the conte...
New

Other popular topics Top

JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
vertexbuffer
Hello, can anybody help here..? I have a list of players and I what to delete an element, but every for loop the list is reverting to ori...
New
johnnyicon
Hi all, I’ve just started learning Elixir and Phoenix Framework, so please pardon my n00bness at this stage. I’m trying to use Postgres...
New
jaysoifer
Is there a way to rollback a specific migration and only that one (“skipping” all the other ones)? Would mix ecto.rollback -v 200809061...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
jay1
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
Nvim
Anybody knows a comprehensive comparison of Django and Phoenix, thanks for the help. Where are they similar? Where do they differ the m...
New
AngeloChecked
What learn first? Rust or Elixir Hi Elixir community! I’m here because i want learn a new language. I’m a junior developer and mainly i ...
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
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement