Skip to content

Commit d1e1dba

Browse files
authored
Add Repo.all_by/3 (#4576)
1 parent 1be980f commit d1e1dba

File tree

5 files changed

+82
-4
lines changed

5 files changed

+82
-4
lines changed

integration_test/cases/repo.exs

+9
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ defmodule Ecto.Integration.RepoTest do
8989
assert catch_error(TestRepo.all("posts", prefix: "oops"))
9090
end
9191

92+
test "all_by" do
93+
post1 = TestRepo.insert!(%Post{title: "a"})
94+
post2 = TestRepo.insert!(%Post{title: "a"})
95+
post3 = TestRepo.insert!(%Post{title: "b"})
96+
97+
assert TestRepo.all_by(Post, title: "a") |> Enum.sort() == [post1, post2]
98+
assert TestRepo.all_by(Post, title: "b") |> Enum.sort() == [post3]
99+
end
100+
92101
test "insert, update and delete" do
93102
post = %Post{title: "insert, update, delete", visits: 1}
94103
meta = post.__meta__

lib/ecto/query/api.ex

+1-1
Original file line numberDiff line numberDiff line change
@@ -504,7 +504,7 @@ defmodule Ecto.Query.API do
504504
The example above will inject the value of `limit` directly
505505
into the query instead of treating it as a query parameter. It will
506506
generate a query such as `SELECT p0.title FROM "posts" AS p0 LIMIT 1`
507-
as opposed to `SELECT p0.title FROM "posts" AS p0` LIMIT $1`.
507+
as opposed to `SELECT p0.title FROM "posts" AS p0 LIMIT $1`.
508508
509509
Note that each different value of `limit` will emit a different query,
510510
which will be independently prepared and cached.

lib/ecto/repo.ex

+55-1
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,17 @@ defmodule Ecto.Repo do
440440
)
441441
end
442442

443+
def all_by(queryable, clauses, opts \\ []) do
444+
repo = get_dynamic_repo()
445+
446+
Ecto.Repo.Queryable.all_by(
447+
repo,
448+
queryable,
449+
clauses,
450+
Ecto.Repo.Supervisor.tuplet(repo, prepare_opts(:all, opts))
451+
)
452+
end
453+
443454
def stream(queryable, opts \\ []) do
444455
repo = get_dynamic_repo()
445456

@@ -824,6 +835,8 @@ defmodule Ecto.Repo do
824835
Returns `nil` if no result was found. If the struct in the queryable
825836
has no or more than one primary key, it will raise an argument error.
826837
838+
See also `c:get!/3`, `c:one/2`, and `c:all_by/3`.
839+
827840
## Options
828841
829842
* `:prefix` - The prefix to run the query on (such as the schema path
@@ -878,6 +891,8 @@ defmodule Ecto.Repo do
878891
879892
Returns `nil` if no result was found. Raises if more than one entry.
880893
894+
See also `c:get/3`, `c:one/2`, and `c:all_by/3`.
895+
881896
## Options
882897
883898
* `:prefix` - The prefix to run the query on (such as the schema path
@@ -1091,6 +1106,8 @@ defmodule Ecto.Repo do
10911106
10921107
Returns `nil` if no result was found. Raises if more than one entry.
10931108
1109+
See also `c:one!/2`, `c:get/3`, and `c:all/2`.
1110+
10941111
## Options
10951112
10961113
* `:prefix` - The prefix to run the query on (such as the schema path
@@ -1280,6 +1297,8 @@ defmodule Ecto.Repo do
12801297
12811298
May raise `Ecto.QueryError` if query validation fails.
12821299
1300+
See also `c:all_by/3`, `c:one/2`, and `c:get/3`.
1301+
12831302
## Options
12841303
12851304
* `:prefix` - The prefix to run the query on (such as the schema path
@@ -1296,12 +1315,47 @@ defmodule Ecto.Repo do
12961315
12971316
# Fetch all post titles
12981317
query = from p in Post,
1299-
select: p.title
1318+
select: p.title
13001319
MyRepo.all(query)
13011320
"""
13021321
@doc group: "Query API"
13031322
@callback all(queryable :: Ecto.Queryable.t(), opts :: Keyword.t()) :: [Ecto.Schema.t() | term]
13041323

1324+
@doc """
1325+
Fetches all entries from the data store matching the given query and conditions.
1326+
1327+
May raise `Ecto.QueryError` if query validation fails.
1328+
1329+
This function is a shortcut for `c:all/2` when adjusting the given query with simple conditions.
1330+
1331+
See also `c:all/2` and `c:get_by/3`.
1332+
1333+
## Options
1334+
1335+
* `:prefix` - The prefix to run the query on (such as the schema path
1336+
in Postgres or the database in MySQL). This will be applied to all `from`
1337+
and `join`s in the query that did not have a prefix previously given
1338+
either via the `:prefix` option on `join`/`from` or via `@schema_prefix`
1339+
in the schema. For more information see the ["Query Prefix"](`m:Ecto.Query#module-query-prefix`) section of the
1340+
`Ecto.Query` documentation.
1341+
1342+
See the ["Shared options"](#module-shared-options) section at the module
1343+
documentation for more options.
1344+
1345+
## Example
1346+
1347+
MyRepo.all_by(Post, author_id: 1)
1348+
1349+
query = from p in Post
1350+
MyRepo.all_by(query, author_id: 1)
1351+
"""
1352+
@doc group: "Query API"
1353+
@callback all_by(
1354+
queryable :: Ecto.Queryable.t(),
1355+
clauses :: Keyword.t() | map,
1356+
opts :: Keyword.t()
1357+
) :: [Ecto.Schema.t() | term]
1358+
13051359
@doc """
13061360
Returns a lazy enumerable that emits all entries from the data store
13071361
matching the given query.

lib/ecto/repo/queryable.ex

+9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ defmodule Ecto.Repo.Queryable do
1919
execute(:all, name, query, tuplet) |> elem(1)
2020
end
2121

22+
def all_by(name, queryable, clauses, tuplet) do
23+
query =
24+
queryable
25+
|> Ecto.Query.where([], ^Enum.to_list(clauses))
26+
|> Ecto.Query.Planner.ensure_select(true)
27+
28+
execute(:all, name, query, tuplet) |> elem(1)
29+
end
30+
2231
def stream(_name, queryable, {adapter_meta, opts}) do
2332
%{adapter: adapter, cache: cache, repo: repo} = adapter_meta
2433

test/ecto/repo_test.exs

+8-2
Original file line numberDiff line numberDiff line change
@@ -1518,7 +1518,7 @@ defmodule Ecto.RepoTest do
15181518
end
15191519
end
15201520

1521-
test "get, get_by, one and all sets schema prefix" do
1521+
test "get, get_by, one, all and all_by sets schema prefix" do
15221522
assert schema = TestRepo.get(MySchema, 123, prefix: "public")
15231523
assert schema.__meta__.prefix == "public"
15241524

@@ -1531,6 +1531,9 @@ defmodule Ecto.RepoTest do
15311531
assert [schema] = TestRepo.all(MySchema, prefix: "public")
15321532
assert schema.__meta__.prefix == "public"
15331533

1534+
assert [schema] = TestRepo.all_by(MySchema, [id: 123], prefix: "public")
1535+
assert schema.__meta__.prefix == "public"
1536+
15341537
assert schema = TestRepo.get(MySchema, 123, prefix: %{key: :public})
15351538
assert schema.__meta__.prefix == %{key: :public}
15361539

@@ -1544,7 +1547,7 @@ defmodule Ecto.RepoTest do
15441547
assert schema.__meta__.prefix == %{key: :public}
15451548
end
15461549

1547-
test "get, get_by, one and all ignores prefix if schema_prefix set" do
1550+
test "get, get_by, one, all, and all_by ignores prefix if schema_prefix set" do
15481551
assert schema = TestRepo.get(MySchemaWithPrefix, 123, prefix: "public")
15491552
assert schema.__meta__.prefix == "private"
15501553

@@ -1568,6 +1571,9 @@ defmodule Ecto.RepoTest do
15681571

15691572
assert [schema] = TestRepo.all(MySchemaWithNonStringPrefix, prefix: %{key: :public})
15701573
assert schema.__meta__.prefix == %{key: :private}
1574+
1575+
assert [schema] = TestRepo.all_by(MySchemaWithNonStringPrefix, [id: 123], prefix: %{key: :public})
1576+
assert schema.__meta__.prefix == %{key: :private}
15711577
end
15721578

15731579
describe "changeset prepare" do

0 commit comments

Comments
 (0)