178 lines
5.2 KiB
Org Mode
178 lines
5.2 KiB
Org Mode
:PROPERTIES:
|
|
:ID: 20230303T095957.835101
|
|
:ROAM_ALIASES: ArcologyWeb
|
|
:END:
|
|
#+TITLE: Arcology Web Elixir Application
|
|
|
|
* Setting up the =ArcologyWeb= module
|
|
:PROPERTIES:
|
|
:ID: 20230303T114356.700426
|
|
:ROAM_ALIASES: ArcologyWeb
|
|
:END:
|
|
|
|
Much of this is boilerplate which I include here only to understand and take apart how Phoenix operates. There's not a whole lot hiding "under the hood" compared to, say, Ruby on Rails, which I really appreciate, to be honest.
|
|
|
|
So starting with the moduledoc:
|
|
|
|
#+begin_src text :noweb-ref ArcologyWeb.moduledoc
|
|
The entrypoint for defining your web interface, such
|
|
as controllers, components, channels, and so on.
|
|
|
|
This can be used in your application as:
|
|
|
|
use ArcologyWeb, :controller
|
|
use ArcologyWeb, :html
|
|
|
|
The definitions below will be executed for every controller,
|
|
component, etc, so keep them short and clean, focused
|
|
on imports, uses and aliases.
|
|
|
|
Do NOT define functions inside the quoted expressions
|
|
below. Instead, define additional modules and import
|
|
those modules here.
|
|
#+end_src
|
|
|
|
This is pretty straightforward, right? Our controllers and views can include a bunch of standard behavior (auth, logging, rate limiting, etc) by invoking those =use= statements. There are a bunch of functions defined below which will be exposed in that fashion using this "magic" =__using__= macro:
|
|
|
|
#+begin_src elixir :noweb-ref ArcologyWeb.__using__
|
|
@doc """
|
|
When used, dispatch to the appropriate controller/view/etc.
|
|
"""
|
|
defmacro __using__(which) when is_atom(which) do
|
|
apply(__MODULE__, which, [])
|
|
end
|
|
#+end_src
|
|
|
|
Some of the helpers include some extra helpers shared among them by "unquoting" a function which returns "quoted" =import= and =alias= statements -- this is the sort of indirection which makes sense in my Lisp brain, but it can be really off-putting to people who aren't exposed to this sort of pattern. The basic idea here is to provide those statements in the function which is "used in" rather than in the private helpers where they would be executed were they not quoted.
|
|
|
|
These functions import functionality for HTML rendering, [[https://hexdocs.pm/phoenix/1.7.0-rc.0/Phoenix.VerifiedRoutes.html][compile-time Verified Route]] generation, and translations:
|
|
|
|
#+begin_src elixir :noweb-ref quoted-helpers
|
|
defp html_helpers do
|
|
quote do
|
|
# HTML escaping functionality
|
|
import Phoenix.HTML
|
|
# Core UI components and translation
|
|
import ArcologyWeb.CoreComponents
|
|
import ArcologyWeb.Gettext
|
|
|
|
# Shortcut for generating JS commands
|
|
alias Phoenix.LiveView.JS
|
|
|
|
# Routes generation with the ~p sigil
|
|
unquote(verified_routes())
|
|
end
|
|
end
|
|
|
|
def verified_routes do
|
|
quote do
|
|
use Phoenix.VerifiedRoutes,
|
|
endpoint: ArcologyWeb.Endpoint,
|
|
router: ArcologyWeb.Router,
|
|
statics: ArcologyWeb.static_paths()
|
|
end
|
|
end
|
|
#+end_src
|
|
|
|
Use'ing =:router= in [[roam:ArcologyWeb.Router]] lets me customize the behavior of the router by introducing [[https://hexdocs.pm/plug/readme.html][Plugs]]; I'll be adding a Plug to attempt to load Arcology pages rather than using a "greedy" route which I used in (for example) the FastAPI [[id:20220225T175638.482695][Arcology Domain Router]].
|
|
|
|
#+begin_src elixir :noweb-ref helpers
|
|
def router do
|
|
quote do
|
|
use Phoenix.Router, helpers: false
|
|
|
|
# Import common connection and controller functions to use in pipelines
|
|
import Plug.Conn
|
|
import Phoenix.Controller
|
|
import Phoenix.LiveView.Router
|
|
end
|
|
end
|
|
#+end_src
|
|
|
|
Routing, controlling, viewing, etc, can all be skipped for =static_paths=, these are files that can just be served out of the filesystem where the application is built and bundled:
|
|
|
|
#+begin_src elixir :noweb-ref helpers
|
|
def static_paths, do: ~w(assets fonts images favicon.ico robots.txt)
|
|
#+end_src
|
|
|
|
The =:controller= helper sets up rendering rules, provides access to translations
|
|
|
|
#+begin_src elixir :noweb-ref helpers
|
|
def controller do
|
|
quote do
|
|
use Phoenix.Controller,
|
|
formats: [:html, :json],
|
|
layouts: [html: ArcologyWeb.Layouts]
|
|
|
|
import Plug.Conn
|
|
import ArcologyWeb.Gettext
|
|
|
|
unquote(verified_routes())
|
|
end
|
|
end
|
|
#+end_src
|
|
|
|
The =:live_view=, =:live_component=, and =:html= helpers set up HTML rendering and dynamic LiveView components using the quoted helpers described above.
|
|
|
|
#+begin_src elixir :noweb-ref helpers
|
|
def live_view do
|
|
quote do
|
|
use Phoenix.LiveView,
|
|
layout: {ArcologyWeb.Layouts, :app}
|
|
|
|
unquote(html_helpers())
|
|
end
|
|
end
|
|
|
|
def live_component do
|
|
quote do
|
|
use Phoenix.LiveComponent
|
|
|
|
unquote(html_helpers())
|
|
end
|
|
end
|
|
|
|
def html do
|
|
quote do
|
|
use Phoenix.Component
|
|
|
|
# Import convenience functions from controllers
|
|
import Phoenix.Controller,
|
|
only: [get_csrf_token: 0, view_module: 1, view_template: 1]
|
|
|
|
# Include general helpers for rendering HTML
|
|
unquote(html_helpers())
|
|
end
|
|
end
|
|
|
|
#+end_src
|
|
|
|
The =:channel= helper will provide =Phoenix.Channel= helpers to PubSub channels, if I decide to use them.
|
|
|
|
#+begin_src elixir :noweb-ref helpers
|
|
def channel do
|
|
quote do
|
|
use Phoenix.Channel
|
|
end
|
|
end
|
|
#+end_src
|
|
|
|
#+begin_src elixir :noweb yes :tangle lib/arcology_web.ex
|
|
defmodule ArcologyWeb do
|
|
@moduledoc """
|
|
<<ArcologyWeb.moduledoc>>
|
|
"""
|
|
|
|
<<helpers>>
|
|
|
|
<<quoted_helpers>>
|
|
|
|
<<ArcologyWeb.__using__>>
|
|
end
|
|
#+end_src
|
|
|
|
* =ArcologyWeb.Endpoint= helpers
|
|
:PROPERTIES:
|
|
:ID: 20230303T104626.041201
|
|
:END:
|