Phoenix Wish List

Sure, but probably not today.

Can you give an example?

Edit: sorry, didn’t notice that you replied already.

use Phoenix.Controller, log: false works right?

Could you explain this more concretely?

@tmbb As in I’d like to have a way to be able to generate my own match functions while still generating helpers and all that. So instead of just this:

    post "/:ah_tag/requirement/:id/student/:pidm/:date/edit", RequirementController, :student_update, as: :ah_requirement

I’d like to be able to add other things such as a function that is called on the id or pidm or date or so variables that ensure validity, or else the match fails on this one and it goes to another one, along with priority and so forth. Though honestly I’d like to actually be able to decorate the callbacks in a module instead and then ‘register’ that module with the Router and have it build up the list. I’d like to be able to do something like:

@route(
  priority: 10,
  params: %{
    is_allowed: can?(&1, %SomePermission{}),
    id: &Accounts.get_id/1,
    date: &Timex.parse(&1, "{YYYY}-{0M}-{D}")},
    can_access_record: can?(&1, %SomePermission{id: &2.id, date: &2.date}), # Referencing past values
)
def list(conn, params) do
  ...
end

But if those tests fail then this route isn’t ever even matched and other can attempt to be matched, etc…

It can still be efficient, phoenix makes a dispatch tree at compile-time of efficient path matcher’s, then it runs down a list with checks (that save values for re-use on later potential matches) until it gets one that matches, then calls it passing in the data. This is the way some native-code web frameworks work and it is so very convenient…

2 Likes

That causes a problem in which you can’t predict the order of route matching. It’s a problem in some python frameworks like flask and pyramid. Registering all your routes in the Router at least makes sure they are matched in a given order

Eh in those perhaps, but in the ones I’ve used it’s entirely predictable. Like the one I’m enjoying most is Rocket in Rust, you should give it a try. :slight_smile:

Hm…That rocket thing still requires you to declare the routes in the Router, but it does allow you to decorate the parameters near the handler. I bet you can do something similar with Elixir by having some macros decorate the conroller actions

Yes, exactly what I was proposing!

The router also needs to be enhanced to allow a fallback if the function fails (a special return type perhaps? then the encoding could happen in-action or so).

That’s a major problem with function clauses and pattern matching: they’re not composable. They are more efficient because the compiler can optimize the checking for patterns, but there’s no way of matching subsequent clauses if the previous one has matched.

The only way to do that is through a (less efficient) linear check, and use seomthing like {:ok, value} tuples in the return values (I’ve learn this the hard way when working on Darwin)

They are though I strongly wager. You use pattern matching on the path and type of method and so forth, then further dispatch via functions. You don’t need to jump out of the pattern matching side, only the function tests, the pattern matching becomes very ‘set’ once you build up the route tree. :slight_smile:

Basically this moves the tests that almost every function does already out of their functions while also allowing them to dispatch based on different things and types.

I wish conn was renamed to req and a separate “context” was also used for storing values. Like if a Plug decodes a header that sets a deadline for this request, doesn’t make sense to put that decoded integer/time in the connection or the request, it goes in the “context”.

Renaming conn is just because I think it is odd, especially since these days a connection can end up being used for multiple HTTP requests.

I think “context” should actually use the process dictionary, but still good to define a separate interface and standard key names because it shouldn’t be tied to Phoenix.

1 Like

Thanks for the input! That’s exactly what I did https://github.com/akoutmos/telemetry_filter :). I’ll go ahead and close the PR now.

1 Like

That would work for controllers that I own…but won’t work for something that is in my dependencies like https://github.com/deadtrickster/prometheus-plugs for example.

1 Like

more jobs and projects.

I am hunting Elixir and Phoenix projects to work on… from a long time…

I have plug for that:

defmodule MyAppWeb.Plugs.Health do
  @behaviour Plug

  import Plug.Conn

  @moduledoc """
  Catches requests to `/health` and returns statistics about running applications.
  """

  @enforce_keys [:applications]
  defstruct [:applications]

  @impl true
  def init(opts) do
    apps = Keyword.fetch!(opts, :applications)

    unless is_list(apps) and Enum.all?(apps, &is_atom/1) do
      raise ArgumentError,
            ":applications is required to be list of atoms, got #{inspect(apps)}"
    end

    %__MODULE__{applications: apps}
  end

  @impl true
  def call(%Plug.Conn{path_info: ["health"]} = conn, opts) do
    versions = for app <- opts.applications, into: %{}, do: {app, version(app)}

    conn
    |> put_resp_header("content-type", "application/json")
    |> send_resp(200, Jason.encode_to_iodata!(%{applications: versions}))
    |> halt()
  end

  def call(conn, _opts), do: conn

  defp version(app) when is_atom(app) do
    case Application.spec(app, :vsn) do
      nil -> nil
      vsn -> List.to_string(vsn)
    end
  end
end

That is simply put before Plug.Telemetry entry in Endpoint and it just never is handled by Phoenix.Logger nor any other handler.

3 Likes

Thank you for such an amazing framework!

This is the single wish my team has after our first Phoenix project in the last 5 months:

I wish for a faster time between I save a file and I see the changes in the browser. This would result in a better developer experience.

Some considerations:

  1. We’ve learned to analyse the compilation tree using mix xref graph and used Module.concat to avoid a lot of dependencies.
  2. It happens that we have some pretty big LiveView templates that take 10 seconds to recompile. We don’t know how to work around other than putting them in separate views. We’re still waiting for a hint here.
  3. There are a few low level files (contexts and schemas) which very many views depend upon. Changing such file results in a slow recompilation time.
1 Like

Edeliver and distillery were driving me bonkers. I switched to a dokku deployment and haven’t looked back.

Fantastic walkthrough here:

1 Like