User |> where(id: ^user_id) を実行しようとすると、 Ecto.Query.where で内部で以下のような実装になっていました。
defmacro where(query, binding \\ [], expr) do
Filter.build(:where, query, binding, expr, __CALLER__)
endexpr に [id: ^user_id] が入っています。
Ecto.Query.Builder.Filter.build/5
def build(kind, query, binding, expr, env) do
binding = Builder.escape_binding(binding)
{expr, params} = escape(kind, expr, binding, env)
...
exprがescapeされているようです。
Ecto.Query.Builder.Filter.escape/4
def escape(kind, expr, vars, env) when is_list(expr) do
{parts, params} =
Enum.map_reduce(expr, %{}, fn
{field, nil}, _acc ->
Builder.error! "nil given for #{inspect field}. Comparison with nil is forbidden as it is unsafe. " <>
"Instead write a query with is_nil/1, for example: is_nil(s.#{field})"
{field, value}, acc when is_atom(field) ->
{value, params} = Builder.escape(value, {0, field}, acc, vars, env)
...field に id、 value に ^user_id が入ります。
# param interpolation
def escape({:^, _, [arg]}, type, params, _vars, _env) do
index = Map.size(params)
params = Map.put(params, index, {arg, type})
expr = {:{}, [], [:^, [], [index]]}
{expr, params}
end^user_id を {:^, _, [arg]}, として受け取っています。
この表記は内部表現と呼ばれるもので、コンソール上で以下のように確認できました。
iex(19)> quote do: ^user_id
{:^, [], [{:user_id, [], Elixir}]}
iex(20)> quote do: ^(1 + 2)
{:^, [], [{:+, [context: Elixir, import: Kernel], [1, 2]}]}これにより、^ がピン演算子として評価されていそうです。