Empty Queries

433 views
Skip to first unread message

Dustin Farris

unread,
Jul 18, 2016, 1:25:25 AM7/18/16
to elixir-ecto
Is it possible to construct an "empty" query in Ecto?

In a private function, I want to return a queryable, but there is a case in the function where there should not be anything there (not authenticated).

The best I could come up with is: User |> limit(0)

Can I do better?

Michał Muskała

unread,
Jul 18, 2016, 2:38:41 AM7/18/16
to elixi...@googlegroups.com
User |> where(false) should work as well.

Michał.

--
You received this message because you are subscribed to the Google Groups "elixir-ecto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-ecto...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/elixir-ecto/c6e98df6-874e-4c30-b922-fd302856eaa3%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

signature.asc

Dustin Farris

unread,
Jul 18, 2016, 2:40:08 AM7/18/16
to elixi...@googlegroups.com
Ok, that looks good.  Thanks.
-- 
Dustin Farris



José Valim

unread,
Jul 18, 2016, 3:28:10 AM7/18/16
to elixi...@googlegroups.com
Ecto.Queryable.to_query(User)



José Valim
Skype: jv.ptec
Founder and Director of R&D

Dustin Farris

unread,
Jul 18, 2016, 3:50:06 AM7/18/16
to elixi...@googlegroups.com
Jose, that doesn't work.  I need a query that returns nothing.

Basically the use case is asking a function for a query for accessible objects, and a certain case there are no objects so it would need to return a query for nothing.

Dustin


For more options, visit https://groups.google.com/d/optout.

-- 
Dustin Farris



Ben Wilson

unread,
Jul 18, 2016, 6:53:33 AM7/18/16
to elixir-ecto
Wouldn't it be preferable to simply not do any query at all in that case?

Dustin Farris

unread,
Jul 18, 2016, 7:37:31 AM7/18/16
to elixi...@googlegroups.com
I suppose.  The idea is that the caller is naive to it all and is going to execute Repo.get on the query it gets back.  So to avoid running the query, I would have to raise NoResultsError myself.  I'm not sure how I feel about preempting Ecto to save a basically no op hit on the db.

What do you think the better design principle would be?

Dustin


For more options, visit https://groups.google.com/d/optout.

-- 
Dustin Farris



Theron Boerner

unread,
Jul 18, 2016, 6:07:36 PM7/18/16
to elixi...@googlegroups.com
Use with/1.

with {:ok, query} <- your_function, do: Repo.all(query)

Dustin Farris

unread,
Jul 18, 2016, 9:54:57 PM7/18/16
to elixi...@googlegroups.com
Ah, that's a lot cleaner!  Thanks!



For more options, visit https://groups.google.com/d/optout.

-- 
Dustin Farris



Dustin Farris

unread,
Jul 18, 2016, 11:53:36 PM7/18/16
to elixi...@googlegroups.com
I'm curious, is there a pro/con of `with` vs. `case`?

e.g.

with {:ok, query} <- all_users(conn) do
  users = query |> Repo.get(id)
  render(conn, "index.json", data: users)
else
  {:error, _} ->
    render(conn, ErrorView, "404.json")
end

vs.

case all_users(conn) do
  {:ok, query} ->
    users = query |> Repo.get(id)
    render(conn, "index.json", data: users)
  {:error, _} ->
    render(conn, ErrorView, "404.json")
end

Dustin


On Jul 19, 2016, at 6:07 AM, Theron Boerner <hunter...@gmail.com> wrote:


For more options, visit https://groups.google.com/d/optout.

-- 
Dustin Farris



Theron Boerner

unread,
Jul 19, 2016, 11:24:00 AM7/19/16
to elixi...@googlegroups.com
Yup. If a statement doesn't match, the with statement will return the value. It's similar to Haskell's do notation except with matching pattern matching instead of bind.

iex(1)> with {:ok, a} <- {:ok, 123},
...(1)> {:ok, b} <- {:error, 456} do
...(1)> a + b
...(1)> end
{:error, 456}
iex(2)>

...Paul

unread,
Jul 19, 2016, 11:55:20 AM7/19/16
to elixir-ecto
with usually takes multiple calls and runs them in sequence until one doesn't match.  It might be more clear to rewrite the example thusly:

with {:ok, query} <- all_users(conn),
     users <- query |> Repo.get(id),
     do:  render(conn, "index.json", data:users)
else
  {:error, _} -> render(conn, ErrorView, "404.json")
end

In contrast, case takes the parameter (all_users(conn)) and then compares the output to the different patterns below, one by one, until it finds a match.  It's only checking the output of a single statement.  with is a really good way to effectively "chain" functions when the outputs are not directly usable by the next in the chain.  Let's say, for example, that your Repo call is a function that ALSO returns a tuple of {:ok, users}, the above could look like this:

with {:ok, query} <- all_users(conn),
     {:ok, users} <- get_users(query)
     do:  render(conn, "index.json", data:users)
else
  {:error, _} -> render(conn, ErrorView, "404.json")
end

If get_users() returns anything but {:ok, <something>}, with will abort and then call the else clause.  If you needed to do this with case you'd have to have multiple cases, and then your error handling clause would have to be duplicated:

case all_users(conn) do
  {:ok, query} ->
    case get_users(query) do
      {:ok, users} -> render(conn, "index.json", data:users)
      {:error, _} -> render(conn, ErrorView, "404.json")
    end
  {:error, _} ->
    render(conn, ErrorView, "404.json")
end

...Paul

...Paul

unread,
Jul 19, 2016, 12:01:44 PM7/19/16
to elixir-ecto
On Tuesday, July 19, 2016 at 8:55:20 AM UTC-7, ...Paul wrote:
If get_users() returns anything but {:ok, <something>}, with will abort and then call the else clause.  If you needed to do this with case you'd have to have multiple cases, and then your error handling clause would have to be duplicated:

case all_users(conn) do
  {:ok, query} ->
    case get_users(query) do
      {:ok, users} -> render(conn, "index.json", data:users)
      {:error, _} -> render(conn, ErrorView, "404.json")
    end
  {:error, _} ->
    render(conn, ErrorView, "404.json")
end

Okay, it doesn't HAVE to be duplicated, but it gets messy.  The above could be written this way:

 case all_users(conn) do
  {:ok, query} -> get_users(query)
  x -> x
end |> case do
  {:ok, users} -> render(conn, "index.json", data:users)
  {:error, _} -> render(conn, ErrorView, "404.json")
end

The first case will return the result of all_users if it's not a success, otherwise returns the result of get_users.  The second case then handles the output of the first, which should only be {:ok, users} if the all_users AND the get_users succeeded, and error otherwise.  In fact, before with actually took an else clause, this was the pattern.  See the commentary on https://github.com/elixir-lang/elixir/issues/4085

...Paul


Dustin Farris

unread,
Jul 19, 2016, 12:09:36 PM7/19/16
to elixi...@googlegroups.com
Thanks, Paul.  Those are great examples.  I'm going to try them out and see what works best.

Sent from my iPhone
--
You received this message because you are subscribed to the Google Groups "elixir-ecto" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elixir-ecto...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages