lucasprag
Hi.
I’m learning about Phoenix by trying to build an app where logged-in users would be able to vote and see each other responses in real time. Like a Sprint Planning Estimation session.
I was able to:
- sign in users with Google
- see who is online using Presence
- all using LiveView, no changes in JavaScript. I would like to keep it this way if possible
Question: where to save the votes so all users see each other votes in real time?
I could save in the user object, but I would prefer to save it in a separate thing so I can use the Presence module to see who is online in other parts of the app.
I would like to avoid saving the votes in the database because they are ephemeral and I don’t need their history.
This is what I have so far:
# lib/app_web/live/online/index.ex
defmodule AppWeb.OnlineLive do
use AppWeb, :live_view
def mount(params, session, socket) do
session_random_id = params["session_random_id"] # random string generated on page load
current_user = session["current_user"] # who is authenticated with Google
socket = stream(socket, :online_users, [])
|> assign(:current_user, current_user)
|> assign(:session_random_id, session_random_id)
socket =
if current_user && connected?(socket) do
# adding the current user to the list of users that are online
AppWeb.Presence.track_user(session_random_id, %{id: current_user.id, name: current_user.name})
# listing to the session channel to know who is online
AppWeb.Presence.subscribe(session_random_id)
stream(socket, :online_users, AppWeb.Presence.list_online_users(session_random_id))
else
socket
end
{:ok, socket}
end
@impl true
def render(assigns) do
~H"""
<h2>Online users:</h2>
<ul id="online_users" phx-update="stream">
<li :for={{dom_id, %{id: id, user: user, metas: _metas}} <- @streams.online_users} id={dom_id}>
id: <%= id %> name: <%= user.name %>
</li>
</ul>
<h2>Click to vote</h2>
<button class="bg-zinc-100 rounded px-2 py-1 hover:bg-zinc-200" phx-click="vote" phx-value-option="2">2</button>
<button class="bg-zinc-100 rounded px-2 py-1 hover:bg-zinc-200" phx-click="vote" phx-value-option="3">3</button>
<button class="bg-zinc-100 rounded px-2 py-1 hover:bg-zinc-200" phx-click="vote" phx-value-option="5">5</button>
<button class="bg-zinc-100 rounded px-2 py-1 hover:bg-zinc-200" phx-click="vote" phx-value-option="8">8</button>
<button class="bg-zinc-100 rounded px-2 py-1 hover:bg-zinc-200" phx-click="vote" phx-value-option="13">13</button>
"""
end
@impl true
def handle_event("vote", %{"option" => option}, socket) do
# QUESTION: where to save the votes so all users see each other votes in real time?
# I could save in the user object, but I would prefer to save it in a separate thing/module/etc
{:noreply, socket}
end
def handle_info({AppWeb.Presence, {:join, presence}}, socket) do
{:noreply, stream_insert(socket, :online_users, presence)}
end
def handle_info({AppWeb.Presence, {:leave, presence}}, socket) do
if presence.metas == [] do
{:noreply, stream_delete(socket, :online_users, presence)}
else
{:noreply, stream_insert(socket, :online_users, presence)}
end
end
end
# lib/app_web/channels/presence.ex
defmodule AppWeb.Presence do
@moduledoc """
Provides presence tracking to channels and processes.
See the [`Phoenix.Presence`](https://hexdocs.pm/phoenix/Phoenix.Presence.html)
docs for more details.
"""
use Phoenix.Presence,
otp_app: :app,
pubsub_server: App.PubSub
def init(_opts) do
{:ok, %{}}
end
def fetch(_topic, presences) do
for {key, %{metas: [meta | metas]}} <- presences, into: %{} do
{key, %{metas: [meta | metas], id: meta.id, user: meta}}
end
end
def handle_metas(topic, %{joins: joins, leaves: leaves}, presences, state) do
for {user_id, presence} <- joins do
user_data = %{id: user_id, user: presence.user, metas: Map.fetch!(presences, user_id)}
msg = {__MODULE__, {:join, user_data}}
Phoenix.PubSub.local_broadcast(App.PubSub, "proxy:#{topic}", msg)
end
for {user_id, presence} <- leaves do
metas =
case Map.fetch(presences, user_id) do
{:ok, presence_metas} -> presence_metas
:error -> []
end
user_data = %{id: user_id, user: presence.user, metas: metas}
msg = {__MODULE__, {:leave, user_data}}
Phoenix.PubSub.local_broadcast(App.PubSub, "proxy:#{topic}", msg)
end
{:ok, state}
end
def list_online_users(session_random_id),
do: list(session_random_id) |> Enum.map(fn {_id, presence} -> presence end)
def track_user(session_random_id, user),
do: track(self(), session_random_id, user.id, user)
def subscribe(session_random_id),
do: Phoenix.PubSub.subscribe(App.PubSub, "proxy:#{session_random_id}")
end
Does anyone know where and how to save the votes?
Thank you for any insight. ![]()
Trending in Questions
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
Hello,
I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
Documentation
While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
So my question is quite simple and i have found no conclusive answer on forum, google or AI.
Should we use :erlang.float for Integer to ...
New
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
Other Trending Topics
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hi there! We created Gust: A task orchestrator inspired by Airflow.
For those who have never heard about Aiflow, it’s a Python-based wor...
New
Hi everyone!
The first release candidate for the Expert language server project is now available!
We’ve published a press release detai...
New
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixirconf-us
- #ai
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #api
- #forms
- #metaprogramming
- #hex
- #security










Showing Posts 1 to 3- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
kokolegorille
It is time for You to learn about GenServer and OTP
A good place to store state is in a process
lucasprag
Ha! Finally I have a use-case to use this GenServer thing. Thank you.
If I’m understanding the docs right, it sounds like I could have a GenServer holding the user votes in a structure like this:
Basically a map mapping user ids to their vote per session.
Is that what you mean? That sounds awesome. I will give it a try.
I still have one question though: how would LiveView know to refresh the list of votes to show in the browser? Do I also need to use another PubSub somehow?
kwando
Simplest would be to let your genserver send a pubsub message to a common channel.