Drab: (KeyError) key :current_user not found in: %{}

phoenix
drab

#1

Hi I am using drab to update chart js in a page
here is defhandler

  defhandler show_dashboard_stats(socket, sender) do
    order_id = sender.params["campaign_id"]
    user_id = socket.assigns.current_user_id
    order = Sales.get_order_by_id(order_id, user_id)
    message_status = Messenger.get_all_message_status(order_id)

    total_sent = Enum.count(message_status)
    deilvered_count = Analytics.get_delivered_message_status_count(message_status)
    undelivered_count = Analytics.get_undelivered_message_status_count(message_status)
    bitly = Bitly.get_bilty_by_order_id(order_id)
    total_clicks = Analytics.load_clicks(bitly)

    poke socket,
                total_sent: total_sent,
                deilvered_count: deilvered_count,
                undelivered_count: undelivered_count,
                total_clicks: total_clicks,
                recent_order: order
    socket |> exec_js!("DashboardChart.update()")
  end

and here is error message.

** (KeyError) key :current_user not found in: %{}
    (texting) lib/texting_web/templates/dashboard/dashboard/index.html.drab:164: TextingWeb.Dashboard.DashboardView."index.html"/1
    (phoenix) lib/phoenix/view.ex:332: Phoenix.View.render_to_iodata/3
    (phoenix) lib/phoenix/view.ex:339: Phoenix.View.render_to_string/3
    (drab) lib/drab/live.ex:662: Drab.Live.rerender_template/4
    (drab) lib/drab/live.ex:617: Drab.Live.process_poke/9
    (texting) lib/texting_web/commanders/dashboard/dashboard_commander.ex:17: TextingWeb.Dashboard.DashboardCommander.show_dashboard_stats/2
    (drab) lib/drab.ex:323: anonymous fn/7 in Drab.handle_event/6

And I don’t understand

** (KeyError) key :current_user not found in: %{}

in my app.html.eex I passed current_user id to drab like this

<%= Drab.Client.run(@conn, [current_user_id: @conn.assigns.current_user.id, order_id: @conn.assigns.recipients.id]) %>

So I inspected socket assigns in defhandler
then it shows correct user id.

So where does this error(KeyError) key :current_user not found in: %{} come from?

Almost same defhandler works in other page.
what is the possible cause?

Thanks


#2

I would start checking with this file, line 164. Can you show it?


#3

sure.
<div class=“stats”>
<i class=“material-icons”>access_time</i> You have <%= @conn.assigns.current_user.credits %> credits.
</div>

I assign :current_user using Plug in load_user.ex

def call(conn, _opts) do
    case user_id = get_session(conn, :user_id) do
    	user_id when user_id != nil ->
    		user = Account.get_user_by_id(user_id)
    		assign(conn, :current_user, user)
    	user_id when is_nil(user_id) ->
    		conn
    		|> put_flash(:error, "You must sign in first")
    		|> redirect(to: Helpers.sign_in_path(conn, :new))
        |> halt()
    end
  end

@conn.assigns.current_user

in router.ex

pipeline :dashboard do
    plug TextingWeb.Plugs.LoadUser
    plug TextingWeb.Plugs.DashboardLayout
    plug TextingWeb.Plugs.FetchRecipients
  end

#4

So this is a conn case again.

Drab uses castrated version of conn, as it could be very heavy, especially when you keep a lot of data in assigns. See this issue for a reference. Drab creates it’s own version of conn, which by default copy only the few fields in private.

This behaviour is documented here. What you need is just to add something like :assigns => :current_user to :live_conn_pass_through config.

I know it is quite counter-intuitive, but I didn’t find any solution to change this, or at least tell user it is using the castrated conn version. Anyone?


#5

OK. I did it exactly you and doc said.
and then I got this error

`

(Protocol.UndefinedError) protocol Enumerable not implemented for %Texting.Account.User{…}

`
What am I missing?


#6

Ok, still I need way more information. On which line of code the error occurs and what’s there? What exactly you’ve set as :live_conn_pass_through in the config? What is %Texting.Account.User{}?


#7

%Texting.Account.User{} is a user struct that I assign to conn as current_user.

live_conn_pass_through: %{
    assigns: %{
      current_user: true
    },
    private: %{
      phoenix_endpoint: true
    }

#8

OK, what about which line of code the error occurs and what’s there?


#9

That is tricky. I thought errors from <%= @conn.assigns.current_user.credits %>
So I comment out that lines but it got me same errors.

And after set :live_conn_pass_through, drab in other page gives me an same error. (it worked ok before set :live_conn_pass) and some other page it shows me errors like this

** (Protocol.UndefinedError) protocol Enumerable not implemented for %Texting.Account.User{}
long line here.......and

(elixir) /home/ubuntu/bob/tmp/6f24db0ee55f44e6b3c1cfcd7feddfd8/elixir/lib/elixir/lib/enum.ex:1: Enumerable.impl_for!/1
    (elixir) /home/ubuntu/bob/tmp/6f24db0ee55f44e6b3c1cfcd7feddfd8/elixir/lib/elixir/lib/enum.ex:141: Enumerable.reduce/3
    (elixir) lib/enum.ex:1911: Enum.reduce/3
    (drab) lib/drab/live/assign.ex:71: anonymous fn/2 in Drab.Live.Assign.deep_merge_map/2
    (stdlib) maps.erl:257: :maps.fold_1/3
    (drab) lib/drab/live/assign.ex:71: anonymous fn/2 in Drab.Live.Assign.deep_merge_map/2
    (stdlib) maps.erl:257: :maps.fold_1/3
    (drab) lib/drab/live/assign.ex:33: Drab.Live.Assign.merge/2
    (drab) lib/drab/live.ex:898: Drab.Live.apply_conn_merge/1
    (elixir) lib/enum.ex:1298: anonymous fn/3 in Enum.map/2
    (stdlib) maps.erl:257: :maps.fold_1/3
    (elixir) lib/enum.ex:1915: Enum.map/2
    (drab) lib/drab/live.ex:894: Drab.Live.assigns_for_partial/5
    (drab) lib/drab/live.ex:584: Drab.Live.do_poke/5
    (drab) lib/drab.ex:323: anonymous fn/7 in Drab.handle_event/6

#10

Wait what?

How is this being hit:


When this is before it:

And that checks if it is a struct?! o.O


#11

Oh, exactly. How come?
This part of code hasn’t been changed for a while.

@wlminimal, please report it as a bug in github, I need to do more research.


#12

The issue was because struct is also a map. Adding one match in lambda at the beginning did the trick:

Enum.reduce(to_merge, base, fn
  {key, %{__struct__: _} = value}, base ->
    Map.put(base, key, value)

  {key, %{} = value}, base ->
    sub = base[key] || %{}

    sub = if is_map(sub), do: deep_merge_map(sub, value), else: sub
    Map.put(base, key, sub)

  {key, value}, base ->
    Map.put(base, key, value)
end)

#13

@wlminimal, it should be fixed by now, you may check the github master now, or wait for 0.9.2 (coming probably tonight).


#14

You are relying on an implementation detail of structs, can’t you use proper syntax support instead:

iex(1)> defmodule S do
...(1)>   defstruct a: 0
...(1)> end
iex(2)> %_{} = %S{}
%S{a: 0}
iex(3)> %_{} = %{}
** (MatchError) no match of right hand side value: %{}

#15

Cool, I didn’t know this syntax, thanks!