diff --git a/.travis.yml b/.travis.yml index 786a4943c3..c3c1fab7f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,8 @@ elixir: - 1.4 - 1.5 - 1.6 + - 1.7 + - 1.8 otp_release: - 19.3 - 20.3 @@ -13,6 +15,14 @@ matrix: otp_release: 21.0 - elixir: 1.4 otp_release: 21.0 + - elixir: 1.7 + otp_release: 19.3 + - elixir: 1.7 + otp_release: 20.3 + - elixir: 1.8 + otp_release: 19.3 + - elixir: 1.8 + otp_release: 20.3 sudo: false env: global: diff --git a/assets/js/phoenix.js b/assets/js/phoenix.js index de56fe015b..df78c0dd8f 100644 --- a/assets/js/phoenix.js +++ b/assets/js/phoenix.js @@ -840,7 +840,6 @@ export class Socket { this.flushSendBuffer() this.reconnectTimer.reset() this.resetHeartbeat() - this.resetChannelTimers() this.stateChangeCallbacks.open.forEach( callback => callback() ) } @@ -989,15 +988,6 @@ export class Socket { } }) } - - /** - * @private - */ - resetChannelTimers() { - this.channels.forEach(channel => { - channel.rejoinTimer.restart() - }) - } } @@ -1358,31 +1348,18 @@ class Timer { reset(){ this.tries = 0 - this.clearTimer() - } - - restart(){ - const processing = this.timer !== null - this.reset() - if (processing){ - this.scheduleTimeout() - } + clearTimeout(this.timer) } /** * Cancels any previous scheduleTimeout and schedules callback */ scheduleTimeout(){ - this.clearTimer() + clearTimeout(this.timer) this.timer = setTimeout(() => { this.tries = this.tries + 1 this.callback() }, this.timerCalc(this.tries + 1)) } - - clearTimer() { - clearTimeout(this.timer) - this.timer = null - } } diff --git a/assets/test/socket_test.js b/assets/test/socket_test.js index fe38507b5d..4790db7759 100644 --- a/assets/test/socket_test.js +++ b/assets/test/socket_test.js @@ -605,25 +605,6 @@ describe("onConnOpen", () => { assert.ok(spy.calledOnce) }) - it("resets all channel timers and schedules a timeout if the timer was in progress", () => { - const channel = socket.channel("topic", {}) - const channel2 = socket.channel("topic2", {}) - - channel.rejoinTimer.tries = 1 - channel2.rejoinTimer.tries = 2 - channel2.rejoinTimer.scheduleTimeout() - - assert.equal(channel.rejoinTimer.timer, null) - assert.notEqual(channel2.rejoinTimer.timer, null) - - socket.onConnOpen() - - assert.equal(channel.rejoinTimer.tries, 0) - assert.equal(channel2.rejoinTimer.tries, 0) - assert.equal(channel.rejoinTimer.timer, null) - assert.notEqual(channel2.rejoinTimer.timer, null) - }) - it("triggers onOpen callback", () => { const spy = sinon.spy() diff --git a/guides/adding_pages.md b/guides/adding_pages.md index 1157a1e03c..4e72744765 100644 --- a/guides/adding_pages.md +++ b/guides/adding_pages.md @@ -182,7 +182,7 @@ end ### A New Template -Phoenix templates are just that, templates into which data can be rendered. The standard templating engine Phoenix uses is EEx, which stands for [Embedded Elixir](https://hexdocs.pm/eex/1.5.1/EEx.html). Phoenix enhances EEx to include automatic escaping of values. This protects you from security vulnerabilities like Cross-Site-Scripting with no extra work on your part. All of our template files will have the `.eex` file extension. +Phoenix templates are just that, templates into which data can be rendered. The standard templating engine Phoenix uses is `EEx`, which stands for Embedded Elixir. Phoenix enhances EEx to include automatic escaping of values. This protects you from security vulnerabilities like Cross-Site-Scripting with no extra work on your part. All of our template files will have the `.eex` file extension. Templates are scoped to a view, which are scoped to controller. Phoenix creates a `lib/hello_web/templates` directory where we can put all these. It is best to namespace these for organization, so for our hello page, that means we need to create a `hello` directory under `lib/hello_web/templates` and then create an `index.html.eex` file within it. @@ -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. @@ -225,7 +227,7 @@ scope "/", HelloWeb do get "/hello/:messenger", HelloController, :show end ``` -Notice that we put the atom `:messenger` in the path. Phoenix will take whatever value that appears in that position in the URL and pass a [Map](https://hexdocs.pm/elixir/1.5.1/Map.html) with the key `messenger` pointing to that value to the controller. +Notice that we put the atom `:messenger` in the path. Phoenix will take whatever value that appears in that position in the URL and pass a `Map` with the key `messenger` pointing to that value to the controller. For example, if we point the browser at: [http://localhost:4000/hello/Frank](http://localhost:4000/hello/Frank), the value of ":messenger" will be "Frank". @@ -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..aaada208ad 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 @@ -176,6 +171,8 @@ Phoenix ships with a JavaScript client that is available when generating a new P - [SwiftPhoenix](https://github.com/davidstump/SwiftPhoenixClient) + Java (Android) - [JavaPhoenixChannels](https://github.com/eoinsha/JavaPhoenixChannels) ++ Kotlin (Android) + - [JavaPhoenixClient](https://github.com/dsrees/JavaPhoenixClient) + C# - [PhoenixSharp](https://github.com/Mazyod/PhoenixSharp) - [dn-phoenix](https://github.com/jfis/dn-phoenix) @@ -380,7 +377,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/contexts.md b/guides/contexts.md index 531a4bcc4d..232002763e 100644 --- a/guides/contexts.md +++ b/guides/contexts.md @@ -9,7 +9,7 @@ Using the context generators is a great way for beginners and intermediate Elixi ## Thinking about design -Contexts are dedicated modules that expose and group related functionality. For example, anytime you call Elixir's standard library, be it `Logger.info/1` or `Stream.map/2`, you are accessing different contexts. Internally, Elixir's logger is made of multiple modules, such as `Logger.Config` and `Logger.Backends`, but we never interact with those modules directly. We call the `Logger` module the context, exactly because it exposes and groups all of the logging functionality. +Contexts are dedicated modules that expose and group related functionality. For example, anytime you call Elixir's standard library, be it `Logger.info/1` or `Stream.map/2`, you are accessing different contexts. Internally, Elixir's logger is made of multiple modules, but we never interact with those modules directly. We call the `Logger` module the context, exactly because it exposes and groups all of the logging functionality. Phoenix projects are structured like Elixir and any other Elixir project – we split our code into contexts. A context will group related functionality, such as posts and comments, often encapsulating patterns such as data access and data validation. By using contexts, we decouple and isolate our systems into manageable, independent parts. @@ -59,8 +59,8 @@ username:string:unique * creating test/hello_web/controllers/user_controller_test.exs * creating lib/hello/accounts/user.ex * creating priv/repo/migrations/20170629175236_create_users.exs -* creating lib/hello/accounts/accounts.ex -* injecting lib/hello/accounts/accounts.ex +* creating lib/hello/accounts.ex +* injecting lib/hello/accounts.ex * creating test/hello/accounts/accounts_test.exs * injecting test/hello/accounts/accounts_test.exs @@ -75,7 +75,7 @@ Remember to update your repository by running migrations: ``` -Phoenix generated the web files as expected in `lib/hello_web/`. We can also see our context files were generated inside a `lib/hello/accounts/` directory. Note the difference between `lib/hello` and `lib/hello_web`. We have an `Accounts` module to serve as the public API for account functionality, as well as an `Accounts.User` struct, which is an Ecto schema for casting and validating user account data. Phoenix also provided web and context tests for us, which we'll look at later. For now, let's follow the instructions and add the route according to the console instructions, in `lib/hello_web/router.ex`: +Phoenix generated the web files as expected in `lib/hello_web/`. We can also see our context files were generated inside a `lib/hello/accounts.ex` file and our user schema in the directory of the same name. Note the difference between `lib/hello` and `lib/hello_web`. We have an `Accounts` module to serve as the public API for account functionality, as well as an `Accounts.User` struct, which is an Ecto schema for casting and validating user account data. Phoenix also provided web and context tests for us, which we'll look at later. For now, let's follow the instructions and add the route according to the console instructions, in `lib/hello_web/router.ex`: ```elixir scope "/", HelloWeb do @@ -158,9 +158,9 @@ 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`: +Next, let's dig deeper and check out our `Accounts` context in `lib/hello/accounts.ex`: ```elixir defmodule Hello.Accounts do @@ -257,7 +257,7 @@ email:string:unique user_id:references:users * creating lib/hello/accounts/credential.ex * creating priv/repo/migrations/20170629180555_create_credentials.exs -* injecting lib/hello/accounts/accounts.ex +* injecting lib/hello/accounts.ex * injecting test/hello/accounts/accounts_test.exs Remember to update your repository by running migrations: @@ -267,7 +267,7 @@ Remember to update your repository by running migrations: This time around, we used the `phx.gen.context` task, which is just like `phx.gen.html`, except it doesn't generate the web files for us. Since we already have controllers and templates for managing users, we can integrate the new credential features into our existing web form. -We can see from the output that Phoenix generated an `accounts/credential.ex` file for our `Accounts.Credential` schema, as well as a migration. Notably, phoenix said it was `* injecting` code into the existing `accounts/accounts.ex` context file and test file. Since our `Accounts` module already exists, Phoenix knows to inject our code here. +We can see from the output that Phoenix generated an `accounts/credential.ex` file for our `Accounts.Credential` schema, as well as a migration. Notably, phoenix said it was `* injecting` code into the existing `accounts.ex` context file and test file. Since our `Accounts` module already exists, Phoenix knows to inject our code here. Before we run our migrations, we need to make one change to the generated migration to enforce data integrity of user account credentials. In our case, we want a user's credentials to be deleted when the parent user is removed. Make the following change to your `*_create_credentials.exs` migration file in `priv/repo/migrations/`: @@ -342,7 +342,7 @@ We used `Ecto.Schema`'s `has_one` macro to let Ecto know how to associate our pa ``` -We used the `belongs_to` macro to map our child relationship to the parent `User`. With our schema associations set up, let's open up `accounts/accounts.ex` and make the following changes to the generated `list_users` and `get_user!` functions: +We used the `belongs_to` macro to map our child relationship to the parent `User`. With our schema associations set up, let's open up `accounts.ex` and make the following changes to the generated `list_users` and `get_user!` functions: ```elixir def list_users do @@ -437,7 +437,7 @@ To start, let's think of a function name that describes what we want to accompli > user = Accounts.authenticate_by_email_password(email, password) -That looks nice. A descriptive name that exposes the intent of our code is best. This function makes it crystal clear what purpose it serves, while allowing our caller to remain blissfully unaware of the internal details. Make the following additions to your `lib/hello/accounts/accounts.ex` file: +That looks nice. A descriptive name that exposes the intent of our code is best. This function makes it crystal clear what purpose it serves, while allowing our caller to remain blissfully unaware of the internal details. Make the following additions to your `lib/hello/accounts.ex` file: ```elixir def authenticate_by_email_password(email, _password) do @@ -596,8 +596,8 @@ views:integer --web CMS * creating test/hello_web/controllers/cms/page_controller_test.exs * creating lib/hello/cms/page.ex * creating priv/repo/migrations/20170629195946_create_pages.exs -* creating lib/hello/cms/cms.ex -* injecting lib/hello/cms/cms.ex +* creating lib/hello/cms.ex +* injecting lib/hello/cms.ex * creating test/hello/cms/cms_test.exs * injecting test/hello/cms/cms_test.exs @@ -686,7 +686,7 @@ genre:string user_id:references:users:unique * creating lib/hello/cms/author.ex * creating priv/repo/migrations/20170629200937_create_authors.exs -* injecting lib/hello/cms/cms.ex +* injecting lib/hello/cms.ex * injecting test/hello/cms/cms_test.exs Remember to update your repository by running migrations: @@ -810,7 +810,7 @@ Next, let's add the association in the other direction in `lib/hello/cms/author. We added the `has_many` association for author pages, and then introduced our data dependency on the `Accounts` context by wiring up the `belongs_to` association to our `Accounts.User` schema. -With our associations in place, let's update our `CMS` context to require an author when creating or updating a page. We'll start off with data fetching changes. Open up your `CMS` context in `lib/hello/cms/cms.ex` and replace the `list_pages/0`, `get_page!/1`, and `get_author!/1` functions with the following definitions: +With our associations in place, let's update our `CMS` context to require an author when creating or updating a page. We'll start off with data fetching changes. Open up your `CMS` context in `lib/hello/cms.ex` and replace the `list_pages/0`, `get_page!/1`, and `get_author!/1` functions with the following definitions: ```elixir alias Hello.CMS.{Page, Author} @@ -837,7 +837,7 @@ With our associations in place, let's update our `CMS` context to require an aut We started by rewriting the `list_pages/0` function to preload the associated author, user, and credential data from the database. Next, we rewrote `get_page!/1` and `get_author!/1` to also preload the necessary data. -With our data access functions in place, let's turn our focus towards persistence. We can fetch authors alongside pages, but we haven't yet allowed authors to be persisted when we create or edit pages. Let's fix that. Open up `lib/hello/cms/cms.ex` and make the following changes: +With our data access functions in place, let's turn our focus towards persistence. We can fetch authors alongside pages, but we haven't yet allowed authors to be persisted when we create or edit pages. Let's fix that. Open up `lib/hello/cms.ex` and make the following changes: ```elixir @@ -1015,7 +1015,7 @@ Again, let's think of a function name that describes what we want to accomplish. That looks great. Our callers will have no confusion over what this function does and we can wrap up the increment in an atomic operation to prevent race conditions. -Open up your CMS context (`lib/hello/cms/cms.ex`), and add this new function: +Open up your CMS context (`lib/hello/cms.ex`), and add this new function: ```elixir diff --git a/guides/controllers.md b/guides/controllers.md index d808a79905..6538882254 100644 --- a/guides/controllers.md +++ b/guides/controllers.md @@ -2,7 +2,7 @@ Phoenix controllers act as intermediary modules. Their functions - called actions - are invoked from the router in response to HTTP requests. The actions, in turn, gather all the necessary data and perform all the necessary steps before invoking the view layer to render a template or returning a JSON response. -Phoenix controllers also build on the Plug package, and are themselves plugs. Controllers provide the functions to do almost anything we need to in an action. If we do find ourselves looking for something that Phoenix controllers don't provide; however, we might find what we're looking for in Plug itself. Please see the [Plug Guide](plug.html) or [Plug Documentation](https://hexdocs.pm/plug/) for more information. +Phoenix controllers also build on the Plug package, and are themselves plugs. Controllers provide the functions to do almost anything we need to in an action. If we do find ourselves looking for something that Phoenix controllers don't provide, we might find what we're looking for in Plug itself. Please see the [Plug Guide](plug.html) or [Plug Documentation](https://hexdocs.pm/plug/) for more information. A newly generated Phoenix app will have a single controller, the `PageController`, which can be found at `lib/hello_web/controllers/page_controller.ex` and looks like this. @@ -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 %> ``` @@ -414,13 +414,13 @@ end We would then need to provide an `index.xml.eex` template which created valid xml, and we would be done. -For a list of valid content mime-types, please see the [mime.types](https://github.com/elixir-lang/mime/blob/master/lib/mime.types) documentation from the mime type library. +For a list of valid content mime-types, please see the [mime.types](https://github.com/elixir-plug/mime/blob/master/priv/mime.types) documentation from the mime type library. ### Setting the HTTP Status We can also set the HTTP status code of a response similarly to the way we set the content type. The `Plug.Conn` module, imported into all controllers, has a `put_status/2` function to do this. -`put_status/2` takes `conn` as the first parameter and as the second parameter either an integer or a "friendly name" used as an atom for the status code we want to set. Here is the list of supported [friendly names](https://github.com/elixir-lang/plug/blob/v1.3.0/lib/plug/conn/status.ex#L9-L69). Please note that the rule to convert a "friendly name" to an atom follows [this rule](https://github.com/elixir-plug/plug/blob/v1.3.0/lib/plug/conn/status.ex#L74-L77). For example, `I'm a teapot` becomes `:im_a_teapot`. +`Plug.Conn.put_status/2` takes `conn` as the first parameter and as the second parameter either an integer or a "friendly name" used as an atom for the status code we want to set. The list of status code atom representations can be found in `Plug.Conn.Status.code/1` documentation. Let's change the status in our `PageController` `index` action. @@ -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/deployment/heroku.md b/guides/deployment/heroku.md index d0070d2c6b..d7319087a8 100644 --- a/guides/deployment/heroku.md +++ b/guides/deployment/heroku.md @@ -149,6 +149,12 @@ config :hello, Hello.Repo, ssl: true ``` +Afterwards, let's tell the Phoenix application to bind to the PORT environment variable provided by Heroku's [dyno networking](https://devcenter.heroku.com/articles/dynos#common-runtime-networking) so that it can accept incoming web traffic. Add this beneath your endpoint configuration: + +```elixir +http: [port: System.get_env("PORT")] +``` + Now, let's tell Phoenix to use our Heroku URL and enforce we only use the SSL version of the website. Find the url line: ```elixir @@ -182,6 +188,7 @@ use Mix.Config ... config :hello, HelloWeb.Endpoint, + http: [port: System.get_env("PORT")], url: [scheme: "https", host: "mysterious-meadow-6277.herokuapp.com", port: 443], force_ssl: [rewrite_on: [:x_forwarded_proto]], cache_static_manifest: "priv/static/cache_manifest.json", @@ -269,6 +276,8 @@ Let's commit all our changes: ``` $ git add config/prod.exs +$ git add phoenix_static_buildpack.config +$ git add compile $ git add Procfile $ git add lib/hello_web/endpoint.ex $ git commit -m "Use production config from Heroku ENV variables and decrease socket timeout" @@ -326,10 +335,7 @@ remote: -----> Phoenix app detected remote: remote: -----> Loading configuration and environment remote: Loading config... -remote: WARNING: phoenix_static_buildpack.config wasn't found in the app -remote: Using default config from Phoenix static buildpack -remote: Will use the following versions: -remote: * Node 0.12.4 +remote: [...] remote: Will export the following config vars: remote: * Config vars DATABASE_URL remote: * MIX_ENV=prod @@ -341,7 +347,6 @@ remote: Using default npm version remote: remote: -----> Building dependencies remote: [...] -remote: Running default compile remote: Building Phoenix static assets remote: 07 Jul 00:06:22 - info: compiled 3 files into 2 files, copied 3 in 3616ms remote: Check your digested files at 'priv/static'. 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 b21de25999..ea3c7ca6a5 100644 --- a/guides/endpoint.md +++ b/guides/endpoint.md @@ -7,10 +7,10 @@ Phoenix applications start the HelloWeb.Endpoint as a supervised process. By def defmodule Hello.Application do use Application def start(_type, _args) do - #... + ... children = [ - supervisor(HelloWeb.Endpoint, []), + HelloWeb.Endpoint ] opts = [strategy: :one_for_one, name: Hello.Supervisor] @@ -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/learning.md b/guides/introduction/learning.md index b072e652fb..a5f24e0718 100644 --- a/guides/introduction/learning.md +++ b/guides/introduction/learning.md @@ -5,7 +5,7 @@ Here's a list of other resources for learning about Phoenix and some of the proj ## Phoenix #### Books -- [Programming Phoenix](https://pragprog.com/book/phoenix/programming-phoenix) (print and ebook) +- [Programming Phoenix](https://pragprog.com/book/phoenix14/programming-phoenix-1-4) (print and ebook) #### Online Resources - [The Phoenix Project](https://github.com/phoenixframework/phoenix) @@ -33,7 +33,7 @@ The project which contains many HTML helper functions used in Phoenix. - [Documentation](https://hexdocs.pm/phoenix_html/) ## ExUnit -- [Documentation](https://hexdocs.pm/ex_unit/1.5.1/ExUnit.html) +- [Documentation](https://hexdocs.pm/ex_unit/ExUnit.html) ## Cowboy The webserver Phoenix is based on. @@ -44,4 +44,4 @@ The webserver Phoenix is based on. ## EEx The default templating system for Phoenix. - [Source Code and Readme](https://github.com/elixir-lang/elixir) -- [Documentation](https://hexdocs.pm/eex/1.5.1/EEx.html) +- [Documentation](https://hexdocs.pm/eex/EEx.html) 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/phoenix_mix_tasks.md b/guides/phoenix_mix_tasks.md index c0171776bc..846b9189ed 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 ] . . . ``` @@ -773,9 +774,9 @@ $ mix ecto.gen.migration -r OurCustom.Repo add_users * creating priv/repo/migrations/20150318172927_add_users.exs ``` For more information on how to modify your database schema please refer to the -ecto's migration dsl [ecto migration docs](https://hexdocs.pm/ecto/Ecto.Migration.html). +ecto's migration dsl [ecto migration docs](https://hexdocs.pm/ecto_sql/Ecto.Migration.html). For example, to alter an existing schema see the documentation on ecto’s -[`alter/2`](https://hexdocs.pm/ecto/Ecto.Migration.html#alter/2) function. +[`alter/2`](https://hexdocs.pm/ecto_sql/Ecto.Migration.html#alter/2) function. That's it! We're ready to run our migration. 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/testing/testing.md b/guides/testing/testing.md index 604a667225..67e286d8ca 100644 --- a/guides/testing/testing.md +++ b/guides/testing/testing.md @@ -4,7 +4,7 @@ Testing has become integral to the software development process, and the ability to easily write meaningful tests is an indispensable feature for any modern web framework. Phoenix takes this seriously, providing support files to make all the major components of the framework easy to test. It also generates test modules with real-world examples alongside any generated modules to help get us going. -Elixir ships with a built-in testing framework called [ExUnit](https://hexdocs.pm/ex_unit/1.5.1/ExUnit.html). ExUnit strives to be clear and explicit, keeping magic to a minimum. Phoenix uses ExUnit for all of its testing, and we will use it here as well. +Elixir ships with a built-in testing framework called [ExUnit](https://hexdocs.pm/ex_unit/ExUnit.html). ExUnit strives to be clear and explicit, keeping magic to a minimum. Phoenix uses ExUnit for all of its testing, and we will use it here as well. ExUnit refers to a test module as a "test case", and we will do the same. @@ -91,7 +91,7 @@ defmodule HelloWeb.ErrorViewTest do end ``` -`HelloWeb.ErrorViewTest` sets `async: true` which means that this test case will be run in parallel with other test cases. While individual tests within the case still run serially, this can greatly increase overall test speeds. It is possible to encounter strange behavior with asynchronous tests, but thanks to the [`Ecto.Adapters.SQL.Sandbox`](https://hexdocs.pm/ecto/Ecto.Adapters.SQL.Sandbox.html), async tests involving a database can be done without worry. This means that the vast majority of tests in your Phoenix application will be able to be run asynchronously. +`HelloWeb.ErrorViewTest` sets `async: true` which means that this test case will be run in parallel with other test cases. While individual tests within the case still run serially, this can greatly increase overall test speeds. It is possible to encounter strange behavior with asynchronous tests, but thanks to the [`Ecto.Adapters.SQL.Sandbox`](https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html), async tests involving a database can be done without worry. This means that the vast majority of tests in your Phoenix application will be able to be run asynchronously. It also imports `Phoenix.View` in order to use the `render_to_string/3` function. With that, all the assertions can be simple string equality tests. diff --git a/guides/testing/testing_controllers.md b/guides/testing/testing_controllers.md index 909bd674c4..02f1b50315 100644 --- a/guides/testing/testing_controllers.md +++ b/guides/testing/testing_controllers.md @@ -150,7 +150,7 @@ defmodule HelloWeb.UserControllerTest do end ``` -Let's take a look at what's going on here. First we alias `Hello.Accounts`, the context module that provides us with our repository manipulation functions. When we use the `HelloWeb.ConnCase` module, it sets things up such that each connection is wrapped in a transaction, *and* all of the database interactions inside of the test use the same database connection and transaction. This module also sets up a `conn` attribute in our ExUnit context, using `Phoenix.ConnCase.build_conn/0`. We then pattern match this to use it in each test case. For details, take a look at the file `test/support/conn_case.ex`, as well as the [Ecto documentation for SQL.Sandbox](https://hexdocs.pm/ecto/Ecto.Adapters.SQL.Sandbox.html). We could put a `build_conn/0` call inside of each test, but it is cleaner to use a setup block to do it. +Let's take a look at what's going on here. First we alias `Hello.Accounts`, the context module that provides us with our repository manipulation functions. When we use the `HelloWeb.ConnCase` module, it sets things up such that each connection is wrapped in a transaction, *and* all of the database interactions inside of the test use the same database connection and transaction. This module also sets up a `conn` attribute in our ExUnit context, using `Phoenix.ConnCase.build_conn/0`. We then pattern match this to use it in each test case. For details, take a look at the file `test/support/conn_case.ex`, as well as the [Ecto documentation for SQL.Sandbox](https://hexdocs.pm/ecto_sql/Ecto.Adapters.SQL.Sandbox.html). We could put a `build_conn/0` call inside of each test, but it is cleaner to use a setup block to do it. The index test then hooks into the context to extract the contents of the `:conn` key. We then create two users using the `Hello.Accounts.create_user/1` function. Again, note that this function accesses the test repo, but even though we don't pass the `conn` variable to the call, it still uses the same connection and puts these new users inside the same database transaction. Next the `conn` is piped to a `get` function to make a `GET` request to our `UserController` index action, which is in turn piped into `json_response/2` along with the expected HTTP status code. This will return the JSON from the response body, when everything is wired up properly. We represent the JSON we want the controller action to return with the variable `expected`, and assert that the `response` and `expected` are the same. 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..3c0bd6e5d1 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() %> ``` @@ -46,7 +46,7 @@ end When we reload the Welcome to Phoenix page, we should see our new title. -The `<%=` and `%>` are from the Elixir [EEx](https://hexdocs.pm/eex/1.5.1/EEx.html) project. They enclose executable Elixir code within a template. The `=` tells EEx to print the result. If the `=` is not there, EEx will still execute the code, but there will be no output. In our example, we are calling the `title/0` function from our `LayoutView` and printing the output into the title tag. +The `<%=` and `%>` are from the Elixir [EEx](https://hexdocs.pm/eex/EEx.html) project. They enclose executable Elixir code within a template. The `=` tells EEx to print the result. If the `=` is not there, EEx will still execute the code, but there will be no output. In our example, we are calling the `title/0` function from our `LayoutView` and printing the output into the title tag. Note that we didn't need to fully qualify `title/0` with `HelloWeb.LayoutView` because our `LayoutView` actually does the rendering. In fact, "templates" in Phoenix are really just function definitions on their view module. You can try this out by temporarily deleting your `lib/hello_web/templates/page/index.html.eex` file and adding this function clause to your `PageView` module in `lib/hello_web/views/page_view.ex`. @@ -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] ``` @@ -73,20 +73,20 @@ When we `use HelloWeb, :view`, we get other conveniences as well. Since `view/0` Let's open up the `lib/hello_web/templates/page/index.html.eex` and locate this stanza. ```html -
-

<%= gettext("Welcome to %{name}!", name: "Phoenix") %>

-

A productive web framework that
does not compromise speed and maintainability.

-
+
+

<%= gettext "Welcome to %{name}!", name: "Phoenix" %>

+

A productive web framework that
does not compromise speed and maintainability.

+
``` Then let's add a line with a link back to the same page. (The objective is to see how path helpers respond in a template, not to add any functionality.) ```html -
-

<%= gettext("Welcome to %{name}!", name: "Phoenix") %>

-

A productive web framework that
does not compromise speed and maintainability.

+
+

<%= gettext "Welcome to %{name}!", name: "Phoenix" %>

+

A productive web framework that
does not compromise speed and maintainability.

Link back to this page

-
+ ``` Now we can reload the page and view source to see what we have. @@ -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: "