にむかひて

ectoでjoin重複を避けるための名前付け

2022年4月 トップ > ひとこと > 調査したことの記録
#Elixir #Phoenix #Ecto

Ecto.Queryを返すパイプラインでつなぐ際にjoinが重複するときがある

例えば、

def query(condition \\ []) do
  Hoge.query_all()
  |> Hoge.query_user_name(Keyword.get(condition, :user_name))
  |> Hoge.query_user_code(Keyword.get(condition, :user_code))
end

defmodule Hoge do
  import Ecto.Query

  def query_user_name(query, nil), do: query

  def query_user_name(query, name) do
    from q in query,
      join: u in assoc(q, :user),
      where: u.name == ^name
  end

  def query_user_code(query, nil), do: query

  def query_user_name(query, code) do
    from q in query,
      join: u in assoc(q, :user),
      where: u.code == ^code
  end
end

のような実装をして、user_nameuser_codeの両方があるとjoinが重複する。

  • Railsのスコープチェインライクのイメージで作っていて、ふとSQLをみると重複していた
  • そもそも実装の設計が悪いかもしれない
  • つまり、そのコンテキストに沿ったきちんと専用の関数を用意すべきかもしれない

結論

  • named_binding を使用して、かつ、そのチェックをすると重複回避ができる
  • ただし、名前付けを統一する必要がある
  • つまるところ、結合度があがるので、少なくとも同じモジュール内での使用にとどめるべき
  def query_user_name(query, name) do
    if has_named_binding?(query, :user) do
      from q in query,
        where: as(:user).name == ^name
    else
      from q in query,
        join: u in assoc(q, :user), as: :user,
        where: u.name == ^name
    end
  end

  def query_user_name(query, code) do
    if has_named_binding?(query, :user) do
      from q in query,
        where: as(:user).code == ^code
    else
      from q in query,
        join: u in assoc(q, :user), as: :user,
        where: u.code == ^code
    end
  end

サイト内検索