Skip to content

Commit

Permalink
Merge remote-tracking branch 'phx/master'
Browse files Browse the repository at this point in the history
* phx/master: (26 commits)
  Support any struct with :endpoint key in helpers
  Inspect body in ConnTest.response/2 (phoenixframework#3267)
  update snippet to agree with latest phx.new (phoenixframework#3277)
  Only enable trim for HTML templates
  Revert reconnect optimizations which introduced regressions. Fixes phoenixframework#3161 (phoenixframework#3272)
  Reword sentence in Controllers guide (phoenixframework#3270)
  update documentation to reflect on function deprecations (phoenixframework#3269)
  Fix warning in presence
  Default log for render errors info should be debug
  Add jason to umbrella ecto deps. Fixes phoenixframework#3263
  Add Elixir Slack community in the help column of the initial default page (phoenixframework#3262)
  Update heroku.md (phoenixframework#3241)
  add JavaPhoenixClient in 3rd party channels client libraries list (phoenixframework#3256)
  fix typespec for put_layout (phoenixframework#3253)
  Add version to umbrellas (mirror Elixir master)
  Fix references in guides (phoenixframework#3251)
  Add $PORT bind step in Heroku deployment guide (phoenixframework#3235)
  update link to mime  types (phoenixframework#3249)
  Add Elixir 1.7 and 1.8 to Travis CI build matrix (phoenixframework#3248)
  Update learning.md (phoenixframework#3247)
  ...
  • Loading branch information
Xiaobin0860 committed Feb 16, 2019
2 parents 7233efd + f003e92 commit f127a33
Show file tree
Hide file tree
Showing 44 changed files with 373 additions and 356 deletions.
10 changes: 10 additions & 0 deletions .travis.yml
Expand Up @@ -3,6 +3,8 @@ elixir:
- 1.4
- 1.5
- 1.6
- 1.7
- 1.8
otp_release:
- 19.3
- 20.3
Expand All @@ -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:
Expand Down
27 changes: 2 additions & 25 deletions assets/js/phoenix.js
Expand Up @@ -840,7 +840,6 @@ export class Socket {
this.flushSendBuffer()
this.reconnectTimer.reset()
this.resetHeartbeat()
this.resetChannelTimers()
this.stateChangeCallbacks.open.forEach( callback => callback() )
}

Expand Down Expand Up @@ -989,15 +988,6 @@ export class Socket {
}
})
}

/**
* @private
*/
resetChannelTimers() {
this.channels.forEach(channel => {
channel.rejoinTimer.restart()
})
}
}


Expand Down Expand Up @@ -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
}
}
19 changes: 0 additions & 19 deletions assets/test/socket_test.js
Expand Up @@ -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()

Expand Down
10 changes: 6 additions & 4 deletions guides/adding_pages.md
Expand Up @@ -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.

Expand All @@ -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.

Expand All @@ -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".

Expand Down Expand Up @@ -267,7 +269,7 @@ And this is what the template should look like:
</div>
```

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:

Expand Down
17 changes: 7 additions & 10 deletions guides/channels.md
Expand Up @@ -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:

<pre>
<code>

```plaintext
+----------------+
+--Topic X-->| Mobile Client |
| +----------------+
Expand All @@ -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 |
+----------------+
</code>
</pre>
```

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.
Expand Down Expand Up @@ -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:

<pre>
<code>
```plaintext
Channel +-------------------------+ +--------+
route | Sending Client, Topic 1 | | Local |
+----------->| Channel.Server |----->| PubSub |--+
Expand Down Expand Up @@ -95,8 +91,7 @@ The message flow looks something like this:
+----------------+ | IoT Client, Topic 1 | | Remote | |
| IoT Client |<-------Transport--------| Channel.Server |<-----| PubSub |<-+
+----------------+ +-------------------------+ +--------+
</code>
</pre>
```

### Endpoint

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
32 changes: 16 additions & 16 deletions guides/contexts.md
Expand Up @@ -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.

Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand All @@ -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/`:

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit f127a33

Please sign in to comment.