Can't get Map, Enum and List code right for GenServer

I struggle with Elixir and can’t get the Elixir right for this code. Desperately need some help here.

I have built a GenServer that will act as my SessionDataServer. My initial plan was to just create a key-store using a map where:

key was a session_id (I get from session in LiveView mount)
value was a struct %SessionData{ date_entered: Date.utc_today, user_data: []}

I was using the following to handle a call to :store_session_data:

@impl true
  def handle_call( {:store_session_data, session_id, user_data}, _from, data ) do
    Map.put(
         data,
         session_id,
         %SessionData{date_entered: Date.utc_today(), user_data: user_data}
       )
    {:reply, data, data}
  end

I got this error and think it’s because I’m passing a string in as the key and Map.put wants an atom:

exited in: GenServer.call(:session_data_server, {:store_session_data, "35ad0255-df9b-4e12-b6cb-9446079d32a4", []}, 5000) ** (EXIT) an exception was raised: ** (Protocol.UndefinedError) protocol String.Chars not implemented for %{} of type Map (elixir 1.13.2) lib/string/chars.ex:3: String.Chars.impl_for!/1 (elixir 1.13.2) lib/string/chars.ex:22: String.Chars.to_string/1 (sketch_links 0.1.0) 

OK, so I redefined my struct to be as follows:

defmodule SessionData do
    defstruct session_id: nil, date_entered: nil, user_data: []
  end

And my GenServer now takes a list at startup because I want it to hold a list of structs:

def start_link(_args) do
    IO.puts "Starting the SessionDataServer ..."
    GenServer.start_link(__MODULE__, [], name: @name)
  end

But I still can’t get at the data. I’m trying to use the following code to delete existing session data if it exists and replace it with new data:

@impl true
  def handle_call( {:store_session_data, session_id, user_data}, _from, data ) do
    # Remove old data for session id
    existing_data = Enum.find(data, fn d -> d.session_id == session_id end)
    data = List.delete(data, existing_data)

    # Add new data
    data = [ %SessionData{
                session_id: session_id,
                date_entered: Date.utc_today(),
                user_data: user_data} | data ]
    {:reply, data, data}
  end

Now I’m getting this error:

exited in: GenServer.call(:session_data_server, {:store_session_data, "35ad0255-df9b-4e12-b6cb-9446079d32a4", []}, 5000) ** (EXIT) an exception was raised: ** (ArgumentError) cannot convert the given list to a string

Why is it trying to convert my list of structs to a string??? I know there is an Elixir master out there who can probably do this in one line. Should I have stuck with using the session_id as the key to my map? If so, how do I get it to stop throwing up an error?

You are trying to print something somewhere I guess. We need the stacktrace of the errors. It does not seem to be because of the code samples you gave.

2 Likes

Head hitting desk … over and over. I had seen someone use this format to embed the inspect call within IO.puts:

    # IO.puts ":clear_old_session data. DATA: #{inspect data}"

But I had used it incorrectly throughout my app by forgetting to include the word “inspect” in those curly brackets.

Thank you so much for catching that. Everything works now.

2 Likes