flp

flp

I’m trying to manipulate the following map in Elixir, but I fail to get it working. I want to set a user_id for all entries of user_sports.

%{"zip" => "",
  "location => "",
  "user_sports" => 
     %{"0" => %{"sport_id" => "1", "user_id" => ""},
       "1" => %{"sport_id" => "1", "user_id" => ""}
     }
}

As far as I understood it, the following makes it harder: The keys are “numbers”, so they aren’t stable; it’s not a single entry, there are multiple.

I have tried Map.replace and Map.put, but it feels like the Enum.each prevents the results being saved in the map.

Enum.each(user_sports, fn {k, v} ->
  Map.replace!(user_sports, k, %{v | "user_id" => user.id})
end)

When I tried to use them with fixed keys, it worked, but unfortunately, I never know how many entries I have.

Map.put(user_sports, "0", %{Map.get(user_sports, "0") | "user_id" => user.id})

I have also tried get_and_update(_in), but I was always running into the given function must return a two-element tuple or :pop, got: :ok:

params = get_and_update_in(user_params, ["user_sports"], fn c -> Enum.each(c, fn {k, v} -> {k, %{v | "user_id" => user.id}} end) end)

I feel, I am not far way from the right solution, but I have a stupid mistake/misunderstanding.

Showing Posts 1 to 10

hubertlepicki

hubertlepicki

Do you know about Kernel.put_in/2 and Kernel.put_in/3? It might be just what you are looking for.

idi527

idi527

user_id = "1"

# extract `user_sports` from data
%{"user_sports" => user_sports} = data = %{
  "zip" => "",
  "location" => "",
  "user_sports" =>  %{
    "0" => %{"sport_id" => "1", "user_id" => ""},
    "1" => %{"sport_id" => "1", "user_id" => ""}
  }
}

# put user_id into user_sports
user_sports =
  user_sports
  |> Enum.map(fn {key, user_sport_info} -> 
    {key, Map.put(user_sport_info, "user_id", user_id)}
  end)
  |> Enum.into(%{})

# update data with new user_sports
data = %{data | "user_sports" => user_sports}

I hoped

put_in(data, ["user_sports", Access.all(), "user_id"], user_id)

would also work, but it didn’t. Access.all() only works with lists:

iex(4)> put_in(data, ["user_sports", Access.all(), "user_id"], user_id)
** (RuntimeError) Access.all/0 expected a list, got: %{"0" => %{"sport_id" => "1", "user_id" => ""}, "1" => %{"sport_id" => "1", "user_id" => ""}}
    (elixir) lib/access.ex:626: Access.all/3
    (elixir) lib/map.ex:773: Map.get_and_update/3
    (elixir) lib/kernel.ex:2057: Kernel.put_in/3
matteosister

matteosister

One way to do it (not tested):

new_user_sports =
  initial_data
  |> get_in(["user_sports"])
  |> Enum.map(fn {k, v} -> 
    {k, Map.put(v, "user_id", user.id)}
  end)

Map.put(initial_data, "user_sports", new_user_sports)
idi527

idi527

Enum.map would return a list of key-value tuples, not a map, so something like Enum.into(%{}) is required for the initial structure to stay intact.

Or a reduce function

Enum.reduce(user_sports, %{}, fn {key, user_sport_info}, acc ->
  Map.put(acc, key, Map.put(user_sport_info, "user_id", user_id))
end)

or a for comprehension

for {key, user_sport_info} <- user_sports, into: %{} do
  {key, Map.put(user_sport_info, "user_id", user_id)}
end

But both of these would be less efficient than a map.

matteosister

matteosister

Enum.into(%{}) is better imho

net

net

update_in(
  map["user_sports"],
  &:maps.map(fn _key, user_sport -> %{user_sport | "user_id" => user_id} end, &1)
)

Also, Map.new(enum, fun) can be used instead of enum |> Enum.map(fun) |> Enum.into(%{}).

jfeng

jfeng

If you don’t mind writing a helper function, the logic becomes simpler when you break it into pieces. For instance,

def update_sports_map(map) do
  Map.new(map, fn({index, attrs}) -> {index, Map.put(attrs, "user_id", "id")} end)
end

new_data = Map.update!(data, "user_sports", &update_sports_map/1)
flp

flp OP

Awesome solutions. Thanks a lot :slight_smile: I feel like I was slowed on the uptake

@idi527: I tried it with Access.fetch too, but unluckily it does not seem to be as flexible as I need to have it

The only thing I still don’t understand is the fact that the following does not work:

Enum.each(user_sports, fn {k, v} ->
  Map.replace!(user_sports, k, %{v | "user_id" => user.id})
end)

Can anyone explain this?

idi527

idi527

That’s because most data types in elixir/erlang are immutable. So Enum.each won’t mutate user_sports.

jfeng

jfeng

To expand on what @idi527 said, since data is immutable the way an update works is to have a function return an updated version. So something like

data = update_fun(data)

works by returning the updated data and switching the label data to the new data. The original data is not modified and is still there, until the VM sees that it’s no longer referenced and garbage collects it.

Enum.each(enumerable, fun) simply calls fun for every element of the enumerable, and then returns :ok. This means any value returned by fun doesn’t get used and winds up being garbage collected. The main use for Enum.each is to call a function with a side-effect, like printing to an IO device or sending a message to another process, for each element.

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
kpanic
Hi everyone, I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding. I sta...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
asweet-confluent
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
apz
I’m new to elixir and just tried to install the elixirLS extension for VScode(ium) and it is throwing some errors that I would like help ...
New

Other Trending Topics Top

GenericJam
Edit: 2026 May 15 - This post is archived. Mob is alive!! Main docs: mob v0.7.11 — Documentation A bit of explanation for the slightly c...
New
JesseHerrick
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
mhanberg
Hi everyone! The first release candidate for the Expert language server project is now available! We’ve published a press release detai...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews