liebus
Help add "message history" & "participants" to simple Chat Room with GenServer
Hi. I am learning Elixir recently, decided to make a small simple pet project “Chat Room” to learn some OTP.
The general points:
Need to create a gen_server which will have a state which
- store last 100 messages
- have a list of chat room participants (using join/leave functions)
- Send message which stores in last 100 messages history AND broadcasts message to a chat room members (using PID, of course)
- Add a monitor by chat room on participants PID to handle room leave when client process dies.
I used the via_tuple example I found. At this point, I can create a chatroom and messages in it. Now the main problem for me, due to lack of experience, is how to create a structure to store message history, members and replace this inside the GenServer so that I can do join/leave inside the room.
I would like at least some example of implementation and I will pick up from there.
This is my code for now:
server.ex
Summary
defmodule Chat.Server do
use GenServer
# Client API
def start_link(name) do
GenServer.start_link(__MODULE__, [], name: via_tuple(name))
end
def add_message(room_name, message) do
GenServer.cast(via_tuple(room_name), {:add_message, message})
end
def get_messages(room_name) do
GenServer.call(via_tuple(room_name), :get_messages)
end
defp via_tuple (room_name) do
{:via, Chat.Registry, {:chat_room, room_name}}
end
# SERVER
@impl true
def init(messages) do
{:ok, messages}
end
@impl true
def handle_cast({:add_message, new_message}, messages) do
{:noreply, [new_message | messages]}
end
@impl true
def handle_call(:get_messages, _from, messages) do
{:reply, messages, messages}
end
end
supervisor.ex
Summary
defmodule Chat.Supervisor do
use DynamicSupervisor
def start_link do
DynamicSupervisor.start_link(__MODULE__, [], name: :chat_supervisor)
end
def start_room(name) do
spec = %{id: Chat.Server, start: {Chat.Server, :start_link, [name]}}
DynamicSupervisor.start_child(:chat_supervisor, spec)
end
def init(_) do
DynamicSupervisor.init(
strategy: :one_for_one,
extra_arguments: []
)
end
end
registry.ex
Summary
defmodule Chat.Registry do
use GenServer
# API
def start_link do
# register our registry, with a simple name,
# just so to reference it in the other functions.
GenServer.start_link(__MODULE__, nil, name: :registry)
end
def whereis_name(room_name) do
GenServer.call(:registry, {:whereis_name, room_name})
end
def register_name(room_name, pid) do
GenServer.call(:registry, {:register_name, room_name, pid})
end
def unregister_name(room_name) do
GenServer.cast(:registry, {:unregister_name, room_name})
end
def send(room_name, message) do
# If we try to send a message to a process
# that is not registered, we return a tuple in the format
# {:badarg, {process_name, error_message}}.
# Otherwise, we just forward the message to the pid of this room.
case whereis_name(room_name) do
:undefined ->
{:badarg, {room_name, message}}
pid ->
Kernel.send(pid, message)
pid
end
end
# SERVER
def init(_) do
{:ok, Map.new}
end
def handle_call({:whereis_name, room_name}, _from, state) do
{:reply, Map.get(state, room_name, :undefined), state}
end
def handle_call({:register_name, room_name, pid}, _from, state) do
case Map.get(state, room_name) do
nil ->
# When a new process is registered, we start monitoring it
Process.monitor(pid)
{:reply, :yes, Map.put(state, room_name, pid)}
_ ->
{:reply, :no, state}
end
end
def handle_info({:DOWN, _, :process, pid, _}, state) do
# When a monitored process dies, we will receive a `:DOWN` message
# that we can use to remove the dead pid from our registry
{:noreply, remove_pid(state, pid)}
end
def remove_pid(state, pid_to_remove) do
# And here we just filter out the dead pid
remove = fn {_key, pid} -> pid != pid_to_remove end
Enum.filter(state, remove) |> Enum.into(%{})
end
end
Popular in Questions
When I run the Plug and I recompile I wind up having to use Ctrl C to quit iex and start again. Witht the help of rlwrap I can use the cu...
New
I want to highlight html closing tags when i click a html tag. That works in .html files but doesnt work for html.eex templates. How can...
New
If I have a post route which an argument:
post /my_post_route/:my_param1, MyController.my_post_handler
How would get the post params ...
New
Hello again - after a longish gap I’ve decided I really must dig into Elixir and see what’s been happening here - so I have a few questio...
New
Why is it that the mnesia database isn’t the most preferred database for use in Elixir/Phoenix?
New
I have a phoenix application from which a user can download multiple(5-6) files of size 1MB. I couldn’t find anything related to sending ...
New
In the code below, if the create action is not set to accept “extra_key” as an input, it errors out with a message shown above. Is there ...
New
Other popular topics
Hi guys, i’m new in the Elixir world, and i have to say, that i love it!
i’m having some problem to understand anonymous functions with ...
New
Lets say I have map like this fetching from my database
%{"_id" => #BSON.ObjectId<58eb1a7a9ad169198c3dXXXX>, "email" => ...
New
Hi All,
I set a environment variables in dev.exs , like below code.
when i start server, how can i set the ${enable} value?
thanks.
d...
New
Credo is smart enough to check for (something like) this:
assert length(the_list) == 0
with this response:
Checking if an enum is empt...
New
What learn first? Rust or Elixir
Hi Elixir community!
I’m here because i want learn a new language. I’m a junior developer and mainly i ...
New
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #hex









