└── README.md /README.md: -------------------------------------------------------------------------------- 1 | ## Who and what is this for? 2 | 3 | This document exists for Elixir and Phoenix application developers to learn from the design missteps of others. 4 | 5 | It's not a manifesto or a bible or even a style guide – it's just a list of guidelines to consider *before* laying out the structure of an application. With a little foresight, the cumulative effect of these individual tips will result in a consistent application structure and, hopefully, a boost in productivity. 6 | 7 | ## Don't reference `Repo` from a controller, view, or channel 8 | 9 | ### Don't do this: 10 | 11 | ```elixir 12 | def show(conn, %{"id" => id}) do 13 | post = MyApp.Repo.get!(Post, id) 14 | render(conn, "show.html", post: post) 15 | end 16 | ``` 17 | 18 | ### Instead, do this: 19 | 20 | ```elixir 21 | def show(conn, %{"id" => id}) do 22 | post = MyApp.Blog.get_post!(id) 23 | render(conn, "show.html", post: post) 24 | end 25 | ``` 26 | 27 | Ecto's `Repo` is an implementation detail internal to your application that should not be exposed to the web layer. Data persistence can be backed by many things: a relational database, a document database, an ETS table, a flat file, an external API, a GenServer… the list goes on. Leave it up to the Context to hide that detail from your external web interface. 28 | 29 | ## Make the first function argument represent the data being acted ON 30 | 31 | ### Don't do this: 32 | 33 | ```elixir 34 | MyApp.Blog.update_post(params, post) 35 | ``` 36 | 37 | ### Instead, do this: 38 | 39 | ```elixir 40 | MyApp.Blog.update_post(post, params) 41 | ``` 42 | 43 | Elixir isn't object-oriented, but the pipe operator `|>` lets us mutate data in a functional way that still feels like calling a method on an object. Take advantage of that exepectation by designing your functions to take the data to be mutated (or a pointer to that data, like an `id`) as the first parameter. 44 | 45 | ## Define Ecto queries directly on the schema module 46 | 47 | ```elixir 48 | # lib/my_app/blog/post.ex 49 | defmodule MyApp.Blog.Post do 50 | import Ecto.Query 51 | 52 | def published(query \\ Post) do 53 | from p in query, where: not is_nil(p.published_at) 54 | end 55 | 56 | def ordered(query \\ Post) do 57 | from p in query, order_by: p.published_at 58 | end 59 | end 60 | 61 | # lib/my_app/blog/blog.ex 62 | defmodule MyApp.Blog do 63 | def list_posts do 64 | Post 65 | |> Post.published 66 | |> Post.ordered 67 | |> Repo.all 68 | end 69 | end 70 | ``` 71 | 72 | Defining simple, composable queries directly on the schema module is very convenient for building up more complex queries at higher levels. 73 | 74 | ## Define Ecto changesets directly on the schema module 75 | 76 | ```elixir 77 | # lib/my_app/blog/post.ex 78 | defmodule MyApp.Blog.Post do 79 | import Ecto.Changeset 80 | 81 | def update(post, params) do 82 | cast(post, params, [:title, :body]) 83 | end 84 | 85 | def publish(post) do 86 | change(post, published_at: DateTime.now_utc) 87 | end 88 | end 89 | 90 | # lib/my_app/blog/blog.ex 91 | defmodule MyApp.Blog do 92 | def update_post(post, params) do 93 | post 94 | |> MyApp.Blog.Post.update(params) 95 | |> Repo.update 96 | end 97 | 98 | def publish_post(post) do 99 | post 100 | |> MyApp.Blog.Post.publish 101 | |> Repo.update 102 | end 103 | end 104 | ``` 105 | 106 | Defining simple, composable changesets directly on the schema module is very convenient for building up more complex queries at higher levels. 107 | 108 | ## Pass the current user directly into authenticated controller actions 109 | 110 | ```elixir 111 | # lib/my_app/web.ex 112 | defmodule MyApp.Web do 113 | # ...Phoenix-generated scopes here... 114 | 115 | def authenticated_controller do 116 | quote do 117 | use MyApp.Web, :controller 118 | 119 | # Example using Guardian 120 | plug Guardian.Plug.EnsureAuthenticated, handler: MyApp.Web.AuthHandler 121 | 122 | # Override Phoenix.Controller.action/2 callback 123 | def action(conn, params) do 124 | args = [conn, params, Guardian.Plug.current_resource(conn)] 125 | apply(__MODULE__, action_name(conn), args) 126 | end 127 | end 128 | end 129 | end 130 | 131 | # lib/my_app/web/controllers/post_controller.ex 132 | defmodule MyApp.Web.PostController do 133 | # Use the new scope 134 | use MyApp.Web, :authenticated_controller 135 | 136 | # Notice the extra parameter 137 | def new(conn, params, current_user) do 138 | changeset = MyApp.Blog.new_post(current_user, params) 139 | render(conn, "new.html", changeset: changeset) 140 | end 141 | end 142 | ``` 143 | 144 | By overriding the default Phoenix controller `action/2` callback, you can inject custom parameters into each controller action. For authenticated controllers, knowing the `current_user` is often required, so pass it in directly to the function for convenience. 145 | --------------------------------------------------------------------------------