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

niku
I have published an elixir project with using Travis CI. I would like to share some tips &amp; thoughts that I was getting through this ...
New
anuragg
Hi everyone! I’m the founder of Render, a new cloud provider with native support for Elixir. When we launched Elixir support the most po...
New
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
berts-4865
Here is a quick guide to uploading a file from the browser to DO spaces. It is crude, but will hopefully save sometime time and frustrat...
New
mhanberg
Hi! I recently finished adding authentication to my Phoenix API, so I wanted to share what I learned. I haven't created authentication ...
New
WolfDan
So my main OS is Windows, I do must of my work with it, Elixir and vscode elixirls works just fine when you’re working only with elixir, ...
New
stryrckt
I’m excited about the new LiveComponents feature in LiveView, but I haven’t seen much written on it, so I decided to write a couple of ar...
New
alejandroErik
POST IN CONSTRUCTION Process for compile erlang otp 20 with odbc-unix for Oracle connections on Solaris 11.3 for 64 bits: Introductio...
New
niku
I write an article Parameterized testing with ExUnit.The key concept is using ExUnit.Case.register_test/4 such as ExUnit.start() defmod...
New

Other popular topics Top

josevalim
Hi everyone, One of the features added to Elixir early on to help integration with Erlang code was the idea of overridable function defi...
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
jononomo
I am trying to figure out how Mix knows whether the environment is test, dev, or prod -- where is this set? Thanks.
New
jerry
Good day to you all. I have been struggling to get a query involving like and ilike to work. Can anyone assist me on this, please? pro...
New
quazar
How to set Jason to encode all fields in ecto schema, I don’t care about security and implementing only is taking long list of attributes...
New
dokuzbir
Hello, I am trying to convert my lists to string without losing brackets.For start i have 3 map. They look like these buyer = %{ id: ...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
mgjohns61585
Could someone help me? I'm making my first elixir program, number guessing game. I can't figure out how to convert the user's guess from ...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
AstonJ
We’ve put together this wiki for Phoenix LiveView - please feel free to add any info you feel is worth including. What is Phoenix LiveV...
New

We're in Beta

About us Mission Statement