Search code examples
elixirphoenix-frameworkecto

Map from Ecto query into Keyword List


I have been getting into Elixir from the Ruby on Rails world.

I have the following table:

 release_track_id | release_id | track_id | release_side_id | track_number
------------------+------------+----------+-----------------+--------------
              347 |         70 |      211 |               3 |            1
              329 |         70 |      193 |               1 |            1
              356 |         70 |      220 |               4 |            1
              338 |         70 |      202 |               2 |            1
              357 |         70 |      221 |               4 |            2
              330 |         70 |      194 |               1 |            2
              348 |         70 |      212 |               3 |            2
              339 |         70 |      203 |               2 |            2
              331 |         70 |      195 |               1 |            3
              349 |         70 |      213 |               3 |            3
              358 |         70 |      222 |               4 |            3

So far the query to pull that works great:

query = from at in ReleaseTrack,
      distinct: true,
      join: s in ReleaseSide, on: [release_side_id: s.release_side_id],
      join: a in Release, on: [release_id: at.release_id],
      where: a.active == true,
      where: at.release_id == ^release_id,

Repo.all(query) 

I get all those rows perfectly.

However, I want to transform what gets outputted:

[
  %App.ReleaseTrack{
    __meta__: #Ecto.Schema.Metadata<:loaded, "release_tracks">,
    inserted_at: ~N[2021-05-26 00:30:34],
    release_id: 70,
    release_side_id: 1,
    release_sides: %App.ReleaseSide{
      __meta__: #Ecto.Schema.Metadata<:loaded, "release_sides">,
      inserted_at: ~N[2021-05-26 00:30:34],
      order_number: 1,
      release: #Ecto.Association.NotLoaded<association :release is not loaded>,
      release_id: 70,
      release_side: "Disc 1",
      release_side_id: 1,
      updated_at: ~N[2021-05-26 00:30:34]
    },
    release_track_id: 329,
    track: %App.Track{
      __meta__: #Ecto.Schema.Metadata<:loaded, "tracks">,
      track_id: 211,
      bpm: 60,
      title: "Ghosts",
      ...
     }
   ],
   ...

What I would like to do is to transform the Repo.all into something like this:

%{ 70 => [track1, track2, track3] %}

If release_side_id pointed to Disc 1, as in the output above, and [track1, track2, track3] represents each of the %App.Track{} result.

Essentially, a keyword pair looking at the table:

%{ release_side_id => [track_id data, track_id data] }

No matter what I can't do, I can't get it to work. In a Ruby world:

tracks = {}

results.each do |result|
  side_name = result["release_side_id"]
  tracks[:release_side_id] ||= []
  tracks[:release_side_id] << results["track_id"]
end

I know Elixir does not keep track of the state of these variables, hence this issue, but there has to be a clever way using map or reduce...?

Thank you


Solution

  • Enum.group_by should help:

    Repo.all(query) 
    |> Enum.group_by(fn x -> x.release_id end, fn t -> t.track end)