(NB: this was done initially for Phoenix 1.3 and I haven't updated it yet.)
- erlang
- elixir
- mix
- phoenix
-
param maps ALWAYS have string keys
def show(conn, %{:messenger => messenger}) do # ⛔️ def show(conn, %{"messenger" => messenger}) do # ✅
the error is:
could not find a matching HelloWeb.HelloController.show clause to process request.
- PARENTHESES MATTER WITH PIPES
**(FunctionClauseError) no function clause matching in Plug.Conn.get_resp_header/2
- csrf
invalid CSRF (Cross Site Request Forgery) token, make sure all requests include a valid '_csrf_token' param or 'x-csrf-token' header
- the view renders -- the template just displays
- e.g., don't in the template do
first <> last-- send in afull_nameto be rendered from the view
- e.g., don't in the template do
- templates belong to views belong to controllers
resources "/users", UserControllerinroutes.ex-- creates the suite of stuff for a Userresources "/users", UserController, only: [:action_one, :action_two] # or except: [...]
mix phx.new <app_name>, e.g.,mix phx.new hello- say yes to stuff re Rebar3, dependencies, etc
cd hellomix ecto.create--role "postgres" does not exist
-
go to
<app>/lib/<app>_web/router.expipeline :browser do- a bunch of plugs
pipeline :api do- more plugs
scope "/", HelloWeb do- a
pipe_through :browser get "/", PageController, :index...
- a
-
add a new route:
<method> "/<route>", <Controller>, :<action> get "/hello", HelloController, :index
<method>: the http route we are targeting"/<route>: the route being targeted<Controller>: controller whose action we will call:<action>: the action we want to do
"WHEN we
METHODtoROUTE, DOACTIONof theCONTROLLER" -
make out new controller
<app>/lib/<app>_web/controllers/<Controller>.exdefmodule <App>Web.<Controller> do ...- it needs to have the
:<action>we gave it above
-
make a corresponding view
<app>/lib/<app>_web/views/<controller>_view.exdefmodule <App>Web.<App>View do ...
-
make a folder if we need it for the new template
<app>/lib/<app>_web/templates/<app>/index.html.eex
<%= "boop" %>will echo<% boop() %>executes but does not render/echo.<%= @user %>-- implies that our controller sent along some variable under 'user', ie,render conn, "boop.html", user: user-- the@is short forassigns.user
- start server:
mix phx.server - inspect routes:
mix phx.routes
- defines a suite of routes based on actions
- forwards to a plug
- can pop through
pipe_throughfirst -- ie java filters -- youpipe_throughapipeline, as defined
-
transforms to the connection
-
module plug: init, call
-
function plugs: defined in a controller,
plug :authenticatethendefp authenticate(conn, _) do ... -
plug :assign_welcome_message, "Welcome back"makes that the default for anything in this controller -- we can override it manually viaassign(:message, "Eff you")defp assign_welcome_message(conn, msg), do: assign(conn, :message, msg) end- if only sometimes:
plug :assign_welcome_message, "Hi!" when action in [:index, :show]
- entrance/exit for ALL requests of the application
- THIS IS WHERE WE ENABLE AUTH, CORS, SUBDOMAIN
- this is our web.xml
- where we setup https
-
index - GET all
-
show - GET by id
-
new - GET form for new
-
create - POST data for new
-
edit - GET by id and display edit form
-
update - PUT params for 1 updated item
-
delete - DELETE by id
-
signature:
def <action>(conn, %{"param_key" => param_key})-- good to pattern match inside the signature itself to pass on simplest data -
rendering
- do NOT require a view or a template
text conn, "Showing id #{id}-- plain textjson conn, %{id: id}-- json (who knew)html conn, """<html> ... </html>"""
render:render conn, "page.html", username: username- CONTROLLER MUST HAVE SAME ROOT NAME AS VIEW
- VIEW MUST HAVE SAME ROOT NAME AS DIRECTORY OF TEMPLATE
- HelloController requires HelloView
- HelloView requires
lib/hello_web/templates/hello - ...which must contain
"page.html"
- do NOT require a view or a template
-
fyi: instead of
# these are equivalent def index(conn, _params) do render conn, "index.html", message: "Welcome back!" end def index(conn, _params) do conn |> assign(:message, "Welcome back!") |> render("index.html" end
-
want a different response? no problem!
def index(conn, _params) do conn |> send_resp(201, "") end
-
diff layout? eyyyyyyyy!
def index(conn, _params) do conn |> put_layout("admin.html") |> render("index.html") end
-
diff status with a render?
def index(conn, _params) do conn |> put_status(202) |> render("index.html" end
-
compose ALL THE THINGS
def index(conn, _params) do conn |> put_status(:not_found) |> put_view(HelloWeb.ErrorView) |> render("404.html") end
-
redirect!
def index(conn, _params) do redirect conn, to: "/redirect_test" end def redirect_test(conn, _params) do text conn, "Redirect!" end
-
default error
- somewhere,
MyFallbackController - in the control we want handled:
action_fallback HelloWeb.MyFallbackController
- somewhere,
-
PostFinder plug
defmodule HelloWeb.PostFinder do
use Plug
import Plug.Conn
alias Hello.Blog
def init(opts), do: opts
def call(conn, _) do
case Blog.get_post(conn.params["id"]) do
{:ok, post} ->
assign(conn, :post, post)
{:error, :notfound} ->
conn
|> send_resp(505, "not found"
|> halt() # this prevents the rest of the pipeline
end
end
endassignissetAttribute ...