【Ecto】prepare_query/3の使い方。 | Candelabrum
Candelabrum

【Ecto】prepare_query/3の使い方。

2023.10.29

Ecto.Repo.prepare_query/3とは

あなたはEcto.Repoの prepare_query/3 という関数をご存じでしょうか?

Ecto.Repo — Ecto v3.10.3

Ecto.Repoのコールバック関数であり、 その名の通りEctoで実行される全てのクエリに 前もって任意の変更を加えることができるものです。

うまく使えば非常に便利な関数なのですが、 現在時点では意外にも日本語の解説記事がほとんど存在しないこともあり、 本日はこちらで公式のサンプルコードに基づいた簡単な解説を試みてみたいと思います。

公式のサンプルコードを読み解く

@impl true
def prepare_query(_operation, query, opts) do
  if opts[:admin] do
    {query, opts}
  else
    query = from(x in query, where: is_nil(x.deleted_at))
    {query, opts}
  end
end
Repo.all(query)              # only non-deleted records are returned
Repo.all(query, admin: true) # all records are returned

Rcto.Repoの関数は Repo.allに限らず全てオプションを(opts)を受け付ける仕様となっています。

こちらのサンプルではprepare_query/3とそのオプションを利用し、 管理者でなければ(opts[:admin] != trueなら) クエリの結果を限定するwhere文を付与するという処理を実装しています。

このように、prepare_query/3がオプションと組み合わせで 全てのクエリに対する一括した条件分岐を この上なく簡潔に記述することができるのです。

◆ 外部キーを用いたマルチテナント設計のサンプルコード


Multi tenancy with foreign keys — Ecto v3.10.3
defmodule MyApp.Repo do
  use Ecto.Repo, otp_app: :my_app

  require Ecto.Query

  @impl true
  def prepare_query(_operation, query, opts) do
    cond do
      opts[:skip_org_id] || opts[:schema_migration] ->
        {query, opts}

      org_id = opts[:org_id] ->
        {Ecto.Query.where(query, org_id: ^org_id), opts}

      true ->
        raise "expected org_id or skip_org_id to be set"
    end
  end
end

こちらは外部キーを用いた マルチテナント設計に対応するサンプルコードです。

org_idをオプション経由で渡すことによって、 org_idが合致するレコードのみを取得する実装を実現しています。

終わりに

Ecto.Repo.prepare_query/3は ストリーム関数を含む全てのクエリAPIで呼び出されるため そこは取り扱いに注意が必要かもしれません。

ともあれ、使い所を見極めて上手に活用していきたいところですね。