James_E

James_E

Need to display resources in the exact order they are declared - ordered Map?

I’m working on a program that needs to display the status of several resources. The dashboard needs to consistently display these resources in the exact order the resources are declared in a config file.

Right now, I’m representing each “resource” as a Struct, and the overall state of the dashboard as a List of these structs, where that list is ordered according to how the resources should display.

defmodule FooApp.Application.Model do
  use GenServer

  require Logger

  defmodule State do
    @enforce_keys [:name, :resources]
    defstruct [:name, :resources, :_pubsub_topic]
  end

  defmodule Resource do
    @enforce_keys [:name]
    defstruct [:name, status: "undefined"]

    @allowable_statuses ["green", "green-with-exception", "red"]
    def status_ok(status) do
      status in @allowable_statuses
    end
  end

  defp list_update_such(list, fun_pred, fun) do
    Enum.map(list, &if fun_pred.(&1) do fun.(&1) else &1 end)
  end

  @impl true
  def init({name, resource_names}) do
    {:ok, %State{
      name: name,
      resources: resource_names |> Enum.map(&%Resource{name: &1}),
      _pubsub_topic: name
    }}
  end

  @impl true
  def handle_continue(:broadcast_statechange, state) do
    Phoenix.PubSub.broadcast!(FooApp.PubSub, state._pubsub_topic, {:statechange, state});
    {:noreply, state}
  end

  @impl true
  def handle_call(:get_state, _from, state) do
    {:reply, state, state}
  end

  @impl true
  def handle_call({:act, actor, action}, from, state) do
    case action do
      {:set_resource_status, resource_name, new_status} -> (
        with \
          true <- actor === "director" || {:error, "Unauthorized"},
          resources = state.resources,
          true <- Resource.status_ok(new_status) || {:error, "Invalid status"}
        do
          {:reply, :ok, %{state |
            resources: list_update_such(resources, &(&1.name === resource_name), &%{&1 | status: new_status})
          }, {:continue, :broadcast_statechange}}
        else
          {:error, error} -> {:reply, {:error, error}, state}
        end
      )

      _ -> (
        Logger.warning("Client #{inspect from} attempted invalid action");
        {:reply, {:error, "Action incongruent with current state"}, state}
      )
    end
  end
end

Now, I have heard it said that you should “make it work, then make it pretty, then if needed make it fast”. The above works, but that list_update_such seems like such a horrible kludge. I did it because it was the prettiest option I could pull out of this list of options I am aware of:

  • A List [resource, ...] is awkward because there isn’t great tooling to update
  • A List [{resource_name, resource}, ...] is strictly more awkward because you have the key in 2 places yet it still doesn’t work with tools like put_in when the “keys” are strings;
  • A Map %{resource_name => resource, ...} is more awkward because the key’s duplicated, and also just straight up unallowable because it doesn’t guarantee any ordering;
  • Expanding the storage to use a separate index, like %S{resource_ordering: [resource_name, ...], resources: %{resource_name => resource, ...}} seems complete over-engineering since we’re not likely to have more than 25 or so resources, and still has that extra elegance fine of the keys being stored in multiple places.

Is there any data structure I’m just completely missing, or am I about on the right track here?

Most Liked

kwando

kwando

Maybe create your own data type for it?

Could be as simple as a tuple with a list and a map.

{["A", "B", "C"], %{"A" => ..., "B" => ...., "C" =>}}

The list contains the order and the map contains the resources keyed by the same value used in the list
.

Edit:

If you make it a struct you can make it play nicely with Enum and friends with some protocols

dimitarvp

dimitarvp

I’d say do what @kwando advised + make accessor functions for that data structure and you are sorted (heh).

LostKobrakai

LostKobrakai

:maps.iterator can give you a stable iteration though a map.

Where Next?

Popular in Questions Top

New
Werner
Hi, I’m using Ubuntu 18.04 and after updating to OTP-24.0 yesterday i have this warning when I run “mix local.hex”: 14:57:30.512 [warn] ...
New
nsuchy
Hi. I’ve noticed that Windows Powershell has it’s own IEX command and you cannot access Elixir’s IEX due to the conflict. This isn’t a cr...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
vac
Hi, I'm quite new in Elixir and I'm trying to format a string to a PEM format. I have the certificate value like MIIDBTCCAe2...... and ...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
qwerescape
Is there a way to get the call stack or stack trace at any point in the code? Not from exceptions, but an expression that returns how the...
New
vonH
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
Codball
Mix format works fine if run from the cmd. I’ve followed this to facilitate the implementation into VSC which involves downloading an ext...
New
joeerl
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

Other popular topics Top

shahryarjb
Hello, I get Persian date from my client and convert it to normal calendar like this: def jalali_string_to_miladi_english_number(persi...
New
aalberti333
As the title describes, I’m trying to run Enum.map() over a list of key/value pairs, where the value is a map. My data looks like this: ...
New
fireproofsocks
I’m working on defining a simple Ecto schema for a table (in PostGres), but I don’t see where I can define a column as NOT NULL. Conside...
New
chrismccord
This release brings a number of exciting features, including integration with the new Phoenix LiveDashboard and Phoenix LiveView. There h...
New
script
If I have a string “1000 cfu/ml” . I want to remove the characters and / and space . So the string is like this "1000" What is the ...
New
mcarvalho
What is the difference between System.get_env and Application.get_env? For example, what are best practices to use one versus another.
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
alice
Hey, Just curious what are the main benefits of Elixir compared to Clojure? When is Elixir more useful than Clojure and vice versa? Th...
New
Fl4m3Ph03n1x
About me? ( if you have nothing better to do than reading about some random guy in the internet :stuck_out_tongue: ) Hello all, this is ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New

We're in Beta

About us Mission Statement