Help with Enum.reduce on a map

Hi,

I’m learning Elixir at the moment and I’m trying to build custom file uploader without arc/waffle. So far, I’ve managed to build uploader for single files and it works fine; uploaded file is validated, uploaded, renamed and inserted into db via embedded schema.

Now, I would like to add the ability to upload multiple files, also using embedded schema, but I got stuck and I hope someone could help me out.

When form is filled out, I get a map with file number as a key and a map with file data as a value

%{
  "0" => %{
    "image" => %Plug.Upload{
      content_type: "image/jpeg",
      filename: "image_1.jpg",
      path: "/tmp/plug-1611/multipart-1611242895-176617937866775-2"
    }
  },
  "1" => %{
    "image" => %Plug.Upload{
      content_type: "image/jpeg",
      filename: "image_2.jpg",
      path: "/tmp/plug-1611/multipart-1611242895-604710812086625-5"
    }
  }
}

As I want to modify %Plug.Upload{} struct and make some changes to it, I have to convert it into a map using Map.from_struct. This all works when uploading single files but how do I do it with multiple files?

My goal is to get the same map but with %Plug.Upload{} structs converted to maps, nothing else.

%{
  "0" => %{
    "image" => %{
      content_type: "image/jpeg",
      filename: "image_1.jpg",
      path: "/tmp/plug-1611/multipart-1611242895-176617937866775-2"
    }
  },
  "1" => %{
    "image" => %{
      content_type: "image/jpeg",
      filename: "image_2.jpg",
      path: "/tmp/plug-1611/multipart-1611242895-604710812086625-5"
    }
  }
}

I’ve tried with Enum.each but it didn’t work and Jose explained why in his answer on StackOverflow https://stackoverflow.com/a/29924465 , he also gave instructions on how to use Enum.reduce() to do this properly. The thing is, after a couple of hours of trying, I still can’t get it working, I don’t understand how to use it properly with a map so I hope someone could help out me here.

Thanks

You want

source
|> Enum.map(fn {k, v} ->
  new_v = Map.put(v, "image", Map.from_struct(v["image"]))
  {k, new_v}
end)
|> Enum.into(%{})
1 Like

It worked, thanks a lot!

To understand this just keep in mind that in elixir maps that are not structs are enumerable as key/value tuples, and you can collect key/value tuples into them.

And the other thing is that generally speaking (there are four exceptions) the only way a value can find its way out of a lexical scope is through the return value of the lexical scope.

1 Like

One tiny optimization here, whenever you do Enum.map/2 followed by Enum.into(..., %{}), that can be replaced with a single call to Map.new/2. So the refactored code would look like

Map.new(source, fn {k, v} ->
  new_v = Map.put(v, "image", Map.from_struct(v["image"]))
  {k, new_v}
end)
6 Likes

Great, thanks!