liebus
How to store "message history" & list of ChatRoom participants (using join/leave functions)?
Hi. I am learning Elixir recently, decided to make a small simple project “Chat Room” for myself to learn some OTP. Please help to implement “message history” (to store 100 messages for example) and list of chat room participants (using join/leave functions).
I do not understand clearly how I can send a message that is stored in the history of the last 100 messages and broadcast to chat members (I know I need to use pid but all other stuff is unclear)
I made some type of pseudocode for ‘Message’ & ‘Room’ modules (and I need ‘User’ module too as I understand) but I have a big troubles with structs - how and where to define them correctly and how integrate them into my existing code
Message:
%Message{from: "username", message: "hello world"}
Room:
%Room{
users: [%User{name: "user1", pid: PID }],
history: [%Message{from: "username", message: "hello world"} ... ],
name: "room name"
}
And some pseudocode callback
# def handle_call(:chat_history, {FromPid, _Ref}, %Room{history: history, users: users} = room) do
# users
# |> Enum.map(&(&1.pid))
# |> then(fn pids -> FromPid in pids end)
# |> case do
# true -> {:reply, history, room}
# false -> {:reply, {:error, "not a chat member"}, room}
# end
Thx for help, this is my code:
server.ex
Summary
defmodule Chat.Server do
use GenServer
#Client
def start_link() do
GenServer.start_link(__MODULE__, [])
end
def send_message(pid, message) do # send_message
GenServer.cast(pid, message)
end
def get_messages(pid) do # get list of messages ref to pid
GenServer.call(pid, :get_messages) # :get_messages - 1st arg of handle_call func
end
def remove_message(pid, message) do
GenServer.cast(pid, {:remove, message}) # :remove will pattern match with callback
end
def stop(pid) do
GenServer.stop(pid, :normal, :infinity) # pid, shutdown reason(:normal - default), timeout(:infinity - default)
end
def init(list) do
{:ok, list}
end
def terminate(_reason, list) do
IO.puts("All messages are done")
IO.inspect(list)
:ok
end
def handle_cast({:remove, message}, list) do # pm ':remove' with tuple in remove_message
updated_list = Enum.reject(list, fn(i) -> i == message end)
{:noreply, updated_list}
end
def handle_cast(message, list) do
updated_list = [message|list]
{:noreply, updated_list}
end
def handle_call(:get_messages, _from, list) do # testing (atom, 2 elem tuple - colors PID, existing state of message list)
{:reply, list, list}
end
end
Marked As Solved
RudManusachi
Hi, @liebus!
May be you could try to think of the behavior of the application:
For example:
- messages are stored in the
Chat.Server - users can read them from there
- users can join the
Chat.Serverand then they will get “notified” about new messages - users could store a number of unread messages associated with a chat
Then model the interface first.. (without implementation.. just as in TDD)
For example:
Say Chat.Server holds the list of users and the list of messages.
{:ok, chat} = Chat.Server.start_link() # chat is a pid of Chat.Server
{:ok, alice} = User.new(name: "Alice") # alice and bob are pids of Users
{:ok, bob} = User.new(name: "Bob")
:ok = Chat.Server.join(chat, alice)
:ok = Chat.Server.join(chat, bob)
# check the list of users
users = Chat.Server.list_users(chat)
# assure that both Alice and Bob are there
alice in users == true
bob in users == true
# send a message from Alice
Chat.Server.send_message(chat, "Hi, there", from: alice)
# check that Bob got an unread message
%{chat => 1} = User.unread_messages(bob)
So Chat.Server’s initial state might look like %{users: [], messages: []}
Chat.Server.join/2 would update that state adding user to the list.
every Chat.Server.send_message/3 will add message to the list and also broadcast a “notification” to all users, except the user that message came from.
User is also represented as a process that holds a map with chats and the number of unread messages. So that when it receives a notification from a Chat.Server it increments it.
BTW, it would be good if you convert that example to ExUnit test case
and keep adding your testing examples and just run mix test to make sure it all works as expected =)
In more realistic scenario when multiple users interact with single Chat.Server fetching the list of users and the list of messages - you would want to move those lists to the DB that provides with concurrent access and keep Chat.Server as an interface to that DB. I think that might be a good example to learn a little bit of :ets ![]()
Popular in Questions
Other popular topics
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
- #advent-of-code
- #elixirconf-us
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #security
- #performance









