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

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kszambelanczyk
Hello! Could someone please give me a help/sample code, how to delete a file from s3 using waffle/waffle_ecto from Phoenix app. I creat...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New
ryanwinchester
apply_graft/2 doesn’t rewrite an add_many sub-workflow’s deps on an add step. Grafted jobs cancel with “upstream job was deleted” Version...
New

Other Trending Topics Top

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
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge &amp; Solve. They are GUI (Emerge) and State management (S...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews