Properly Testing Telegram integration within app

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
3 Likes