Search code examples
postgresqlelixirphoenix-framework

How to correctly pass Postgrex.Notifications to a supervisor


I'm trying to setup postgres triggers in phoenix.

The docs state

In order to use it, first you need to start the notification process. In your supervision tree:

{Postgrex.Notifications, name: MyApp.Notifications}

So here's my implementation:

application.ex

defmodule Foo.Application do
  ...

  def start do
    children = [Foo.Repo, {Postgrex.Notifications, Name: Foo.Notifier}]
    opts = [strategy: :one_for_one, name: Foo.Supervisor]
    Supervisor.start_link(children, ops)
  end
end

notifier.ex

defmodule Foo.Notifier do
  def child_spec(opts) do
    %{
      id: __MODULE__,
      start: {__MODULE__, :start_link, [opts]}
    }
  end

  def start_link(opts \\ []),
    do: GenServer.start_link(__MODULE__, opts)

  def init(opts) do
    # setup postgres listeners
  end
end

I get an error message saying

The module Postgrex.Notifications was given as a child to a supervisor but it does not implement child_spec/1.

If you own the given module, please define a child_spec/1 function that receives an argument and returns a child specification as a map.

However, if you don't own the given module and it doesn't implement child_spec/1, instead of passing the module name directly as a supervisor child, you will have to pass a child specification as a map

So I followed the example in the error

    %{
      id: Postgrex.Notifications,
      start: {Postgrex.Notifications, :start_link, [arg1, arg2]}
    }

my implementation:

children = [
  Foo.Repo,
  %{
    id: Postgrex.Notifications,
    start: {Postgrex.Notifications, :start_link, opts}
  }
]

Supervisor.start_link(children, opts)

But now I get the error

** (Mix) Could not start application survey: exited in: Foo.Application.start(:normal, [])
    ** (EXIT) an exception was raised:
        ** (UndefinedFunctionError) function Supervisor.start_link/1 is undefined or private

Solution

  • I found this tutorial

    Instead of passing a tuple to the supervisor tree just pass your module and call start_link manually

    application.ex

    children = [Foo.Repo, Foo.Notifier]
    

    notifier.ex

    @impl true
    def init(opts) do
      with {:ok, _pid, _ref} <- Repo.listen("some_channel_name") do
        {:ok, opts}
      else
        error -> {:stop, error}
      end
    end
    

    repo.ex

    defmodule Foo.Repo do
      def listen(channel) do
        with {:ok, pid} <- Postgrex.Notifications.start_link(__MODULE__.config()),
             {:ok, ref} <- Postgrex.Notifications.listen(pid, channel) do
          {:ok, pid, ref}
      end
    end