📝

Elixir/Ecto: Named Bindingを使ってクエリを組み立てる

2021/08/29に公開

Mediumを見ているとPhoenix/Elixir: Chain Composable Queries with Ecto Named Bindings.という記事が流れてきました。

https://brooklinmyers.medium.com/phoenix-elixir-chain-composable-queries-with-ecto-named-bindings-7a3ec5ba93

この記事に感化されて、日本語でも解説しておきたいと思い、EctoのNamed Bindingsの使い方について解説してみます。

https://hexdocs.pm/ecto/Ecto.Query.html#module-named-bindings

お題

posts has many comments という構造で、comments.bodyに含まれる文字列でpostsを検索するとします。

Named Bindingsを使わない場合(Positional Bindings)

Named Bindingsを使わない場合、次のように書けます。

  def search_posts_by_comment_word(comment_word) do
    Post
    |> join(:left, [post], _ in assoc(post, :comments))
    |> where([_post, comment], like(comment.body, ^"%#{comment_word}%")) # 元のPostが1番目, commentsテーブルのjoinが2番目
    |> preload([_post, comment], comments: comment)
    |> Sample.Repo.all()
  end

この場合、joinの順序に依存して where, select, preload などを書いていく必要があります。

ドキュメント上では Positional bindings という用語で記載されています。

https://hexdocs.pm/ecto/Ecto.Query.html#module-positional-bindings

この書き方だと実装の順序に依存し、何番目にjoinしたかというのを把握した上で後続の処理を記述する必要があります。実装が変更して、joinが増減した場合にツラいです。

Named Bindingsを使う場合

as: を利用してそれぞれのjoinに名前をつけることができ、後続の処理でbindingsのリストから参照できます。

  def search_posts_by_comment_word(comment_word) do
    Post
    |> join(:left, [post], _ in assoc(post, :comments), as: :comment)
    |> where([comment: comment], like(comment.body, ^"%#{comment_word}%")) # as句で指定したatomで参照できる
    |> preload([comment: comment], comments: comment)
    |> Sample.Repo.all()
  end

Named Bindingsであれば、どう命名したかは把握しておく必要がありますが、順序に依存しなくなるため、whereやselect部分だけを関数に切り出しやすくなります。

  def filter_by_word(queryable, word) do
    queryable
    |> where([comment: comment], like(comment.body, ^"%#{word}%"))
  end
end

すでにある名前でbindされているかどうか判定するための has_named_binding?/2 という関数も用意されているため、bindされている場合のみ処理を実行するなど、分岐も可能です。

https://hexdocs.pm/ecto/Ecto.Query.html#has_named_binding?/2

こちらの書き方の方が順序に依存しない分、joinの追加、削除時に楽です。

まとめ

簡単ですが、Named Bindingsを利用してクエリを組み立てる方法について解説しました。順序を意識せずにpipeでつらつらと書ける方が楽ですので、joinが増えた場合はNamed Bindingsを使った書き方の方がベターそうです。

日本語だと

  • Named Bindings: 名前付きバインディング
  • Positional Bindings: 位置バインディング

みたいな訳になるのでしょうかね。この辺りは英語のまま解釈した方が分かりやすいかもしれません。

Discussion