I am not sure if is appropriate to ask for help for a refactoring problem in this forum. In case it isn’t I’ll delete the post right away.
I wrote a little Phoenix 1.6 LiveView demo which simulates a basic stock market.
Here’s the code:
defmodule DemoWeb.StockWatchLive do
use DemoWeb, :live_view
def mount(_params, _session, socket) do
if connected?(socket), do: Process.send_after(self(), :tick, 2500)
{:ok, update_world(socket)}
end
def render(assigns) do
~H"""
<p>Available money: <%= @balance %><br/>
Value of the portfolio: <%= @portfolio_value %></p>
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Portfolio</th>
<th colspan="2"></th>
</tr>
</thead>
<tbody>
<%= for {stock_name, value} <- @stocks do %>
<tr>
<td><%= stock_name %></td>
<td><%= value %></td>
<td><%= Map.get(@portfolio, stock_name) %></td>
<td width="20%">
<%= if value <= @balance do %>
<button phx-click="buy" phx-value-ref={stock_name}>Buy!</button>
<% end %>
</td>
<td width="20%">
<%= if Map.has_key?(@portfolio, stock_name) && Map.get(@portfolio, stock_name) > 0 do %>
<button phx-click="sell" phx-value-ref={stock_name}>Sell!</button>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
"""
end
def handle_info(:tick, socket) do
Process.send_after(self(), :tick, 2500)
{:noreply, update_world(socket)}
end
def handle_event("buy", %{"ref" => stock_name}, socket) do
{:noreply, execute_order(stock_name, 1, socket)}
end
def handle_event("sell", %{"ref" => stock_name}, socket) do
{:noreply, execute_order(stock_name, -1, socket)}
end
defp stock_price(socket, stock_name) do
Map.get(socket.assigns.stocks, stock_name)
end
defp portfolio_value(socket) do
if Map.has_key?(socket.assigns, :portfolio) do
portfolio = socket.assigns.portfolio
stocks_value_list =
for {stock_name, amount} <-
portfolio,
into: [],
do: stock_price(socket, stock_name) * amount
Enum.sum(stocks_value_list)
else
0
end
end
defp execute_order(stock_name, amount, socket) do
price = stock_price(socket, stock_name)
portfolio =
socket.assigns.portfolio
|> Map.update(stock_name, 1, &(&1 + amount))
balance = socket.assigns.balance + -1 * amount * price
socket = assign(socket, portfolio: portfolio)
portfolio_value = portfolio_value(socket)
socket
|> assign(portfolio_value: portfolio_value)
|> assign(balance: balance)
end
defp update_world(socket) do
if Map.has_key?(socket.assigns, :stocks) do
stocks =
for {k, v} <-
socket.assigns.stocks,
into: %{},
do: {k, random_fluctuation(v)}
socket
|> assign(stocks: stocks)
|> assign(portfolio_value: portfolio_value(socket))
else
initialize_world(socket)
end
end
defp random_fluctuation(value) do
case Enum.random(1..4) do
4 -> value + Enum.random(-5..6)
_ -> value
end
end
defp initialize_world(socket) do
socket
|> assign(
stocks: %{
"Stock A" => 100,
"Stock B" => 200,
"Stock C" => 300,
"Stock D" => 400
}
)
|> assign(portfolio: %{})
|> assign(balance: 1_000)
|> assign(portfolio_value: 0)
end
end
The code doesn’t feel as clean and tidy as I’d like it to be. It works but is feels messy. But I don’t have a good idea how to refactor it.
How can I improve it?