Why is this controller pattern match not working?

I find myself unable to figure out why this pattern match isn’t working in my controller:

From organisation_controller.ex:

  def switch(conn, %{switch: %{org: org_id}}) do
    Logger.warn("MATCHED ORG_ID")

    conn |> do_something()
  end

  def switch(conn, _params) do
    Logger.warn("FALLBACK")

    conn
    |> put_status(:bad_request)
    |> put_view(Pult.ErrorView)
    |> render("400.html")
  end

From router.ex

post "/organisations/switch", OrganisationController, :switch

The server logs then have the following:

info] POST /organisations/switch
[debug] Processing with Pult.OrganisationController.switch/2
  Parameters: %{"_csrf_token" => "[snipped]", "_utf8" => "✓", "switch" => %{"org" => "3"}}
  Pipelines: [:browser, :protected]
[warn] FALLBACK
[info] Sent 400 in 6ms

So the [warn] FALLBACK indicate the first match/2 is not being matched, although the parameters should match %{switch: %{org: org_id}}?

If I run the code in iex, it appears to match correctly:

iex(4)> switch(Phoenix.ConnTest.build_conn(), %{"switch": %{"org": "8"}})
[warn] MATCHED ORG_ID
%Plug.Conn{...}

What am I missing here?

Shouldn’t the matching in the controller match on string keys rather than atoms?

I.e.

%{"switch" => %{"org" => org_id}}

In your test example you create a map using keywords. Even if they are string the key get converted to an atom.

iex> %{"hello": "world"}
%{hello: "world"}

vs

iex> %{"hello" => "world"}
%{"hello" => "world"}
3 Likes

I’ve tried that, makes no difference, but the elixir compiler yields the following warning:

found quoted keyword "org" but the quotes are not required. Note that keywords are always atoms, even when quoted. Similar to atoms, keywords made exclusively of Unicode letters, numbers, underscore, and @ do not require quotes

So as far as I can tell, %{"hello": "world"} is interpreted exactly the same as %{hello: "world"} by Elixir.

In %{"hello": "world"} - :"hello" is still an atom.

%{"hello" : "world"} is syntax sugar for %{:"hello" => "world"}.

In %{"hello" => "world"} - "hello" is a string - the => with the lack of the leading colon on the key makes all the difference.


https://elixir-lang.org/getting-started/keywords-and-maps.html#maps

When all the keys in a map are atoms, you can use the keyword syntax for convenience:

1 Like

Ah, right, %{switch: %{org: org_id}} is a keyword list. If I change it to %{"switch" => %{"org" => org_id}}, everything works as expected, thanks to both of you.

1 Like

No, its a map with atom keys.

A keyword list is a list of two-tuples where the first element is an atom.

4 Likes

A map using the keyword syntax


%{:switch => %{:org => org_id}}

Is still a map with atom keys - but it doesn’t use the keyword syntax.


  • [{:a, 1}, {:b, 2}] - a keyword list
  • [a: 1, b: 2] - the same keyword list in the special keyword syntax
2 Likes