From dbcfe8e1597a04e745051dc4a77de806d94100d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=81=C4=99picki?= Date: Mon, 7 Jan 2019 20:18:48 +0100 Subject: [PATCH 01/26] Fix Controller.put_flash and Presence.list arities in docs (#3232) --- guides/contexts.md | 2 +- lib/phoenix/presence.ex | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/guides/contexts.md b/guides/contexts.md index 531a4bcc4d..d605c9ce39 100644 --- a/guides/contexts.md +++ b/guides/contexts.md @@ -158,7 +158,7 @@ end We've seen how controllers work in our [controller guide](controllers.html), so the code probably isn't too surprising. What is worth noticing is how our controller calls into the `Accounts` context. We can see that the `index` action fetches a list of users with `Accounts.list_users/0`, and how users are persisted in the `create` action with `Accounts.create_user/1`. We haven't yet looked at the accounts context, so we don't yet know how user fetching and creation is happening under the hood – *but that's the point*. Our Phoenix controller is the web interface into our greater application. It shouldn't be concerned with the details of how users are fetched from the database or persisted into storage. We only care about telling our application to perform some work for us. This is great because our business logic and storage details are decoupled from the web layer of our application. If we move to a full-text storage engine later for fetching users instead of a SQL query, our controller doesn't need to be changed. Likewise, we can reuse our context code from any other interface in our application, be it a channel, mix task, or long-running process importing CSV data. -In the case of our `create` action, when we successfully create a user, we use `Phoenix.Controller.put_flash/2` to show a success message, and then we redirect to the `user_path`'s show page. Conversely, if `Accounts.create_user/1` fails, we render our `"new.html"` template and pass along the Ecto changeset for the template to lift error messages from. +In the case of our `create` action, when we successfully create a user, we use `Phoenix.Controller.put_flash/3` to show a success message, and then we redirect to the `user_path`'s show page. Conversely, if `Accounts.create_user/1` fails, we render our `"new.html"` template and pass along the Ecto changeset for the template to lift error messages from. Next, let's dig deeper and check out our `Accounts` context in `lib/hello/accounts/accounts.ex`: diff --git a/lib/phoenix/presence.ex b/lib/phoenix/presence.ex index e16c2581b2..6fa582a80f 100644 --- a/lib/phoenix/presence.ex +++ b/lib/phoenix/presence.ex @@ -369,7 +369,7 @@ defmodule Phoenix.Presence do ## Examples - Uses the same data format as `Phoenix.Presence.list/1`, but only + Uses the same data format as `Phoenix.Presence.list/2`, but only returns metadata for the presences under a topic and key pair. For example, a user with key `"user1"`, connected to the same chat room `"room:1"` from two devices, could return: @@ -377,7 +377,7 @@ defmodule Phoenix.Presence do iex> MyPresence.get_by_key("room:1", "user1") %{name: "User 1", metas: [%{device: "Desktop"}, %{device: "Mobile"}]} - Like `Phoenix.Presence.list/1`, the presence metadata is passed to the `fetch` + Like `Phoenix.Presence.list/2`, the presence metadata is passed to the `fetch` callback of your presence module to fetch any additional information. """ def get_by_key(module, topic, key) do From f1682423737f2b24963e3c8f1953ae74595a47e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=81=C4=99picki?= Date: Mon, 7 Jan 2019 20:20:12 +0100 Subject: [PATCH 02/26] Use Elixir v1.5's streamlined child specs in docs (#3233) * Use Elixir v1.5's streamlined child specs in Endpoint docs * Use Elixir v1.5's streamlined child specs in mix ecto.gen.repo docs --- guides/endpoint.md | 2 +- guides/phoenix_mix_tasks.md | 11 ++++++----- lib/phoenix/endpoint.ex | 4 +++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/guides/endpoint.md b/guides/endpoint.md index b21de25999..c0dc01dfee 100644 --- a/guides/endpoint.md +++ b/guides/endpoint.md @@ -10,7 +10,7 @@ defmodule Hello.Application do #... children = [ - supervisor(HelloWeb.Endpoint, []), + HelloWeb.Endpoint ] opts = [strategy: :one_for_one, name: Hello.Supervisor] diff --git a/guides/phoenix_mix_tasks.md b/guides/phoenix_mix_tasks.md index c0171776bc..e5fed5f894 100644 --- a/guides/phoenix_mix_tasks.md +++ b/guides/phoenix_mix_tasks.md @@ -713,13 +713,14 @@ We certainly should follow the instructions and add our new repo to our supervis ```elixir . . . children = [ - # Start the endpoint when the application starts - supervisor(HelloWeb.Endpoint, []), # Start the Ecto repository - worker(Hello.Repo, []), + Hello.Repo, + # Start the endpoint when the application starts + HelloWeb.Endpoint, + # Starts a worker by calling: Hello.Worker.start_link(arg) + # {Hello.Worker, arg}, # Here you could define other workers and supervisors as children - # worker(Hello.Worker, [arg1, arg2, arg3]), - worker(OurCustom.Repo, []), + OurCustom.Repo ] . . . ``` diff --git a/lib/phoenix/endpoint.ex b/lib/phoenix/endpoint.ex index 2418f2b941..af6b93e4e6 100644 --- a/lib/phoenix/endpoint.ex +++ b/lib/phoenix/endpoint.ex @@ -38,7 +38,9 @@ defmodule Phoenix.Endpoint do to the supervision tree in generated applications. Endpoints can be added to the supervision tree as follows: - supervisor(YourApp.Endpoint, []) + children = [ + YourApp.Endpoint + ] ### Endpoint configuration From fdfa225845a6e6d28072db2a4b0ddafb7ccb3c93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20=C5=81=C4=99picki?= Date: Mon, 7 Jan 2019 20:29:52 +0100 Subject: [PATCH 03/26] Improve code formatting consistency in guides (and additional guides fixes) (#3230) * Fix typo in the Overview guide: its -> it's * Clear up wording in the Installation guide. Postgrex will be installed when building our app, not when starting it. * Fix links to repo in the Community guide. * Use console code fence in the Up and Running guide * Fix in Up and Running guide: Webpacks'->Webpack's * Do not wrap linked guide name in backticks in the Up and Running guide * Wrap eex code in html fence in Adding Pages guide. * Add missing `a` article in the Addding pages guide It is special bit -> It is a special bit * Remove $ from commands embedded in text in Routing guide. Reason: consistency with other guides. Also, in next version of ex_doc, mix tasks will be automatically linked to. * Consistently use three dots to imply more code in the Routing guide. * Do not indent elixir code blocks in the Routing guide. * Fix plug order in the Routing guide. * Do not indent elixir code in the Endpoint guide. * Update code blocks in Endpoint guide. - #... -> ... - Update Endpoint plug code with current generator output - Format SSL config for readability * Improve code blocks formatting consistency in Controllers guide. * Improve code blocks formatting consistency in Views guide. * Improve code blocks formatting consistency in Presence guide. * Improve code blocks formatting consistency in Ecto guide. * Remove empty line in Channels guide diagram * Consistently use three dots to imply more code in the Channels guide. * Use sql fence for sql code in Ecto guide. * Use plaintext code fence in Presence guide to avoid colors * Use plaintext fence for diagrams in Channels guide --- guides/adding_pages.md | 6 +- guides/channels.md | 15 ++--- guides/controllers.md | 43 ++++++------- guides/ecto.md | 94 ++++++++++++++--------------- guides/endpoint.md | 68 +++++++++++---------- guides/introduction/community.md | 2 +- guides/introduction/installation.md | 2 +- guides/introduction/overview.md | 2 +- guides/presence.md | 16 ++--- guides/routing.md | 78 ++++++++++++------------ guides/up_and_running.md | 10 +-- guides/views.md | 56 ++++++++--------- 12 files changed, 199 insertions(+), 193 deletions(-) diff --git a/guides/adding_pages.md b/guides/adding_pages.md index 1157a1e03c..b96a7ef91b 100644 --- a/guides/adding_pages.md +++ b/guides/adding_pages.md @@ -200,7 +200,9 @@ Now that we've got the route, controller, view, and template, we should be able There are a couple of interesting things to notice about what we just did. We didn't need to stop and re-start the server while we made these changes. Yes, Phoenix has hot code reloading! Also, even though our `index.html.eex` file consisted of only a single `div` tag, the page we get is a full HTML document. Our index template is rendered into the application layout - `lib/hello_web/templates/layout/app.html.eex`. If you open it, you'll see a line that looks like this: - <%= render(@view_module, @view_template, assigns) %> +```html +<%= render(@view_module, @view_template, assigns) %> +``` which is what renders our template into the layout before the HTML is sent off to the browser. @@ -267,7 +269,7 @@ And this is what the template should look like: ``` -Our messenger appears as `@messenger`. In this case, this is not a module attribute. It is special bit of metaprogrammed syntax which stands in for `assigns.messenger`. The result is much nicer on the eyes and much easier to work with in a template. +Our messenger appears as `@messenger`. In this case, this is not a module attribute. It is a special bit of metaprogrammed syntax which stands in for `assigns.messenger`. The result is much nicer on the eyes and much easier to work with in a template. We're done. If you point your browser here: [http://localhost:4000/hello/Frank](http://localhost:4000/hello/Frank), you should see a page that looks like this: diff --git a/guides/channels.md b/guides/channels.md index ae6c35c41a..3d2179b9bc 100644 --- a/guides/channels.md +++ b/guides/channels.md @@ -14,9 +14,7 @@ Conceptually, Channels are pretty simple. Clients connect and subscribe to one or more topics, whether that's `public_chat` or `updates:user1`. Any message sent on a topic, whether from the server or from a client, is sent to all clients subscribed to that topic (including the sender, if it's subscribed), like this: -
-
-
+```plaintext
                                                                   +----------------+
                                                      +--Topic X-->| Mobile Client  |
                                                      |            +----------------+
@@ -28,8 +26,7 @@ Any message sent on a topic, whether from the server or from a client, is sent t
                                                      |            +----------------+
                                                      +--Topic X-->|   IoT Client   |
                                                                   +----------------+
-
-
+``` Channels can support any kind of client: a browser, native app, smart watch, embedded device, or anything else that can connect to a network. All the client needs is a suitable library; see the [Client Libraries](#client-libraries) section below. @@ -61,8 +58,7 @@ Because only one message has to be sent per additional node, the performance cos The message flow looks something like this: -
-
+```plaintext
                                  Channel   +-------------------------+      +--------+
                                   route    | Sending Client, Topic 1 |      | Local  |
                               +----------->|     Channel.Server      |----->| PubSub |--+
@@ -95,8 +91,7 @@ The message flow looks something like this:
 +----------------+                         |   IoT Client, Topic 1   |      | Remote |  |
 |   IoT Client   |<-------Transport--------|     Channel.Server      |<-----| PubSub |<-+
 +----------------+                         +-------------------------+      +--------+
-
-
+``` ### Endpoint @@ -380,7 +375,7 @@ Let's say we have an authentication plug in our app called `OurAuth`. When `OurA ```elixir pipeline :browser do - # ... + ... plug OurAuth plug :put_user_token end diff --git a/guides/controllers.md b/guides/controllers.md index d808a79905..eee95bad97 100644 --- a/guides/controllers.md +++ b/guides/controllers.md @@ -39,7 +39,7 @@ As long as we change the action name in the `PageController` to `test` as well, ```elixir defmodule HelloWeb.PageController do - . . . + ... def test(conn, _params) do render(conn, "index.html") @@ -65,7 +65,7 @@ The second parameter is `params`. Not surprisingly, this is a map which holds an ```elixir defmodule HelloWeb.HelloController do - . . . + ... def show(conn, %{"messenger" => messenger}) do render(conn, "show.html", messenger: messenger) @@ -93,7 +93,7 @@ To do this we modify the `index` action as follows: ```elixir defmodule HelloWeb.PageController do - . . . + ... def index(conn, _params) do conn |> put_flash(:info, "Welcome to Phoenix, from flash info!") @@ -234,7 +234,7 @@ defmodule HelloWeb.PageController do use HelloWeb, :controller plug :assign_welcome_message, "Hi!" when action in [:index, :show] -. . . +... ``` ### Sending responses directly @@ -353,7 +353,7 @@ end ``` What it doesn't have is an alternative template for rendering text. Let's add one at `lib/hello_web/templates/page/index.text.eex`. Here is our example `index.text.eex` template. -```elixir +```html OMG, this is actually some text. ``` @@ -369,7 +369,7 @@ defmodule HelloWeb.Router do plug :protect_from_forgery plug :put_secure_browser_headers end -. . . +... ``` We also need to tell the controller to render a template with the same format as the one returned by `Phoenix.Controller.get_format/1`. We do that by substituting the name of the template "index.html" with the atom version `:index`. @@ -392,7 +392,7 @@ end And let's add a bit to our text template. -```elixir +```html OMG, this is actually some text. <%= @message %> ``` @@ -468,10 +468,10 @@ In order to try out `redirect/2`, let's create a new route in `lib/hello_web/rou ```elixir defmodule HelloWeb.Router do use HelloWeb, :router - . . . + ... scope "/", HelloWeb do - . . . + ... get "/", PageController, :index end @@ -479,7 +479,7 @@ defmodule HelloWeb.Router do scope "/", HelloWeb do get "/redirect_test", PageController, :redirect_test, as: :redirect_test end - . . . + ... end ``` @@ -644,6 +644,7 @@ end If we call this plug as part of the plug pipeline any downstream plugs will still be processed. If we want to prevent downstream plugs from being processed in the event of the 404 response we can simply call `Plug.Conn.halt/1`. ```elixir + ... case Blog.get_post(conn.params["id"]) do {:ok, post} -> assign(conn, :post, post) @@ -657,24 +658,24 @@ If we call this plug as part of the plug pipeline any downstream plugs will stil It's important to note that `halt/1` simply sets the `:halted` key on `Plug.Conn.t` to `true`. This is enough to prevent downstream plugs from being invoked but it will not stop the execution of code locally. As such ```elixir - conn - |> send_resp(404, "Not found") - |> halt() +conn +|> send_resp(404, "Not found") +|> halt() ``` ... is functionally equivalent to... ```elixir - conn - |> halt() - |> send_resp(404, "Not found") +conn +|> halt() +|> send_resp(404, "Not found") ``` It's also important to note that halting will only stop the plug pipeline from continuing. Function plugs will still execute unless their implementation checks for the `:halted` value. -``` - def post_authorization_plug(%{halted: true} = conn, _), do: conn - def post_authorization_plug(conn, _) do - . . . - end +```elixir +def post_authorization_plug(%{halted: true} = conn, _), do: conn +def post_authorization_plug(conn, _) do + ... +end ``` diff --git a/guides/ecto.md b/guides/ecto.md index 349a4da54a..a28855fa31 100644 --- a/guides/ecto.md +++ b/guides/ecto.md @@ -18,7 +18,7 @@ This guide assumes that we have generated our new application with Ecto integrat The default Postgres configuration has a superuser account with username 'postgres' and the password 'postgres'. If you take a look at the file `config/dev.exs`, you'll see that Phoenix works off this assumption. If you don't have this account already setup on your machine, you can connect to your postgres instance by typing `psql` and then entering the following commands: -``` +```sql CREATE USER postgres; ALTER USER postgres PASSWORD 'postgres'; ALTER USER postgres WITH SUPERUSER; @@ -188,11 +188,11 @@ Changesets define a pipeline of transformations our data needs to undergo before Let's take a closer look at our default changeset function. ```elixir - def changeset(%User{} = user, attrs) do - user - |> cast(attrs, [:name, :email, :bio, :number_of_pets]) - |> validate_required([:name, :email, :bio, :number_of_pets]) - end +def changeset(%User{} = user, attrs) do + user + |> cast(attrs, [:name, :email, :bio, :number_of_pets]) + |> validate_required([:name, :email, :bio, :number_of_pets]) +end ``` Right now, we have two transformations in our pipeline. In the first call, we invoke `Ecto.Changeset`'s `cast/3`, passing in our external parameters and marking which fields are required for validation. @@ -210,7 +210,7 @@ Hello.User Next, let's build a changeset from our schema with an empty `User` struct, and an empty map of parameters. -```console +```elixir iex> changeset = User.changeset(%User{}, %{}) #Ecto.Changeset changeset = User.changeset(%User{}, %{}) Once we have a changeset, we can check it if it is valid. -```console +```elixir iex> changeset.valid? false ``` Since this one is not valid, we can ask it what the errors are. -```console +```elixir iex> changeset.errors [name: {"can't be blank", [validation: :required]}, email: {"can't be blank", [validation: :required]}, @@ -268,7 +268,7 @@ What happens if we pass a key/value pair that is in neither defined in the schem Inside our existing IEx shell, let's create a `params` map with valid values plus an extra `random_key: "random value"`. -```console +```elixir iex> params = %{name: "Joe Example", email: "joe@example.com", bio: "An example to all", number_of_pets: 5, random_key: "random value"} %{email: "joe@example.com", name: "Joe Example", bio: "An example to all", number_of_pets: 5, random_key: "random value"} @@ -276,7 +276,7 @@ number_of_pets: 5, random_key: "random value"} Next, let's use our new `params` map to create another changeset. -```console +```elixir iex> changeset = User.changeset(%User{}, params) #Ecto.Changeset changeset = User.changeset(%User{}, params) Our new changeset is valid. -```console +```elixir iex> changeset.valid? true ``` We can also check the changeset's changes - the map we get after all of the transformations are complete. -```console +```elixir iex(9)> changeset.changes %{bio: "An example to all", email: "joe@example.com", name: "Joe Example", number_of_pets: 5} @@ -306,18 +306,18 @@ We can validate more than just whether a field is required or not. Let's take a What if we had a requirement that all biographies in our system must be at least two characters long? We can do this easily by adding another transformation to the pipeline in our changeset which validates the length of the `bio` field. ```elixir - def changeset(%User{} = user, attrs) do - user - |> cast(attrs, [:name, :email, :bio, :number_of_pets]) - |> validate_required([:name, :email, :bio, :number_of_pets]) - |> validate_length(:bio, min: 2) - end +def changeset(%User{} = user, attrs) do + user + |> cast(attrs, [:name, :email, :bio, :number_of_pets]) + |> validate_required([:name, :email, :bio, :number_of_pets]) + |> validate_length(:bio, min: 2) +end ``` Now, if we try to cast data containing a value of "A" for our user's bio, we should see the failed validation in the changeset's errors. -```console +```elixir iex> changeset = User.changeset(%User{}, %{bio: "A"}) iex> changeset.errors[:bio] {"should be at least %{count} character(s)", @@ -327,31 +327,31 @@ iex> changeset.errors[:bio] If we also have a requirement for the maximum length that a bio can have, we can simply add another validation. ```elixir - def changeset(%User{} = user, attrs) do - user - |> cast(attrs, [:name, :email, :bio, :number_of_pets]) - |> validate_required([:name, :email, :bio, :number_of_pets]) - |> validate_length(:bio, min: 2) - |> validate_length(:bio, max: 140) - end +def changeset(%User{} = user, attrs) do + user + |> cast(attrs, [:name, :email, :bio, :number_of_pets]) + |> validate_required([:name, :email, :bio, :number_of_pets]) + |> validate_length(:bio, min: 2) + |> validate_length(:bio, max: 140) +end ``` Let's say we want to perform at least some rudimentary format validation on the `email` field. All we want to check for is the presence of the "@". The `validate_format/3` function is just what we need. ```elixir - def changeset(%User{} = user, attrs) do - user - |> cast(attrs, [:name, :email, :bio, :number_of_pets]) - |> validate_required([:name, :email, :bio, :number_of_pets]) - |> validate_length(:bio, min: 2) - |> validate_length(:bio, max: 140) - |> validate_format(:email, ~r/@/) - end +def changeset(%User{} = user, attrs) do + user + |> cast(attrs, [:name, :email, :bio, :number_of_pets]) + |> validate_required([:name, :email, :bio, :number_of_pets]) + |> validate_length(:bio, min: 2) + |> validate_length(:bio, max: 140) + |> validate_format(:email, ~r/@/) +end ``` If we try to cast a user with an email of "example.com", we should see an error message like the following. -```console +```elixir iex> changeset = User.changeset(%User{}, %{email: "example.com"}) iex> changeset.errors[:email] {"has invalid format", [validation: :format]} @@ -365,7 +365,7 @@ We've talked a lot about migrations and data-storage, but we haven't yet persist Let's head back over to IEx with `iex -S mix`, and insert a couple of users into the database. -```console +```elixir iex> alias Hello.{Repo, User} [Hello.Repo, Hello.User] iex> Repo.insert(%User{email: "user1@example.com"}) @@ -389,7 +389,7 @@ INSERT INTO "users" ("email","inserted_at","updated_at") VALUES ($1,$2,$3) RETUR We started by aliasing our `User` and `Repo` modules for easy access. Next, we called `Repo.insert/1` and passed a user struct. Since we're in the `dev` environment, we can see the debug logs for the query our Repo performed when inserting the underlying `%User{}` data. We received a 2-tuple back with `{:ok, %User{}}`, which lets us know the insertion was successful. With a couple of users inserted, let's fetch them back out of the repo. -```console +```elixir iex> Repo.all(User) [debug] QUERY OK source="users" db=2.7ms SELECT u0."id", u0."bio", u0."email", u0."name", u0."number_of_pets", u0."inserted_at", u0."updated_at" FROM "users" AS u0 [] @@ -405,7 +405,7 @@ SELECT u0."id", u0."bio", u0."email", u0."name", u0."number_of_pets", u0."insert That was easy! `Repo.all/1` takes a data source, our `User` schema in this case, and translates that to an underlying SQL query against our database. After it fetches the data, the Repo then uses our Ecto schema to map the database values back into Elixir data-structures according to our `User` schema. We're not just limited to basic querying – Ecto includes a full-fledged query DSL for advanced SQL generation. In addition to a natural Elixir DSL, Ecto's query engine gives us multiple great features, such as SQL injection protection and compile-time optimization of queries. Let's try it out. -```console +```elixir iex> import Ecto.Query Ecto.Query @@ -417,7 +417,7 @@ SELECT u0."email" FROM "users" AS u0 [] First, we imported `Ecto.Query`, which imports the `from` macro of Ecto's Query DSL. Next, we built a query which selects all the email addresses in our users table. Let's try another example. -```console +```elixir iex)> Repo.one(from u in User, where: ilike(u.email, "%1%"), select: count(u.id)) [debug] QUERY OK source="users" db=1.6ms SELECT count(u0."id") FROM "users" AS u0 WHERE (u0."email" ILIKE '%1%') [] @@ -426,7 +426,7 @@ iex)> Repo.one(from u in User, where: ilike(u.email, "%1%"), Now we're starting to get a taste of Ecto's rich querying capabilities. We used `Repo.one/1` to fetch the count of all users with an email address containing "1", and received the expected count in return. This just scratches the surface of Ecto's query interface, and much more is supported such as sub-querying, interval queries, and advanced select statements. For example, let's build a query to fetch a map of all user id's to their email addresses. -```console +```elixir iex> Repo.all(from u in User, select: %{u.id => u.email}) [debug] QUERY OK source="users" db=0.9ms SELECT u0."id", u0."email" FROM "users" AS u0 [] @@ -447,7 +447,7 @@ Phoenix applications are configured to use PostgreSQL by default, but what if we If we are about to create a new application, configuring our application to use MySQL is easy. We can simply pass the `--database mysql` flag to `phx.new` and everything will be configured correctly. -``` +```console $ mix phx.new hello_phoenix --database mysql ``` @@ -459,7 +459,7 @@ To switch adapters, we need to remove the Postgrex dependency and add a new one Let's open up our `mix.exs` file and do that now. -``` +```elixir defmodule HelloPhoenix.MixProject do use Mix.Project @@ -485,7 +485,7 @@ end Next, we need to configure our new adapter. Let's open up our `config/dev.exs` file and do that. -``` +```elixir config :hello_phoenix, HelloPhoenix.Repo, username: "root", password: "", @@ -498,20 +498,20 @@ The last change is to open up `lib/hello_phoenix/repo.ex` and make sure to set t Now all we need to do is fetch our new dependency, and we'll be ready to go. -``` +```console $ mix do deps.get, compile ``` With our new adapter installed and configured, we're ready to create our database. -``` +```console $ mix ecto.create ``` The database for HelloPhoenix.repo has been created. We're also ready to run any migrations, or do anything else with Ecto that we might choose. -``` +```console $ mix ecto.migrate [info] == Running HelloPhoenix.Repo.Migrations.CreateUser.change/0 forward [info] create table users diff --git a/guides/endpoint.md b/guides/endpoint.md index c0dc01dfee..ea3c7ca6a5 100644 --- a/guides/endpoint.md +++ b/guides/endpoint.md @@ -7,7 +7,7 @@ Phoenix applications start the HelloWeb.Endpoint as a supervised process. By def defmodule Hello.Application do use Application def start(_type, _args) do - #... + ... children = [ HelloWeb.Endpoint @@ -33,13 +33,15 @@ end The first call inside of our Endpoint module is the `use Phoenix.Endpoint` macro with the `otp_app`. The `otp_app` is used for the configuration. This defines several functions on the `HelloWeb.Endpoint` module, including the `start_link` function which is called in the supervision tree. ```elixir - use Phoenix.Endpoint, otp_app: :hello +use Phoenix.Endpoint, otp_app: :hello ``` Next the endpoint declares a socket on the "/socket" URI. "/socket" requests will be handled by the `HelloWeb.UserSocket` module which is declared elsewhere in our application. Here we are just declaring that such a connection will exist. ```elixir - socket "/socket", HelloWeb.UserSocket +socket "/socket", HelloWeb.UserSocket, + websocket: true, + longpoll: false ``` Next comes a series of plugs that are relevant to all requests in our application. We can customize some of the features, for example, enabling `gzip: true` when deploying to production to gzip the static files. @@ -47,40 +49,42 @@ Next comes a series of plugs that are relevant to all requests in our applicatio Static files are served from `priv/static` before any part of our request makes it to a router. ```elixir - plug Plug.Static, - at: "/", from: :hello, gzip: false, - only: ~w(css fonts images js favicon.ico robots.txt) +plug Plug.Static, + at: "/", + from: :hello, + gzip: false, + only: ~w(css fonts images js favicon.ico robots.txt) ``` If code reloading is enabled, a socket will be used to communicate to the browser that the page needs to be reloaded when code is changed on the server. This feature is enabled by default in the development environment. This is configured using `config :hello, HelloWeb.Endpoint, code_reloader: true`. ```elixir - if code_reloading? do - socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket - plug Phoenix.LiveReloader - plug Phoenix.CodeReloader - end +if code_reloading? do + socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket + plug Phoenix.LiveReloader + plug Phoenix.CodeReloader +end ``` [Plug.RequestId](https://hexdocs.pm/plug/Plug.RequestId.html) generates a unique id for each request and [Plug.Logger](https://hexdocs.pm/plug/Plug.Logger.html) logs the request path, status code and request time by default. ```elixir - plug Plug.RequestId - plug Plug.Logger +plug Plug.RequestId +plug Plug.Logger ``` [Plug.Session](https://hexdocs.pm/plug/Plug.Session.html) handles the session cookies and session stores. ```elixir - plug Plug.Session, - store: :cookie, - key: "_hello_key", - signing_salt: "change_me" +plug Plug.Session, + store: :cookie, + key: "_hello_key", + signing_salt: "change_me" ``` By default the last plug in the endpoint is the router. The router matches a path to a particular controller action or plug. The router is covered in the [Routing Guide](routing.html). ```elixir - plug HelloWeb.Router +plug HelloWeb.Router ``` The endpoint can be customized to add additional plugs, to allow HTTP basic authentication, CORS, subdomain routing and more. @@ -105,12 +109,14 @@ config :hello, HelloWeb.Endpoint, http: [port: {:system, "PORT"}], url: [host: "example.com"], cache_static_manifest: "priv/static/cache_manifest.json", - https: [port: 443, - otp_app: :hello, - keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), - certfile: System.get_env("SOME_APP_SSL_CERT_PATH"), - cacertfile: System.get_env("INTERMEDIATE_CERTFILE_PATH") # OPTIONAL Key for intermediate certificates - ] + https: [ + port: 443, + otp_app: :hello, + keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"), + certfile: System.get_env("SOME_APP_SSL_CERT_PATH"), + # OPTIONAL Key for intermediate certificates: + cacertfile: System.get_env("INTERMEDIATE_CERTFILE_PATH") + ] ``` @@ -131,13 +137,13 @@ With your self-signed certificate, your development configuration in `config/dev ```elixir config :my_app, MyApp.Endpoint, - # ... - https: [ - port: 4001, - cipher_suite: :strong, - keyfile: "priv/cert/selfsigned_key.pem", - certfile: "priv/cert/selfsigned.pem" - ] + ... + https: [ + port: 4001, + cipher_suite: :strong, + keyfile: "priv/cert/selfsigned_key.pem", + certfile: "priv/cert/selfsigned.pem" + ] ``` This can replace your `http` configuration, or you can run HTTP and HTTPS servers on different ports. diff --git a/guides/introduction/community.md b/guides/introduction/community.md index fe325c967b..2eb374bd62 100644 --- a/guides/introduction/community.md +++ b/guides/introduction/community.md @@ -10,4 +10,4 @@ There are a number of places to connect with community members at all experience * Ask or answer questions about Phoenix on [Elixir Forum](https://elixirforum.com/c/phoenix-forum) or [Stack Overflow](http://stackoverflow.com/questions/tagged/phoenix-framework). * To discuss new features in the framework, email the [phoenix-core mailing list](https://groups.google.com/group/phoenix-core). * Follow the Phoenix Framework on [Twitter](https://twitter.com/elixirphoenix). -* The source for these guides is [on GitHub](https://github.com/phoenixframework/phoenix_guides). To help improve the guides, please report an [issue](https://github.com/phoenixframework/phoenix_guides/issues) or open a [pull request](https://github.com/phoenixframework/phoenix_guides/pulls). +* The source for these guides is [on GitHub](https://github.com/phoenixframework/phoenix/tree/master/guides). To help improve the guides, please report an [issue](https://github.com/phoenixframework/phoenix/issues) or open a [pull request](https://github.com/phoenixframework/phoenix/pulls). diff --git a/guides/introduction/installation.md b/guides/introduction/installation.md index 2869054e0e..ca63491888 100644 --- a/guides/introduction/installation.md +++ b/guides/introduction/installation.md @@ -88,7 +88,7 @@ PostgreSQL is a relational database server. Phoenix configures applications to u When we work with Ecto schemas in these guides, we will use PostgreSQL and the Postgrex adapter for it. In order to follow along with the examples, we should install PostgreSQL. The PostgreSQL wiki has [installation guides](https://wiki.postgresql.org/wiki/Detailed_installation_guides) for a number of different systems. -Postgrex is a direct Phoenix dependency, and it will be automatically installed along with the rest of our dependencies as we start our app. +Postgrex is a direct Phoenix dependency, and it will be automatically installed along with the rest of our dependencies as we start building our app. ### inotify-tools (for linux users) diff --git a/guides/introduction/overview.md b/guides/introduction/overview.md index b89fbbcd04..93d6287d9f 100644 --- a/guides/introduction/overview.md +++ b/guides/introduction/overview.md @@ -47,7 +47,7 @@ Phoenix is made up of a number of distinct parts, each with its own purpose and ## Phoenix Layers -We just covered the internal parts that make up Phoenix, but its important to remember Phoenix itself is actually the top layer of a multi-layer system designed to be modular and flexible. The other layers include Cowboy, Plug, and Ecto. +We just covered the internal parts that make up Phoenix, but it's important to remember Phoenix itself is actually the top layer of a multi-layer system designed to be modular and flexible. The other layers include Cowboy, Plug, and Ecto. ### Cowboy diff --git a/guides/presence.md b/guides/presence.md index 102570f235..f3bb602f90 100644 --- a/guides/presence.md +++ b/guides/presence.md @@ -22,11 +22,11 @@ You're all set! See the Phoenix.Presence docs for more details: http://hexdocs.pm/phoenix/Phoenix.Presence.html ``` -If we open up the `hello_web/channels/presence.ex` file, we will see the following line: +If we open up the `lib/hello_web/channels/presence.ex` file, we will see the following line: ```elixir - use Phoenix.Presence, otp_app: :hello, - pubsub_server: Hello.PubSub +use Phoenix.Presence, otp_app: :hello, + pubsub_server: Hello.PubSub ``` This sets up the module for presence, defining the functions we require for tracking presences. As mentioned in the generator task, we should add this module to our supervision tree in @@ -34,7 +34,7 @@ This sets up the module for presence, defining the functions we require for trac ```elixir children = [ - # ... + ... HelloWeb.Presence, ] ``` @@ -64,9 +64,9 @@ end We also need to change our connect function to take a `user_id` from the params and assign it on the socket. In production you may want to use `Phoenix.Token` if you have real users that are authenticated. ```elixir - def connect(params, socket) do - {:ok, assign(socket, :user_id, params["user_id"])} - end +def connect(params, socket) do + {:ok, assign(socket, :user_id, params["user_id"])} +end ``` Next, we will create the channel that we'll communicate presence over. After a user joins we can push the list of presences down the channel and then track the connection. We can also provide a map of additional information to track. @@ -133,7 +133,7 @@ channel.join() We can ensure this is working by opening 3 browser tabs. If we navigate to on two browser tabs and then we should see: -``` +```plaintext Alice (count: 2) Bob (count: 1) ``` diff --git a/guides/routing.md b/guides/routing.md index 1dd4d2e751..79edd7107b 100644 --- a/guides/routing.md +++ b/guides/routing.md @@ -41,7 +41,7 @@ Scopes have their own section in this guide, so we won't spend time on the `scop Inside the scope block, however, we have our first actual route: ```elixir - get "/", PageController, :index +get "/", PageController, :index ``` `get` is a Phoenix macro which expands out to define one clause of the `match/5` function. It corresponds to the HTTP verb GET. Similar macros exist for other HTTP verbs including POST, PUT, PATCH, DELETE, OPTIONS, CONNECT, TRACE and HEAD. @@ -51,7 +51,7 @@ The first argument to these macros is the path. Here, it is the root of the appl If this were the only route in our router module, the clause of the `match/5` function would look like this after the macro is expanded: ```elixir - def match(:get, "/", PageController, :index, []) +def match(:get, "/", PageController, :index, []) ``` The body of the `match/5` function sets up the connection and invokes the matched controller action. @@ -68,13 +68,13 @@ Define this route at the bottom of the `scope "/", HelloWeb do` block in the rou get "/", RootController, :index ``` -Then run `$ mix compile` at the root of your project. +Then run `mix compile` at the root of your project. ## Examining Routes Phoenix provides a great tool for investigating routes in an application, the mix task `phx.routes`. -Let's see how this works. Go to the root of a newly-generated Phoenix application and run `$ mix phx.routes`. (If you haven't already done so, you'll need to run `$ mix do deps.get, compile` before running the `routes` task.) You should see something like the following, generated from the only route we currently have: +Let's see how this works. Go to the root of a newly-generated Phoenix application and run `mix phx.routes`. (If you haven't already done so, you'll need to run `mix do deps.get, compile` before running the `routes` task.) You should see something like the following, generated from the only route we currently have: ```console $ mix phx.routes @@ -100,7 +100,7 @@ end ``` For this purpose, it doesn't matter that we don't actually have a `HelloWeb.UserController`. -Then go to the root of your project, and run `$ mix phx.routes` +Then go to the root of your project, and run `mix phx.routes` You should see something like the following: @@ -136,7 +136,7 @@ Let's say we have a read-only posts resource. We could define it like this: resources "/posts", PostController, only: [:index, :show] ``` -Running `$ mix phx.routes` shows that we now only have the routes to the index and show actions defined. +Running `mix phx.routes` shows that we now only have the routes to the index and show actions defined. ```elixir post_path GET /posts HelloWeb.PostController :index @@ -149,7 +149,7 @@ Similarly, if we have a comments resource, and we don't want to provide a route resources "/comments", CommentController, except: [:delete] ``` -Running `$ mix phx.routes` now shows that we have all the routes except the DELETE request to the delete action. +Running `mix phx.routes` now shows that we have all the routes except the DELETE request to the delete action. ```elixir comment_path GET /comments HelloWeb.CommentController :index @@ -171,10 +171,10 @@ The `Phoenix.Router.forward/4` macro can be used to send all requests that start defmodule HelloWeb.Router do use HelloWeb, :router - #... + ... scope "/", HelloWeb do - #... + ... end forward "/jobs", BackgroundJob.Plug @@ -189,7 +189,7 @@ We can even use the `forward/4` macro in a pipeline. If we wanted to ensure that defmodule HelloWeb.Router do use HelloWeb, :router - #... + ... scope "/" do pipe_through [:authenticate_user, :ensure_admin] @@ -203,7 +203,7 @@ This means that the plugs in the `authenticate_user` and `ensure_admin` pipeline The `opts` that are passed to the `init/1` callback of a Plug can be passed as a 3rd argument. For example, maybe the background job page lets you set the name of your application to be displayed on the page. This could be passed with: ```elixir - forward "/jobs", BackgroundJob.Plug, name: "Hello Phoenix" +forward "/jobs", BackgroundJob.Plug, name: "Hello Phoenix" ``` There is a fourth `router_opts` argument that can be passed. These options are outlined in the `Phoenix.Router.scope/2` documentation. @@ -240,7 +240,7 @@ end Path helpers are functions which are dynamically defined on the `Router.Helpers` module for an individual application. For us, that is `HelloWeb.Router.Helpers`. Their names are derived from the name of the controller used in the route definition. Our controller is `HelloWeb.PageController`, and `page_path` is the function which will return the path to the root of our application. -That's a mouthful. Let's see it in action. Run `$ iex -S mix` at the root of the project. When we call the `page_path` function on our router helpers with the `Endpoint` or connection and action as arguments, it returns the path to us. +That's a mouthful. Let's see it in action. Run `iex -S mix` at the root of the project. When we call the `page_path` function on our router helpers with the `Endpoint` or connection and action as arguments, it returns the path to us. ```elixir iex> HelloWeb.Router.Helpers.page_path(HelloWeb.Endpoint, :index) @@ -313,10 +313,10 @@ resources "/users", UserController do resources "/posts", PostController end ``` -When we run `$ mix phx.routes` now, in addition to the routes we saw for `users` above, we get the following set of routes: +When we run `mix phx.routes` now, in addition to the routes we saw for `users` above, we get the following set of routes: ```elixir -. . . +... user_post_path GET /users/:user_id/posts HelloWeb.PostController :index user_post_path GET /users/:user_id/posts/:id/edit HelloWeb.PostController :edit user_post_path GET /users/:user_id/posts/new HelloWeb.PostController :new @@ -363,7 +363,7 @@ The paths to the user facing reviews would look like a standard resource. /reviews /reviews/1234 /reviews/1234/edit -. . . +... ``` The admin review paths could be prefixed with `/admin`. @@ -372,7 +372,7 @@ The admin review paths could be prefixed with `/admin`. /admin/reviews /admin/reviews/1234 /admin/reviews/1234/edit -. . . +... ``` We accomplish this with a scoped route that sets a path option to `/admin` like this one. For now, let's not nest this scope inside of any other scopes (like the `scope "/", HelloWeb do` one provided for us in a new app). @@ -387,10 +387,10 @@ end Note also, that the way this scope is currently defined, we need to fully qualify our controller name, `HelloWeb.Admin.ReviewController`. We'll fix that in a minute. -Running `$ mix phx.routes` again, in addition to the previous set of routes we get the following: +Running `mix phx.routes` again, in addition to the previous set of routes we get the following: ```elixir -. . . +... review_path GET /admin/reviews HelloWeb.Admin.ReviewController :index review_path GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit review_path GET /admin/reviews/new HelloWeb.Admin.ReviewController :new @@ -406,9 +406,9 @@ This looks good, but there is a problem here. Remember that we wanted both user ```elixir scope "/", HelloWeb do pipe_through :browser - . . . + ... resources "/reviews", ReviewController - . . . + ... end scope "/admin" do @@ -416,10 +416,10 @@ scope "/admin" do end ``` -and we run `$ mix phx.routes`, we get this output: +and we run `mix phx.routes`, we get this output: ```elixir -. . . +... review_path GET /reviews HelloWeb.ReviewController :index review_path GET /reviews/:id/edit HelloWeb.ReviewController :edit review_path GET /reviews/new HelloWeb.ReviewController :new @@ -428,7 +428,7 @@ review_path POST /reviews HelloWeb.ReviewController :create review_path PATCH /reviews/:id HelloWeb.ReviewController :update PUT /reviews/:id HelloWeb.ReviewController :update review_path DELETE /reviews/:id HelloWeb.ReviewController :delete -. . . +... review_path GET /admin/reviews HelloWeb.Admin.ReviewController :index review_path GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit review_path GET /admin/reviews/new HelloWeb.Admin.ReviewController :new @@ -444,9 +444,9 @@ The actual routes we get all look right, except for the path helper `review_path ```elixir scope "/", HelloWeb do pipe_through :browser - . . . + ... resources "/reviews", ReviewController - . . . + ... end scope "/admin", as: :admin do @@ -454,10 +454,10 @@ scope "/admin", as: :admin do end ``` -`$ mix phx.routes` now shows us we have what we are looking for. +`mix phx.routes` now shows us we have what we are looking for. ```elixir -. . . +... review_path GET /reviews HelloWeb.ReviewController :index review_path GET /reviews/:id/edit HelloWeb.ReviewController :edit review_path GET /reviews/new HelloWeb.ReviewController :new @@ -466,7 +466,7 @@ end review_path PATCH /reviews/:id HelloWeb.ReviewController :update PUT /reviews/:id HelloWeb.ReviewController :update review_path DELETE /reviews/:id HelloWeb.ReviewController :delete -. . . +... admin_review_path GET /admin/reviews HelloWeb.Admin.ReviewController :index admin_review_path GET /admin/reviews/:id/edit HelloWeb.Admin.ReviewController :edit admin_review_path GET /admin/reviews/new HelloWeb.Admin.ReviewController :new @@ -477,7 +477,7 @@ admin_review_path PATCH /admin/reviews/:id HelloWeb.Admin.Review admin_review_path DELETE /admin/reviews/:id HelloWeb.Admin.ReviewController :delete ``` -The path helpers now return what we want them to as well. Run `$ iex -S mix` and give it a try yourself. +The path helpers now return what we want them to as well. Run `iex -S mix` and give it a try yourself. ```elixir iex(1)> HelloWeb.Router.Helpers.review_path(HelloWeb.Endpoint, :index) @@ -499,10 +499,10 @@ scope "/admin", as: :admin do end ``` -Here's what `$ mix phx.routes` tells us: +Here's what `mix phx.routes` tells us: ```elixir -. . . +... admin_image_path GET /admin/images HelloWeb.Admin.ImageController :index admin_image_path GET /admin/images/:id/edit HelloWeb.Admin.ImageController :edit admin_image_path GET /admin/images/new HelloWeb.Admin.ImageController :new @@ -541,7 +541,7 @@ scope "/admin", HelloWeb.Admin, as: :admin do end ``` -Now run `$ mix phx.routes` again and you can see that we get the same result as above when we qualified each controller name individually. +Now run `mix phx.routes` again and you can see that we get the same result as above when we qualified each controller name individually. This doesn't just apply to nested routes, we can even nest all of the routes for our application inside a scope that simply has an alias for the name of our Phoenix app, and eliminate the duplication of our application name in our controller names. @@ -561,7 +561,7 @@ defmodule HelloWeb.Router do end ``` -Again `$ mix phx.routes` tells us that all of our controllers now have the correct, fully-qualified names. +Again `mix phx.routes` tells us that all of our controllers now have the correct, fully-qualified names. ```elixir image_path GET /images HelloWeb.ImageController :index @@ -597,7 +597,7 @@ scope "/api", HelloWeb.Api, as: :api do end ``` -`$ mix phx.routes` tells us that we have the routes we're looking for. +`mix phx.routes` tells us that we have the routes we're looking for. ```elixir api_v1_image_path GET /api/v1/images HelloWeb.Api.V1.ImageController :index @@ -635,7 +635,7 @@ This router is perfectly fine with two scopes defined for the same path. ```elixir defmodule HelloWeb.Router do use Phoenix.Router - . . . + ... scope "/", HelloWeb do pipe_through :browser @@ -647,10 +647,10 @@ defmodule HelloWeb.Router do resources "/posts", PostController end - . . . + ... end ``` -And when we run `$ mix phx.routes`, we see the following output. +And when we run `mix phx.routes`, we see the following output. ```elixir user_path GET /users HelloWeb.UserController :index @@ -687,12 +687,12 @@ Endpoints organize all the plugs common to every request, and apply them before - [Plug.Static](https://hexdocs.pm/plug/Plug.Static.html) - serves static assets. Since this plug comes before the logger, serving of static assets is not logged +- [Phoenix.CodeReloader](https://hexdocs.pm/phoenix/Phoenix.CodeReloader.html) - a plug that enables code reloading for all entries in the web directory. It is configured directly in the Phoenix application + - [Plug.RequestId](https://hexdocs.pm/plug/Plug.RequestId.html) - generates a unique request id for each request. - [Plug.Logger](https://hexdocs.pm/plug/Plug.Logger.html) - logs incoming requests -- [Phoenix.CodeReloader](https://hexdocs.pm/phoenix/Phoenix.CodeReloader.html) - a plug that enables code reloading for all entries in the web directory. It is configured directly in the Phoenix application - - [Plug.Parsers](https://hexdocs.pm/plug/Plug.Parsers.html) - parses the request body when a known parser is available. By default parsers parse urlencoded, multipart and json (with `jason`). The request body is left untouched when the request content-type cannot be parsed - [Plug.MethodOverride](https://hexdocs.pm/plug/Plug.MethodOverride.html) - converts the request method to diff --git a/guides/up_and_running.md b/guides/up_and_running.md index c17f3baed3..36c594d4d1 100644 --- a/guides/up_and_running.md +++ b/guides/up_and_running.md @@ -14,7 +14,7 @@ We can run `mix phx.new` from any directory in order to bootstrap our Phoenix ap $ mix phx.new hello ``` -> A note about [webpack](https://webpack.js.org/) before we begin: Phoenix will use webpack for asset management by default. Webpacks' dependencies are installed via the node package manager, not mix. Phoenix will prompt us to install them at the end of the `mix phx.new` task. If we say "no" at that point, and if we don't install those dependencies later with `npm install`, our application will raise errors when we try to start it, and our assets may not load properly. If we don't want to use webpack at all, we can simply pass `--no-webpack` to `mix phx.new`. +> A note about [webpack](https://webpack.js.org/) before we begin: Phoenix will use webpack for asset management by default. Webpack's dependencies are installed via the node package manager, not mix. Phoenix will prompt us to install them at the end of the `mix phx.new` task. If we say "no" at that point, and if we don't install those dependencies later with `npm install`, our application will raise errors when we try to start it, and our assets may not load properly. If we don't want to use webpack at all, we can simply pass `--no-webpack` to `mix phx.new`. > A note about [Ecto](https://hexdocs.pm/phoenix/ecto.html): Ecto allows our Phoenix application to communicate with a data store, such as PostgreSQL or MongoDB. If our application will not require this component we can skip this dependency by passing the `--no-ecto` flag to `mix phx.new`. This flag may also be combined with `--no-webpack` to create a skeleton application. @@ -59,15 +59,17 @@ You can also run your app inside IEx (Interactive Elixir) as: Once our dependencies are installed, the task will prompt us to change into our project directory and start our application. -Phoenix assumes that our PostgreSQL database will have a `postgres` user account with the correct permissions and a password of "postgres". If that isn't the case, please see the [`Mix Tasks Guide`](phoenix_mix_tasks.html#ecto-specific-mix-tasks) to learn more about the `mix ecto.create` task. +Phoenix assumes that our PostgreSQL database will have a `postgres` user account with the correct permissions and a password of "postgres". If that isn't the case, please see the [Mix Tasks Guide](phoenix_mix_tasks.html#ecto-specific-mix-tasks) to learn more about the `mix ecto.create` task. Ok, let's give it a try. First, we'll `cd` into the `hello/` directory we've just created: - $ cd hello +```console +$ cd hello +``` Now we'll create our database: -``` +```console $ mix ecto.create Compiling 13 files (.ex) Generated hello app diff --git a/guides/views.md b/guides/views.md index 0d7291432e..8eac156a23 100644 --- a/guides/views.md +++ b/guides/views.md @@ -28,7 +28,7 @@ Let's open up our application layout template, `lib/hello_web/templates/layout/a to call a `title/0` function, like this. -```elixir +```html <%= title() %> ``` @@ -61,7 +61,7 @@ end ``` Now if you fire up the server with `mix phx.server` and visit `http://localhost:4000`, you should see the following text below your layout header instead of the main template page: -``` +```console rendering with assigns [:conn, :view_module, :view_template] ``` @@ -125,7 +125,7 @@ This is the message: <%= message() %> This doesn't correspond to any action in our controller, but we'll exercise it in an `iex` session. At the root of our project, we can run `iex -S mix`, and then explicitly render our template. -```console +```elixir iex(1)> Phoenix.View.render(HelloWeb.PageView, "test.html", %{}) {:safe, [["" | "This is the message: "] | "Hello from the view!"]} ``` @@ -140,7 +140,7 @@ This is the message: <%= message() %> Note the `@` in the top line. Now if we change our function call, we see a different rendering after recompiling `PageView` module. -```console +```elixir iex(2)> r HelloWeb.PageView warning: redefining module HelloWeb.PageView (current version loaded from _build/dev/lib/hello/ebin/Elixir.HelloWeb.PageView.beam) lib/hello_web/views/page_view.ex:1 @@ -154,7 +154,7 @@ iex(3)> Phoenix.View.render(HelloWeb.PageView, "test.html", message: "Assigns ha ``` Let's test out the HTML escaping, just for fun. -```console +```elixir iex(4)> Phoenix.View.render(HelloWeb.PageView, "test.html", message: "") {:safe, [[[["" | "I came from assigns: "] | @@ -164,11 +164,11 @@ iex(4)> Phoenix.View.render(HelloWeb.PageView, "test.html", message: "