6

Organizando queries com query composition no elixir

 3 years ago
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.
neoserver,ios ssh client

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK