Phoenix

Phoenix notes based on “Programming Phoenix” book by Chris McCord and official guides.


endpoints

https://hexdocs.pm/phoenix/routing.html#path-helpers:

Think of endpoints as the entity that handles requests just up to the point where the router takes over. That includes starting the app/server, applying configuration, and applying the plugs common to all requests.

https://hexdocs.pm/phoenix/endpoint.html:

Phoenix applications starts the HelloWeb.Endpoint as a supervised process. By default, the Endpoint is added to the supervision tree in lib/hello/application.ex as a supervised process. Each request begins and ends its lifecycle inside your application in an endpoint. The endpoints handles starting the web server and transforming requests through several defined plugs before calling the Router.

Endpoints gather together common functionality and serve as entrance and exit for all of the HTTP requests to your application. The endpoint holds plugs that are common to all requests coming into your application.

If code reloading is enabled, a socket will be used to communicate to the browser that the page needs to be reloaded when code is changed on the server.

It is also possible for an application to have multiple endpoints, each with its own supervision tree.

routes

  1. https://hexdocs.pm/phoenix/routing.html
  • Routes which begin with an HTTP verb name expand to a single clause of the match function.
  • Routes which begin with ‘resources’ expand to 8 clauses of the match function.
  • Resources may restrict the number of match function clauses by using the only: or except: options.
  • Any of these routes may be nested.
  • Any of these routes may be scoped to a given path.
  • Using the as: helper option in a scope can reduce duplication.
  • Using the as: helper option for scoped routes eliminates unreachable paths.

as: option

  1. https://hexdocs.pm/phoenix/routing.html#scoped-routes

passing as: for scope option allows to change the name of path helpers for all routes inside scope block:

scope "/v1", as: :v1 do
  get "/cards", CardController
  # other routes
end

or else it’s possible to do it on per route basis by passing as: option to individual routes:

scope "/v1" do
  get "/cards", CardController, as: :v1
  # other routes
end

singleton routes (singular resources in Rails)

resources "/sessions", SessionController, singleton: true

=

resource :session

scopes

  1. https://hexdocs.pm/phoenix/routing.html#scoped-routes

Note that Phoenix will assume that the path we set ought to begin with a slash, so scope “/admin” do and scope “admin” do will both produce the same results.

according to my experience this is not true:

scope "/", BillingWeb do
  pipe_through :api

  scope "v1" do
    # ...
  end
end
$ mix phx.routes
warning: router paths should begin with a forward slash, got: "v1"

accepts plug

accepts plug in router lists MIME types supported in Accept request header - it doesn’t work with Content-Type request header.

if MIME type is not specified in Accept request header response is still rendered as usual - only when unsupported MIME type is explicitly set in Accept request header is error raised (rendering error page in Markdown for some reason):

12:02:59.285 [debug] ** (Phoenix.NotAcceptableError) no supported media type in accept header.

Expected one of ["json"] but got the following formats:

  * "application/html" with extensions: []

plugs

  1. https://hexdocs.pm/phoenix/plug.html

The basic idea of Plug is to unify the concept of a “connection” that we operate on. This differs from other HTTP middleware layers such as Rack, where the request and response are separated in the middleware stack.

module plug:

The module only needs to implement two functions:

  • init/1 which initializes any arguments or options to be passed to call/2
  • call/2 which carries out the connection transformation. call/2 is just a function plug that we saw earlier

controllers

  1. https://hexdocs.pm/phoenix/controllers.html

Phoenix controllers also build on the Plug package, and are themselves plugs.

A step beyond this is rendering pure JSON with the json/2 function. We need to pass it something that the Poison library can parse into JSON, such as a map.

It is worth noting that the text/2, json/2, and html/2 functions require neither a Phoenix view, nor a template to render.

Phoenix allows us to change formats on the fly with the _format query string parameter.

if no format is specified explicitly with _format query parameter it’s considered to be the first one accepted by current pipeline in router (say, json format for request that is processed with api pipeline).

NOTE: all keys and values if incoming params are strings!

scrubbing params

  1. https://stackoverflow.com/a/33976964/3632318
  2. https://hexdocs.pm/phoenix/Phoenix.Controller.html#scrub_params/2

it doesn’t make sense to use it when providing API.

status codes

https://hexdocs.pm/plug/Plug.Conn.Status.html:

https://stackoverflow.com/a/2342589/3632318:

For a PUT request: HTTP 200 (OK) or HTTP 204 (No Content) should imply “resource updated successfully”.

For a DELETE request: HTTP 200 (OK) or HTTP 204 (No Content) should imply “resource deleted successfully”. HTTP 202 can also be returned which would imply that the instruction was accepted by the server and the “resource was marked for deletion”.

send response without content (head)

  1. https://github.com/phoenixframework/phoenix/pull/818
  2. https://stackoverflow.com/a/37318445/3632318
  3. https://hexdocs.pm/plug/1.4.5/Plug.Conn.html#send_resp/3
  4. https://medium.com/@kaisersly/render-different-formats-in-phoenix-2nd-attempt-8775a289ebb1

Rails version: head(:ok). send empty body in Phoenix:

send_resp(conn, :ok, "")

views

  1. https://hexdocs.pm/phoenix/views.html

If you are familiar with decorators or the facade pattern, this is similar.

Note that we didn’t need to fully qualify title/0 with HelloWeb.LayoutView because our LayoutView actually does the rendering (it’s a local function call). In fact, “templates” in Phoenix area really just function definitions on their view module.

At compile-time, Phoenix precompiles all *.html.eex templates and turns them into render/2 function clauses on their respective view modules. At runtime, all templates are already loaded in memory.

say, we can replace page/index.html.eex with the following render/2 function:

defmodule HelloWeb.PageView do
  use HelloWeb, :view

  def render("index.html", assigns) do
    # html string with interpolated variables from `assigns` map
  end
end

NOTE: there’re render/2 and render/3 functions - the former implies current view while the latter states the view explicitly.

so any template will be precompiled into corresponding render/2 function inside view. also it’s possible to create such function manually - say, in order to render different template or return text instead as it’s done in ErrorView module:

defmodule HelloWeb.ErrorView do
  use HelloWeb, :view

  def render("404.html", _assigns) do
    "Page not found"
  end
end

I guess if you create such function:

def render("404.html", assigns) do
  render("404.html", assigns)
end

you’ll end up with an infinite loop.

rendering in IEx

we can render any template manually in IEx:

iex> Phoenix.View.render(HelloWeb.PageView, "test.html", %{})

that’s how any template is rendered inside layout:

<%= render @view_module, @view_template, assigns %>

rendering JSON

Phoenix uses Poison to encode Maps to JSON, so all we need to do in our views is format the data we’d like to respond with as a Map, and Phoenix will do the rest. It is possible to respond with JSON back directly from the controller and skip the View. However, if we think about a controller as having the responsibilities of receiving a request and fetching data to be sent back, data manipulation and formatting don’t fall under those responsibilities.

rendering errors

  1. https://hexdocs.pm/phoenix/Phoenix.Endpoint.html
  2. https://github.com/phoenixframework/phoenix/issues/1945

for error template to be rendered correctly:

rendering both HTML and JSON errors

debug pages vs. error templates (ErrorView)

templates

http://www.jeramysingleton.com/phoenix-templates-are-just-functions/:

At compile time, def render("index.html", assigns), do: # your compiled eex template here would be injected into your module

It is exactly the same as if you wrote the function yourself

https://hexdocs.pm/phoenix/adding_pages.html:

in templates properties from assigns can be accessed with @ (it’s not module attribute in this case): @user = assigns.user.

https://hexdocs.pm/phoenix/templates.html:

The way we pass data into a template is by the assigns map, and the way we get the values out of the assigns map is by referencing the keys with a preceding @. @ is actually a macro that translates @key to Map.get(assigns, :key)

assets

  1. https://hexdocs.pm/phoenix/static_assets.html
  2. https://til.hashrocket.com/posts/1a3639476e-serve-static-filesdirectories-in-phoenix
  3. https://hexdocs.pm/phoenix/deployment.html#compiling-your-application-assets

I generated project with --no-brunch option so first of all I had to restore default configuration for Brunch and assets directory structure.