Cannot use ^user.email() outside of match clauses

Please add the Phoenix tag (as well as two other tags) when posting threads in this section. Thanks!

def sign_in_user(conn, %{"user" => user}) do
        try do # Attempt to retrieve exactly one user from the DB, whose email matches the one provided with the login request
        user = User
        |> where(email: ^user.email)
        |> Repo.one!
            
            cond do
                true ->
                # Successful login, Encode a JWT
                { :ok, jwt, _ } = Guardian.encode_and_sign(user, :token)
                    auth_conn = Guardian.Plug.api_sign_in(conn, user)
                    jwt = Guardian.Plug.current_token(auth_conn)
                    {:ok, claims} = Guardian.Plug.claims(auth_conn)

                    auth_conn
                    |> put_resp_header("authorization", "Bearer #{jwt}")
                    |> json(%{access_token: jwt})   # Return token to the client
                
                false ->   # Unsuccessful login
                    conn
                    |> put_status(401)
                    |> render(StoryWeb.ErrorView, "401.json-api")
            end
        
        rescue
            e ->
                IO.inspect e
                # Print error to the console for debugging
                # Successful registration
                sign_up_user(conn, %{"user" => user})
        end

Pin operator would not work with dot notation. You should either assign the value to the local variable upfront and pin it, or simply destructure user in the function clause.

def sign_in_user(conn, %{"user" => user}) do
email = user.email
user = User |> where(email: ^email) |> Repo.one!
...

or

def sign_in_user(conn, %{"user" => %{email: email}}) do
user = User |> where(email: ^email) |> Repo.one!
...
3 Likes

This should be working too (not tested)

^(user.email)
1 Like

Could it be that you forgot to import/require Ecto.Query?

5 Likes

No, you cannot have remote calls in the bind as foo.bar expands to:

case foo do
  %{bar: val} -> val
  %{} -> raise KeyError
  mod -> apply(mod, :bar, [])
end

Which cannot be bind in any reasonable way.

@kokolegorille this will change nothing as ^ has lower precedence than .:

iex> raw = quote do: ^user.email
{:^, [], [{{:., [], [{:user, [], Elixir}, :email]}, [], []}]}
iex> raw == quote(do: ^(user.email))
true
1 Like

Strangely, it works in my case…

iex(1)> import Ecto.Query                                              
Ecto.Query
iex(2)> bilbo = Entities.get_subject_by_name "bilboket"                
[debug] QUERY OK source="subjects" db=0.4ms queue=0.9ms
SELECT s0."id", s0."name", s0."password_hash", s0."inserted_at", s0."updated_at" FROM "subjects" AS s0 WHERE (s0."name" = $1) ["bilboket"]
%Rbax.Entities.Subject{
  __meta__: #Ecto.Schema.Metadata<:loaded, "subjects">,
  id: 2,
  inserted_at: ~N[2019-11-28 05:37:08],
  name: "bilboket",
  password: nil,
  password_hash: "x",
  permissions: #Ecto.Association.NotLoaded<association :permissions is not loaded>,
  roles: #Ecto.Association.NotLoaded<association :roles is not loaded>,
  updated_at: ~N[2019-11-28 08:45:03]
}
iex(3)> from(Rbax.Entities.Subject, where: [id: ^bilbo.id]) |> Repo.all
[debug] QUERY OK source="subjects" db=1.7ms
SELECT s0."id", s0."name", s0."password_hash", s0."inserted_at", s0."updated_at" FROM "subjects" AS s0 WHERE (s0."id" = $1) [2]
[
  %Rbax.Entities.Subject{
    __meta__: #Ecto.Schema.Metadata<:loaded, "subjects">,
    id: 2,
    inserted_at: ~N[2019-11-28 05:37:08],
    name: "bilboket",
    password: nil,
    password_hash: "x", 
    permissions: #Ecto.Association.NotLoaded<association :permissions is not loaded>,
    roles: #Ecto.Association.NotLoaded<association :roles is not loaded>,
    updated_at: ~N[2019-11-28 08:45:03]
  }
]

This one as well

iex(4)> from(Rbax.Entities.Subject, where: [name: ^bilbo.name]) |> Repo.all
[debug] QUERY OK source="subjects" db=0.7ms
SELECT s0."id", s0."name", s0."password_hash", s0."inserted_at", s0."updated_at" FROM "subjects" AS s0 WHERE (s0."name" = $1) ["bilboket"]
...

In the Ecto query, you are pinning a binding, whereas in the match expression, you are having a get operation (the dot notation) on a variable.

Hence they behave differently.

1 Like

TIL how foo.bar expands, but as far as I know you can use map.key in the query functions.

# Ecto.Query imported
iex(15)> map = %{id: 666}
%{id: 666}
iex(16)> Account |> where(id: ^map.id)
#Ecto.Query<from a0 in Account, where: a0.id == ^666>

But yeah, now that I think again, there would have been a different error message, if where was not imported…

Unfortunately this is not possible unless compiler would become aware of Ecto as a library, and that would be dangerous precedence.

Your advice was helpful.

1 Like

For future readers: the issue with this code is that the pin operator isn’t needed here - the where is expecting a plain keyword list: where(email: user.email)

This is different from the behavior of where inside a from expression, because from manipulates the supplied AST a lot.

1 Like