In both approaches, I focused on how to map processes by a given set of identifiers or groups, and then display the saved structure for filtering data.
%{group => [users]} .
I realized that groups would be limited as opposed to users, so I created one process module that uses group names as keys.
I am afraid that in the future there will be many users in several groups, so my question is how can I split the current UserGroupServer module to save many separate processes identified by group names? I would like to keep the functionality of the current module inside the init processes according to the list of groups, in addition, I do not know how to map each process to get groups by user_id?
Currently, I run only one process in Phoenix lib/myapp.ex , including the module in the list of child trees, so I can directly call UserGroupServer in the channels.
defmodule UserGroupServer do use GenServer ## Client API def start_link(opts \\ []) do GenServer.start_link(__MODULE__, :ok, opts) end def update_user_groups_state(server, data) do {groups, user_id} = data GenServer.call(server, {:clean_groups, user_id}, :infinity) users = Enum.map(groups, fn(group) -> GenServer.call(server, {:add_user_group, group, user_id}, :infinity) end) Enum.count(Enum.uniq(List.flatten(users))) end def get_user_groups(server, user_id) do GenServer.call(server, {:get_user_groups, user_id}) end def users_count_in_gorup(server, group) do GenServer.call(server, {:users_count_in_gorup, group}) end ## Callbacks (Server API) def init(_) do {:ok, Map.new} end def handle_call({:clean_groups, user_id}, _from, user_group_dict) do user_group_dict = user_group_dict |> Enum.map(fn({gr, users}) -> {gr, List.delete(users, user_id)} end) |> Enum.into(%{}) {:reply, user_group_dict, user_group_dict} end def handle_call({:add_user_group, group, user_id}, _from, user_group_dict) do user_group_dict = if Map.has_key?(user_group_dict, group) do Map.update!(user_group_dict, group, fn(users) -> [user_id | users] end) else Map.put(user_group_dict, group, [user_id]) end {:reply, Map.fetch(user_group_dict, group), user_group_dict} end end
Test:
defmodule MyappUserGroupServerTest do use ExUnit.Case, async: false setup do {:ok, server_pid} = UserGroupServer.start_link {:ok, server_pid: server_pid} end test "add users", context do c1 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:a, :b, :c], 1}) assert(1 == c1) c2 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:c, :d], 2}) assert(2 == c2) c3 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:x], 2}) assert(1 == c3) c4 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:d], 1}) assert(1 == c4) c5 = UserGroupServer.update_user_groups_state(context[:server_pid], {[:d, :c], 2}) assert(2 == c5) end end
Old approach %{user => [groups]}
The monitor saves the list of groups assigned by user_id. How to find users in this group? Should I create separate processes that will handle the m..n relationship between groups and user IDs? What should I change to get each user group and then display them?
Server implementation:
defmodule Myapp.Monitor do use GenServer def create(user_id) do case GenServer.whereis(ref(user_id)) do nil -> Myapp.Supervisor.start_child(user_id) end end def start_link(user_id) do GenServer.start_link(__MODULE__, [], name: ref(user_id)) end def set_groups(user_pid, groups) do try_call user_pid, {:set_groups, groups} end def handle_call({:set_groups, groups}, _from, state) do { :reply, groups, groups }
Head:
defmodule Myapp.Supervisor do use Supervisor def start_link do Supervisor.start_link(__MODULE__, :ok, name: __MODULE__) end def start_child(user_id) do Supervisor.start_child(__MODULE__, [user_id]) end def init(:ok) do supervise([worker(Myapp.Monitor, [], restart: :temporary)], strategy: :simple_one_for_one) end end
Example:
Monitor.create(5) Monitor.set_groups(5, ['a', 'b', 'c']) Monitor.create(6) Monitor.set_groups(6, ['a', 'b']) Monitor.set_groups(6, ['a', 'c']) # Monitor.users_in_gorup('a') # -> 2 # Monitor.users_in_gorup('b') # -> 1 # Monitor.users_in_gorup('c') # -> 2 # or eventually more desired: # Monitor.unique_users_in_groups(['a', 'b', 'c']) # -> 2 # or return in set_groups unique_users_in_groups result