Francesco

Francesco

Best practice for pattern matching ecto changeset error constraints?

Hello!

I have a user table where I don’t allow two verified users with the same email to exist. This is enforced by a unique constraint on the table and a corresponding unique constraint on the changeset. When this case occurs I will get back a changeset that looks like:

#Ecto.Changeset<
  action: :insert,
  changes: %{...},
  errors: [
    email: {"has already been taken",
     [constraint: :unique, constraint_name: "users_verified_email_index"]}
  ],
  data: #Scribe.Accounts.User<>,
  valid?: false
>

I want to pattern match on this particular error constraint however this is a bit of a full on pattern match and it involves keyword list which means I have to get the number and order of items to match, i.e.

{:error, %Ecto.Changeset{ errors: [email: {_, [constraint: :unique, constraint_name: _]}] }} ->

Is there a better way to pattern match on these error constraints? Or is there an alternative way of approaching the problem?

Perhaps instead of trying to insert a user and acting on the error I should be checking if the user exists first in the user context function and if it does just return that user object and don’t try to insert anything. So to anyone using this new get_or_insert function they can assume that any error indicates that something bad happened instead of having to make sense of the error.

Thanks in advance for the help!

Most Liked

dimitarvp

dimitarvp

I usually do almost the same as you here, with the exception of making a function to search for a specific error in the list of changeset errors:

defp email_taken?({:email, {_, [constraint: :unique, constraint_name: _]}}), do: true
defp email_taken?(_), do: false

def do_stuff() do
  cs = insert_stuff()
  email_is_taken = Enum.any?(cs.errors, &email_taken?/1)
end

The advantage of this approach is that you can also check for other errors, the code looks a bit cleaner and more readable (IMO), and the whole thing is composable and the approach can be reused (you just need to add new checker functions).

stefanchrobot

stefanchrobot

I’m doing it this way:

  def create_changeset(...) do
    ...
    |> unique_constraint(:some_id, message: "already_taken")
  end

  # in the same module
  def already_taken?(%Ecto.Changeset{} = changeset) do
    case changeset.errors[:some_id] do
      {"already_taken", _} -> true
      _ -> false
    end
  end

I would advise against doing that, since you’re defeating the purpose of RDBMS-based validations that Ecto embraces. If you were to choose that approach, you’d still need to keep the code for handling failed constraint to make sure nobody inserts the user between your SELECT and INSERT, so you’re back where you started.

Last Post!

thojanssens1

thojanssens1

Unfortunately that code logic won’t work if you wanna use it in a transaction.

** (Postgrex.Error) ERROR 25P02 (in_failed_sql_transaction) current transaction is aborted, commands ignored until end of transaction block

Looks like once you got a constraint error, the transaction is aborted. Seems like you have to call get, and if nil then create the record. It’s less safe if another process created the record between the get and create call, but I don’t see any easy safer way.

Where Next?

Popular in Questions Top

rms.mrcs
Hi, I need to transform a list of numbers into a map where the keys are the indexes and the values are the original values of the list. ...
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
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
Lily
In templates/appointment/index.html.eex: &lt;%= for appointment &lt;- @appointments do %&gt; &lt;tr&gt; &lt;td&gt;&lt;%= appoi...
New
dblack
I’ve got an issue with an app and I’ve no idea of how to troubleshoot it. I’m hoping someone here might have seen something similar. I p...
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
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

Other popular topics Top

joaquinalcerro
Hi there, I am working with Ecto-Postgresql and I need to call all of the records from a specific table but the table has 40,000 records...
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
hariharasudhan94
Lets say I have map like this fetching from my database %{"_id" =&gt; #BSON.ObjectId&lt;58eb1a7a9ad169198c3dXXXX&gt;, "email" =&gt; ...
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
romenigld
I am trying to run a deploy with docker and I successfully runned with this command: docker build -t romenigld/blog-prod . but when I t...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42716 114
New

We're in Beta

About us Mission Statement