Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions lib/swiss_schema.ex
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,36 @@ defmodule SwissSchema do
opts :: Keyword.t()
) :: Ecto.Schema.t()

@doc """
Allow complex queries to be made over the data store.

`c:query/2` accepts a function to intercept the query and modify as needed.

## Examples

iex> import Ecto.Query, only: [where: 3]
iex> User.query(fn query -> query |> where([u], u.name == "John Lennon") end)
{:ok, [%User{name: "John Lennon"}]}
"""
@doc group: "SwissSchema API"
@callback query(hook :: (Ecto.Query.t() -> Ecto.Query.t()), opts :: Keyword.t()) ::
{:ok, list(Ecto.Schema.t())} | {:error, %Ecto.QueryError{}}

@doc """
Allow complex queries to be made over the data store.

`c:query!/2` accepts a function to intercept the query and modify as needed.

## Examples

iex> import Ecto.Query, only: [where: 2]
iex> User.query!(fn query -> query |> where([u], u.name == "John Lennon") end)
[%User{name: "John Lennon"}]
"""
@doc group: "SwissSchema API"
@callback query!(hook :: (Ecto.Query.t() -> Ecto.Query.t()), opts :: Keyword.t()) ::
list(Ecto.Schema.t())

@doc """
Returns a lazy enumerable that emits all entries from the data store.

Expand Down Expand Up @@ -443,6 +473,7 @@ defmodule SwissSchema do
end

quote do
import Ecto.Query, only: [from: 1, select: 3]
@behaviour SwissSchema

@_swiss_schema %{
Expand Down Expand Up @@ -611,6 +642,37 @@ defmodule SwissSchema do
insert_or_update!.(changeset, opts)
end

@impl SwissSchema
def query(hook, opts \\ [])
when is_function(hook, 1) do
repo = Keyword.get(opts, :repo, unquote(repo))
all = Function.capture(repo, :all, 2)

query =
from(s in __MODULE__)
|> select([s], s)
|> then(hook)

{:ok, all.(query, opts)}
rescue
error in Ecto.QueryError -> {:error, error}
exception -> reraise exception, __STACKTRACE__
end

@impl SwissSchema
def query!(hook, opts \\ [])
when is_function(hook, 1) do
repo = Keyword.get(opts, :repo, unquote(repo))
all = Function.capture(repo, :all, 2)

query =
from(s in __MODULE__)
|> select([s], s)
|> then(hook)

all.(query, opts)
end

@deprecated "Use Ecto.Repo's stream/2 instead"
@impl SwissSchema
def stream(opts \\ []) do
Expand Down
43 changes: 43 additions & 0 deletions test/swiss_schema_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,10 @@ defmodule SwissSchemaTest do
assert function_exported?(SwissSchemaTest.User, :get_by!, 2)
end

test "define query/1" do
assert function_exported?(SwissSchemaTest.User, :query, 1)
end

test "define stream/0" do
assert function_exported?(SwissSchemaTest.User, :stream, 0)
end
Expand Down Expand Up @@ -574,6 +578,45 @@ defmodule SwissSchemaTest do
end
end

describe "query/2" do
setup do: Enum.each(1..3, fn i -> user_mock(lucky_number: i) |> Repo.insert!() end)

test "accept a function to customize the query" do
assert {:ok, [%User{}, %User{}, %User{}]} = User.query(fn q -> q end)
end

test "captures Ecto.QueryError exceptions and return them as error tuples" do
import Ecto.Query, only: [where: 3]

assert {:error, %Ecto.QueryError{}} =
User.query(fn q -> q |> where([u], u.wrong_field == 123) end)
end

test "accepts a custom Ecto repo thru :repo opt" do
1..3
|> Enum.map(fn _ -> user_mock() |> Map.drop([:__struct__, :__meta__, :id]) end)
|> then(&Repo2.insert_all(User, &1))

assert {:ok, [%User{}, %User{}, %User{}]} = User.query(fn q -> q end, repo: Repo2)
end
end

describe "query!/2" do
setup do: Enum.each(1..3, fn i -> user_mock(lucky_number: i) |> Repo.insert!() end)

test "accept a function to customize the query" do
assert [%User{}, %User{}, %User{}] = User.query!(fn q -> q end)
end

test "accepts a custom Ecto repo thru :repo opt" do
1..3
|> Enum.map(fn _ -> user_mock() |> Map.drop([:__struct__, :__meta__, :id]) end)
|> then(&Repo2.insert_all(User, &1))

assert [%User{}, %User{}, %User{}] = User.query!(fn q -> q end, repo: Repo2)
end
end

describe "update_all/2" do
setup do: Enum.each(1..5, fn i -> user_mock(lucky_number: i) |> Repo.insert!() end)

Expand Down