A good amount of the documentation around Phoenix channels is based on a simple chat application from Chris McCord. The app allows users to choose a username, join a chatroom, and send/receive messages in real time using web sockets through Phoenix channels.
I have been delving deeper into Elixir/Erlang, and Erlang Term Storage (also known as ETS) is a core Erlang feature that I’d been wanting to try out. ETS is basically an in-memory database, somewhat similar to Memcached or Redis. This also means server restarts will drop everything in it. It can be avoided by taking advantage of Erlang’s hot code reloading when deploying, or using DETS, which is very similar to ETS but reads and writes to disk, making it much slower.
Since we’re in Elixir, I thought it would make sense to wrap the functionality of storing the chat logs in a GenServer named
ChatLog. We’ll override
start_link to give it those options, but could also allow users of ChatLog to pass in whichever options work best for them here as well:
The ChatLog accepts two options, one for the ETS table name, and another to limit the number of messages stored. The ChatLog will only have two public helper functions, one to add a message to the log, and another to fetch. They will only make their respective GenServer call:
The sample chat application this we’re building off of does not initially support multiple rooms, but we may eventually, so the ChatLog takes a room argument to specify exactly which room the message was sent in. The next step will actually be interacting with ETS in the GenServer calls to store and fetch messages. Fetching the messages is relatively easy, and it’ll be done directly in the call method, but the more complex insert is in its own function.
The insert checks whether the ETS table already exists, because if it doesn’t exist, we can’t use the head/tail syntax to insert an object into the list. In the case that the table doesn’t it exist, we simply insert a list with the first message as the single element. The fetch method does a straightforward lookup on the key.
The work up to this point should cover the internals necessary for the ChatLog, and all that’s left is to start using it in the chat application. Since we added a new GenServer, we’ll add it to our base app so it gets started when our application starts:
We’ll also add the chat logging into the channel that receives new messages so they get logged. We’ll store them the same way we send them through the web socket and the front-end of the application can treat them the same:
We can display old messages for people just joining a chat by fetching and then rendering our old messages in the template.
:ets.lookup will give us an empty list if nothing exists yet, which works perfectly, but the logs will have the most recent message first.
Once the messages are being passed to the view, we iterate over and render HTML for the list of messages:
And now the chat application will display old messages for new users and between refreshes!