# MVC with Elixir [gregorymcintyre.com](https://gregorymcintyre.com) 2017 ==== ## What is MVC? A proven way of organising lots of code ==== ## This Presentation 1. Try to implement YouTube 2. Write some messy code 3. Organise code better with MVC ==== ## The Situation <img class="borderless" src="browser-server.svg" width="740"> ==== ## How? ```elixir def youtube_search(query) do # lots of code? end ``` ==== ## First Draft ```elixir def youtube_search(query) do # 1. search results from a database # 2. send back HTML with videos end ``` <!-- .element: class="small" --> ==== ## 1. Search Database ```elixir list_of_videos = query_sql_database(" SELECT title, href, img_src, ... FROM videos WHERE $query IN search_terms") ``` <!-- .element: class="small" --> ==== ## 2. Send Back HTML ```elixir send_back "<html><body>" send_back "<ol>" Enum.each list_of_videos, fn(v) -> send_back "<video>#{v}</video>" end send_back "</ol>" send_back "</body></html>" ``` <!-- .element: class="small" --> ==== ## Summary ```elixir def youtube_search(request) do list_of_videos = database("SELECT * FROM videos ... ") send_back "<html> ... videos ... </html>" end ``` <!-- .element: class="small" --> ==== ## It Works, Time for a Break ==== ## Next Day ```elixir def my_channel(request) do list_of_videos = database(" SELECT title, href, img_src, ... FROM videos WHERE uploader = $logged_in_user") send_back "<html> ... videos ... </html>" end ``` <!-- .element: class="small" --> ==== <!-- .slide: data-transition="slide-in fade-out" --> ## ...Next Day ```elixir def youtube_search(request) do list_of_videos = database("... videos WHERE $query IN search_terms") send_back "<html> ... videos ... </html>" end def my_channel(request) do list_of_videos = database("... videos WHERE $logged_in_user_id = uploader_id") send_back "<html> ... videos ... </html>" end def subscriptions(request) do list_of_videos = database("... videos WHERE $logged_in_user_id IN subscribers") send_back "<html> ... videos ... </html>" end ``` <!-- .element: class="tiny" --> ==== <!-- .slide: data-transition="fade-in fade-out" --> ## Pattern ```elixir def youtube_search(request) do # talk with database # send back HTML end def my_channel(request) do # talk with database # send back HTML end def subscriptions(request) do # talk with database # send back HTML end ``` <!-- .element: class="tiny" --> ==== <!-- .slide: data-transition="fade-in slide-out" --> ## Group Similar Code Into Modules ```elixir defmodule Controller do def youtube_search(request) do result = Model.get_search_videos(request.query) View.send_search_page(result) end def my_channel(request) do result = Model.get_channel_videos(request.user) View.send_channel_page(result) end def subscriptions(request) do result = Model.get_subscription_videos(request.user) View.send_subscriptions_page(result) end end ``` <!-- .element: class="tiny" --> ==== <!-- .slide: data-transition="fade-in slide-out" --> ## Group All the Database Code ```elixir defmodule Model do def get_search_videos(query) do return query_giant_database(...) end def get_channel_videos(user) do return query_giant_database(...) end def get_subscription_videos(user) do return query_giant_database(...) end end ``` <!-- .element: class="tiny" --> ==== <!-- .slide: data-transition="fade-in slide-out" --> ## Group All the HTML Code ```elixir defmodule View do def send_search_page(list_of_videos) do send_back header() send_back "<h1>Search Results</h1>" send_back "... #{list_of_videos} ..." send_back footer() end def send_channel_page(list_of_videos) do send_back header() send_back "<h1>My Uploads</h1>" send_back "... #{list_of_videos} ..." send_back footer() end def send_subscriptions_page(list_of_videos) do send_back header() send_back "<h1>Subscriptions</h1>" send_back "... #{list_of_videos} ..." send_back footer() end end ``` <!-- .element: class="tiny" --> ==== ## Feels Neater ==== ## Phoenix Controller ```elixir defmodule YouTubeWeb.VideoController do # ... def index(conn, request) do videos = YouTube.search_videos(request.query) render(conn, "index.html", videos: videos) end # ... end ``` <!-- .element: class="small" --> ==== ## Phoenix Service ```elixir defmodule YouTube do def search_videos(query) do Repo.all( from video in Video, where: query in video.search_terms ) end end ``` <!-- .element: class="small" --> ==== ## Phoenix Template ```erb <h2>Listing Videos</h2> <%= for video <- @videos do %> <div> <h3><%= video.name %></h3> <div> <%= link "Show", to: video_path(@conn, :show, video) %> <%= link "Edit", to: video_path(@conn, :edit, video) %> <%= link "Delete", to: video_path(@conn, :delete, video), method: :delete %> </div> </div> <% end %> <p><%= link "New video", to: video_path(@conn, :new) %></p> ``` <!-- .element: class="tiny" --> ==== ## Service? Template? <table> <thead> <tr> <th>MVC Category</th><th>Phoenix Concepts</th> </tr> </thead> <tbody> <tr> <td>Controller</td> <td>Router, Plugs, Controllers, Channels</td> </tr> <tr> <td>Model</td> <td>Apps, Services, Ecto Models</td> </tr> <tr> <td>View</td> <td>Views, Templates, HTML, CSS, JavaScript</td> </tr> </tbody> </table> ==== ## History This pattern was adapted from desktop applications ==== ## Where Do I Put Code That Deals With... <img class="borderless" src="workstation.svg" width="600"> ==== ## Jargon: Controller * Functions to handle events (like web requests) * Let you "control" the computer ==== ## Jargon: Model * Functions to fetch/search/read/write information * Your computer "modelling" of a situation ==== ## Jargon: View * Functions to update what you see (e.g. send back HTML) * Your "view" of the computer modelling ==== <!-- .slide: data-transition="slide-in fade-out" --> ## Next Time You're Doing This <img class="borderless" src="browser-server.svg" width="740"> ==== <!-- .slide: data-transition="fade-in slide-out" --> ## Imagine This <img class="borderless" src="mvc.svg" width="740"> ==== ## Thanks <img class="borderless" src="blake-logo.svg" width="400"> ==== [gregorymcintyre.com/slides/mvc-elixir/](https://gregorymcintyre.com/slides/mvc-elixir/#/) <small class="wysiwyg-font-size-large">(face icon by <a title="Freepik" href="http://www.freepik.com">Freepik</a> from <a title="Flaticon" href="http://www.flaticon.com">www.flaticon.com</a>)</small>