sample Project would be
videologue; allowing live chat over elsewhere hosted videos embbeded at it
- we are going to handle users; for a controller that handles users
connection |> endpoint() |> router() |> browser_pipeline() |> UserController.action()
# breaking down last part further
..connection |> UserController.index() |> UserView.render("index.html")
request enters via endpoint.ex; then to router.ex; then calling
UserControlleractionassuming request invokes
indexfunction the View and Template constructs need be prepared
-
Phoenix Context groups function with common buziness logic purpose; like CRUD for Users can be in a single module
-
Controller exists to work with Context and translate their result into something for end-user
-
Context are unaware of Controllers
-
Business Logic Changes would just restrict to Context; Unit test business logic via Context API; integration tests focused on Contoller
mix phx.new videologue
mix ecto.create
mix phx.server
- update index.html.eex to a simple Welcome as
<section class="phx-hero">
<h1><%= gettext "Welcome to %{name}!", name: "Video.logue" %></h1>
<p>Start the videologue..</p>
</section>
shall act like Bounded Contexts from DDD
-
starting with hard-coded data for short term allowing build rapidly and test the app; later replace with full db backed Ecto repo
-
create
Accountsto group user concerns together
start with file user.ex with basic struct
defmodule Videologue.Accounts.User do
defstruct [:id, :name, :username]
end
-
Elixir Structs are special Maps with
__struct__key -
add
list_users/0,get_user/1,get_user_by/1functions to interact with User struct at accounts.ex initiationg Accounts Context
this would let you do things like
iex(1)> Videologue.Accounts.list_users
[
%Videologue.Accounts.User{id: "1", name: "Alice", username: "Alice InChains"},
%Videologue.Accounts.User{id: "2", name: "Bob", username: "Bob Cat"},
%Videologue.Accounts.User{id: "3", name: "Chad", username: "Chad Wick"}
]
iex(2)> Videologue.Accounts.get_user("2")
%Videologue.Accounts.User{id: "2", name: "Bob", username: "Bob Cat"}
iex(3)> Videologue.Accounts.get_user("@")
nil
iex(4)> Videologue.Accounts.get_user_by(name: "Alice")
%Videologue.Accounts.User{id: "1", name: "Alice", username: "Alice InChains"}
-
can create all routes by
resourcesmacro; but that later -
add
UserController.index&UserController.showroutes
:show,:index,:new,:create,:edit,:update,:deleteactions are frequently/conventionally used
- add
VideologueWeb.UserController.index/2at user_controller.ex
defmodule VideologueWeb.UserController do
use VideologueWeb, :controller
alias Videologue.Accounts
def index(conn, _params) do
users = Accounts.list_users()
render(conn, "index.html", users: users)
end
...
viewis a module containing rendering functions to HTML/JSON from data
add user_view.ex with
VideologueWeb.UserView.first_name/1returning first-name for a user
templateis a function on module; compiled from a file containing raw markup & embedded elixir
add index.html.eex with
VideologueWeb.UserView.first_name/1returning first-name for a user
- template here shows multiline split looping with value chaining in html; alongwith
link ...addition using existing Route function
- templates are functions; Phoenix builds them using linked lists instead of traditional string concat it doesn't have to make giant string copies
this blog explains it well
uses I/O List data structure to leverage
writevto minimize data copies when writing for a limited element list to use no-Copy magic
-
are simple Elixir functions like
linkbefore -
helpers are available from
use VideologueWeb, :viewwhich atlib/videologue_web.ex#view
viewusesquoteto inject some code into each view; one is touse Phoenix.HTMLwhich also provides HTML safety
-
add
VideologueWeb.UserController.show/2with%{"id" => id}for params -
add show.html.eex utilizing
@userfrom Assigns
Controller
UserControllerwould infer its View nameUserView; this view would look for templates atlib/videologue_web/templates/user
- can nest templates like adding
templates/user/user.html.eex& inject intoshow.html.eexas
<h1>User Details</h1>
<%= render "user.html", user: @user %>
here View is like a Module and Template its functions; each template in app becomes
render(template_name, assigns)utilizing it can simple respond for specific error messages in
VideologueWeb.ErrorViewby adding something likedef render("401.html", _assigns), do: "Who are you to try this?"
-
by default error view implements
template_not_found/2callback -
when
rendergets called in Controller it first renders Layout view; then renders actual template in pre-def markup -
each template receive certain special assigns like
@view_module,@view_templatewhich gets rendered fromapp.html.eex -
@connis also available in layouts to access any other Phx helpers