how do I start up a new GenServer for every user that engages and have it linked to the Supervisor
For that you need a new supervisor with :simple_one_for_one
strategy (in elixir 1.7 it will be replaces by DynamicSupervisor
which better describes what it does), which would be in the children
list of your :one_for_one
supervisor which also starts the registry and RegistrationServer
.
defmodule My.Application do
@moduledoc false
use Application
def start(_type, _args) do
# List all child processes to be supervised
children = [
{Registry, keys: :unique, name: My.Registry},
My.Registration.Supervisor
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_one, name: My.Supervisor]
Supervisor.start_link(children, opts)
end
end
and
defmodule My.Registration.Supervisor do
@moduledoc "Supervises registration processes"
use Supervisor
@spec start_link(any) :: Supervisor.on_start
def start_link(_opts) do # don't care about opts
Supervisor.start_link(__MODULE__, [], name: __MODULE__)
end
@doc "Starts a registration process for a user"
@spec start_registration(My.User.telegram_id) :: Supervisor.on_start_child
def start_registration(telegram_id) do
Supervisor.start_child(__MODULE__, [telegram_id])
end
@doc "Stops a registration process"
@spec stop_registration(My.User.telegram_id) :: true
def stop_registration(telegram_id) when is_integer(telegram_id) do # what if you pass a string, I sometimes did and couldn't understand why it wasn't working
case Registry.lookup(My.Registry, {:registration, telegram_id}) do
[] -> true
[{pid, _}] -> Process.exit(pid, :shutdown) # or something like this see https://hexdocs.pm/elixir/Process.html#exit/2 for more ways to stop a process
end
end
@spec init(list) :: {:ok, {:supervisor.sup_flags, [:supervisor.child_spec]}} | :ignore
def init([]) do
children = [
worker(My.Registration, [], restart: :transient) # restrarted only on fail (not on success)
]
opts = [
strategy: :simple_one_for_one
]
supervise(children, opts)
end
end
defmodule My.Registration do
@moduledoc "Keeps the state of a registration process for a user"
use GenServer
defstruct [
telegram_id: nil,
state: %{} # or something else
]
@doc """
* `:telegram_id` - telegram ID, equals to `:telegram_id` in corresponding `%My.User{}`
* `:state` - current state of the registration process
"""
@type t :: %__MODULE__{
telegram_id: My.User.telegram_id, # non_neg_integer
state: map
}
@spec start_link(My.User.telegram_id) :: GenServer.on_start
def start_link(telegram_id) do
GenServer.start_link(__MODULE__, telegram_id, name: via(telegram_id))
end
@spec via(My.User.telegram_id) :: {:via, module, {module, {:registration, My.User.telegram_id}}}
def via(telegram_id) when is_integer(telegram_id) do
{:via, Registry, {My.Registry, {:registration, telegram_id}}}
end
@spec init(My.User.telegram_id) :: {:ok, t}
def init(telegram_id) do
{:ok, %__MODULE__{telegram_id: telegram_id}}
end
# ... other functions for handling registration
end