KA01
KA01

Reputation: 4251

KeyError in Plug connection or session

So I was getting the error below because I was trying to access the :id key. No surprise here. However, my actual question is why is the user placed inside the :session key? Is this Plug specific? I don't remember setting user to the key :session as you can see in the view below.

Error:

** (KeyError) key :id not found in: 
%{
  session: %ExampleApp.User{
    __meta__: #Ecto.Schema.Metadata<:loaded, "users">,
    email: "[email protected]", 
    first_name: "keith", 
    id: 356, 
    inserted_at: #Ecto.DateTime<2016-08-25 00:56:51>, 
    last_name: "a.", 
    updated_at: #Ecto.DateTime<2016-08-25 00:56:51>, 
    username: "keith"
  }, 
  view_module: ExampleApp.SessionView, 
  view_template: "show.json"
}

View that caused error:

defmodule ExampleApp.SessionView do
use ExampleApp.Web, :view

def render("show.json", %{jwt: jwt, user: user, exp: exp}) do
  %{
    jwt: jwt,
    exp: exp,
    user: render_one(user, __MODULE__, "show.json")
  }
end

def render("show.json", user) do
  %{
    id: user.id, #<------------------------------------------ HERE
    username: user.username,
    last_name: user.last_name,
    first_name: user.first_name
  }
end

I had to do the following to access the user's information:

...

def render("show.json", user) do
  %{
    id: user.session.id, #<------------------------------------------ HERE
    username: user.session.username,
    last_name: user.session.last_name,
    first_name: user.session.first_name
  }
end

Here's the controller if it helps:

defmodule ExampleApp.RegistrationController do
use ExampleApp.Web, :controller

alias ExampleApp.{Repo, User, SessionView}

def create(conn, %{"user" => user_params}) do
  changeset = User.new_changeset(%User{}, user_params)
  case Repo.insert(changeset) do
    {:ok, user} ->
      {:ok, jwt, claims} = user |> Guardian.encode_and_sign(:token)
      exp = Map.get(claims, "exp")

      conn
      |> put_status(:created)
      |> render(SessionView, "show.json", %{jwt: jwt, user: user, exp: exp})

    {:error, changeset} ->
      conn
      |> put_status(:unprocessable_entity)
      |> render("error.json", changeset: changeset)
    end
  end
end

What am I doing wrong here? Thank you in advance for your help.

Upvotes: 1

Views: 172

Answers (1)

Dogbert
Dogbert

Reputation: 222040

However, my actual question is why is the user placed inside the :session key?

That's because render_one tries to guess the key name using the name of the view you passed. In this case, you passed SessionView, so the key to use for the model is inferred to be :session. If you instead want it to be in :user, you can pass as: :user:

def render("show.json", %{jwt: jwt, user: user, exp: exp}) do
  %{
    jwt: jwt,
    exp: exp,
    user: render_one(user, __MODULE__, "show.json", as: :user)
  }
end

You can read more about this in the documentation for Phoenix.View.render_one/4.

...

render_one user, UserView, "show.html"

...

The underlying user is passed to the view and template as :user, which is inflected from the view name. The name of the key in assigns can be customized with the :as option:

render_one user, UserView, "show.html", as: :data

...

Upvotes: 2

Related Questions