Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Build your own web framework in Elixir

Build your own web framework in Elixir

Do you use Phoenix, but you don’t know how it works underneath the hood? Do you have a nagging feeling that you should know more about the ins and outs of processing a web request and turning it into a rendered page?

Good, then this talk is for you!

Understanding how a web framework turns a request into a rendered page is an important aspect of building web applications. And what better way to learn the fundamentals of how a web framework works than to build one in Elixir? So join me as we build a web framework in Elixir complete with a router, controllers, and templates. We’ll take a look at how Plug has already done most of the work for us and at how we can leverage such a powerful abstraction to extend what it gives us. We’ll even look at how to make our templates render blazingly fast by compiling them into functions.

You may not be ready to bet your business on this new framework, but by the end of the talk, you’ll certainly be better equipped to understand what Phoenix is doing underneath macros and to extend it with new functionality. And hey, if you really want to create a new framework, you’ll get a solid start.

German Velasco

March 02, 2018
Tweet

More Decks by German Velasco

Other Decks in Programming

Transcript

  1. Build your own web framework
    German Velasco
    @germsvel

    View full-size slide

  2. github.com/
    germsvel/daze

    View full-size slide

  3. Cowboy
    Plug
    EEx

    View full-size slide

  4. Plug's specification
    defmodule Daze.Plug do
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    end
    end

    View full-size slide

  5. Plug's specification
    defmodule Daze.Plug do
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    end
    end

    View full-size slide

  6. Plug's specification
    defmodule Daze.Plug do
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    end
    end

    View full-size slide

  7. Plug's specification
    defmodule Daze.Plug do
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    end
    end

    View full-size slide

  8. mix new daze --sup

    View full-size slide

  9. config/
    deps/
    lib/
    test/
    mix.exs
    mix.lock

    View full-size slide

  10. defmodule Daze.Mixfile do
    def application do
    [
    extra_applications: [:logger, :cowboy, :plug],
    mod: {Daze.Application, []}
    ]
    end
    defp deps do
    [
    {:cowboy, "~> 1.1.0"},
    {:plug, "~> 1.4.0"}
    ]
    end
    end

    View full-size slide

  11. defmodule Daze.Mixfile do
    def application do
    [
    extra_applications: [:logger, :cowboy, :plug],
    mod: {Daze.Application, []}
    ]
    end
    defp deps do
    [
    {:cowboy, "~> 1.1.0"},
    {:plug, "~> 1.4.0"}
    ]
    end
    end

    View full-size slide

  12. defmodule Daze.Mixfile do
    def application do
    [
    extra_applications: [:logger, :cowboy, :plug],
    mod: {Daze.Application, []}
    ]
    end
    defp deps do
    [
    {:cowboy, "~> 1.1.0"},
    {:plug, "~> 1.4.0"}
    ]
    end
    end

    View full-size slide

  13. mix deps.get

    View full-size slide

  14. config/
    deps/
    lib/
    daze/
    application.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  15. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View full-size slide

  16. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, }
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View full-size slide

  17. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, scheme: :http, }
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View full-size slide

  18. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, scheme: :http, options: [port: 4001]}
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View full-size slide

  19. defmodule Daze.Application do
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, scheme: :http, plug: Daze.Router, options: [port: 4001]}
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View full-size slide

  20. config/
    deps/
    lib/
    daze/
    + router.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  21. defmodule Daze.Router do
    end

    View full-size slide

  22. defmodule Daze.Router do
    use Plug.Router
    end

    View full-size slide

  23. defmodule Daze.Router do
    use Plug.Router
    plug :match
    plug :dispatch
    end

    View full-size slide

  24. defmodule Daze.Router do
    use Plug.Router
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View full-size slide

  25. mix run --no-halt

    View full-size slide

  26. Are we getting requests?

    View full-size slide

  27. defmodule Daze.Router do
    use Plug.Router
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View full-size slide

  28. defmodule Daze.Router do
    use Plug.Router
    plug Plug.Logger
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View full-size slide

  29. mix daze.server
    config/
    deps/
    lib/
    mix/
    tasks/
    + daze.server.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  30. defmodule Mix.Tasks.Daze.Server do
    use Mix.Task
    def run(args) do
    Mix.Tasks.Run.run(args ++ ["--no-halt"])
    end
    end

    View full-size slide

  31. defmodule Mix.Tasks.Daze.Server do
    use Mix.Task
    def run(args) do
    Mix.Tasks.Run.run(args ++ ["--no-halt"])
    end
    end

    View full-size slide

  32. defmodule Mix.Tasks.Daze.Server do
    use Mix.Task
    require Logger
    def run(args) do
    Logger.info("Starting Daze Server")
    Mix.Tasks.Run.run(args ++ ["--no-halt"])
    end
    end

    View full-size slide

  33. defmodule Daze.Router do
    use Plug.Router
    if Mix.env() == :dev do
    use Plug.Debugger
    end
    plug Plug.Logger
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View full-size slide

  34. defmodule Daze.Router do
    use Plug.Router
    if Mix.env() == :dev do
    use Plug.Debugger, style: [primary: "#c0392b", accent: "#41B577"]
    end
    plug Plug.Logger
    plug :match
    plug :dispatch
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View full-size slide

  35. Controllers, Views,
    and Templates

    View full-size slide

  36. Extract
    controller logic

    View full-size slide

  37. defmodule Daze.Router do
    use Plug.Router
    # snip
    get "/" do
    send_resp(conn, 200, "We have a router!")
    end
    end

    View full-size slide

  38. defmodule Daze.Router do
    use Plug.Router
    # snip
    get "/", to: Daze.Actions.Posts.Index
    end

    View full-size slide

  39. config/
    deps/
    lib/
    daze/
    actions/
    posts/
    + index.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  40. Plugs, remember?
    defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View full-size slide

  41. Plugs, remember?
    defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View full-size slide

  42. Plugs, remember?
    defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View full-size slide

  43. Plugs, remember?
    defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View full-size slide

  44. Templates with
    EEx

    View full-size slide

  45. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View full-size slide

  46. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View full-size slide

  47. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View full-size slide

  48. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex"
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View full-size slide

  49. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:adjective]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, "Plugs all the way down!")
    end
    end

    View full-size slide

  50. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:adjective]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index("coolest"))
    end
    end

    View full-size slide

  51. config/
    deps/
    lib/
    daze/
    templates/
    posts/
    + index.html.ex
    test/
    mix.exs
    mix.lock

    View full-size slide






  52. EEx is the <%= adjective %>!


    View full-size slide






  53. EEx is the <%= adjective %>!


    View full-size slide






  54. EEx is the <%= adjective1 %> and the <%= adjective2 %>!


    View full-size slide

  55. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:adjective1, :adjective2]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index("coolest", "fastest"))
    end
    end

    View full-size slide

  56. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:assigns]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View full-size slide

  57. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:assigns]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View full-size slide






  58. EEx is the <%= @adjective1 %> and the <%= @adjective2 %>!


    View full-size slide

  59. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    require EEx
    EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:assigns]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View full-size slide

  60. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    - require EEx
    - EEx.function_from_file :def, :index, "lib/daze/templates/posts/index.html.eex", [:assigns]
    def init(opts), do: opts
    def call(conn, _opts) do
    send_resp(conn, 200, index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View full-size slide

  61. config/
    deps/
    lib/
    daze/
    views/
    + posts.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  62. defmodule Daze.Views.Posts do
    end

    View full-size slide

  63. defmodule Daze.Views.Posts do
    require EEx
    EEx.function_from_file
    :def,
    :index,
    "lib/daze/templates/posts/index.html.eex",
    [:assigns]
    end

    View full-size slide

  64. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    alias Daze.Views.Posts
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    |> send_resp(200, Posts.index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View full-size slide

  65. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    alias Daze.Views.Posts
    def init(opts), do: opts
    def call(conn, _opts) do
    conn
    |> send_resp(200, Posts.index(adjective1: "coolest", adjective2: "fastest"))
    end
    end

    View full-size slide

  66. Handling forms

    View full-size slide






  67. Posts


    Post:

    Author

    Post




    View full-size slide






  68. Posts


    Post:

    Author

    Post




    View full-size slide

  69. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    end

    View full-size slide

  70. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    end

    View full-size slide

  71. conn # =>
    %Plug.Conn{
    query_params: _,
    body_params: _,
    params: _
    }

    View full-size slide

  72. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    end

    View full-size slide

  73. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    post "/posts", to: Daze.Actions.Posts.Create
    end

    View full-size slide

  74. config/
    deps/
    lib/
    daze/
    actions/
    posts/
    + create.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  75. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    end
    end

    View full-size slide

  76. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    end

    View full-size slide

  77. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    end
    end

    View full-size slide

  78. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    conn
    end
    end

    View full-size slide

  79. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    conn
    |> put_resp_header("location", url)
    end
    end

    View full-size slide

  80. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    end
    end

    View full-size slide

  81. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end

    View full-size slide

  82. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end

    View full-size slide

  83. config/
    deps/
    lib/
    daze/
    + repo.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  84. defmodule Daze.Repo do
    use Agent
    end

    View full-size slide

  85. defmodule Daze.Repo do
    use Agent
    def start_link(_) do
    Agent.start_link(fn -> Map.new end, name: __MODULE__)
    end
    end

    View full-size slide

  86. defmodule Daze.Repo do
    use Agent
    # snip
    def insert(key, value) do
    Agent.update(__MODULE__, fn map ->
    data =
    case Map.get(map, key) do
    nil -> []
    values -> values
    end
    Map.put(map, key, [value | data])
    end)
    end
    end

    View full-size slide

  87. defmodule Daze.Repo do
    use Agent
    # snip
    def all do
    Agent.get(__MODULE__, fn map ->
    map
    |> Map.values()
    |> Enum.concat()
    end)
    end
    end

    View full-size slide

  88. defmodule Daze.Application do
    @moduledoc false
    use Application
    def start(_type, _args) do
    children = [
    {Plug.Adapters.Cowboy, scheme: :http, plug: Daze.Router, options: [port: 4001]},
    {Daze.Repo, []}
    ]
    opts = [strategy: :one_for_one, name: Daze.Supervisor]
    Supervisor.start_link(children, opts)
    end
    end

    View full-size slide

  89. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    alias Daze.Views.Posts
    def init(opts), do: opts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    conn
    |> send_resp(200, Posts.index(posts: posts))
    end
    end

    View full-size slide



  90. # snip
    <%= if @posts do %>

    <%= for post <- @posts do %>

    <%= post.body %>
    by <%= post.author %>

    <% end %>

    <% end %>



    View full-size slide



  91. # snip
    <%= if @posts do %>

    <%= for post <- @posts do %>

    <%= post.body %>
    by <%= post.author %>

    <% end %>

    <% end %>



    View full-size slide



  92. # snip
    <%= if @posts do %>

    <%= for post <- @posts do %>

    <%= post.body %>
    by <%= post.author %>

    <% end %>

    <% end %>



    View full-size slide

  93. defmodule Daze.Actions.Posts.Index do
    import Plug.Conn
    alias Daze.Views.Posts
    def init(opts), do: opts
    def call(conn, _opts) do
    # snip
    end
    end

    View full-size slide

  94. defmodule Daze.Actions.Posts.Create do
    import Plug.Conn
    def init(opts), do: opts
    def call(conn, _opts) do
    # snip
    end
    end

    View full-size slide

  95. config/
    deps/
    lib/
    daze/
    + action.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  96. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    end
    end
    end

    View full-size slide

  97. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    def init(opts), do: opts
    end
    end
    end

    View full-size slide

  98. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, Posts.index(posts: posts))
    end
    end

    View full-size slide

  99. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, Posts.index(posts: posts))
    end
    end

    View full-size slide

  100. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    render(conn, Posts.index(posts: posts))
    end
    def render(conn, body) do
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, body)
    end
    end

    View full-size slide

  101. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    def init(opts), do: opts
    end
    end
    end

    View full-size slide

  102. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    end

    View full-size slide

  103. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    end
    end

    View full-size slide

  104. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    import Plug.Conn
    def render(conn, body) do
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, body)
    end
    end
    end

    View full-size slide

  105. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    render(conn, Posts.index(posts: posts))
    end
    end

    View full-size slide

  106. defmodule Daze.Actions.Posts.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end

    View full-size slide

  107. defmodule Daze.Actions.Posts.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    url = "/"
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end

    View full-size slide

  108. defmodule Daze.Actions.Posts.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    redirect(conn, to: "/")
    end
    def redirect(conn, to: url) do
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end

    View full-size slide

  109. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    import Plug.Conn
    def render(conn, body) do
    conn
    |> put_resp_content_type("text/html")
    |> send_resp(200, body)
    end
    end
    end

    View full-size slide

  110. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    import Plug.Conn
    def render(conn, body) do
    # snip
    end
    def redirect(conn, to: url) do
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end
    end

    View full-size slide

  111. defmodule Daze.Action do
    defmacro __using__(opts) do
    quote do
    import Plug.Conn
    import Daze.Action.Helpers
    def init(opts), do: opts
    end
    end
    defmodule Helpers do
    import Plug.Conn
    def render(conn, body) do
    # snip
    end
    def redirect(conn, to: url) do
    conn
    |> put_resp_header("location", url)
    |> put_resp_content_type("text/html")
    |> send_resp(302, "You are being redirected to #{url}")
    end
    end
    end

    View full-size slide

  112. defmodule Daze.Actions.Posts.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{ "post" => post, "author" => author } = conn.params
    Daze.Repo.insert(author, %{body: post, author: author})
    redirect(conn, to: "/")
    end
    end

    View full-size slide

  113. defmodule Daze.Views.Posts do
    require EEx
    EEx.function_from_file
    :def,
    :index,
    "lib/daze/templates/posts/index.html.eex",
    [:assigns]
    end

    View full-size slide

  114. defmodule Daze.Views.Posts do
    require EEx
    EEx.function_from_file
    :def,
    :index,
    "lib/daze/templates/posts/index.html.eex",
    [:assigns]
    end

    View full-size slide

  115. config/
    deps/
    lib/
    daze/
    + view.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  116. defmodule Daze.View do
    @root_path "lib/daze/templates"
    end

    View full-size slide

  117. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    end
    end

    View full-size slide

  118. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    end
    end

    View full-size slide

  119. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    quote do
    require EEx
    EEx.function_from_file(
    :def
    )
    end
    end
    end

    View full-size slide

  120. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    quote do
    require EEx
    EEx.function_from_file(
    :def,
    unquote(function_name),
    unquote(template_path)
    )
    end
    end
    end

    View full-size slide

  121. defmodule Daze.View do
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    quote do
    require EEx
    EEx.function_from_file(
    :def,
    unquote(function_name),
    unquote(template_path),
    [:assigns]
    )
    end
    end
    end

    View full-size slide

  122. defmodule Daze.View do
    defmacro __using__(_) do
    quote do
    import Daze.View
    end
    end
    @root_path "lib/daze/templates"
    defmacro deftemplate(template_name, function_name) do
    template_path = Path.join(@root_path, template_name)
    quote do
    require EEx
    EEx.function_from_file(
    :def,
    unquote(function_name),
    unquote(template_path),
    [:assigns]
    )
    end
    end
    end

    View full-size slide

  123. defmodule Daze.Views.Posts do
    use Daze.View
    deftemplate("posts/index.html.eex", :index)
    end

    View full-size slide

  124. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :dispatch
    # snip
    end

    View full-size slide

  125. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug Plug.Session,
    plug :dispatch
    # snip
    end

    View full-size slide

  126. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug Plug.Session, store: :cookie,
    plug :dispatch
    # snip
    end

    View full-size slide

  127. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug Plug.Session, store: :cookie, key: "_daze_app",
    plug :dispatch
    # snip
    end

    View full-size slide

  128. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :dispatch
    # snip
    end

    View full-size slide

  129. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :dispatch
    # snip
    def put_secret_key_base(conn, _) do
    end
    end

    View full-size slide

  130. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :dispatch
    # snip
    def put_secret_key_base(conn, _) do
    put_in(
    conn.secret_key_base,
    "tosp2eJ0EVDXq/litnqe3iTMf4oGJuos0PbJk1NzkL4SaMEPB8OGu4T8Ym+WavGY"
    )
    end
    end

    View full-size slide

  131. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View full-size slide

  132. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "_daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    post "/posts", to: Daze.Actions.Posts.Create
    get "/sign_in", to: Daze.Actions.Sessions.New
    # snip
    end

    View full-size slide

  133. config/
    deps/
    lib/
    daze/
    actions/
    sessions/
    + new.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  134. defmodule Daze.Actions.Sessions.New do
    use Daze.Action
    end

    View full-size slide

  135. defmodule Daze.Actions.Sessions.New do
    use Daze.Action
    def call(conn, _opts) do
    render(conn, Daze.Views.Sessions.new([]))
    end
    end

    View full-size slide

  136. config/
    deps/
    lib/
    daze/
    views/
    + sessions.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  137. defmodule Daze.Views.Sessions do
    use Daze.View
    deftemplate("sessions/new.html.eex", :new)
    end

    View full-size slide

  138. config/
    deps/
    lib/
    daze/
    templates/
    sessions/
    + new.html.eex
    test/
    mix.exs
    mix.lock

    View full-size slide






  139. Sign in


    Email:

    Password:

    Sign in




    View full-size slide






  140. Sign in


    Email:

    Password:

    Sign in




    View full-size slide

  141. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    get "/", to: Daze.Actions.Posts.Index
    post "/posts", to: Daze.Actions.Posts.Create
    get "/sign_in", to: Daze.Actions.Sessions.New
    post "/sessions", to: Daze.Actions.Sessions.Create
    # snip
    end

    View full-size slide

  142. config/
    deps/
    lib/
    daze/
    actions/
    sessions/
    + create.ex
    test/
    mix.exs
    mix.lock

    View full-size slide

  143. defmodule Daze.Actions.Sessions.Create do
    use Daze.Action
    end

    View full-size slide

  144. defmodule Daze.Actions.Sessions.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{"email" => email} = conn.params
    conn
    |> put_session(:current_user_email, email)
    |> redirect(to: "/")
    end
    end

    View full-size slide

  145. defmodule Daze.Actions.Sessions.Create do
    use Daze.Action
    def call(conn, _opts) do
    %{"email" => email} = conn.params
    conn
    |> put_session(:current_user_email, email)
    |> redirect(to: "/")
    end
    end

    View full-size slide

  146. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    conn
    |> render(Posts.index(posts: posts))
    end
    end

    View full-size slide

  147. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    current_user_email = get_session(conn, :current_user_email)
    conn
    |> render(Posts.index(posts: posts))
    end
    end

    View full-size slide

  148. defmodule Daze.Actions.Posts.Index do
    use Daze.Action
    alias Daze.Views.Posts
    def call(conn, _opts) do
    posts = Daze.Repo.all()
    current_user_email = get_session(conn, :current_user_email)
    conn
    |> render(Posts.index(posts: posts, current_user_email: current_user_email))
    end
    end

    View full-size slide






  149. Posts
    <%= if @current_user_email do %>
    Welcome <%= @current_user_email %>
    <% else %>
    Sign In
    <% end %>
    # snip



    View full-size slide

  150. Static assets

    View full-size slide

  151. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View full-size slide

  152. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug Plug.Static,
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View full-size slide

  153. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug Plug.Static, from: :daze, at: "/",
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View full-size slide

  154. defmodule Daze.Router do
    use Plug.Router
    # snip
    plug Plug.Logger
    plug Plug.Static, from: :daze, at: "/", only: ["images", "css"]
    plug :match
    plug Plug.Parsers, parsers: [:urlencoded, :multipart]
    plug :put_secret_key_base
    plug Plug.Session, store: :cookie, key: "daze_app", signing_salt: "daze salt"
    plug :fetch_session
    plug :dispatch
    # snip
    end

    View full-size slide

  155. config/
    deps/
    lib/
    priv/
    static/
    css/
    + app.css
    test/
    mix.exs
    mix.lock

    View full-size slide

  156. body {
    background-color: #fff;
    font-family: 'Open Sans', sans-serif;
    }
    .container {
    display: flex;
    flex-direction: column;
    }
    .header {
    display: flex;
    flex-flow: row nowrap;
    justify-content: space-evenly;
    align-items: center;
    margin: 4em;
    }
    .banner-image {
    width: 100px;
    }
    .main {
    display: flex;
    flex-flow: column nowrap;
    margin: auto;
    }
    .posts {
    margin: 3em 0.5em;
    }
    .post-item {
    margin: 0 0.5em 3em 0.5em;
    list-style: none;
    }
    .post-body {
    font-size: 1.5em;
    }
    .post-author {
    margin-top: 0.3em;
    display: block;
    text-align: right;
    }

    View full-size slide

  157. config/
    deps/
    lib/
    priv/
    static/
    images/
    + elixir-daze.svg
    test/
    mix.exs
    mix.lock

    View full-size slide

  158. Final thoughts

    View full-size slide

  159. github.com/germsvel/daze
    @germsvel

    View full-size slide