Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Chapter 6

Chapters/Chapter_6.livemd

Chapter 6

Section

defmodule ServerProcess do
  def start(callback_module) do
    spawn(fn ->
      initial_state = callback_module.init()
      loop(callback_module, initial_state)
    end)
  end

  defp loop(callback_module, current_state) do
    receive do
      {:call, request, caller} ->
        {response, new_state} =
          callback_module.handle_call(
            request,
            current_state
          )

        send(caller, {:response, response})
        loop(callback_module, new_state)

      {:cast, request} -> # This is the new line for :cast
        new_state =
          callback_module.handle_cast(
            request,
            current_state
          )

        loop(callback_module, new_state)
    end
  end

  def call(server_pid, request) do
    send(server_pid, {:call, request, self()})

    receive do
      {:response, response} ->
        response
    end
    # ServerProcess.call(pid, {:entries, date})
  end

  def cast(server_pid, request) do # This is the new helper function for :cast
    send(server_pid, {:cast, request})
    # don't need the caller as there is no response
  end
end
defmodule KeyValueStore do
  def init do
    # You only need to have a final line that is the data storage for the state.
    %{}
  end

  def start do
    ServerProcess.start(KeyValueStore)
  end

  def put(pid, key, value) do
    ServerProcess.cast(pid, {:put, key, value}) # Changed call to cast 
  end

  def get(pid, key) do
    ServerProcess.call(pid, {:get, key})
  end

  def handle_cast({:put, key, value}, state) do # Implemetation for the cast.
    Map.put(state, key, value)
  end

  def handle_call({:put, key, value}, state) do
    # second param is the state after the pass
    {:ok, Map.put(state, key, value)}
  end

  def handle_call({:get, key}, state) do
    # first value can be the response
    {Map.get(state, key), state}
  end
end
# if ServerProcess.where_is(pid), do: "", else: pid = ServerProcess.start(KeyValueStore)

# pid = ServerProcess.start(KeyValueStore)

# ServerProcess.call(pid, {:put, :some_key, :some_value})
# ServerProcess.call(pid, {:get, :some_key})
# Now we can use the KeyValueStore
# pid = KeyValueStore.start()

# KeyValueStore.put(pid, :some_key, :some_value)
# :ok
# KeyValueStore.get(pid, :some_key)
# The diffence here is that there is no response for the put.
pid = KeyValueStore.start()
KeyValueStore.put(pid, :some_key, :some_value)
KeyValueStore.get(pid, :some_key)
defmodule TodoList do
  defstruct next_id: 1, entries: %{}

  # def new(), do: %TodoList{}

  def new(entries \\ []) do
    Enum.reduce(entries, %TodoList{}, fn entry, todo_list ->
      add_entry(todo_list, entry)
    end)

    # entires, %TodoList{}, &add_entry(&2, &1)
    # Needs to be in this order as the lambda will pass the entry then the acc.
  end

  def add_entry(todo_list, entry) do
    # adds the id to the entry
    entry = Map.put(entry, :id, todo_list.next_id)

    new_entries =
      Map.put(
        todo_list.entries,
        todo_list.next_id,
        entry
      )

    # adds the new entry to the todo_list

    # updates the next id
    %TodoList{todo_list | entries: new_entries, next_id: todo_list.next_id + 1}
  end

  def entries(todo_list, date) do
    todo_list.entries
    |> Map.values()
    |> Enum.filter(fn entry -> entry.date == date end)
  end

  def entries(todo_list) do
    todo_list.entries
    |> Map.values()
  end

  def update_entries(todo_list, entry_id, updater_fun) do
    case Map.fetch(todo_list.entries, entry_id) do
      :error ->
        todo_list

      {:ok, old_entry} ->
        new_entry = updater_fun.(old_entry)
        new_entries = Map.put(todo_list.entries, new_entry.id, new_entry)
        %TodoList{todo_list | entries: new_entries}
    end
  end

  def delete_entry(todo_list, entry_id) do
    case Map.fetch(todo_list.entries, entry_id) do
      :error ->
        IO.inspect("cant find")
        todo_list

      {:ok, _entry} ->
        updated_entries = Map.delete(todo_list.entries, entry_id)
        %TodoList{todo_list | entries: updated_entries}
    end
  end
end
defmodule TodoServer do
  @moduledoc """
  This will be used to have a server that will take and process TodoList items. 
  """
  def start do
    ServerProcess.start(TodoServer)
  end

  def add_entry(todo_server, new_entry) do
    ServerProcess.cast(todo_server, {:add_entry, new_entry})
  end

  def entries(todo_server, date) do
    ServerProcess.call(todo_server, {:entries, date})
  end

  def init do
    TodoList.new()
  end

  def handle_cast({:add_entry, new_entry}, todo_list) do
    TodoList.add_entry(todo_list, new_entry)
  end

  def handle_call({:entries, date}, todo_list) do
    {TodoList.entries(todo_list, date), todo_list}
  end
end
pid = TodoServer.start()

TodoServer.add_entry(pid, %{date: ~D[2023-12-20], title: "Shopping"})
TodoServer.entries(pid, ~D[2023-12-20])
# Using GenServer this and the next cell are just to see the functions that
# use GenServer inject.
defmodule KeyValueStore.GenServer do
  use GenServer

  def start do
    GenServer.start(KeyValueStore.GenServer, nil, name: :keyvalue)
  end

  def put(entries) do
    GenServer.cast(:keyvalue, {:put, entries})
  end

  def get(date) do
    GenServer.call(:keyvalue, {:get, date})
  end

  def delete(id) do
    GenServer.cast(:keyvalue, {:delete, id})
  end

  def init(_) do
    {:ok, TodoList.new()}
  end

  def handle_cast({:delete, id}, state) do
    {:no_reply, TodoList.delete_entry(state, id)}
  end

  def handle_cast({:put, entries}, state) do
    {:noreply, TodoList.add_entry(state, entries)}
  end

  def handle_call({:get, key}, _, state) do
    {:reply, TodoList.entries(state, key), state}
  end
end
{:ok, pid} = KeyValueStore.GenServer.start()

KeyValueStore.GenServer.put(%{date: ~D[2023-12-20], title: "Shopping"})

KeyValueStore.GenServer.get(~D[2023-12-20])

# KeyValueStore.GenServer.delete(1)

KeyValueStore.GenServer.get(~D[2023-12-20])
# KeyValueStore.GenServer.__info__(:functions)
# Great little bit of info to use the __info__/1 to get info about...
# {:ok, pid} = KeyValueStore.GenServer.start()

# KeyValueStore.GenServer.put(pid, :some_key, :some_value)

# KeyValueStore.GenServer.get(pid, :some_key)
# little test with the :timer.send_interval/2
# defmodule KeyValueStore.Timer do
#   use GenServer

#   def init(_) do
#     :timer.send_interval(5000, :cleanup)
#     {:ok, %{}}
#   end

#   def handle_info(:cleanup, state) do
#     IO.puts("performing cleanup...")
#     {:noreply, state}
#   end
# end
# {_, pid} = GenServer.start(KeyValueStore.Timer, nil, name: :some_name)
# GenServer.stop(:some_name)