Organizando queries com query composition no elixir
source link: https://dev.to/viniciussilveira/organizando-queries-com-query-composition-no-elixir-301p
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Organizando queries com query composition no elixir
Jul 29
・3 min read
O Elixir fornece um módulo chamado Ecto.Query
para que possa ser possível efetuar consultas e alterações nos dados de uma aplicação.
Dessa forma podemos escrever queries sem a necessidade da escrita de SQLs, dando maior facilidade e flexibilidade na escrita de consultas.
Porém em projetos grandes, podemos acabar tendo queries
mais complexas e maiores, o que pode tornar a utilização do Ecto.Query
um problema se não tiver um certo cuidado.
Para exemplificar vamos pegar um exemplo onde fazemos algumas consultas de pessoas usuárias de um determinado sistema:
defmodule MyApp.Users do
import Ecto.Query, only: [from: 2, where: 2]
alias MyApp.Users.User
def get_user_by_email(email) do
Repo.get_by(User, email: email)
end
def get_users_by_city_name(city_name) do
from(user in User, join: city in City, on: city.id == user.city_id, where: city == ^city_name)
|> Repo.all()
end
def get_user_by_email_and_city(email, city_name) do
from(user in User,
join: city in City,
on: city.id == user.city_id,
where: city.name == ^city_name,
where: user.email == ^email
)
|> Repo.one()
end
end
Perceba que temos aqui 3 buscas, uma por email, uma por city, e uma terceira que busca uma pessoa por email e pela city registrada. Se analisarmos com calma, podemos ver que a terceira consulta é na verdade uma junção das 2 primeiras. Então podemos utilizar Query composition para diminuir a duplicação de código:
defmodule MyApp.Users do
import Ecto.Query, only: [from: 2]
alias MyApp.Repo
def get_user_by_email(email) do
email
|> user_by_email()
|> Repo.one()
end
def get_users_by_city_name(city_name) do
city_name
|> users_by_city_name()
|> Repo.all()
end
def get_user_by_email_and_city(email, city) do
email
|> user_by_email()
|> users_by_city_name(city)
|> Repo.one()
end
defp user_by_email(query \\ base(), email) do
from(user in query,
where: user.email == ^email
)
end
defp users_by_city_name(query \\ base(), city_name) do
from(user in query,
join: city in City,
on: city.id == user.city_id,
where: city.name == ^city_name
)
end
defp base, do: __MODULE__
end
Mas agora vamos supor que esse sistema vá crescendo e o contexto fique cada vez maior, com mais funções e mais queries. Para resolver esse problema podemos criar um módulo específico para as queries desse contexto:
# my_app/lib/my_app/users/user_queries.ex
defmodule MyApp.Users.UserQueries do
import Ecto.Query, only: [from: 2]
alias MyApp.Users.User
def user_by_email(query \\ base(), email) do
from(user in query,
where: user.email == ^email
)
end
def users_by_city_name(query \\ base(), city_name) do
from(user in query,
join: city in City,
on: city.id == user.city_id,
where: city.name == ^city_name
)
end
defp base, do: User
end
# my_app/lib/my_app/users.ex
defmodule MyApp.Users do
alias MyApp.Repo
alias MyApp.Users.UserQueries
def get_user_by_email(email) do
email
|> UserQueries.user_by_email()
|> Repo.one()
end
def get_users_by_city_name(city) do
city
|> UserQueries.users_by_city_name()
|> Repo.all()
end
def get_user_by_email_and_city(email, city_name) do
email
|> UserQueries.user_by_email()
|> UserQueries.users_by_city_name(city_name)
|> Repo.one()
end
end
A função Repo.one() retorna o registro, caso encontrado, ou nil. Para não deixarmos o tratamento desse erro para camadas superiores do projeto, podemos fazer uma última refatoração no contexto de Users , tratando esses erros e deixar os retornos mais nítidos:
defmodule MyApp.Users do
alias MyApp.Users.{User, UserQueries}
def get_user_by_email(email) do
case do_get_user_by_email(email) do
%User{} = user -> {:ok, user}
nil -> {:error, {:not_found, "User not found"}}
end
end
def get_users_by_city_name(city) do
city
|> UserQueries.users_by_city()
|> Repo.all()
end
def get_user_by_email_and_city_name(email, city_name) do
case do_get_user_by_email_and_city_name(email, city_name) do
%User{} = user -> {:ok, user}
nil -> {:error, {:not_found, "User not found"}}
end
end
defp do_get_user_by_email(email) do
email
|> UserQueries.user_by_email()
|> Repo.one()
end
defp do_get_user_by_email_and_city_name(email, city_name) do
email
|> UserQueries.user_by_email()
|> UserQueries.users_by_city_name(city_name)
|> Repo.one()
end
end
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK