grych / drab

Remote controlled frontend framework for Phoenix.

Home Page:https://tg.pl/drab

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Odd bug with Plug.Conn

antalvarenga opened this issue · comments

I'm having problems using drab in a template with view functions.
The bug is although the page loads fine and I can link an event to the commander, when loading the page it prompts the alert window error without an apparent reason.
This is the error:

[error] Drab Handler failed with the following exception:
** (KeyError) key :phoenix_action not found in: %{phoenix_endpoint: RiskWeb.Endpoint}
    (risk) lib/risk_web/views/evaluation_view.ex:41: RiskWeb.EvaluationView.check_action/3
    (risk) lib/risk_web/templates/evaluation/form.html.drab:101: anonymous fn/2 in RiskWeb.EvaluationView."form.html"/1
    (phoenix_html) lib/phoenix_html/form.ex:291: anonymous fn/2 in Phoenix.HTML.Form.inputs_for/4
    (elixir) lib/enum.ex:1294: Enum."-map/2-lists^map/1-0-"/2
    (phoenix_html) lib/phoenix_html/form.ex:289: Phoenix.HTML.Form.inputs_for/4
    (risk) lib/risk_web/templates/evaluation/form.html.drab:100: anonymous fn/2 in RiskWeb.EvaluationView."form.html"/1
    (phoenix_html) lib/phoenix_html/form.ex:253: Phoenix.HTML.Form.form_for/4
    (risk) lib/risk_web/templates/evaluation/form.html.drab:1: RiskWeb.EvaluationView."form.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
    (drab) lib/drab.ex:286: anonymous fn/3 in Drab.handle_callback/3

This is the check_action call in the template:

     ...
     <%= inputs_for f, :activities, fn fa -> %>
         <%= if check_action(@conn, @changeset, fa) do %>
           <tr>
           ...

and the definition in the view:

  def check_action(conn, changeset, fa) do
    if conn.private.phoenix_action === :new and changeset.action === nil do
      fa.data.frequent
    else
      true
    end
  end

The error arises because at a certain point the conn struct looks empty, like this:

%Plug.Conn{
  adapter: {Plug.MissingAdapter, :...},
  assigns: %{},
  before_send: [],
  body_params: %Plug.Conn.Unfetched{aspect: :body_params},
  cookies: %Plug.Conn.Unfetched{aspect: :cookies},
  halted: false,
  host: "www.example.com",
  method: "GET",
  owner: nil,
  params: %Plug.Conn.Unfetched{aspect: :params},
  path_info: [],
  path_params: %{},
  peer: nil,
  port: 0,
  private: %{phoenix_endpoint: RiskWeb.Endpoint},
  query_params: %Plug.Conn.Unfetched{aspect: :query_params},
  query_string: "",
  remote_ip: nil,
  req_cookies: %Plug.Conn.Unfetched{aspect: :cookies},
  req_headers: [],
  request_path: "",
  resp_body: nil,
  resp_cookies: %{},
  resp_headers: [{"cache-control", "max-age=0, private, must-revalidate"}],
  scheme: :http,
  script_name: [],
  secret_key_base: nil,
  state: :unset,
  status: nil
}

despite check_action being called successfully before.
Any ideas on where the problem might be?

It has nothing to do with using view functions. Accessing Plug.Conn throws the error.

The problem was I had the callback onload(:page_loaded) in the commander which I had just for testing. Removing it solved the issue

Reopening this issue because it turns out the error arises when using poke. Any idea on what is causing this?

Using a partial solves the issue. Can be a bit of a pain if there are several events in the same page.

Hi @antalvarenga, could you pls be a bit more specific? How can I reproduce it, and how did you solve it using partials?

Anywhere in the template check something in the conn struct like in my previous comments. Try accessing conn.private.phoenix_action for instance.
Add some event like the uppercase button and the corresponding handler in the commander.
If you poke like this:
poke(socket, text: String.upcase(text))
you should get that error.

Wrapping

<form>
  <input name="text_to_uppercase" value="<%= @text %>">
  <button drab="click:uppercase">Upcase</button>
</form>

in a partial like "text.html.drab" and rendering in the main template like this:
<%= render "text.html", text: @text %>

and poking like this:

defhandler uppercase(socket, sender) do
    text = sender.params["text_to_uppercase"]
    poke(socket, "text.html", text: String.upcase(text))
end

solves the issue.

I guess each time you poke or do some change on the page it runs all the elixir again, but after the first load the conn struct is no longer available.

conn.private.phoenix_action

@conn is a special case in Drab, because it is big and contains sensible data. Check the documentation here.

You need to set live_conn_pass_through config to add private to the conn structure.

Thanks. Yeah I'm not very pleased with that code. I'm accessing private because I have a field coming from the changeset which is pre-populated, and accessing that changes according to the action, like this:

  case conn.private.phoenix_action do
      :edit ->
        changeset.data.activity_name.name

      :update ->
        changeset.data.activity_name.name

      :new ->
        changeset.data.title

      :create ->
        changeset.params["title"]
    end

But it's not a huge deal, right?

I would do Phoenix.Controller.action_name(conn) instead. But under the hood, this function does the same, accessing private field, so you will need to add it to the live_conn_pass_through config.

Please leave this issue opened, as I think it would be good to add action and controller to the conn by default: it is nothing fragile and not a lot of data.

Thanks for reporting!