dstpierre

dstpierre

Map with atom, string, keyword list - it's blocking me so much as a beginner Elixr enthusiast

Hi,

I’m constantly being blocked by maps’ different construct %{:atom => “value”} vs. %{“string” => “value”} vs %{name: “value”}

It does not appears to be consistent enough and in my case are often the cause of all “blockage” I have.

I understand them, I’m a long time Elm dev/fan and used to FP, but I don’t know why Elixir’s maps are so weird to me.

For instance, I have this simple test that I’m trying to understand the error:

test "renders created category when valid", %{conn: conn} do
      conn = post(conn, Routes.category_path(conn, :create), %{name: "cat name here"})
      assert %{"name" => "cat name here"}
    end

In my controller:

def create(conn, %{} = payload) do
      {account_id, _user_id} = conn.assigns.current_user

      payload = Map.put(payload, :account_id, account_id)

      cat = KB.create_category(payload)
      render(conn, "short.html", %{category: cat})
    end

The error I’m getting:

1) test creates category renders created category when valid (Parle.CategoryControllerTest)
     test/parle_web/controllers/category_controller_test.exs:19
     ** (Ecto.CastError) expected params to be a map with atoms or string keys, got a map with mixed keys: %{:account_id => "14ab1382-fe6a-4fd4-af6c-58c6a86c995e", "name" => "cat name here"}
     code: conn = post(conn, Routes.category_path(conn, :create), %{name: "cat name here"})

If I use something like this instead in the test `%{:name => “cat name here”} I’m getting:

1) test creates category renders created category when valid (Parle.CategoryControllerTest)
     test/parle_web/controllers/category_controller_test.exs:19
     ** (Ecto.CastError) expected params to be a map with atoms or string keys, got a map with mixed keys: %{:account_id => "91caf133-d2ae-4db0-b325-bef317f50eb3", "name" => "cat name here"}
     code: conn = post(conn, Routes.category_path(conn, :create), %{:name => "cat name here"})

This one just does not make sense to me.

Changing the Map.put(payload, "account_id", account_id) does not really help either.

I’m not coming from RoR, so to me sugar syntax is not attracting at all, and I prefer clarity, but still wants to jump on Elixir.

My main question would be, how am I suppose to pass data in the test that I can add an entry in the map so the schema validation works and still have the ~same atom/strong/keyword whatever identical everywhere.

Also if there’s an obvious way to grasp this part that I might have miss, I’d appreciate any pointer, they are just not very clear to me it appears.

Thanks :wink:

Marked As Solved

Nicd

Nicd

Note that %{name: "cat name here"} and %{:name => "cat name here"} are the same thing, they are just alternative syntaxes for it. The reason for the first syntax is that it’s very common to build atom-keyed maps in your own code and that is a more readable syntax for it (that also reminds of keyword lists).

Phoenix controllers convert all input params to string-keyed maps. This is because converting untrusted strings to atoms is dangerous as it can lead to overflowing of the atom table (atoms are not garbage collected), which kills the VM. This is why your map is string-keyed inside the controller action even though you sent it in the test as atom-keyed.

As to your issue, you could indeed do Map.put(payload, "account_id", account_id) and it should fix the error. Or you could modify your KB.create_category function to take in the user account ID as argument. So it would be KB.create_category(payload, account_id), and you’d add the account ID as a change in the changeset with put_change(:account_id, account_id) or something similar.

Also Liked

Aetherus

Aetherus

@dstpierre The normal way is either pass %{optional(String.t) => any} or %{optional(atom) => any} to the Ecto.Changeset.cast, but maps with hybrid types of keys.

In tests, it’s handy to just use atom keys, but you can also use string keys (actually, I recommend using string keys). In production, you should not convert string keys to atom keys because atoms are not garbage collected, and the internet is scary.

NobbZ

NobbZ

Your controller will always receive their “payload” as %{optional(String.t) => String.t} due to limitations of how HTTP works.

If you want to pass the payload as is to the changeset you need to use a string key in the Map.put/3 call.

Also there is no semantic difference between %{:foo => "bar"} and %{foo: "bar"} both maps are exactly identical, it is just some syntactic sugar.

Where Next?

Popular in Questions Top

nobody
How to bind a phoenix app to a specific ip address? could not find anything about that, nowhere, unfortunately, but for me this is quite...
New
New
electic
Hi, I am new to Elixir. I am trying to use the DateTime component to insert a date into MySQL however the there seems to be no way to fo...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
JulienCorb
I am trying to implement my new.html.eex file to create new posts on my website. new.html.eex: <h1>Create Post</h1> <%= ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
jason.o
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
Brian
What is the proper way to load a module from a file in to IEX? In the python world, doing something like this pretty standard: from ....
New
hariharasudhan94
I would like to know what is the best IDE for elixir development?
New
lanycrost
Hi everyone! I need implement if…else if…else condition from my elixir code, and anymore of this control flow structures not work proper...
New

Other popular topics Top

Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
Darmani72
If I have a post route which an argument: post /my_post_route/:my_param1, MyController.my_post_handler How would get the post params ...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 41989 114
New
skosch
To my knowledge, put_in, Map.update etc. all have the one limitation of not automatically creating intermediate keys when needed (for exa...
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
Emily
I have VueJS GUIs with the project generated using Webpack. I have Elixir modules that will need to be used by the VueJS GUIs. I forese...
New
RisingFromAshes
I’ve read in another post that it may be possible with a router helper - but I couldn’t find an appropriate one, and tbh, I’m still just ...
New
nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
komlanvi
Hi everyone, I was playing with phoenix liveView but I run into an issue. I have a form and want to validate each input text when the te...
New
dokuzbir
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New

Latest on Elixir Forum

Elixir Forum

We're in Beta

About us Mission Statement