acrolink

acrolink

I am using something like this to filter Ecto.Query results:

  def build_query(query, "order_by", order_by, _conn) when order_by != "" do
    case order_by do
      "desc_inserted_at" -> Ecto.Query.order_by(query, [p], [desc: p.inserted_at])
      "asc_inserted_at" -> Ecto.Query.order_by(query, [p], [asc: p.inserted_at])
      "desc_title" -> Ecto.Query.order_by(query, [p], [desc: p.title])
      "asc_title" -> Ecto.Query.order_by(query, [p], [asc: p.title])
      _ -> query
    end
  end

I will be passing to this method order_by variables with values like name, id, inserted_at, etc preceded by either desc or asc, e.g. desc_inserted_at.

My question, how to make it in less repetitive code, especially when it comes to specifying the direction desc or asc ? Thank you.

Showing Posts 1 to 10

kokolegorille

kokolegorille

It would be easier to have a dir and order…

eg.

[h, t] = order_by |> String.split("_")
dir = h |> String.to_atom
order = t |> Enum.join("_") |> String.to_atom
Ecto.Query.order_by(query, [p], Keyword.put([], dir, Keyword.get(p, order)))

That does not check input validity, but You might see what I mean. It is not tested…

BTW You can use Regex like this to get metadata

iex> Regex.named_captures ~r/(?<dir>asc|desc)_(?<order>.*)/, "desc_inserted_at"
%{"dir" => "desc", "order" => "inserted_at"}

UPDATE: I have should have written [h | t], thanks @blatyo for spotting typo

blatyo

blatyo

Conduit Core Team

I tend to use pattern matching in function heads. Here’s how I might approach it.

@order_by_fields ["inserted_at", "title"]
def build_query(query, "order_by", "", _conn), do: query
def build_query(query, "order_by", "desc_" <> field, _conn) when field in @order_by_fields do
  Ecto.Query.order_by(query, [p], [desc: String.to_atom(field)])
end
def build_query(query, "order_by", "asc_" <> field, _conn) when field in @order_by_fields do
  Ecto.Query.order_by(query, [p], [asc: String.to_atom(field)])
end
blatyo

blatyo

Conduit Core Team

Looks like you meant [h | t] = order_by |> String.split("_")

kokolegorille

kokolegorille

Oh Yes, my bad :slight_smile:

McElaney

McElaney

Just add a step to the transformation that handles the repetitive bits. Easier to read than trying to meta program your way in to it.

  def build_query(query, "order_by", order_by, _conn) when order_by != "" do
    order_by
    |> case do
         "desc_inserted_at" -> [desc: p.inserted_at]
         "asc_inserted_at" -> [asc: p.inserted_at]
         "desc_title" -> [desc: p.title]
         "asc_title" -> [asc: p.title]
         _ ->[]
       end
    |> build_query_order
  end

  defp build_query_order([]), do: query
  defp build_query_order(params), Ecto.Query.order_by(query, [p], params)
idi527

idi527

  def build_query(query, "order_by", order_by, _conn) when order_by != "" do
    order_by = case order_by do
      "desc_" <> field -> [desc: String.to_existing_atom(field)]
      "asc_" <> field -> [asc: String.to_existing_atom(field)]
      _other -> [] # will be ignored by the ecto query builder (the final sql statement won't have an ORDER BY)
    end
    Ecto.Query.order_by(query, order_by)
  end

I wouldn’t pass strings to a “context”, though. I would parse these strings at the boundary and turn them into more manageable data structures like {:order_by, :desc, :inserted_at}, which would be easier to handle inside the “context”.

On the boundary (maybe a controller):

valid_order_bys = [
  {"desc_inserted_at", {:desc, :inserted_at}}, # these can be automatically generated as well
  # etc ...
]

Enum.map(valid_order_bys, fn {valid_order_by_string, valid_order_by_tuple} ->
  defp parse_order_by(unquote(valid_order_by_string)), do: unquote(valid_order_by_tuple)
end)
defp parse_order_by(invalid_order_by_string) do
  # raise or log an error
end

# other parse_search_options clauses
defp parse_search_options([{"order_by", order_by} | rest], acc) do # I suspect it's for a search, but you can name it whatever you want
  parse_search_options(rest, [{:order_by, parse_order_by(order_by)} | acc])
end
# other parse_search_options clauses

Then in the “context”

# other build_query clauses
defp build_query([{:order_by, {direction, field} = opts} | rest], acc_query) do
  build_query(rest, Ecto.Query.order_by(acc_query, [opts]))
end
# other build_query clauses
acrolink

acrolink OP

Thank you all for all possible solutions provided, I simply love Elixir. Much can be done with little code. I suspect other languages like JAVA won’t allow similar shortcuts, power and flexibility.

jordiee

jordiee

I may be wrong here but is it not slightly dangerous to use String.to_atom on what I suspect is user passed strings. This opens up an attack vector for memory issues because atoms are not garbage collected. Again correct me if I am wrong.

idi527

idi527

Yeah, it is dangerous. String.to_existing_atom/1 can be used instead.

kokolegorille

kokolegorille

Yes… You need to validate input in my code to avoid bad surprise :slight_smile:

I like @blatyo pattern matching solution, as it includes sanity check, but the main point is to separate order_by in direction/order, so that You need only one Ecto.Query command.

Where Next? Top

Trending in Questions Top

katta
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
achenet
Hello, I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind. However, when I launch mix phx.server, I get an error...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
Cxx-mlr
I’m working on a small exercise involving update_in/3, and I came up with this solution: data = %{ name: "Periodic Table", category:...
New
ChrisAmelia
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication): toke...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
budgie
A little off-topic, but I feel like people here have a good head on their shoulders. I used to be quite good at making software. Was luc...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews