Nona
Nona

Reputation: 5472

How to use Repo functionality in router.ex file?

So I'm doing some metaprogramming magic in Phoenix 1.3 router.ex file.

I need to use Demo.Repo to fetch some data from the database while I'm magically assembling some routes. How do I use this in router.ex?

When I run mix phx.routes, I get an error:

repo Demo.Repo is not started, please ensure it is part of your supervision tree

Do I need to edit the application.ex file somehow to start it in a different supervision tree?

Here's what I have:

The router.ex file

defmodule DemoWeb.Router do

  use Utils

 # other routes not included
   car_resources("/cars", CarController, 1)
end

The Utils module

defmodule Utils do
  @moduledoc """
  Helper functions for autogenerating routes
  """
  alias Demo.Repo
  alias CarType

  defmacro __using__(_options) do
    # quote gets the representation of any expression
    quote do
      import unquote(__MODULE__) # import __MODULE__ into the user's code
      @before_compile unquote(__MODULE__)
    end
  end

  defmacro car_resources(path, controller, level) do
    quote do
      resources unquote(base_path(path, level), unquote(controller)
    end
  end

  def base_path(path, level) do
     # do stuff
  end

  def cars do
    %{
      1 => Repo.get_by!(CarType, name: "ford"),
      2 => Repo.get_by!(CarType, name: "honda"),
    }
  end
end

Upvotes: 1

Views: 148

Answers (2)

José Valim
José Valim

Reputation: 51429

This approach doesn't work because you are depending on the database during compilation time. So in order to define the routes, you need to compile CarType and then start the repository by calling start_link on it.

While Elixir will be smart enough to figure those dependencies out, it shows a dangerous level of coupling. For example, if you add a new CarType, you need to recompile your code. It feels awkward having to deploy new code because a new type of cars was added to the database.

Instead I would nest the routes by the :car_type and always fetch the car type dynamically:

scope "/cars/:car_type" do
  resources "/cars", CarController
end

Then you fetch the :car_type in a plug and store it in the connection.

Upvotes: 1

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121010

Ecto.Repo is a GenServer in a nutshell, so one might go with explicit start/stop lifecycle:

def cars do
  with {:ok, pid} <- Demo.Repo.start_link() do
    cars = %{
      1 => Repo.get_by!(CarType, name: "ford"),
      2 => Repo.get_by!(CarType, name: "honda"),
    }
    Demo.Repo.stop(pid)
    cars
  else
    error -> IO.inspect(error, label: "unable to connect")
  end 
end

While this would probably work, I would strongly suggest using some text file (or any other independent source) rather than a database. First of all, test database is recreated on each subsequent run and you’ll likely end up with empty routes file during test sessions. Also, reading the config from the thing that is configured by the same config smells like the chicken-egg issue.

Upvotes: 2

Related Questions