How to make a list of maps/structs(Ecto, Multi)

Hi I’m inserting a Playlist with Multi and that seems to work. Now I’d like to insert a list of PlaylistTitle which are many-to-one(PlaylistTitle-to-Playlist). I pass in a list of localized_titles like:
[debug] localized_titles %{attributes: [%{"en" => "The English Title of a Playlist"}, %{"fr" => "Le French Title of a Playlist"}]}

But this Multi.run() chunk returns [:ok, :ok] for each map instead of a new PlaylistTitle. Not sure how to return back a list of maps?

(full code at: https://gist.github.com/mazz/bf3e0cdfdd682273f65b5c0ed7b5492a)

    |> Multi.run(:add_localized_titles, fn _repo, %{item_without_hash_id: playlist} ->

      Logger.debug("localized_titles #{inspect(%{attributes: localized_titles})}")

      maps = Enum.map(localized_titles, fn(title) ->
        Logger.debug("title #{inspect(%{attributes: title})}")
        map = Enum.each(title, fn {k, v} ->
          IO.puts "#{k} --> #{v}"

          # return a PlaylistTitle struct with the playlist_id from above
          %PlaylistTitle{language_id: k, localizedname: v, uuid: Ecto.UUID.generate(), playlist_id: playlist.id}
          # %{lang: k, value: v}
        end)
      end)
      Logger.debug("maps #{inspect(%{attributes: maps})}")

      # insert all the new PlaylistItem
      # {count, _} = Repo.insert_all(PlaylistTitle, maps)
    end)
    |> Repo.transaction()

output:

[debug] QUERY OK db=2.7ms
INSERT INTO "playlists" ("channel_id","media_category","multilanguage","ordinal","uuid","inserted_at","updated_at") VALUES ($1,$2,$3,$4,$5,$6,$7) RETURNING "id" [1, 0, false, 99, <<102, 126, 60, 219, 143, 23, 76, 178, 153, 254, 248, 45, 210, 252, 176, 170>>, ~U[2019-11-09 15:58:28Z], ~U[2019-11-09 15:58:28Z]]
[debug] QUERY OK db=1.0ms
UPDATE "playlists" SET "hash_id" = $1, "updated_at" = $2 WHERE "id" = $3 ["adyy", ~U[2019-11-09 15:58:28Z], 123]
en --> The English Title of a Playlist
[debug] localized_titles %{attributes: [%{"en" => "The English Title of a Playlist"}, %{"fr" => "Le French Title of a Playlist"}]}
[debug] title %{attributes: %{"en" => "The English Title of a Playlist"}}
[debug] title %{attributes: %{"fr" => "Le French Title of a Playlist"}}
fr --> Le French Title of a Playlist
[debug] maps %{attributes: [:ok, :ok]}
[debug] QUERY OK db=0.3ms

This fragment there returns :ok (almost always, you can assume that always). That is the problem. If you want to return more meaningful response then add maps below this line to return your new list.

weird, adding maps alone on the line right after
Logger.debug("maps #{inspect(%{attributes: maps})}")
seems to have no effect.
Is the issue that I’m using Enum.each()? Is there a way to access k and v without using Enum.each()?

Yes, Enum.map. But in general you code can be rewritten with for:

    |> Multi.run(:add_localized_titles, fn _repo, %{item_without_hash_id: playlist} ->

      Logger.debug("localized_titles #{inspect(%{attributes: localized_titles})}")

      maps =
        for title <- localized_titles,
            _ = Logger.debug("title #{inspect(%{attributes: title})}"),
            {k, v} <- title do
          IO.puts "#{k} --> #{v}"

          # return a PlaylistTitle struct with the playlist_id from above
          %PlaylistTitle{language_id: k, localizedname: v, uuid: Ecto.UUID.generate(), playlist_id: playlist.id}
          # %{lang: k, value: v}
        end
      Logger.debug("maps #{inspect(%{attributes: maps})}")

      # insert all the new PlaylistItem
      # {count, _} = Repo.insert_all(PlaylistTitle, maps)

      maps
    end)
    |> Repo.transaction()
1 Like

Thanks Lukasz. Here’s my final code to help out any others who are learning about ecto, multi, loops, maps and iterating over maps.

  def add_playlist(
    ordinal,
    basename,
    small_thumbnail_path,
    med_thumbnail_path,
    large_thumbnail_path,
    banner_path,
    media_category,
    localized_titles,
    channel_id
    ) do

    changeset = Playlist.changeset(%Playlist{basename: basename}, %{
      ordinal: ordinal,
      basename: basename,
      small_thumbnail_path: small_thumbnail_path,
      med_thumbnail_path: med_thumbnail_path,
      large_thumbnail_path: large_thumbnail_path,
      banner_path: banner_path,
      media_category: media_category,
      channel_id: channel_id,
      uuid: Ecto.UUID.generate()
      })

    Multi.new()
    |> Multi.insert(:item_without_hash_id, changeset)
    |> Multi.run(:playlist, fn _repo, %{item_without_hash_id: playlist} ->
      playlist
      |> Playlist.changeset_generate_hash_id()
      |> Repo.update()
    end)
    |> Multi.run(:add_localized_titles, fn _repo, %{playlist: playlist} ->
      maps =
        for title <- localized_titles,
            _ = Logger.debug("title #{inspect(%{attributes: title})}"),
            {k, v} <- title do
          IO.puts "#{k} --> #{v}"

          Repo.insert(%PlaylistTitle{language_id: k, localizedname: v, uuid: Ecto.UUID.generate(), playlist_id: playlist.id})
        end
      Logger.debug("maps #{inspect(%{attributes: maps})}")
      {:ok, maps}
    end)
    |> Repo.transaction()
  end
2 Likes