fuelen

fuelen

Cond in Ecto query DSL

Hi all!

Just want to share a small code snippet which allows writing CASE expressions using macro which is similar to cond.
Here is an example of usage:

import Ecto.Extension.Query.API
import Ecto.Query

from users in "users",
  select: %{
    email: users.email,
    role_label:
      cond_ do
        users.role == "director" -> "DIRECTOR!"
        users.inserted_at < ago(6, "month") -> "OLD!"
        true -> type(users.role, :string)
      end
  }

generated SQL:

SELECT u0."email", CASE WHEN u0."role" = 'director' THEN 'DIRECTOR!' WHEN u0."inserted_at" < $1::timestamp + (-6::decimal::numeric * interval '1 month') THEN 'OLD!' WHEN TRUE THEN u0."role"::varchar END FROM "users" AS u0

and implementation:

defmodule Ecto.Extension.Query.API do
  defmacro cond_(do: block) do
    bindings =
      block
      |> Enum.reduce([], fn
        {:->, _, [[clause], branch]}, acc ->
          [branch, clause | acc]
      end)
      |> Enum.reverse()

    bindings_number = length(bindings)

    sql =
      IO.iodata_to_binary([
        "CASE",
        List.duplicate(" WHEN ? THEN ?", div(bindings_number, 2)),
        " END"
      ])

    quote do
      fragment(unquote(sql), unquote_splicing(bindings))
    end
  end
end

I hope someone will find this useful :slight_smile:

Most Liked

fuelen

fuelen

Well, this is my first attempt to implement @hauleth idea. Some kind of composability is still possible

defmodule CustomEctoFrom do
  defmacro __using__(map) do
    quote do
      require Ecto.Query

      defmacro from(expr, kw) do
        kw = CustomEctoFrom.traverse(kw, unquote(map))

        quote do
          Ecto.Query.from(unquote(expr), unquote(kw))
        end
      end
    end
  end

  def traverse({function, args}, map) when is_atom(function) do
    {function, traverse(args, map)}
  end

  def traverse({function, meta, args}, map) do
    function =
      case Map.fetch(map, function) do
        {:ok, aliased_to} -> aliased_to
        :error -> function
      end

    {traverse(function, map), meta, traverse(args, map)}
  end

  def traverse(kw, map) when is_list(kw) do
    for elem <- kw, do: traverse(elem, map)
  end

  def traverse(term, _map), do: term
end

defmodule OtherModuleWithExtensions do
  defmacro concat(a, b) do
    quote do
      fragment("? || ?", unquote(a), unquote(b))
    end
  end
end

defmodule Test do
  import Ecto.Extension.Query.API
  import OtherModuleWithExtensions
  use CustomEctoFrom, %{cond: :cond_, <>: :concat}

  def test do
    from users in "users",
      select: %{
        full_name: users.first_name <> " " <> users.last_name,
        email: users.email,
        role_label:
          cond do
            users.role == "director" -> "DIRECTOR!"
            users.inserted_at < ago(6, "month") -> "OLD!"
            true -> type(users.role, :string)
          end,
        id: users.id
      }
  end
end

Where Next?

Popular in Guides/Tuts Top

Morzaram
Hey guys I’ve made a guide on how to connect Quill to Phoenix. For the sake of formatting it might be easier to view it on my Notion Doc...
New
njwest
Greetings: I just wrote a step-by-step guide on building a Phoenix 1.3 JWT Auth API with Guardian JWTs and Comeonin password hashing. I ...
New
tomciopp
TLS 1.3 has been out for a little over a year now, but it has been unavailable in Phoenix due to erlang’s handling of ssl. With the most ...
New
zachallaun
Hey friends, wanted to share a tiny shell script I’ve been using to start Livebook with easy access to either a running production server...
New
marcelo
I wrote a small article on how to use current_user with coherence on Phoenix. There are already a couple of blogposts about it but I thin...
New
njwest
In the process of developing a Phx-based multiplayer experience, I found myself with so many browser tabs open with Elixir gaming resourc...
New
caspg
Hi everyone, I recently implemented a real-time search feature in a Phoenix application using LiveView and Tailwind, and I wanted to sha...
New
bluegene
Hi guys, I’ve been on a personal journey to learn Elixir for the past two years. During this journey I’ve been using the spaced repetiti...
New
f0rest8
Hi everyone, Just wanted to say that the new Self-referencing many to many guide is now up on the official Hex docs (at least I just not...
New
anuragg
We just published a guide to automatic clustering in Elixir 1.9, with Mix releases and libcluster. The cluster automatically discovers n...
New

Other popular topics Top

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 th...
New
albydarned
Hello all! I am typing this post from my new MacBook Pro with the M1 chip. I’m loving it so far, and will probably use it as my daily dr...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
grych
Hi folks, Few months ago I have announced the proof-of-concept of the library to manipulate the browsers DOM objects directly from Elixi...
639 49522 488
New
minhajuddin
I have seen a lot of code which picks the first element from a list using Enum.at(0) instead of List.first. Is there a reason why people ...
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
ovidiubadita
Hey all, I discovered Elixir and I love it. I always wanted to learn a functional programming and I intended to go for Haskell, but afte...
New
chrisalley
ExUnit now has describe blocks which is a welcome addition coming from RSpec. In the docs, it states that nested hierarchies of describe ...
New
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
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

We're in Beta

About us Mission Statement