{"componentChunkName":"component---src-templates-talk-js","path":"/talks/going-realtime-with-ember/","result":{"data":{"site":{"siteMetadata":{"host":"xenodochial-pasteur-bb9d87.netlify.com"}},"allSlidesMarkdown":{"edges":[{"node":{"id":"3ff0dd8d-fe9b-5172-ad18-89d346f7f718","slug":"/talks/going-realtime-with-ember/","abstract":"<p>It used to be that only the most impressive websites would update data live as you sat on the page.\nNow, as the lines between native apps and websites blur, this is becoming expected behavior. What\nwas once cutting-edge tech is now standard-issue for a good user experience.</p>\n<p>See how HashiCorp made the UI for the cluster scheduler software Nomad realtime with Ember\nConcurrency, Ember Data, and the rendering layer we know and love.</p>\n","metadata":{"title":"Going Realtime With Ember","conferences":["EmberFest 2018"],"date":"2018/10/11","primary":"#000000","secondary":"#25ba80","video":"https://www.youtube.com/watch?v=9Uh5V1sHgSw"},"slides":[{"markdown":"","html":""},{"markdown":"\nHi, I'm Michael. I'm a UI engineer at HashiCorp.\n","html":"<p>Hi, I'm Michael. I'm a UI engineer at HashiCorp.</p>"},{"markdown":"\nHashiCorp is a cloud infrastructure automation company.\n","html":"<p>HashiCorp is a cloud infrastructure automation company.</p>"},{"markdown":"\nWe currently have six products in our product suite. [Vagrant](https://www.vagrantup.com/),\n[Packer](https://packer.io/), [Terraform](https://www.terraform.io/),\n[Vault](https://www.vaultproject.io/), [Nomad](https://www.nomadproject.io/), and\n[Consul](https://www.consul.io/).\n","html":"<p>We currently have six products in our product suite. <a href=\"https://www.vagrantup.com/\">Vagrant</a>,\n<a href=\"https://packer.io/\">Packer</a>, <a href=\"https://www.terraform.io/\">Terraform</a>,\n<a href=\"https://www.vaultproject.io/\">Vault</a>, <a href=\"https://www.nomadproject.io/\">Nomad</a>, and\n<a href=\"https://www.consul.io/\">Consul</a>.</p>"},{"markdown":"\nThe product I work on is Nomad.\n","html":"<p>The product I work on is Nomad.</p>"},{"markdown":"\nNomad is a cluster scheduler, or container orchestrator. It helps you easily deploy applications at\nany scale.\n","html":"<p>Nomad is a cluster scheduler, or container orchestrator. It helps you easily deploy applications at\nany scale.</p>"},{"markdown":"\nThis isn't a Nomad talk so I'll keep this short, but it helps to know what we're building a UI for.\nWithin Nomad, there are lots of things going on all the time. Most action is coming from the cluster\nitself, but users still want to stay informed. We can't wait on user action to update the state\nof the world.\n","html":"<p>This isn't a Nomad talk so I'll keep this short, but it helps to know what we're building a UI for.\nWithin Nomad, there are lots of things going on all the time. Most action is coming from the cluster\nitself, but users still want to stay informed. We can't wait on user action to update the state\nof the world.</p>"},{"markdown":"\nOne way to do this is to add a refresh button, but that's kind of clunky. Furthermore, it's dated.\nThe seams are starting to show between the people who build websites and those who use websites.\nAny casual web user can look at this refresh button and have a simple questions.\n","html":"<p>One way to do this is to add a refresh button, but that's kind of clunky. Furthermore, it's dated.\nThe seams are starting to show between the people who build websites and those who use websites.\nAny casual web user can look at this refresh button and have a simple questions.</p>"},{"markdown":"\nCan't it just do that automatically? Twitter telle me about new tweets. My email clients pushes\nnotifications. Lyft will at least attempt to show me where my ride is. And Spotify will tell me\nwhat my friends are listening to even though I never asked for that and I have never cared.\n\nSo why doesn't _your_ web app do that?\n","html":"<p>Can't it just do that automatically? Twitter telle me about new tweets. My email clients pushes\nnotifications. Lyft will at least attempt to show me where my ride is. And Spotify will tell me\nwhat my friends are listening to even though I never asked for that and I have never cared.</p>\n<p>So why doesn't <em>your</em> web app do that?</p>"},{"markdown":"\nEverything nodel and great eventually becomes commodity. Great becomes standard. Standard becomes\ndated.\n","html":"<p>Everything nodel and great eventually becomes commodity. Great becomes standard. Standard becomes\ndated.</p>"},{"markdown":"\nSo. Let's go realtime.\n","html":"<p>So. Let's go realtime.</p>"},{"markdown":"\nThis is exactly what we set out to do. Over the course of a month or so in early 2018, I made the\nNomad UI realtime, and this is my story.\n","html":"<p>This is exactly what we set out to do. Over the course of a month or so in early 2018, I made the\nNomad UI realtime, and this is my story.</p>"},{"markdown":"\nFirst I want to warn you, this talk isn't going to end in an `ember install`. This is a use case\ntalk that only directly applies to the Nomad UI. However, I think the lessons learned along the way\nare broadly applicable.\n","html":"<p>First I want to warn you, this talk isn't going to end in an <code class=\"language-text\">ember install</code>. This is a use case\ntalk that only directly applies to the Nomad UI. However, I think the lessons learned along the way\nare broadly applicable.</p>"},{"markdown":"\nImplementing this functionality was a five step progress.\n\n1. Understand the API\n2. Break down the problem\n3. Make it work for one page\n4. Find the right abstractions\n5. Celebrate\n\nFirst, let's look at the API.\n","html":"<p>Implementing this functionality was a five step progress.</p>\n<ol>\n<li>Understand the API</li>\n<li>Break down the problem</li>\n<li>Make it work for one page</li>\n<li>Find the right abstractions</li>\n<li>Celebrate</li>\n</ol>\n<p>First, let's look at the API.</p>"},{"markdown":"\nNomad uses a feature called [Blocking Queries](https://www.nomadproject.io/api/index.html#blocking-queries)\nto support realtime data fetching. It's an implementation of long-polling that uses an index query\nparam on any supported URL to invoke. When invoked, the request may block depending on the current\nindex value for the request. Each URL maintains a monotonic index, which is sent in every request as\nthe value for the `X-Nomad-Index` header.\n","html":"<p>Nomad uses a feature called <a href=\"https://www.nomadproject.io/api/index.html#blocking-queries\">Blocking Queries</a>\nto support realtime data fetching. It's an implementation of long-polling that uses an index query\nparam on any supported URL to invoke. When invoked, the request may block depending on the current\nindex value for the request. Each URL maintains a monotonic index, which is sent in every request as\nthe value for the <code class=\"language-text\">X-Nomad-Index</code> header.</p>"},{"markdown":"\nIn this first example, `/job/job-1`, the request will immediately resolve because there is no\n`index` query parameter.\n\nIn the second example, `/job/job-1?index=1`, the request will still immediately resolve because the\n`index` query parameter value is behind the current index for the URL.\n\nIn the third example, `/job/job-1?index=10`, the request will remain open (block) until the index\nfor the URL increments (e.g., job is stopped server-side).\n","html":"<p>In this first example, <code class=\"language-text\">/job/job-1</code>, the request will immediately resolve because there is no\n<code class=\"language-text\">index</code> query parameter.</p>\n<p>In the second example, <code class=\"language-text\">/job/job-1?index=1</code>, the request will still immediately resolve because the\n<code class=\"language-text\">index</code> query parameter value is behind the current index for the URL.</p>\n<p>In the third example, <code class=\"language-text\">/job/job-1?index=10</code>, the request will remain open (block) until the index\nfor the URL increments (e.g., job is stopped server-side).</p>"},{"markdown":"\nSo with those details in mind, we can imagine a blocking query request loop. Where first an initial\nrequest is made, from the response we extract the `index` value from the `X-Nomad-Index` header, do\nwhatever we want with the data, immediately make a new request for the same data with the `index`\nquery parameter this time, eventually get a response from that request, and then start the process\nall over.\n","html":"<p>So with those details in mind, we can imagine a blocking query request loop. Where first an initial\nrequest is made, from the response we extract the <code class=\"language-text\">index</code> value from the <code class=\"language-text\">X-Nomad-Index</code> header, do\nwhatever we want with the data, immediately make a new request for the same data with the <code class=\"language-text\">index</code>\nquery parameter this time, eventually get a response from that request, and then start the process\nall over.</p>"},{"markdown":"\nAt this point you might be thinking this is all very odd.\n","html":"<p>At this point you might be thinking this is all very odd.</p>"},{"markdown":"\nWhy not use WebSockets? Isn't it meant for this?\n","html":"<p>Why not use WebSockets? Isn't it meant for this?</p>"},{"markdown":"\nIf you're hip on all the tools we have available to us you might be thinking this is a job for\nServerSentEvents and EventSource.\n","html":"<p>If you're hip on all the tools we have available to us you might be thinking this is a job for\nServerSentEvents and EventSource.</p>"},{"markdown":"\nOr, the one that is tossed around all too often, \"Why not just change the API?\"\n","html":"<p>Or, the one that is tossed around all too often, \"Why not just change the API?\"</p>"},{"markdown":"\nThe short answer is the API already exists. APIs can't always be changed on a whim for the benefit\nof the UI. Plus this API pre-dates the UI. Plus this API works just fine.\n","html":"<p>The short answer is the API already exists. APIs can't always be changed on a whim for the benefit\nof the UI. Plus this API pre-dates the UI. Plus this API works just fine.</p>"},{"markdown":"\nIn fact it's actually really good. It's a low-tech solution that can be used in a variety of tools,\nranging from `cURL` to Ember. It's entirely stateless, unlike WebSockets which relies on long-lived\nconnections. This is critical for Nomad since it's designed to withstand complete server failure.\nIt's also naturally fault tolerant. In the event that a connection is lost for whatever reason,\nthere is no state to rehydrate, or event listeners to reattach. You just make the same request\nagain.\n","html":"<p>In fact it's actually really good. It's a low-tech solution that can be used in a variety of tools,\nranging from <code class=\"language-text\">cURL</code> to Ember. It's entirely stateless, unlike WebSockets which relies on long-lived\nconnections. This is critical for Nomad since it's designed to withstand complete server failure.\nIt's also naturally fault tolerant. In the event that a connection is lost for whatever reason,\nthere is no state to rehydrate, or event listeners to reattach. You just make the same request\nagain.</p>"},{"markdown":"\nSo that's the API. Now we have to somehow integrate this with an Ember app. Let's break down the\nproblem.\n","html":"<p>So that's the API. Now we have to somehow integrate this with an Ember app. Let's break down the\nproblem.</p>"},{"markdown":"\nWe need to implement long-polling (since that's what Blocking Queries is), then we have to update\nall the pages in the app to use long-polling, and finally we need to re-render all the things when\nlong-polls resolve with new data.\n","html":"<p>We need to implement long-polling (since that's what Blocking Queries is), then we have to update\nall the pages in the app to use long-polling, and finally we need to re-render all the things when\nlong-polls resolve with new data.</p>"},{"markdown":"\nSince there's always that guy on Hacker News (and it's always a guy) who likes to invalidate other\npeople's hard work by writing a half-baked alternative in bash, I decided to get ahead of the story\nand do that myself.\n\nHere is long-polling in bash. It's less than 30 lines including comments.\n\n```bash\n# Starting index\nidx=\"1\"\nwhile true\ndo\n  # Append index as a query param\n  url=\"http://localhost:4646/v1/job/example?index=$idx\"\n  echo -e \"\\nCurling $url...\"\n  curl -i -s $url | grep -v \"^\" > polltmp\n\n  while read -r line\n  do\n    # Use the X-Nomad-Index header to set the new index\n    key=\"$(echo $line | cut -d ':' -f 1)\"\n    val=\"$(echo $line | cut -d ':' -f 2 | tr -d '[:space:]')\"\n    if [ \"$key\" = \"X-Nomad-Index\" ]\n    then\n      idx=\"$val\"\n    fi\n  done < polltmp\n\n  # Do stuff with the payload\n  tail -1 polltmp | jq \". | {Name, Status, ModifyIndex}\"\ndone\n```\n","html":"<p>Since there's always that guy on Hacker News (and it's always a guy) who likes to invalidate other\npeople's hard work by writing a half-baked alternative in bash, I decided to get ahead of the story\nand do that myself.</p>\n<p>Here is long-polling in bash. It's less than 30 lines including comments.</p>\n<div class=\"gatsby-highlight\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token comment\"># Starting index</span>\n<span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"1\"</span>\n<span class=\"token keyword\">while</span> <span class=\"token boolean\">true</span>\n<span class=\"token keyword\">do</span>\n  <span class=\"token comment\"># Append index as a query param</span>\n  <span class=\"token assign-left variable\">url</span><span class=\"token operator\">=</span><span class=\"token string\">\"http://localhost:4646/v1/job/example?index=<span class=\"token variable\">$idx</span>\"</span>\n  <span class=\"token builtin class-name\">echo</span> -e <span class=\"token string\">\"<span class=\"token entity\" title=\"\\n\">\\n</span>Curling <span class=\"token variable\">$url</span>...\"</span>\n  <span class=\"token function\">curl</span> -i -s <span class=\"token variable\">$url</span> <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> -v <span class=\"token string\">\"^\"</span> <span class=\"token operator\">></span> polltmp\n\n  <span class=\"token keyword\">while</span> <span class=\"token builtin class-name\">read</span> -r line\n  <span class=\"token keyword\">do</span>\n    <span class=\"token comment\"># Use the X-Nomad-Index header to set the new index</span>\n    <span class=\"token assign-left variable\">key</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">1</span><span class=\"token variable\">)</span></span>\"</span>\n    <span class=\"token assign-left variable\">val</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">2</span> <span class=\"token operator\">|</span> <span class=\"token function\">tr</span> -d <span class=\"token string\">'[:space:]'</span><span class=\"token variable\">)</span></span>\"</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token string\">\"<span class=\"token variable\">$key</span>\"</span> <span class=\"token operator\">=</span> <span class=\"token string\">\"X-Nomad-Index\"</span> <span class=\"token punctuation\">]</span>\n    <span class=\"token keyword\">then</span>\n      <span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\">$val</span>\"</span>\n    <span class=\"token keyword\">fi</span>\n  <span class=\"token keyword\">done</span> <span class=\"token operator\">&lt;</span> polltmp\n\n  <span class=\"token comment\"># Do stuff with the payload</span>\n  <span class=\"token function\">tail</span> -1 polltmp <span class=\"token operator\">|</span> jq <span class=\"token string\">\". | {Name, Status, ModifyIndex}\"</span>\n<span class=\"token keyword\">done</span></code></pre></div>"},{"markdown":"\nFirst we set a starting index (`idx`) of 1, since this is the lowest possible index value.\n\n```bash{1-2}\n# Starting index\nidx=\"1\"\nwhile true\ndo\n  # Append index as a query param\n  url=\"http://localhost:4646/v1/job/example?index=$idx\"\n  echo -e \"\\nCurling $url...\"\n  curl -i -s $url | grep -v \"^\" > polltmp\n\n  while read -r line\n  do\n    # Use the X-Nomad-Index header to set the new index\n    key=\"$(echo $line | cut -d ':' -f 1)\"\n    val=\"$(echo $line | cut -d ':' -f 2 | tr -d '[:space:]')\"\n    if [ \"$key\" = \"X-Nomad-Index\" ]\n    then\n      idx=\"$val\"\n    fi\n  done < polltmp\n\n  # Do stuff with the payload\n  tail -1 polltmp | jq \". | {Name, Status, ModifyIndex}\"\ndone\n```\n","html":"<p>First we set a starting index (<code class=\"language-text\">idx</code>) of 1, since this is the lowest possible index value.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"gatsby-highlight-code-line\"><span class=\"token comment\"># Starting index</span></span><span class=\"gatsby-highlight-code-line\"><span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"1\"</span></span><span class=\"token keyword\">while</span> <span class=\"token boolean\">true</span>\n<span class=\"token keyword\">do</span>\n  <span class=\"token comment\"># Append index as a query param</span>\n  <span class=\"token assign-left variable\">url</span><span class=\"token operator\">=</span><span class=\"token string\">\"http://localhost:4646/v1/job/example?index=<span class=\"token variable\">$idx</span>\"</span>\n  <span class=\"token builtin class-name\">echo</span> -e <span class=\"token string\">\"<span class=\"token entity\" title=\"\\n\">\\n</span>Curling <span class=\"token variable\">$url</span>...\"</span>\n  <span class=\"token function\">curl</span> -i -s <span class=\"token variable\">$url</span> <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> -v <span class=\"token string\">\"^\"</span> <span class=\"token operator\">></span> polltmp\n\n  <span class=\"token keyword\">while</span> <span class=\"token builtin class-name\">read</span> -r line\n  <span class=\"token keyword\">do</span>\n    <span class=\"token comment\"># Use the X-Nomad-Index header to set the new index</span>\n    <span class=\"token assign-left variable\">key</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">1</span><span class=\"token variable\">)</span></span>\"</span>\n    <span class=\"token assign-left variable\">val</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">2</span> <span class=\"token operator\">|</span> <span class=\"token function\">tr</span> -d <span class=\"token string\">'[:space:]'</span><span class=\"token variable\">)</span></span>\"</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token string\">\"<span class=\"token variable\">$key</span>\"</span> <span class=\"token operator\">=</span> <span class=\"token string\">\"X-Nomad-Index\"</span> <span class=\"token punctuation\">]</span>\n    <span class=\"token keyword\">then</span>\n      <span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\">$val</span>\"</span>\n    <span class=\"token keyword\">fi</span>\n  <span class=\"token keyword\">done</span> <span class=\"token operator\">&lt;</span> polltmp\n\n  <span class=\"token comment\"># Do stuff with the payload</span>\n  <span class=\"token function\">tail</span> -1 polltmp <span class=\"token operator\">|</span> jq <span class=\"token string\">\". | {Name, Status, ModifyIndex}\"</span>\n<span class=\"token keyword\">done</span></code></pre></div>"},{"markdown":"\nThen, in an infinite loop, construct a URL using this index value and fetch it with cURL.\n\n```bash{5-9}\n# Starting index\nidx=\"1\"\nwhile true\ndo\n  # Append index as a query param\n  url=\"http://localhost:4646/v1/job/example?index=$idx\"\n  echo -e \"\\nCurling $url...\"\n  curl -i -s $url | grep -v \"^\" > polltmp\n\n  while read -r line\n  do\n    # Use the X-Nomad-Index header to set the new index\n    key=\"$(echo $line | cut -d ':' -f 1)\"\n    val=\"$(echo $line | cut -d ':' -f 2 | tr -d '[:space:]')\"\n    if [ \"$key\" = \"X-Nomad-Index\" ]\n    then\n      idx=\"$val\"\n    fi\n  done < polltmp\n\n  # Do stuff with the payload\n  tail -1 polltmp | jq \". | {Name, Status, ModifyIndex}\"\ndone\n```\n","html":"<p>Then, in an infinite loop, construct a URL using this index value and fetch it with cURL.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token comment\"># Starting index</span>\n<span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"1\"</span>\n<span class=\"token keyword\">while</span> <span class=\"token boolean\">true</span>\n<span class=\"token keyword\">do</span>\n<span class=\"gatsby-highlight-code-line\">  <span class=\"token comment\"># Append index as a query param</span></span><span class=\"gatsby-highlight-code-line\">  <span class=\"token assign-left variable\">url</span><span class=\"token operator\">=</span><span class=\"token string\">\"http://localhost:4646/v1/job/example?index=<span class=\"token variable\">$idx</span>\"</span></span><span class=\"gatsby-highlight-code-line\">  <span class=\"token builtin class-name\">echo</span> -e <span class=\"token string\">\"<span class=\"token entity\" title=\"\\n\">\\n</span>Curling <span class=\"token variable\">$url</span>...\"</span></span><span class=\"gatsby-highlight-code-line\">  <span class=\"token function\">curl</span> -i -s <span class=\"token variable\">$url</span> <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> -v <span class=\"token string\">\"^\"</span> <span class=\"token operator\">></span> polltmp</span><span class=\"gatsby-highlight-code-line\"></span>  <span class=\"token keyword\">while</span> <span class=\"token builtin class-name\">read</span> -r line\n  <span class=\"token keyword\">do</span>\n    <span class=\"token comment\"># Use the X-Nomad-Index header to set the new index</span>\n    <span class=\"token assign-left variable\">key</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">1</span><span class=\"token variable\">)</span></span>\"</span>\n    <span class=\"token assign-left variable\">val</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">2</span> <span class=\"token operator\">|</span> <span class=\"token function\">tr</span> -d <span class=\"token string\">'[:space:]'</span><span class=\"token variable\">)</span></span>\"</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token string\">\"<span class=\"token variable\">$key</span>\"</span> <span class=\"token operator\">=</span> <span class=\"token string\">\"X-Nomad-Index\"</span> <span class=\"token punctuation\">]</span>\n    <span class=\"token keyword\">then</span>\n      <span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\">$val</span>\"</span>\n    <span class=\"token keyword\">fi</span>\n  <span class=\"token keyword\">done</span> <span class=\"token operator\">&lt;</span> polltmp\n\n  <span class=\"token comment\"># Do stuff with the payload</span>\n  <span class=\"token function\">tail</span> -1 polltmp <span class=\"token operator\">|</span> jq <span class=\"token string\">\". | {Name, Status, ModifyIndex}\"</span>\n<span class=\"token keyword\">done</span></code></pre></div>"},{"markdown":"\nAfter we get a response, we then loop over the headers looking for the header named `X-Nomad-Token`.\nWe set `idx` to the value of this header. It represents the new current server-side index value.\n\n```bash{12-18}\n# Starting index\nidx=\"1\"\nwhile true\ndo\n  # Append index as a query param\n  url=\"http://localhost:4646/v1/job/example?index=$idx\"\n  echo -e \"\\nCurling $url...\"\n  curl -i -s $url | grep -v \"^\" > polltmp\n\n  while read -r line\n  do\n    # Use the X-Nomad-Index header to set the new index\n    key=\"$(echo $line | cut -d ':' -f 1)\"\n    val=\"$(echo $line | cut -d ':' -f 2 | tr -d '[:space:]')\"\n    if [ \"$key\" = \"X-Nomad-Index\" ]\n    then\n      idx=\"$val\"\n    fi\n  done < polltmp\n\n  # Do stuff with the payload\n  tail -1 polltmp | jq \". | {Name, Status, ModifyIndex}\"\ndone\n```\n","html":"<p>After we get a response, we then loop over the headers looking for the header named <code class=\"language-text\">X-Nomad-Token</code>.\nWe set <code class=\"language-text\">idx</code> to the value of this header. It represents the new current server-side index value.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token comment\"># Starting index</span>\n<span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"1\"</span>\n<span class=\"token keyword\">while</span> <span class=\"token boolean\">true</span>\n<span class=\"token keyword\">do</span>\n  <span class=\"token comment\"># Append index as a query param</span>\n  <span class=\"token assign-left variable\">url</span><span class=\"token operator\">=</span><span class=\"token string\">\"http://localhost:4646/v1/job/example?index=<span class=\"token variable\">$idx</span>\"</span>\n  <span class=\"token builtin class-name\">echo</span> -e <span class=\"token string\">\"<span class=\"token entity\" title=\"\\n\">\\n</span>Curling <span class=\"token variable\">$url</span>...\"</span>\n  <span class=\"token function\">curl</span> -i -s <span class=\"token variable\">$url</span> <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> -v <span class=\"token string\">\"^\"</span> <span class=\"token operator\">></span> polltmp\n\n  <span class=\"token keyword\">while</span> <span class=\"token builtin class-name\">read</span> -r line\n  <span class=\"token keyword\">do</span>\n<span class=\"gatsby-highlight-code-line\">    <span class=\"token comment\"># Use the X-Nomad-Index header to set the new index</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token assign-left variable\">key</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">1</span><span class=\"token variable\">)</span></span>\"</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token assign-left variable\">val</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">2</span> <span class=\"token operator\">|</span> <span class=\"token function\">tr</span> -d <span class=\"token string\">'[:space:]'</span><span class=\"token variable\">)</span></span>\"</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token string\">\"<span class=\"token variable\">$key</span>\"</span> <span class=\"token operator\">=</span> <span class=\"token string\">\"X-Nomad-Index\"</span> <span class=\"token punctuation\">]</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">then</span></span><span class=\"gatsby-highlight-code-line\">      <span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\">$val</span>\"</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">fi</span></span>  <span class=\"token keyword\">done</span> <span class=\"token operator\">&lt;</span> polltmp\n\n  <span class=\"token comment\"># Do stuff with the payload</span>\n  <span class=\"token function\">tail</span> -1 polltmp <span class=\"token operator\">|</span> jq <span class=\"token string\">\". | {Name, Status, ModifyIndex}\"</span>\n<span class=\"token keyword\">done</span></code></pre></div>"},{"markdown":"\nFinally, we do stuff with the data. In this example `jq` is used to filter the JSON response. And\nthen the cycle repeats since this is all in an infinite loop.\n\n_The live presentation includes a demonstration of this code._\n\n```bash{21-23}\n# Starting index\nidx=\"1\"\nwhile true\ndo\n  # Append index as a query param\n  url=\"http://localhost:4646/v1/job/example?index=$idx\"\n  echo -e \"\\nCurling $url...\"\n  curl -i -s $url | grep -v \"^\" > polltmp\n\n  while read -r line\n  do\n    # Use the X-Nomad-Index header to set the new index\n    key=\"$(echo $line | cut -d ':' -f 1)\"\n    val=\"$(echo $line | cut -d ':' -f 2 | tr -d '[:space:]')\"\n    if [ \"$key\" = \"X-Nomad-Index\" ]\n    then\n      idx=\"$val\"\n    fi\n  done < polltmp\n\n  # Do stuff with the payload\n  tail -1 polltmp | jq \". | {Name, Status, ModifyIndex}\"\ndone\n```\n","html":"<p>Finally, we do stuff with the data. In this example <code class=\"language-text\">jq</code> is used to filter the JSON response. And\nthen the cycle repeats since this is all in an infinite loop.</p>\n<p><em>The live presentation includes a demonstration of this code.</em></p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"bash\"><pre class=\"language-bash\"><code class=\"language-bash\"><span class=\"token comment\"># Starting index</span>\n<span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"1\"</span>\n<span class=\"token keyword\">while</span> <span class=\"token boolean\">true</span>\n<span class=\"token keyword\">do</span>\n  <span class=\"token comment\"># Append index as a query param</span>\n  <span class=\"token assign-left variable\">url</span><span class=\"token operator\">=</span><span class=\"token string\">\"http://localhost:4646/v1/job/example?index=<span class=\"token variable\">$idx</span>\"</span>\n  <span class=\"token builtin class-name\">echo</span> -e <span class=\"token string\">\"<span class=\"token entity\" title=\"\\n\">\\n</span>Curling <span class=\"token variable\">$url</span>...\"</span>\n  <span class=\"token function\">curl</span> -i -s <span class=\"token variable\">$url</span> <span class=\"token operator\">|</span> <span class=\"token function\">grep</span> -v <span class=\"token string\">\"^\"</span> <span class=\"token operator\">></span> polltmp\n\n  <span class=\"token keyword\">while</span> <span class=\"token builtin class-name\">read</span> -r line\n  <span class=\"token keyword\">do</span>\n    <span class=\"token comment\"># Use the X-Nomad-Index header to set the new index</span>\n    <span class=\"token assign-left variable\">key</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">1</span><span class=\"token variable\">)</span></span>\"</span>\n    <span class=\"token assign-left variable\">val</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\"><span class=\"token variable\">$(</span><span class=\"token builtin class-name\">echo</span> $line <span class=\"token operator\">|</span> <span class=\"token function\">cut</span> -d <span class=\"token string\">':'</span> -f <span class=\"token number\">2</span> <span class=\"token operator\">|</span> <span class=\"token function\">tr</span> -d <span class=\"token string\">'[:space:]'</span><span class=\"token variable\">)</span></span>\"</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">[</span> <span class=\"token string\">\"<span class=\"token variable\">$key</span>\"</span> <span class=\"token operator\">=</span> <span class=\"token string\">\"X-Nomad-Index\"</span> <span class=\"token punctuation\">]</span>\n    <span class=\"token keyword\">then</span>\n      <span class=\"token assign-left variable\">idx</span><span class=\"token operator\">=</span><span class=\"token string\">\"<span class=\"token variable\">$val</span>\"</span>\n    <span class=\"token keyword\">fi</span>\n  <span class=\"token keyword\">done</span> <span class=\"token operator\">&lt;</span> polltmp\n\n<span class=\"gatsby-highlight-code-line\">  <span class=\"token comment\"># Do stuff with the payload</span></span><span class=\"gatsby-highlight-code-line\">  <span class=\"token function\">tail</span> -1 polltmp <span class=\"token operator\">|</span> jq <span class=\"token string\">\". | {Name, Status, ModifyIndex}\"</span></span><span class=\"gatsby-highlight-code-line\"><span class=\"token keyword\">done</span></span></code></pre></div>"},{"markdown":"\nSo there you have it. It works. We must be done. Not quite, I'd say. Whenever I'm writing any code,\nI have this internal checklist where I\n\n  1. **Make it work:** Code has to do what it's supposed to, first and foremost.\n  2. **Make it nice:** There are many ways to solve problems, but some solutions are better than\n     others.\n  3. **Make it fast:** You should avoid premature optimization, but sometimes code is too slow and\n     needs to be changed to be faster.\n","html":"<p>So there you have it. It works. We must be done. Not quite, I'd say. Whenever I'm writing any code,\nI have this internal checklist where I</p>\n<ol>\n<li><strong>Make it work:</strong> Code has to do what it's supposed to, first and foremost.</li>\n<li><strong>Make it nice:</strong> There are many ways to solve problems, but some solutions are better than\nothers.</li>\n<li><strong>Make it fast:</strong> You should avoid premature optimization, but sometimes code is too slow and\nneeds to be changed to be faster.</li>\n</ol>"},{"markdown":"\nMaking code work is generally straight-forward. It might be a doozy of a problem you are working on,\nbut at least you know what you are working towards. Similarly making something fast is\nstraight-forward. Even if the process of optimizing is challenging, benchmarks will tell you if you\nare doing better or worse.\n\nIt's this \"Make it nice\" box that ends up being the hardest to check. Nice is subjective.\n","html":"<p>Making code work is generally straight-forward. It might be a doozy of a problem you are working on,\nbut at least you know what you are working towards. Similarly making something fast is\nstraight-forward. Even if the process of optimizing is challenging, benchmarks will tell you if you\nare doing better or worse.</p>\n<p>It's this \"Make it nice\" box that ends up being the hardest to check. Nice is subjective.</p>"},{"markdown":"\nWe like to consider our work to be purely logical where every decision is black and white, but\nthat's frankly not true. There are many ways to write code that performs the same function. Each\nwith different squishy tradeoffs.\n\nWe have all experienced code we didn't like, we have probably all written that code we didn't like.\nWe also throw out terms like \"this code smells bad\", or this _feels_ dangerous. These aren't\nobjective critiques, but there is merit to them.\n\nHow do you choose the best code? How do you make code that is \"nice\"?\n","html":"<p>We like to consider our work to be purely logical where every decision is black and white, but\nthat's frankly not true. There are many ways to write code that performs the same function. Each\nwith different squishy tradeoffs.</p>\n<p>We have all experienced code we didn't like, we have probably all written that code we didn't like.\nWe also throw out terms like \"this code smells bad\", or this <em>feels</em> dangerous. These aren't\nobjective critiques, but there is merit to them.</p>\n<p>How do you choose the best code? How do you make code that is \"nice\"?</p>"},{"markdown":"\n\"Oh, the Places You'll Go!\" is an appropriate book to show here in its own right, but I honestly\nonly chose it because of this kids face. That's the expression you make when faced with the paradox\nof choice for the first time.\n","html":"<p>\"Oh, the Places You'll Go!\" is an appropriate book to show here in its own right, but I honestly\nonly chose it because of this kids face. That's the expression you make when faced with the paradox\nof choice for the first time.</p>"},{"markdown":"\nBut it can be a big choice and it's one that has to be made. If all you ever do is write code that\nsimply works, you're slowly building a house of cards that will eventually collapse under the weight\nof its own complexity.\n\n\"Collapse\" is a word that is well-rooted in the physical world, so I think it's worth expanding on\nwhat code \"collapsing\" looks like.\n","html":"<p>But it can be a big choice and it's one that has to be made. If all you ever do is write code that\nsimply works, you're slowly building a house of cards that will eventually collapse under the weight\nof its own complexity.</p>\n<p>\"Collapse\" is a word that is well-rooted in the physical world, so I think it's worth expanding on\nwhat code \"collapsing\" looks like.</p>"},{"markdown":"\nWhen code collapses under its own complexity, you will be experiencing some of these symptoms.\n\n  1. **Inaccurate project estimates:** As code increases in complexity, it becomes less and less\n     understood. The less understood code is, the harder it becomes to accurately estimate how much\n     effort is involved in making changes.\n  2. **Fear of certain files or subsystems:** It only takes getting burned by a bad estimate or two\n     to become conditioned to fear parts of a code base. We learn where the dragons are hiding,\n     there are bound to be unforeseen consequences.\n  3. **Habitual Refactoring:** The brave among us may take it upon ourselves to refactor these scary\n     places, but odds are they will fail. It's all too common to think you finally understand a\n     complex system completely only to be surprised halfway in. This leads to incomplete refactoring,\n     or maybe backing out of a refactor and starting again from scratch, or multiple refactors\n     because no interation is quite right.\n  4. **Unhappy developers:** This work environment sucks, to be blunt. No one likes being asked for\n     an estimate they know won't be accurate. No one likes missing deadlines, or spending afternoons\n     debugging nonsensical cruft.\n  5. **Unconfident developers:** Even worse than unhappiness, this is the kind of code that can\n     shake confidence. Newer engineers, or even tenured engineers that have their own reasons for\n     feeling vulnerable can easily misplace blame on themselves for not \"getting it\" rather than\n     blaming the code for being overly complex.\n","html":"<p>When code collapses under its own complexity, you will be experiencing some of these symptoms.</p>\n<ol>\n<li><strong>Inaccurate project estimates:</strong> As code increases in complexity, it becomes less and less\nunderstood. The less understood code is, the harder it becomes to accurately estimate how much\neffort is involved in making changes.</li>\n<li><strong>Fear of certain files or subsystems:</strong> It only takes getting burned by a bad estimate or two\nto become conditioned to fear parts of a code base. We learn where the dragons are hiding,\nthere are bound to be unforeseen consequences.</li>\n<li><strong>Habitual Refactoring:</strong> The brave among us may take it upon ourselves to refactor these scary\nplaces, but odds are they will fail. It's all too common to think you finally understand a\ncomplex system completely only to be surprised halfway in. This leads to incomplete refactoring,\nor maybe backing out of a refactor and starting again from scratch, or multiple refactors\nbecause no interation is quite right.</li>\n<li><strong>Unhappy developers:</strong> This work environment sucks, to be blunt. No one likes being asked for\nan estimate they know won't be accurate. No one likes missing deadlines, or spending afternoons\ndebugging nonsensical cruft.</li>\n<li><strong>Unconfident developers:</strong> Even worse than unhappiness, this is the kind of code that can\nshake confidence. Newer engineers, or even tenured engineers that have their own reasons for\nfeeling vulnerable can easily misplace blame on themselves for not \"getting it\" rather than\nblaming the code for being overly complex.</li>\n</ol>"},{"markdown":"\nAlright, say you're convinced that this is a bad situation. That complexity is dangerous and\ncompounds. What do you do about it? How do we manage complexity?\n","html":"<p>Alright, say you're convinced that this is a bad situation. That complexity is dangerous and\ncompounds. What do you do about it? How do we manage complexity?</p>"},{"markdown":"\nThe one word answer is abstractions!\n\nThis is a word that gets thrown around a lot. There are plenty of definitions for it online, but\nI'm a visual person, so I came up with a visual metaphor for abstractions.\n","html":"<p>The one word answer is abstractions!</p>\n<p>This is a word that gets thrown around a lot. There are plenty of definitions for it online, but\nI'm a visual person, so I came up with a visual metaphor for abstractions.</p>"},{"markdown":"\nFirst, consider _Minerva in Her Study_, a 17th century masterpiece by local hero Rembrandt. Every\ndetail in this painting is perfect. Every crease in her clothing, the texture of the fur, the candid\nexpression on her face, the harsh lighting that is still somehow warm and soft.\n","html":"<p>First, consider <em>Minerva in Her Study</em>, a 17th century masterpiece by local hero Rembrandt. Every\ndetail in this painting is perfect. Every crease in her clothing, the texture of the fur, the candid\nexpression on her face, the harsh lighting that is still somehow warm and soft.</p>"},{"markdown":"\nCompare this to _Smiley in Open Sans_, a composition by yours truly in 2018.\n\nI would never ever say that these are equal. Rembrandt was considered a master among masters, while\nI'm just dangerous in Photoshop. Not that I needed Photoshop to put text on a white background, but\nthat's neither here nor there. The point I wish to make here is that there is a time and a place for\nboth these compositions.\n","html":"<p>Compare this to <em>Smiley in Open Sans</em>, a composition by yours truly in 2018.</p>\n<p>I would never ever say that these are equal. Rembrandt was considered a master among masters, while\nI'm just dangerous in Photoshop. Not that I needed Photoshop to put text on a white background, but\nthat's neither here nor there. The point I wish to make here is that there is a time and a place for\nboth these compositions.</p>"},{"markdown":"\n_Minerva in Her Study_ is 0% abstract. Every single detail is realized and unique. Rembrandt had\nultimate control over every color, every brush stroke, and all the little pieces that come together\nto complete the painting. But in doing so, this work would take 100s of hours to recreate and could\nonly be done by a fellow master painter.\n\n_Smiley in Open Sans_ is 100% abstract. It's rigid. There is no room for free expression, but I\nchallenge you to come up with a simpler composition than three symbols that still results in a\nnearly universally understood smiling face. And anyone in this room could recreate this work in\nunder 30 seconds.\n","html":"<p><em>Minerva in Her Study</em> is 0% abstract. Every single detail is realized and unique. Rembrandt had\nultimate control over every color, every brush stroke, and all the little pieces that come together\nto complete the painting. But in doing so, this work would take 100s of hours to recreate and could\nonly be done by a fellow master painter.</p>\n<p><em>Smiley in Open Sans</em> is 100% abstract. It's rigid. There is no room for free expression, but I\nchallenge you to come up with a simpler composition than three symbols that still results in a\nnearly universally understood smiling face. And anyone in this room could recreate this work in\nunder 30 seconds.</p>"},{"markdown":"\nThe moral of the story is there is no one true Goldilocks level of abstraction.\n","html":"<p>The moral of the story is there is no one true Goldilocks level of abstraction.</p>"},{"markdown":"\nIt's entirely situational. Sometimes you are going to want something incredibly abstract. Imagine\nsetting up an e-commerce store: you aren't going to want to write a custom credit card payment\nprocessor if you can instead use a library that lets you start accepting payments with a single line\nof code.\n\nA bank on the other hand is deeply concerned with credit card payments. This would be within their\ncore competencies and they would absolutely not want to abstract away every detail.\n","html":"<p>It's entirely situational. Sometimes you are going to want something incredibly abstract. Imagine\nsetting up an e-commerce store: you aren't going to want to write a custom credit card payment\nprocessor if you can instead use a library that lets you start accepting payments with a single line\nof code.</p>\n<p>A bank on the other hand is deeply concerned with credit card payments. This would be within their\ncore competencies and they would absolutely not want to abstract away every detail.</p>"},{"markdown":"\nOkay, where were we. Right, we made it work.\n","html":"<p>Okay, where were we. Right, we made it work.</p>"},{"markdown":"\nExcept we wrote that in bash, and we're gonna need that in JavaScript, so we haven't even done that.\nHalfway through the presentation and no boxes checked.\n\nMaybe that other list is more favorable.\n","html":"<p>Except we wrote that in bash, and we're gonna need that in JavaScript, so we haven't even done that.\nHalfway through the presentation and no boxes checked.</p>\n<p>Maybe that other list is more favorable.</p>"},{"markdown":"\nStep 3 of 5. Not bad. We have gone over the API, and we have broken down the problem, let's see if\nwe can't implement an end-to-end solution for a single page.\n","html":"<p>Step 3 of 5. Not bad. We have gone over the API, and we have broken down the problem, let's see if\nwe can't implement an end-to-end solution for a single page.</p>"},{"markdown":"\nWe know that the first thing we need to do is thread this `index` query param into data requests\nsome how, so let's stat with the data layer.\n\nWithin the adapters layer, we need to include the `index` query param. We will also want to do this\nconditionally, since now all requests are going to be blocking. And this is going to eventually\nneed to work for all adapters that support blocking queries.\n\nFor serializers, we don't need to make any changes; the responses are the same.\n\nFor models, we also don't need to make any changes; there is no new state to save for records.\n\nHow nice that we only need to touch one part of Ember Data to do this. It's clear that the minds\nbehind Ember Data took the time to think through the right abstractions required to make this code\nnice.\n","html":"<p>We know that the first thing we need to do is thread this <code class=\"language-text\">index</code> query param into data requests\nsome how, so let's stat with the data layer.</p>\n<p>Within the adapters layer, we need to include the <code class=\"language-text\">index</code> query param. We will also want to do this\nconditionally, since now all requests are going to be blocking. And this is going to eventually\nneed to work for all adapters that support blocking queries.</p>\n<p>For serializers, we don't need to make any changes; the responses are the same.</p>\n<p>For models, we also don't need to make any changes; there is no new state to save for records.</p>\n<p>How nice that we only need to touch one part of Ember Data to do this. It's clear that the minds\nbehind Ember Data took the time to think through the right abstractions required to make this code\nnice.</p>"},{"markdown":"\nLet's start by getting that optional query parameter into a request somehow. Since it's optional,\nwe want to add this where we make the request, rather than bury it in the adapter. We can do this\nusing the Ember Data feature `adapterOptions`. It's a generic hash of stuff that gets sent to the\nadapter for the adapter to decide what to deal with.\n\nI ended up going with `watch: true`. Notice that we aren't doing anything with the index value here,\njust hinting to the adapter that we want to make this request a blocking one.\n\n```js{4}\n// route.js\nthis.get('store').findRecord('job', id, {\n  reload: true,\n  adapterOptions: { watch: true },\n});\n```\n","html":"<p>Let's start by getting that optional query parameter into a request somehow. Since it's optional,\nwe want to add this where we make the request, rather than bury it in the adapter. We can do this\nusing the Ember Data feature <code class=\"language-text\">adapterOptions</code>. It's a generic hash of stuff that gets sent to the\nadapter for the adapter to decide what to deal with.</p>\n<p>I ended up going with <code class=\"language-text\">watch: true</code>. Notice that we aren't doing anything with the index value here,\njust hinting to the adapter that we want to make this request a blocking one.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// route.js</span>\n<span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'store'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">findRecord</span><span class=\"token punctuation\">(</span><span class=\"token string\">'job'</span><span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n  reload<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n<span class=\"gatsby-highlight-code-line\">  adapterOptions<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> watch<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>"},{"markdown":"\nWe can start to imagine what the other side of this `findRecord` call looks like now. Some adapter,\nlet's call it `watchable.js`, overrides `findRecord`, looks for the `watch` flag, and then does\nsome blocking query stuff when the flag is set.\n\n```js{3-5}\n// adapters/watchable.js\nfindRecord(store, type, id, snapshot) {\n  if (get(snapshot || {}, 'adapterOptions.watch')) {\n    // do some stuff for blocking queries\n  }\n\n  return this._super(...arguments);\n}\n```\n","html":"<p>We can start to imagine what the other side of this <code class=\"language-text\">findRecord</code> call looks like now. Some adapter,\nlet's call it <code class=\"language-text\">watchable.js</code>, overrides <code class=\"language-text\">findRecord</code>, looks for the <code class=\"language-text\">watch</code> flag, and then does\nsome blocking query stuff when the flag is set.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/watchable.js</span>\n<span class=\"token function\">findRecord</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">store<span class=\"token punctuation\">,</span> type<span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">,</span> snapshot</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n<span class=\"gatsby-highlight-code-line\">  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span>snapshot <span class=\"token operator\">||</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'adapterOptions.watch'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token comment\">// do some stuff for blocking queries</span></span><span class=\"gatsby-highlight-code-line\">  <span class=\"token punctuation\">}</span></span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">_super</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>"},{"markdown":"\nAlright, this looks nice, but there's a glaring open question. How do we track that `X-Nomad-Index`\nvalue? Where does that state live?\n\nOne answer is a WatchList service.\n","html":"<p>Alright, this looks nice, but there's a glaring open question. How do we track that <code class=\"language-text\">X-Nomad-Index</code>\nvalue? Where does that state live?</p>\n<p>One answer is a WatchList service.</p>"},{"markdown":"\nHere's what Ember Data looks like out of the box. Your view-ish-looking code makes a request to the\nstore, the store communicates to the adapter to make a request to the Nomad Server agents, the\nagents respond with some data, the store passes that data into the corresponding serializer, the\nserializer returns the normalized version of the response data, the store takes that  normalized\ndata and creates/updates/deletes models with it, and finally returns the appropriate models back to\nyour view.\n","html":"<p>Here's what Ember Data looks like out of the box. Your view-ish-looking code makes a request to the\nstore, the store communicates to the adapter to make a request to the Nomad Server agents, the\nagents respond with some data, the store passes that data into the corresponding serializer, the\nserializer returns the normalized version of the response data, the store takes that  normalized\ndata and creates/updates/deletes models with it, and finally returns the appropriate models back to\nyour view.</p>"},{"markdown":"\nThe beautiful thing about Services in Ember is you can just sorta stick them anywhere. In this case,\nwe can stick it up there by the adapter. So now before making a request, the adapter can communicate\nwith this WatchList service to get an index value first.\n","html":"<p>The beautiful thing about Services in Ember is you can just sorta stick them anywhere. In this case,\nwe can stick it up there by the adapter. So now before making a request, the adapter can communicate\nwith this WatchList service to get an index value first.</p>"},{"markdown":"\nIt looks a bit like this.\n\n```js{9}\n// adapters/watchable.js\nwatchList: service(),\n// ...\nfindRecord(store, type, id, snapshot) {\n  const fullUrl = this.buildURL(type.modelName, id, snapshot, 'findRecord');\n  let [url, params] = fullUrl.split('?');\n  params = assign(queryString.parse(params) || {}, this.buildQuery());\n\n  if (get(snapshot || {}, 'adapterOptions.watch')) {\n    params.index = this.get('watchList').getIndexFor(url);\n  }\n\n  return this.ajax(url, 'GET', {\n    data: params,\n  });\n},\n```\n\nWe can set the `index` query param to a value we look up on the WatchList using the URL to request\nas a key.\n","html":"<p>It looks a bit like this.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/watchable.js</span>\nwatchList<span class=\"token operator\">:</span> <span class=\"token function\">service</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token comment\">// ...</span>\n<span class=\"token function\">findRecord</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">store<span class=\"token punctuation\">,</span> type<span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">,</span> snapshot</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> fullUrl <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">buildURL</span><span class=\"token punctuation\">(</span>type<span class=\"token punctuation\">.</span>modelName<span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">,</span> snapshot<span class=\"token punctuation\">,</span> <span class=\"token string\">'findRecord'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">let</span> <span class=\"token punctuation\">[</span>url<span class=\"token punctuation\">,</span> params<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> fullUrl<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">'?'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  params <span class=\"token operator\">=</span> <span class=\"token function\">assign</span><span class=\"token punctuation\">(</span>queryString<span class=\"token punctuation\">.</span><span class=\"token function\">parse</span><span class=\"token punctuation\">(</span>params<span class=\"token punctuation\">)</span> <span class=\"token operator\">||</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">buildQuery</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n<span class=\"gatsby-highlight-code-line\">  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span>snapshot <span class=\"token operator\">||</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'adapterOptions.watch'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span></span>    params<span class=\"token punctuation\">.</span>index <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchList'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getIndexFor</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">ajax</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">,</span> <span class=\"token string\">'GET'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    data<span class=\"token operator\">:</span> params<span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></code></pre></div>\n<p>We can set the <code class=\"language-text\">index</code> query param to a value we look up on the WatchList using the URL to request\nas a key.</p>"},{"markdown":"\nWe also have to set this `index` value somewhere. This also happens in the adapter, this time in the\n`handleResponse` method. The `handleResponse` method is where we have access to the headers, which\nis where the new `index` value is sent.\n\nThe `setIndexFor` method on the WatchList service is straight-forward: it takes a url (a key), and\nand index (a value).\n\n```js{7}\n// adapters/watchable.js\nhandleResponse(status, headers, payload, requestData) {\n  // Some browsers lowercase all headers. Other keep them\n  // case sensitive.\n  const newIndex = headers['x-nomad-index'] || headers['X-Nomad-Index'];\n  if (newIndex) {\n    this.get('watchList').setIndexFor(requestData.url, newIndex);\n  }\n\n  return this._super(...arguments);\n}\n```\n","html":"<p>We also have to set this <code class=\"language-text\">index</code> value somewhere. This also happens in the adapter, this time in the\n<code class=\"language-text\">handleResponse</code> method. The <code class=\"language-text\">handleResponse</code> method is where we have access to the headers, which\nis where the new <code class=\"language-text\">index</code> value is sent.</p>\n<p>The <code class=\"language-text\">setIndexFor</code> method on the WatchList service is straight-forward: it takes a url (a key), and\nand index (a value).</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/watchable.js</span>\n<span class=\"token function\">handleResponse</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">status<span class=\"token punctuation\">,</span> headers<span class=\"token punctuation\">,</span> payload<span class=\"token punctuation\">,</span> requestData</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token comment\">// Some browsers lowercase all headers. Other keep them</span>\n  <span class=\"token comment\">// case sensitive.</span>\n  <span class=\"token keyword\">const</span> newIndex <span class=\"token operator\">=</span> headers<span class=\"token punctuation\">[</span><span class=\"token string\">'x-nomad-index'</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">||</span> headers<span class=\"token punctuation\">[</span><span class=\"token string\">'X-Nomad-Index'</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>newIndex<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n<span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchList'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">setIndexFor</span><span class=\"token punctuation\">(</span>requestData<span class=\"token punctuation\">.</span>url<span class=\"token punctuation\">,</span> newIndex<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span>  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">_super</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>"},{"markdown":"\nSo that's how the WatchList gets used, but what does the implementation of the WatchList look like?\nTurns out it's rather unexciting. It's just a mapping of URLs to values with some guarding against\naccidentally overriding the complete list and some type casting since header values are always\nstrings.\n\n```js\nimport { readOnly } from '@ember/object/computed';\nimport { copy } from '@ember/object/internals';\nimport Service from '@ember/service';\n\nlet list = {};\n\nexport default Service.extend({\n  list: readOnly(function() {\n    return copy(list, true);\n  }),\n\n  init() {\n    list = {};\n  },\n\n  getIndexFor(url) {\n    return list[url] || 1;\n  },\n\n  setIndexFor(url, value) {\n    list[url] = +value;\n  },\n})\n```\n","html":"<p>So that's how the WatchList gets used, but what does the implementation of the WatchList look like?\nTurns out it's rather unexciting. It's just a mapping of URLs to values with some guarding against\naccidentally overriding the complete list and some type casting since header values are always\nstrings.</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> readOnly <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/object/computed'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> copy <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/object/internals'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> Service <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/service'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">let</span> list <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> Service<span class=\"token punctuation\">.</span><span class=\"token function\">extend</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  list<span class=\"token operator\">:</span> <span class=\"token function\">readOnly</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> <span class=\"token function\">copy</span><span class=\"token punctuation\">(</span>list<span class=\"token punctuation\">,</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\n  <span class=\"token function\">init</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    list <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  <span class=\"token function\">getIndexFor</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">url</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span> list<span class=\"token punctuation\">[</span>url<span class=\"token punctuation\">]</span> <span class=\"token operator\">||</span> <span class=\"token number\">1</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  <span class=\"token function\">setIndexFor</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">url<span class=\"token punctuation\">,</span> value</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    list<span class=\"token punctuation\">[</span>url<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token operator\">+</span>value<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span></code></pre></div>"},{"markdown":"\nAt this point, all the Ember Data parts are done. Next we have to change the route to request data\nin a loopy fashion.\n\nThis won't affect the model hook, since we still want to load data immediately, but after the\ninitial load we need to start some sort of polling mechanism like that bash loop from earlier.\n\nThat bash loop was pretty great from a readability perspective, but it was also entirely synchronous.\nThis doesn't fly in a web app since we still need to keep the thread open for various other activity.\n","html":"<p>At this point, all the Ember Data parts are done. Next we have to change the route to request data\nin a loopy fashion.</p>\n<p>This won't affect the model hook, since we still want to load data immediately, but after the\ninitial load we need to start some sort of polling mechanism like that bash loop from earlier.</p>\n<p>That bash loop was pretty great from a readability perspective, but it was also entirely synchronous.\nThis doesn't fly in a web app since we still need to keep the thread open for various other activity.</p>"},{"markdown":"\nFortunately for us, we live in the same time period as Ember Concurrency. It's a great way to make\nasynchronous loops look like they are synchronous. It also has task cancelation built right in.\n","html":"<p>Fortunately for us, we live in the same time period as Ember Concurrency. It's a great way to make\nasynchronous loops look like they are synchronous. It also has task cancelation built right in.</p>"},{"markdown":"\nHere's a first stab at a polling loop using Ember Concurrency. In the `setupController` hook, after\nthe model hook has safely completed, we perform this `watch` task, providing the model as an\nargument.\n\nThe `watch` task uses a common Ember Concurrency pattern, utilizing an infinite `while` loop in\nwhich we request data and wait a couple second before making another blocking request.\n\n```js\n// routes/jobs/job/index.js\nimport Route from '@ember/routing/route';\nimport { task, timeout } from 'ember-concurrency';\n\nexport default Route.extend({\n  setupController(controller, model) {\n    this.get('watch').perform(model);\n    return this._super(...arguments);\n  },\n\n  watch: task(function*(model) {\n    while (!Ember.testing) {\n      try {\n        yield this.get('store').findRecord('job', model.get('id'), {\n          reload: true,\n          adapterOptions: { watch: true },\n        });\n        yield timeout(2000);\n      } catch (e) {\n        yield e;\n      }\n    }\n  }),\n});\n```\n","html":"<p>Here's a first stab at a polling loop using Ember Concurrency. In the <code class=\"language-text\">setupController</code> hook, after\nthe model hook has safely completed, we perform this <code class=\"language-text\">watch</code> task, providing the model as an\nargument.</p>\n<p>The <code class=\"language-text\">watch</code> task uses a common Ember Concurrency pattern, utilizing an infinite <code class=\"language-text\">while</code> loop in\nwhich we request data and wait a couple second before making another blocking request.</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// routes/jobs/job/index.js</span>\n<span class=\"token keyword\">import</span> Route <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/routing/route'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> task<span class=\"token punctuation\">,</span> timeout <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ember-concurrency'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> Route<span class=\"token punctuation\">.</span><span class=\"token function\">extend</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  <span class=\"token function\">setupController</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">controller<span class=\"token punctuation\">,</span> model</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watch'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">perform</span><span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">_super</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  watch<span class=\"token operator\">:</span> <span class=\"token function\">task</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token operator\">*</span><span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>Ember<span class=\"token punctuation\">.</span>testing<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">yield</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'store'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">findRecord</span><span class=\"token punctuation\">(</span><span class=\"token string\">'job'</span><span class=\"token punctuation\">,</span> model<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'id'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n          reload<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n          adapterOptions<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> watch<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">yield</span> <span class=\"token function\">timeout</span><span class=\"token punctuation\">(</span><span class=\"token number\">2000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">yield</span> e<span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>"},{"markdown":"\nYou can see in this `watch` task the Ember Data request from earlier.\n\n```js{14-17}\n// routes/jobs/job/index.js\nimport Route from '@ember/routing/route';\nimport { task, timeout } from 'ember-concurrency';\n\nexport default Route.extend({\n  setupController(controller, model) {\n    this.get('watch').perform(model);\n    return this._super(...arguments);\n  },\n\n  watch: task(function*(model) {\n    while (!Ember.testing) {\n      try {\n        yield this.get('store').findRecord('job', model.get('id'), {\n          reload: true,\n          adapterOptions: { watch: true },\n        });\n        yield timeout(2000);\n      } catch (e) {\n        yield e;\n      }\n    }\n  }),\n});\n```\n\nYou may also have noticed that we aren't doing anything with the result of `findRecord`. That's\nbecause we don't have to. The records that `findRecord` returns are references to persistent objects\nin the store.\n\nThis means even though the poll loop in this EC task does nothing with the return value, the model\non the controller is still updated, since it points to the same persistent object that reloaded.\n","html":"<p>You can see in this <code class=\"language-text\">watch</code> task the Ember Data request from earlier.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// routes/jobs/job/index.js</span>\n<span class=\"token keyword\">import</span> Route <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/routing/route'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> task<span class=\"token punctuation\">,</span> timeout <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ember-concurrency'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> Route<span class=\"token punctuation\">.</span><span class=\"token function\">extend</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  <span class=\"token function\">setupController</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">controller<span class=\"token punctuation\">,</span> model</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watch'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">perform</span><span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">_super</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  watch<span class=\"token operator\">:</span> <span class=\"token function\">task</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token operator\">*</span><span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>Ember<span class=\"token punctuation\">.</span>testing<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n<span class=\"gatsby-highlight-code-line\">        <span class=\"token keyword\">yield</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'store'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">findRecord</span><span class=\"token punctuation\">(</span><span class=\"token string\">'job'</span><span class=\"token punctuation\">,</span> model<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'id'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">          reload<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></span><span class=\"gatsby-highlight-code-line\">          adapterOptions<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> watch<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></span><span class=\"gatsby-highlight-code-line\">        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span>        <span class=\"token keyword\">yield</span> <span class=\"token function\">timeout</span><span class=\"token punctuation\">(</span><span class=\"token number\">2000</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">yield</span> e<span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>You may also have noticed that we aren't doing anything with the result of <code class=\"language-text\">findRecord</code>. That's\nbecause we don't have to. The records that <code class=\"language-text\">findRecord</code> returns are references to persistent objects\nin the store.</p>\n<p>This means even though the poll loop in this EC task does nothing with the return value, the model\non the controller is still updated, since it points to the same persistent object that reloaded.</p>"},{"markdown":"\nOkay! So that's it, right? The adapter changes make it so we can make blocking requests, the service\nallows us to keep track of the current index value for any URl, and the route changes mean we're\nindefinitely polling for changes.\n\nDone.\n","html":"<p>Okay! So that's it, right? The adapter changes make it so we can make blocking requests, the service\nallows us to keep track of the current index value for any URl, and the route changes mean we're\nindefinitely polling for changes.</p>\n<p>Done.</p>"},{"markdown":"\nUnfortunately, not quite. This works well for watching a single resource, but we also have to watch\nlists and relationships. And a consequence of lists and relationships means removing things from\nthe store.\n","html":"<p>Unfortunately, not quite. This works well for watching a single resource, but we also have to watch\nlists and relationships. And a consequence of lists and relationships means removing things from\nthe store.</p>"},{"markdown":"\nLet's start with watching lists. This one is straight forward. We have to override `findAll` in the\nadapter, and we get to recycle WatchList, since it works on any URL.\n","html":"<p>Let's start with watching lists. This one is straight forward. We have to override <code class=\"language-text\">findAll</code> in the\nadapter, and we get to recycle WatchList, since it works on any URL.</p>"},{"markdown":"\nThis should look familiar. Same pattern as `findRecord` now applied to `findAll`.\n\n```js{9}\n// adapters/watchable.js\nwatchList: service(),\n// ...\nfindAll(store, type, sinceToken, snapshotRecordArray) {\n  const params = this.buildQuery();\n  const url = this.urlForFindAll(type.modelName);\n\n  if (get(snapshotRecordArray || {}, 'adapterOptions.watch')) {\n    params.index = this.get('watchList').getIndexFor(url);\n  }\n\n  return this.ajax(url, 'GET', {\n    data: params,\n  });\n},\n```\n","html":"<p>This should look familiar. Same pattern as <code class=\"language-text\">findRecord</code> now applied to <code class=\"language-text\">findAll</code>.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/watchable.js</span>\nwatchList<span class=\"token operator\">:</span> <span class=\"token function\">service</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token comment\">// ...</span>\n<span class=\"token function\">findAll</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">store<span class=\"token punctuation\">,</span> type<span class=\"token punctuation\">,</span> sinceToken<span class=\"token punctuation\">,</span> snapshotRecordArray</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> params <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">buildQuery</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">urlForFindAll</span><span class=\"token punctuation\">(</span>type<span class=\"token punctuation\">.</span>modelName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span>snapshotRecordArray <span class=\"token operator\">||</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'adapterOptions.watch'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n<span class=\"gatsby-highlight-code-line\">    params<span class=\"token punctuation\">.</span>index <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchList'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getIndexFor</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span>  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">ajax</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">,</span> <span class=\"token string\">'GET'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    data<span class=\"token operator\">:</span> params<span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></code></pre></div>"},{"markdown":"\nNext up, watching relationships. This one is trickier.\n\nThere is no existing store method for fetching a relationship. Under Ember Data, these requests are\nmade by reading relationship properties on models.\n\nSo I had to invent something. A new `reloadRelationship` method.\n","html":"<p>Next up, watching relationships. This one is trickier.</p>\n<p>There is no existing store method for fetching a relationship. Under Ember Data, these requests are\nmade by reading relationship properties on models.</p>\n<p>So I had to invent something. A new <code class=\"language-text\">reloadRelationship</code> method.</p>"},{"markdown":"\nSo here's a big pile of code. It's not as clean as the changes to `findRecord` and `findAll`, but\nof course it isn't. This is an entirely new method. I'm just pleased that I was able to write it at\nall.\n\nModels have a relationship API that can be used to discover the appropriate HTTP request to make\ngiven just a model and a relationship name.\n\n```js\nreloadRelationship(model, relationshipName, watch = false) {\n  const relationship = model.relationshipFor(relationshipName);\n  if (relationship.kind !== 'belongsTo' && relationship.kind !== 'hasMany') {\n    throw new Error(\n      `${relationship.key} must be a belongsTo or hasMany, instead it was ${relationship.kind}`\n    );\n  } else {\n    const url = model[relationship.kind](relationship.key).link();\n    let params = {};\n\n    if (watch) {\n      params.index = this.get('watchList').getIndexFor(url);\n    }\n\n    // Avoid duplicating existing query params by passing them to ajax\n    // in the URL and in options.data\n    if (url.includes('?')) {\n      const paramsInUrl = queryString.parse(url.split('?')[1]);\n      Object.keys(paramsInUrl).forEach(key => {\n        delete params[key];\n      });\n    }\n\n    return this.ajax(url, 'GET', {\n      data: params,\n    }).then(\n      json => {\n        const store = this.get('store');\n        const normalizeMethod =\n        relationship.kind === 'belongsTo'\n        ? 'normalizeFindBelongsToResponse'\n        : 'normalizeFindHasManyResponse';\n        const serializer = store.serializerFor(relationship.type);\n        const modelClass = store.modelFor(relationship.type);\n        const normalizedData = serializer[normalizeMethod](store, modelClass, json);\n        store.push(normalizedData);\n      },\n      error => {\n        if (error instanceof AbortError) {\n          return relationship.kind === 'belongsTo' ? {} : [];\n        }\n        throw error;\n      }\n    );\n  }\n},\n```\n","html":"<p>So here's a big pile of code. It's not as clean as the changes to <code class=\"language-text\">findRecord</code> and <code class=\"language-text\">findAll</code>, but\nof course it isn't. This is an entirely new method. I'm just pleased that I was able to write it at\nall.</p>\n<p>Models have a relationship API that can be used to discover the appropriate HTTP request to make\ngiven just a model and a relationship name.</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token function\">reloadRelationship</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">model<span class=\"token punctuation\">,</span> relationshipName<span class=\"token punctuation\">,</span> watch <span class=\"token operator\">=</span> <span class=\"token boolean\">false</span></span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> relationship <span class=\"token operator\">=</span> model<span class=\"token punctuation\">.</span><span class=\"token function\">relationshipFor</span><span class=\"token punctuation\">(</span>relationshipName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>relationship<span class=\"token punctuation\">.</span>kind <span class=\"token operator\">!==</span> <span class=\"token string\">'belongsTo'</span> <span class=\"token operator\">&amp;&amp;</span> relationship<span class=\"token punctuation\">.</span>kind <span class=\"token operator\">!==</span> <span class=\"token string\">'hasMany'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span>\n      <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>relationship<span class=\"token punctuation\">.</span>key<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> must be a belongsTo or hasMany, instead it was </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>relationship<span class=\"token punctuation\">.</span>kind<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> model<span class=\"token punctuation\">[</span>relationship<span class=\"token punctuation\">.</span>kind<span class=\"token punctuation\">]</span><span class=\"token punctuation\">(</span>relationship<span class=\"token punctuation\">.</span>key<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">link</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">let</span> params <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>watch<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      params<span class=\"token punctuation\">.</span>index <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchList'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getIndexFor</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token comment\">// Avoid duplicating existing query params by passing them to ajax</span>\n    <span class=\"token comment\">// in the URL and in options.data</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">.</span><span class=\"token function\">includes</span><span class=\"token punctuation\">(</span><span class=\"token string\">'?'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">const</span> paramsInUrl <span class=\"token operator\">=</span> queryString<span class=\"token punctuation\">.</span><span class=\"token function\">parse</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">'?'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">[</span><span class=\"token number\">1</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      Object<span class=\"token punctuation\">.</span><span class=\"token function\">keys</span><span class=\"token punctuation\">(</span>paramsInUrl<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">key</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">delete</span> params<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">ajax</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">,</span> <span class=\"token string\">'GET'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n      data<span class=\"token operator\">:</span> params<span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span>\n      <span class=\"token parameter\">json</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">const</span> store <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'store'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">const</span> normalizeMethod <span class=\"token operator\">=</span>\n        relationship<span class=\"token punctuation\">.</span>kind <span class=\"token operator\">===</span> <span class=\"token string\">'belongsTo'</span>\n        <span class=\"token operator\">?</span> <span class=\"token string\">'normalizeFindBelongsToResponse'</span>\n        <span class=\"token operator\">:</span> <span class=\"token string\">'normalizeFindHasManyResponse'</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">const</span> serializer <span class=\"token operator\">=</span> store<span class=\"token punctuation\">.</span><span class=\"token function\">serializerFor</span><span class=\"token punctuation\">(</span>relationship<span class=\"token punctuation\">.</span>type<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">const</span> modelClass <span class=\"token operator\">=</span> store<span class=\"token punctuation\">.</span><span class=\"token function\">modelFor</span><span class=\"token punctuation\">(</span>relationship<span class=\"token punctuation\">.</span>type<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">const</span> normalizedData <span class=\"token operator\">=</span> serializer<span class=\"token punctuation\">[</span>normalizeMethod<span class=\"token punctuation\">]</span><span class=\"token punctuation\">(</span>store<span class=\"token punctuation\">,</span> modelClass<span class=\"token punctuation\">,</span> json<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        store<span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span>normalizedData<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token parameter\">error</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>error <span class=\"token keyword\">instanceof</span> <span class=\"token class-name\">AbortError</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n          <span class=\"token keyword\">return</span> relationship<span class=\"token punctuation\">.</span>kind <span class=\"token operator\">===</span> <span class=\"token string\">'belongsTo'</span> <span class=\"token operator\">?</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span> <span class=\"token operator\">:</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n        <span class=\"token punctuation\">}</span>\n        <span class=\"token keyword\">throw</span> error<span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></code></pre></div>"},{"markdown":"\nNow to remove records. There's the `findAll` case and the `watchRelationship` case. The `findAll`\ncase is pretty simple: if a record in the store isn't also in the new response, then that record\nneeds to be removed from the store.\n\nFor the `watchRelationship` case, we need to remove records from the store of the relationship type\nthat's being watched that aren't also in this response.\n\nIt's a tricky mouthful of words, so I think a diagram might help explain this.\n","html":"<p>Now to remove records. There's the <code class=\"language-text\">findAll</code> case and the <code class=\"language-text\">watchRelationship</code> case. The <code class=\"language-text\">findAll</code>\ncase is pretty simple: if a record in the store isn't also in the new response, then that record\nneeds to be removed from the store.</p>\n<p>For the <code class=\"language-text\">watchRelationship</code> case, we need to remove records from the store of the relationship type\nthat's being watched that aren't also in this response.</p>\n<p>It's a tricky mouthful of words, so I think a diagram might help explain this.</p>"},{"markdown":"\nHere's a job with a hasMany relationship to allocs. The job has three allocations. The allocations\nare also associated to nodes. Each alloc has a belongsTo relationship with a node. Nodes have a\nhasMany relationship with allocs.\n","html":"<p>Here's a job with a hasMany relationship to allocs. The job has three allocations. The allocations\nare also associated to nodes. Each alloc has a belongsTo relationship with a node. Nodes have a\nhasMany relationship with allocs.</p>"},{"markdown":"\nBy default, `findHasMany` adds and removes relationship links related to this model, but it doesn't\nremove anything from the store.\n\n```js\n// adapters/application.js\nfindHasMany(store, snapshot, link, relationship) {\n  return this._super(...arguments);\n}\n```\n\nSo if we don't touch this default behavior, the impact on the object graph looks like this.\n","html":"<p>By default, <code class=\"language-text\">findHasMany</code> adds and removes relationship links related to this model, but it doesn't\nremove anything from the store.</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/application.js</span>\n<span class=\"token function\">findHasMany</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">store<span class=\"token punctuation\">,</span> snapshot<span class=\"token punctuation\">,</span> link<span class=\"token punctuation\">,</span> relationship</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">_super</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>So if we don't touch this default behavior, the impact on the object graph looks like this.</p>"},{"markdown":"\nThe link between the job and allocation is gone, but the allocation remains. We know from the latest\n`watchRelationship` response that the allocation is gone, so we need to remove it somehow.\n","html":"<p>The link between the job and allocation is gone, but the allocation remains. We know from the latest\n<code class=\"language-text\">watchRelationship</code> response that the allocation is gone, so we need to remove it somehow.</p>"},{"markdown":"\nWe can remove related records by using the relationship API to traverse records and find the records\non the other side of the relationship (allocations in this case) that are associated with the model\nwhose relationship we are watching (job in this case).\n\n```js{5,10,12}\n// adapters/application.js\nfindHasMany(store, snapshot, link, relationship) {\n  return this._super(...arguments).then(payload => {\n    const relationshipType = relationship.type;\n    const inverse = snapshot.record.inverseFor(relationship.key);\n\n    if (inverse) {\n      store\n        .peekAll(relationshipType)\n        .filter(record => record.get(`${inverse.name.id}`) === snapshot.id)\n        .forEach(record => {\n          record.unloadRecord();\n        });\n    }\n    return payload;\n  });\n}\n```\n\nFirst, we this `inverse` object, which contains the property name for the other side of the\nrelationship. Then, we filter all records of the relationship type (allocations) down to only the\nrecords whose value for `inverse.name.id` (the property name for the other side of the relationship)\nis equal to this snapshot's id.\n\nFor each of those matching records, we call `unloadRecord` to get it out of the store.\n","html":"<p>We can remove related records by using the relationship API to traverse records and find the records\non the other side of the relationship (allocations in this case) that are associated with the model\nwhose relationship we are watching (job in this case).</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/application.js</span>\n<span class=\"token function\">findHasMany</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">store<span class=\"token punctuation\">,</span> snapshot<span class=\"token punctuation\">,</span> link<span class=\"token punctuation\">,</span> relationship</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">_super</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">then</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">payload</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> relationshipType <span class=\"token operator\">=</span> relationship<span class=\"token punctuation\">.</span>type<span class=\"token punctuation\">;</span>\n<span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">const</span> inverse <span class=\"token operator\">=</span> snapshot<span class=\"token punctuation\">.</span>record<span class=\"token punctuation\">.</span><span class=\"token function\">inverseFor</span><span class=\"token punctuation\">(</span>relationship<span class=\"token punctuation\">.</span>key<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>inverse<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      store\n        <span class=\"token punctuation\">.</span><span class=\"token function\">peekAll</span><span class=\"token punctuation\">(</span>relationshipType<span class=\"token punctuation\">)</span>\n<span class=\"gatsby-highlight-code-line\">        <span class=\"token punctuation\">.</span><span class=\"token function\">filter</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">record</span> <span class=\"token operator\">=></span> record<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>inverse<span class=\"token punctuation\">.</span>name<span class=\"token punctuation\">.</span>id<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">===</span> snapshot<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">)</span></span>        <span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">record</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n<span class=\"gatsby-highlight-code-line\">          record<span class=\"token punctuation\">.</span><span class=\"token function\">unloadRecord</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span>        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">return</span> payload<span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>First, we this <code class=\"language-text\">inverse</code> object, which contains the property name for the other side of the\nrelationship. Then, we filter all records of the relationship type (allocations) down to only the\nrecords whose value for <code class=\"language-text\">inverse.name.id</code> (the property name for the other side of the relationship)\nis equal to this snapshot's id.</p>\n<p>For each of those matching records, we call <code class=\"language-text\">unloadRecord</code> to get it out of the store.</p>"},{"markdown":"\nThis still isn't quite enough. This leaves us with this object graph, which is clearly invalid. The\nallocation was deleted, and the link from the job to the allocation was removed, but the other\nrelationships on that allocation are still present.\n\nThis leads to runtime issues, believe me, I tried.\n","html":"<p>This still isn't quite enough. This leaves us with this object graph, which is clearly invalid. The\nallocation was deleted, and the link from the job to the allocation was removed, but the other\nrelationships on that allocation are still present.</p>\n<p>This leads to runtime issues, believe me, I tried.</p>"},{"markdown":"\nOkay, simple enough, we just need to clear those relationships out before unloading the record.\n\nThere's a neat trick for doing this. `store.push` is typically associated with putting data into the\nEmber Data store, but it can also be thought of as a low-level API for manipulating the data in the\nstore.\n\nWe can create a JSONAPI payload that updates the record we want to remove to null out any\nrelationships.\n\nFor `belongsTo` relationships, this means setting the relationship value to `null`. For `hasMany`\nrelationships, this means setting the relationship value to `[]`.\n\nWe can replace our earlier call to `record.unloadRecord` with a call to our new util:\n`removeRecord(store, record)`.\n\n```js\n// utils/remove-record.js\nexport default function removeRecord(store, record) {\n  const relationshipMeta = [];\n  record.eachRelationship((key, { kind }) => {\n    relationshipMeta.push({ key, kind });\n  });\n\n  store.push({\n    data: {\n      id: record.get('id'),\n      type: record.constructor.modelName,\n      relationships: relationshipMeta.reduce((hash, rel) => {\n        hash[rel.key] = { data: rel.kind === 'hasMany' ? [] : null };\n        return hash;\n      }, {}),\n    },\n  });\n\n  record.unloadRecord();\n}\n\n// Sample JSONAPI payload provided to store.push\n// {\n//   id: “alloc-1”,\n//   type: “allocation”,\n//   relationships: {\n//     job: null,\n//     node: null,\n//     evaluations: [],\n//   }\n// }\n```\n","html":"<p>Okay, simple enough, we just need to clear those relationships out before unloading the record.</p>\n<p>There's a neat trick for doing this. <code class=\"language-text\">store.push</code> is typically associated with putting data into the\nEmber Data store, but it can also be thought of as a low-level API for manipulating the data in the\nstore.</p>\n<p>We can create a JSONAPI payload that updates the record we want to remove to null out any\nrelationships.</p>\n<p>For <code class=\"language-text\">belongsTo</code> relationships, this means setting the relationship value to <code class=\"language-text\">null</code>. For <code class=\"language-text\">hasMany</code>\nrelationships, this means setting the relationship value to <code class=\"language-text\">[]</code>.</p>\n<p>We can replace our earlier call to <code class=\"language-text\">record.unloadRecord</code> with a call to our new util:\n<code class=\"language-text\">removeRecord(store, record)</code>.</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// utils/remove-record.js</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> <span class=\"token keyword\">function</span> <span class=\"token function\">removeRecord</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">store<span class=\"token punctuation\">,</span> record</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> relationshipMeta <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n  record<span class=\"token punctuation\">.</span><span class=\"token function\">eachRelationship</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">key<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span> kind <span class=\"token punctuation\">}</span></span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n    relationshipMeta<span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span> key<span class=\"token punctuation\">,</span> kind <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  store<span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n    data<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n      id<span class=\"token operator\">:</span> record<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'id'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      type<span class=\"token operator\">:</span> record<span class=\"token punctuation\">.</span>constructor<span class=\"token punctuation\">.</span>modelName<span class=\"token punctuation\">,</span>\n      relationships<span class=\"token operator\">:</span> relationshipMeta<span class=\"token punctuation\">.</span><span class=\"token function\">reduce</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">hash<span class=\"token punctuation\">,</span> rel</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n        hash<span class=\"token punctuation\">[</span>rel<span class=\"token punctuation\">.</span>key<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span> data<span class=\"token operator\">:</span> rel<span class=\"token punctuation\">.</span>kind <span class=\"token operator\">===</span> <span class=\"token string\">'hasMany'</span> <span class=\"token operator\">?</span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span> <span class=\"token operator\">:</span> <span class=\"token keyword\">null</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">return</span> hash<span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  record<span class=\"token punctuation\">.</span><span class=\"token function\">unloadRecord</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token comment\">// Sample JSONAPI payload provided to store.push</span>\n<span class=\"token comment\">// {</span>\n<span class=\"token comment\">//   id: “alloc-1”,</span>\n<span class=\"token comment\">//   type: “allocation”,</span>\n<span class=\"token comment\">//   relationships: {</span>\n<span class=\"token comment\">//     job: null,</span>\n<span class=\"token comment\">//     node: null,</span>\n<span class=\"token comment\">//     evaluations: [],</span>\n<span class=\"token comment\">//   }</span>\n<span class=\"token comment\">// }</span></code></pre></div>"},{"markdown":"\nAnd now, we have the updated object graph we desire.\n","html":"<p>And now, we have the updated object graph we desire.</p>"},{"markdown":"\nBut I have to hit the pause button here. There's a good chance you're currently thinking, \"This is\nabsurd. Why can't Ember Data do this automatically?\"\n\nThat's fair question, one I pondered too, but it doesn't make sense for Ember Data to do this out of\nthe box. Ember Data's behavior always errs on the side of caution, and this removal behavior I want\nisn't universally true.\n\nNot all hasMany relationships should delete the record when the relationship is removed. Alex Jones\nmay have been removed from Twitter, but he wasn't deleted from the universe.\n\nYou can make an argument that it should be _easier_ to do what I'm doing here, but there is no case\nto be made for it to happen automatically.\n","html":"<p>But I have to hit the pause button here. There's a good chance you're currently thinking, \"This is\nabsurd. Why can't Ember Data do this automatically?\"</p>\n<p>That's fair question, one I pondered too, but it doesn't make sense for Ember Data to do this out of\nthe box. Ember Data's behavior always errs on the side of caution, and this removal behavior I want\nisn't universally true.</p>\n<p>Not all hasMany relationships should delete the record when the relationship is removed. Alex Jones\nmay have been removed from Twitter, but he wasn't deleted from the universe.</p>\n<p>You can make an argument that it should be <em>easier</em> to do what I'm doing here, but there is no case\nto be made for it to happen automatically.</p>"},{"markdown":"\nBack to the talk. At this point everything works.\n\n- There's a new service for tracking indices by URL\n- `findRecord` was overridden to optionally append the index query param to requests\n- `findAll` was overridden in a similar way\n- There's a new `reloadRelationship` method on adapters for reloading a relationship separately from\n  property lookup on a model.\n- Data gets removed from the store to keep the client-side state of the world in sync with the\n  server-side state of the world.\n","html":"<p>Back to the talk. At this point everything works.</p>\n<ul>\n<li>There's a new service for tracking indices by URL</li>\n<li><code class=\"language-text\">findRecord</code> was overridden to optionally append the index query param to requests</li>\n<li><code class=\"language-text\">findAll</code> was overridden in a similar way</li>\n<li>There's a new <code class=\"language-text\">reloadRelationship</code> method on adapters for reloading a relationship separately from\nproperty lookup on a model.</li>\n<li>Data gets removed from the store to keep the client-side state of the world in sync with the\nserver-side state of the world.</li>\n</ul>"},{"markdown":"\nNow that it works, the next thing to do is make it nice. We do this by finding the right\nabstractions.\n","html":"<p>Now that it works, the next thing to do is make it nice. We do this by finding the right\nabstractions.</p>"},{"markdown":"\nLet's review what our abstractions look like right now.\n\nUnder the developer's concerns are requesting models, deciding what data to poll, deciding how to\npoll, and deciding when to poll.\n\nThe abstracted details are index tracking (which happens in the adapter and WathList service\nabstractions), and remove stale data (which happens in the application adapter, along with that\nremove record util).\n","html":"<p>Let's review what our abstractions look like right now.</p>\n<p>Under the developer's concerns are requesting models, deciding what data to poll, deciding how to\npoll, and deciding when to poll.</p>\n<p>The abstracted details are index tracking (which happens in the adapter and WathList service\nabstractions), and remove stale data (which happens in the application adapter, along with that\nremove record util).</p>"},{"markdown":"\nIf you recall, the more stuff we have under the developer's concerns, the closer we get to _Minerva\nin Her Study_, and the more stuff we have under abstracted details, the closer we get to _Smiley in\nOpen Sans_.\n\nIt's important to really emphasize that there is no wrong or right abstraction. It's a subjective\nprediction made with the goal of managing complexity without limiting developers by locking away\nsituational parameters they need to adjust.\n","html":"<p>If you recall, the more stuff we have under the developer's concerns, the closer we get to <em>Minerva\nin Her Study</em>, and the more stuff we have under abstracted details, the closer we get to <em>Smiley in\nOpen Sans</em>.</p>\n<p>It's important to really emphasize that there is no wrong or right abstraction. It's a subjective\nprediction made with the goal of managing complexity without limiting developers by locking away\nsituational parameters they need to adjust.</p>"},{"markdown":"\nFrom here we _could_ go less abstract by moving the removal of stale data under the developer's\nconcerns. Afterall, we already determining that the way we are removing data isn't universally sound.\n","html":"<p>From here we <em>could</em> go less abstract by moving the removal of stale data under the developer's\nconcerns. Afterall, we already determining that the way we are removing data isn't universally sound.</p>"},{"markdown":"\nWe could also go all in on abstractions by putting everything other than requesting models under\nabstracted details. This would look something like all data automatically polling without any\nchanges to your route code. Which sounds nice, but it prohibits fetching data without watching it\nunless you work around the abstraction.\n","html":"<p>We could also go all in on abstractions by putting everything other than requesting models under\nabstracted details. This would look something like all data automatically polling without any\nchanges to your route code. Which sounds nice, but it prohibits fetching data without watching it\nunless you work around the abstraction.</p>"},{"markdown":"\nKnowing what I know about Ember, Ember Data, and most importantly, the Nomad UI, this is the\nlevel of abstraction I went with. How and when to poll can be safely abstracted, but which models\nto request and what data should be polled remains a developer concern.\n","html":"<p>Knowing what I know about Ember, Ember Data, and most importantly, the Nomad UI, this is the\nlevel of abstraction I went with. How and when to poll can be safely abstracted, but which models\nto request and what data should be polled remains a developer concern.</p>"},{"markdown":"\nLet's start with abstracting how to poll. How do we do this?\n\nWe're currently using Ember Concurrency tasks for polling, and these tasks are essentially a\nspecialized computed property. Ember has this existing concept of computed property macros, we can\nleverage that concept here.\n\nA computed property macro can be written many different ways, how do we choose which way to go with?\nThe question I ask is always, \"what are truly **parameters** and not the **mechanics** of polling?\"\n","html":"<p>Let's start with abstracting how to poll. How do we do this?</p>\n<p>We're currently using Ember Concurrency tasks for polling, and these tasks are essentially a\nspecialized computed property. Ember has this existing concept of computed property macros, we can\nleverage that concept here.</p>\n<p>A computed property macro can be written many different ways, how do we choose which way to go with?\nThe question I ask is always, \"what are truly <strong>parameters</strong> and not the <strong>mechanics</strong> of polling?\"</p>"},{"markdown":"\nHere's what that task looks like:\n\n```js{8-22}\n// utils/properties/watch.js\nimport Ember from 'ember';\nimport { get } from '@ember/object';\nimport RSVP from 'rsvp';\nimport { task, timeout } from 'ember-concurrency';\n\nexport function watchRecord(modelName) {\n  return task(function*(id, throttle = 2000) {\n    if (typeof id === 'object') {\n      id = get(id, 'id');\n    }\n    while (!Ember.testing) {\n      try {\n        yield this.get('store').findRecord(modelName, id, {\n          reload: true,\n          adapterOptions: { watch: true },\n        });\n        yield timeout(throttle);\n      } catch (e) {\n        yield e;\n      }\n    }\n  }).drop();\n}\n```\n\nNotice how the bulk of this task macro is the original task extracted from the route and moved into\nthis util.\n\nThe other detail of note is how the `watchRecord` task macro expects a `modelName` parameter. Then,\nthe task itself closes over this argument to create a task that now only expects an `id` and an\noptional `throttle` parameter.\n","html":"<p>Here's what that task looks like:</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// utils/properties/watch.js</span>\n<span class=\"token keyword\">import</span> Ember <span class=\"token keyword\">from</span> <span class=\"token string\">'ember'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> get <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/object'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token constant\">RSVP</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'rsvp'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> task<span class=\"token punctuation\">,</span> timeout <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ember-concurrency'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">watchRecord</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">modelName</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n<span class=\"gatsby-highlight-code-line\">  <span class=\"token keyword\">return</span> <span class=\"token function\">task</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token operator\">*</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">,</span> throttle <span class=\"token operator\">=</span> <span class=\"token number\">2000</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span> id <span class=\"token operator\">===</span> <span class=\"token string\">'object'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">      id <span class=\"token operator\">=</span> <span class=\"token function\">get</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">,</span> <span class=\"token string\">'id'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token punctuation\">}</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>Ember<span class=\"token punctuation\">.</span>testing<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">      <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">        <span class=\"token keyword\">yield</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'store'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">findRecord</span><span class=\"token punctuation\">(</span>modelName<span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">          reload<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span></span><span class=\"gatsby-highlight-code-line\">          adapterOptions<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> watch<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></span><span class=\"gatsby-highlight-code-line\">        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span><span class=\"gatsby-highlight-code-line\">        <span class=\"token keyword\">yield</span> <span class=\"token function\">timeout</span><span class=\"token punctuation\">(</span>throttle<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span><span class=\"gatsby-highlight-code-line\">      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">        <span class=\"token keyword\">yield</span> e<span class=\"token punctuation\">;</span></span><span class=\"gatsby-highlight-code-line\">      <span class=\"token punctuation\">}</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token punctuation\">}</span></span>  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">drop</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Notice how the bulk of this task macro is the original task extracted from the route and moved into\nthis util.</p>\n<p>The other detail of note is how the <code class=\"language-text\">watchRecord</code> task macro expects a <code class=\"language-text\">modelName</code> parameter. Then,\nthe task itself closes over this argument to create a task that now only expects an <code class=\"language-text\">id</code> and an\noptional <code class=\"language-text\">throttle</code> parameter.</p>"},{"markdown":"\nOnto abstracting when to poll. Currently we are starting polls in the `setupController` method of\nroutes. The typical way to abstract route behaviors is through inheritence. In Ember, we do this\nwith Mixins.\n","html":"<p>Onto abstracting when to poll. Currently we are starting polls in the <code class=\"language-text\">setupController</code> method of\nroutes. The typical way to abstract route behaviors is through inheritence. In Ember, we do this\nwith Mixins.</p>"},{"markdown":"\nThis is what that mixin looks like. It still uses `setupController`, since this will get mixed into\nthe route, but now it's out of sight out of mind. We also fixed a bug: we weren't canceling tasks\nwhen we left pages before now.\n\n```js\n// mixins/with-watchers.js\nimport Mixin from '@ember/object/mixin';\nimport { computed } from '@ember/object';\nimport { assert } from '@ember/debug';\n\nexport default Mixin.create({\n  watchers: computed(() => []),\n\n  cancelAllWatchers() {\n    this.get('watchers').forEach(watcher => {\n      assert(\n        'Watchers must be Ember Concurrency Tasks.',\n        !!watcher.cancelAll\n      );\n      watcher.cancelAll();\n    });\n  },\n\n  startWatchers() {\n    assert('startWatchers needs to be overridden in the Route', false);\n  },\n\n  setupController() {\n    this.startWatchers(...arguments);\n    return this._super(...arguments);\n  },\n\n  actions: {\n    willTransition() {\n      this.cancelAllWatchers();\n    },\n  },\n});\n```\n","html":"<p>This is what that mixin looks like. It still uses <code class=\"language-text\">setupController</code>, since this will get mixed into\nthe route, but now it's out of sight out of mind. We also fixed a bug: we weren't canceling tasks\nwhen we left pages before now.</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// mixins/with-watchers.js</span>\n<span class=\"token keyword\">import</span> Mixin <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/object/mixin'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> computed <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/object'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> assert <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/debug'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> Mixin<span class=\"token punctuation\">.</span><span class=\"token function\">create</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">{</span>\n  watchers<span class=\"token operator\">:</span> <span class=\"token function\">computed</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">[</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\n  <span class=\"token function\">cancelAllWatchers</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchers'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">forEach</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">watcher</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span>\n      <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span>\n        <span class=\"token string\">'Watchers must be Ember Concurrency Tasks.'</span><span class=\"token punctuation\">,</span>\n        <span class=\"token operator\">!</span><span class=\"token operator\">!</span>watcher<span class=\"token punctuation\">.</span>cancelAll\n      <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      watcher<span class=\"token punctuation\">.</span><span class=\"token function\">cancelAll</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  <span class=\"token function\">startWatchers</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">assert</span><span class=\"token punctuation\">(</span><span class=\"token string\">'startWatchers needs to be overridden in the Route'</span><span class=\"token punctuation\">,</span> <span class=\"token boolean\">false</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  <span class=\"token function\">setupController</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">startWatchers</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">_super</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  actions<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token function\">willTransition</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">cancelAllWatchers</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>"},{"markdown":"\nThis is what a route looks like now. So much cleaner!\n\n```js{8-10}\n// routes/jobs/index.js\nimport Route from '@ember/routing/route';\nimport { collect } from '@ember/object/computed';\nimport { watchAll } from 'nomad-ui/utils/properties/watch';\nimport WithWatchers from 'nomad-ui/mixins/with-watchers';\n\nexport default Route.extend(WithWatchers, {\n  startWatchers(controller) {\n    controller.set('modelWatch', this.get('watch').perform());\n  },\n\n  watch: watchAll('job'),\n  watchers: collect('watch'),\n});\n```\n\nLooking at this, you might be compelled to reduce it further. Maybe by moving this `startWatchers`\nnonsense into the mixin. If you recall, some of the `watcher` task macros take arguments to calls to\n`perform`. We can't abstract these details away.\n","html":"<p>This is what a route looks like now. So much cleaner!</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// routes/jobs/index.js</span>\n<span class=\"token keyword\">import</span> Route <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/routing/route'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> collect <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/object/computed'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> watchAll <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'nomad-ui/utils/properties/watch'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> WithWatchers <span class=\"token keyword\">from</span> <span class=\"token string\">'nomad-ui/mixins/with-watchers'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> Route<span class=\"token punctuation\">.</span><span class=\"token function\">extend</span><span class=\"token punctuation\">(</span>WithWatchers<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n<span class=\"gatsby-highlight-code-line\">  <span class=\"token function\">startWatchers</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">controller</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">    controller<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token string\">'modelWatch'</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watch'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">perform</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span><span class=\"gatsby-highlight-code-line\">  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></span>\n  watch<span class=\"token operator\">:</span> <span class=\"token function\">watchAll</span><span class=\"token punctuation\">(</span><span class=\"token string\">'job'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  watchers<span class=\"token operator\">:</span> <span class=\"token function\">collect</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watch'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>\n<p>Looking at this, you might be compelled to reduce it further. Maybe by moving this <code class=\"language-text\">startWatchers</code>\nnonsense into the mixin. If you recall, some of the <code class=\"language-text\">watcher</code> task macros take arguments to calls to\n<code class=\"language-text\">perform</code>. We can't abstract these details away.</p>"},{"markdown":"\nHere's an example of another, more involved, route. It has multiple watchers to start, some of them\nrequire arguments, and a couple are even optionally started based on the shape of the model.\n\n```js{14,17-19}\n// routes/jobs/job/index.js\nimport Route from '@ember/routing/route';\nimport { collect } from '@ember/object/computed';\nimport { watchRecord, watchRelationship, watchAll } from 'nomad-ui/utils/properties/watch';\nimport WithWatchers from 'nomad-ui/mixins/with-watchers';\n\nexport default Route.extend(WithWatchers, {\n  startWatchers(controller, model) {\n    if (!model) {\n      return;\n    }\n    controller.set('watchers', {\n      model: this.get('watch').perform(model),\n      summary: this.get('watchSummary').perform(model.get('summary')),\n      allocations: this.get('watchAllocations').perform(model),\n      evaluations: this.get('watchEvaluations').perform(model),\n      latestDeployment:\n        model.get('supportsDeployments') && this.get('watchLatestDeployment').perform(model),\n      list: model.get('hasChildren') && this.get('watchAll').perform(),\n    });\n  },\n\n  watch: watchRecord('job'),\n  watchAll: watchAll('job'),\n  watchSummary: watchRecord('job-summary'),\n  watchAllocations: watchRelationship('allocations'),\n  watchEvaluations: watchRelationship('evaluations'),\n  watchLatestDeployment: watchRelationship('latestDeployment'),\n\n  watchers: collect(\n  'watch',\n  'watchAll',\n  'watchSummary',\n  'watchAllocations',\n  'watchEvaluations',\n  'watchLatestDeployment'\n  ),\n});\n```\n","html":"<p>Here's an example of another, more involved, route. It has multiple watchers to start, some of them\nrequire arguments, and a couple are even optionally started based on the shape of the model.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// routes/jobs/job/index.js</span>\n<span class=\"token keyword\">import</span> Route <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/routing/route'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> collect <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'@ember/object/computed'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> watchRecord<span class=\"token punctuation\">,</span> watchRelationship<span class=\"token punctuation\">,</span> watchAll <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'nomad-ui/utils/properties/watch'</span><span class=\"token punctuation\">;</span>\n<span class=\"token keyword\">import</span> WithWatchers <span class=\"token keyword\">from</span> <span class=\"token string\">'nomad-ui/mixins/with-watchers'</span><span class=\"token punctuation\">;</span>\n\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">default</span> Route<span class=\"token punctuation\">.</span><span class=\"token function\">extend</span><span class=\"token punctuation\">(</span>WithWatchers<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token function\">startWatchers</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">controller<span class=\"token punctuation\">,</span> model</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>model<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">return</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    controller<span class=\"token punctuation\">.</span><span class=\"token function\">set</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchers'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n      model<span class=\"token operator\">:</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watch'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">perform</span><span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"gatsby-highlight-code-line\">      summary<span class=\"token operator\">:</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchSummary'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">perform</span><span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'summary'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span></span>      allocations<span class=\"token operator\">:</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchAllocations'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">perform</span><span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      evaluations<span class=\"token operator\">:</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchEvaluations'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">perform</span><span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"gatsby-highlight-code-line\">      latestDeployment<span class=\"token operator\">:</span></span><span class=\"gatsby-highlight-code-line\">        model<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'supportsDeployments'</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">&amp;&amp;</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchLatestDeployment'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">perform</span><span class=\"token punctuation\">(</span>model<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span></span><span class=\"gatsby-highlight-code-line\">      list<span class=\"token operator\">:</span> model<span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'hasChildren'</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">&amp;&amp;</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchAll'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">perform</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span></span>    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n  watch<span class=\"token operator\">:</span> <span class=\"token function\">watchRecord</span><span class=\"token punctuation\">(</span><span class=\"token string\">'job'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  watchAll<span class=\"token operator\">:</span> <span class=\"token function\">watchAll</span><span class=\"token punctuation\">(</span><span class=\"token string\">'job'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  watchSummary<span class=\"token operator\">:</span> <span class=\"token function\">watchRecord</span><span class=\"token punctuation\">(</span><span class=\"token string\">'job-summary'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  watchAllocations<span class=\"token operator\">:</span> <span class=\"token function\">watchRelationship</span><span class=\"token punctuation\">(</span><span class=\"token string\">'allocations'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  watchEvaluations<span class=\"token operator\">:</span> <span class=\"token function\">watchRelationship</span><span class=\"token punctuation\">(</span><span class=\"token string\">'evaluations'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  watchLatestDeployment<span class=\"token operator\">:</span> <span class=\"token function\">watchRelationship</span><span class=\"token punctuation\">(</span><span class=\"token string\">'latestDeployment'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n\n  watchers<span class=\"token operator\">:</span> <span class=\"token function\">collect</span><span class=\"token punctuation\">(</span>\n  <span class=\"token string\">'watch'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token string\">'watchAll'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token string\">'watchSummary'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token string\">'watchAllocations'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token string\">'watchEvaluations'</span><span class=\"token punctuation\">,</span>\n  <span class=\"token string\">'watchLatestDeployment'</span>\n  <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></code></pre></div>"},{"markdown":"\nSo that's that. It's all the patterns I needed to make all of the views in the Nomad UI realtime. I\nimplement this on every page, and I'm feeling really good about this. Before getting ready to merge\nthis work, I want to make sure it works, so I start clicking around.\n","html":"<p>So that's that. It's all the patterns I needed to make all of the views in the Nomad UI realtime. I\nimplement this on every page, and I'm feeling really good about this. Before getting ready to merge\nthis work, I want to make sure it works, so I start clicking around.</p>"},{"markdown":"\n<p class=\"note\">\n  This slide is a video. Watch the recording to see the UI change states and reveal the bug.\n</p>\n\nOkay, here's a cluster. Everything looks good. Hmm, MongoDB, that can't be good. Let's see what it\nis up to. Everything is fine, but I'm skeptical. Okay, versions seem fine, deployments seem fine. Is\nthe client doing okay?\n\nWait, why isn't the client page loading? What's going on the network tab? What's up with all these\npending requests? Shouldn't the model hook still return immediately instead of blocking?\n","html":"<p class=\"note\">\n  This slide is a video. Watch the recording to see the UI change states and reveal the bug.\n</p>\n<p>Okay, here's a cluster. Everything looks good. Hmm, MongoDB, that can't be good. Let's see what it\nis up to. Everything is fine, but I'm skeptical. Okay, versions seem fine, deployments seem fine. Is\nthe client doing okay?</p>\n<p>Wait, why isn't the client page loading? What's going on the network tab? What's up with all these\npending requests? Shouldn't the model hook still return immediately instead of blocking?</p>"},{"markdown":"\n_Sigh..._\n\nStep 5: Fight the bugs.\n","html":"<p><em>Sigh...</em></p>\n<p>Step 5: Fight the bugs.</p>"},{"markdown":"\nSo requets are stuck in a pending state. Why is that?\n\nTurns out this has nothing to do with Ember. It also has nothing to do with the code we just wrote.\nThat code is flawless, believe it or not. This has everything to do with browsers having a max\nnumber of concurrent HTTP connections per domain.\n","html":"<p>So requets are stuck in a pending state. Why is that?</p>\n<p>Turns out this has nothing to do with Ember. It also has nothing to do with the code we just wrote.\nThat code is flawless, believe it or not. This has everything to do with browsers having a max\nnumber of concurrent HTTP connections per domain.</p>"},{"markdown":"\nNo matter what framework you choose, eventually browsers will be your problem.\n\nEarlier on in my career, I would have thrown my hands in the air and bemoaned technology. I would\nhave cried about how this is why we can't have nice things.\n","html":"<p>No matter what framework you choose, eventually browsers will be your problem.</p>\n<p>Earlier on in my career, I would have thrown my hands in the air and bemoaned technology. I would\nhave cried about how this is why we can't have nice things.</p>"},{"markdown":"\nIn fact, eight years ago, I did nearly exactly this. I said, \"Software Engineering; the only\nindustry where every problem you could ever encounter is the result of human error.\"\n\nThere is still some truth to this, by nowadays my outlook is a little more positive.\n","html":"<p>In fact, eight years ago, I did nearly exactly this. I said, \"Software Engineering; the only\nindustry where every problem you could ever encounter is the result of human error.\"</p>\n<p>There is still some truth to this, by nowadays my outlook is a little more positive.</p>"},{"markdown":"\nNow I would say something like, \"Most people are making the best decisions they can in the\nsituations they are in.\"\n","html":"<p>Now I would say something like, \"Most people are making the best decisions they can in the\nsituations they are in.\"</p>"},{"markdown":"\nTurns out this is also true. I dug up the original HTTP/1.1 RFC, numbered 2616 to read the original\nreasoning.\n\n> A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. &ellip;\n  These guidelines are intended to improve HTTP response times and avoid congestion.\n\n_&mdash; [https://tools.ietf.org/html/rfc2616#page-46](https://tools.ietf.org/html/rfc2616#page-46)_\n\nBrowsers today are actually breaking the rules and being _generous_ by allowing six or more\nconcurrent connections per server or proxy. Brwoser vendors recognize that the Internet is different\nnow, and networking gear (especially on the server side) is much better.\n\nNonetheless, we're in this situation, so what do we do about it?\n","html":"<p>Turns out this is also true. I dug up the original HTTP/1.1 RFC, numbered 2616 to read the original\nreasoning.</p>\n<blockquote>\n<p>A single-user client SHOULD NOT maintain more than 2 connections with any server or proxy. &#x26;ellip;\nThese guidelines are intended to improve HTTP response times and avoid congestion.</p>\n</blockquote>\n<p><em>— <a href=\"https://tools.ietf.org/html/rfc2616#page-46\">https://tools.ietf.org/html/rfc2616#page-46</a></em></p>\n<p>Browsers today are actually breaking the rules and being <em>generous</em> by allowing six or more\nconcurrent connections per server or proxy. Brwoser vendors recognize that the Internet is different\nnow, and networking gear (especially on the server side) is much better.</p>\n<p>Nonetheless, we're in this situation, so what do we do about it?</p>"},{"markdown":"\nOne option is using HTTP/2, which is in fact better than HTTP/1.1 in every single way. It will cure\nall your problems and it is literally magic. For our purposes, HTTP/2 is an option because it allows\nfor unlimited numbers of concurrent connection per host by means of actually having a single\nmultiplexed long-lived connection per host. Sounds perfect.\n","html":"<p>One option is using HTTP/2, which is in fact better than HTTP/1.1 in every single way. It will cure\nall your problems and it is literally magic. For our purposes, HTTP/2 is an option because it allows\nfor unlimited numbers of concurrent connection per host by means of actually having a single\nmultiplexed long-lived connection per host. Sounds perfect.</p>"},{"markdown":"\nUnfortuantely, we can't use HTTP/2. Despite the HTTP/2 spec not mandating a secure connection,\nbrowsers, will still only use HTTP/2 over TLS.\n\nAs it turns out, Nomad supports TLS because security is important, but it is still optional. We\ncan't say TLS is optional but required to use the web UI. That's unacceptable.\n","html":"<p>Unfortuantely, we can't use HTTP/2. Despite the HTTP/2 spec not mandating a secure connection,\nbrowsers, will still only use HTTP/2 over TLS.</p>\n<p>As it turns out, Nomad supports TLS because security is important, but it is still optional. We\ncan't say TLS is optional but required to use the web UI. That's unacceptable.</p>"},{"markdown":"\nAnother possible solution is domain sharding.\n\nThis is when you use multiple sub-domains to bypass the max concurrent connections per domain limit.\n\nIt doesn't matter if all you're doing is creating a reverse proxy to the same exact server, which is\namusing, but at least browser try to prevent you from shooting yourself in the foot.\n","html":"<p>Another possible solution is domain sharding.</p>\n<p>This is when you use multiple sub-domains to bypass the max concurrent connections per domain limit.</p>\n<p>It doesn't matter if all you're doing is creating a reverse proxy to the same exact server, which is\namusing, but at least browser try to prevent you from shooting yourself in the foot.</p>"},{"markdown":"\nBut Nomad isn't <abbr title=\"Software as a Service\">SaaS</abbr>! Customers install Nomad on their\nown machines and we take operational simplicity very seriously.\n","html":"<p>But Nomad isn't <abbr title=\"Software as a Service\">SaaS</abbr>! Customers install Nomad on their\nown machines and we take operational simplicity very seriously.</p>"},{"markdown":"\nIf our getting started guide read \"Using the Web UI. The first step is DNS\" we would have to\nreconsider some things.\n","html":"<p>If our getting started guide read \"Using the Web UI. The first step is DNS\" we would have to\nreconsider some things.</p>"},{"markdown":"\nSo that's out. What about option 3: request cancellation?\n\nThere's a good chance this is a corner of HTTP you haven't had to deal with, but HTTP requests can\nbe cancelled. `XMLHttpRequest` objects have an abort method for triggering cancellation. Ember Data\nuses <abbr title=\"XMLHttpRequest\">XHRs</abbr> out of the box, so that's a good sign. But Ember Data\ndoes not provide abort hooks.\n","html":"<p>So that's out. What about option 3: request cancellation?</p>\n<p>There's a good chance this is a corner of HTTP you haven't had to deal with, but HTTP requests can\nbe cancelled. <code class=\"language-text\">XMLHttpRequest</code> objects have an abort method for triggering cancellation. Ember Data\nuses <abbr title=\"XMLHttpRequest\">XHRs</abbr> out of the box, so that's a good sign. But Ember Data\ndoes not provide abort hooks.</p>"},{"markdown":"\nWell I have no other options, so let's do request cancellation with Ember Data anyway. Conceptually,\nthis can be done in four steps.\n\n1. Capture XHRs in some sort of cache to abort later\n2. Remove XHRs from the cache when they resolve\n3. Write cancel methods that mirror the watchable method signatures\n4. Call the cancel methods in our polling code\n\nLet's see where we can stick the code to do these things.\n","html":"<p>Well I have no other options, so let's do request cancellation with Ember Data anyway. Conceptually,\nthis can be done in four steps.</p>\n<ol>\n<li>Capture XHRs in some sort of cache to abort later</li>\n<li>Remove XHRs from the cache when they resolve</li>\n<li>Write cancel methods that mirror the watchable method signatures</li>\n<li>Call the cancel methods in our polling code</li>\n</ol>\n<p>Let's see where we can stick the code to do these things.</p>"},{"markdown":"\nFirst: capture all the XHRs. We can do this by making a registry of requests in a similar manner to\nthe WatchList service.\n\n```js\n// adapters/watchable.js\nxhrs: computed(function() {\n  return {\n    list: {},\n    track(key, xhr) {\n      if (this.list[key]) {\n        this.list[key].push(xhr);\n      } else {\n        this.list[key] = [xhr];\n      }\n    },\n    cancel(key) {\n      while (this.list[key] && this.list[key].length) {\n        this.remove(key, this.list[key][0]);\n      }\n    },\n    remove(key, xhr) {\n      if (this.list[key]) {\n        xhr.abort();\n        this.list[key].removeObject(xhr);\n      }\n    },\n  };\n}),\n```\n\nThis is pretty straightfoward. XHRs are tracked by key (which ends up being a combination of the\nURL and HTTP method). Requests can be canceled, and when this happens, they are removed. When XHRs\nare removed, `xhr.abort()` is called, since that's the whole point of doing this, and then removed\nfrom the registry.\n","html":"<p>First: capture all the XHRs. We can do this by making a registry of requests in a similar manner to\nthe WatchList service.</p>\n<div class=\"gatsby-highlight\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/watchable.js</span>\nxhrs<span class=\"token operator\">:</span> <span class=\"token function\">computed</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token punctuation\">{</span>\n    list<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function\">track</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">key<span class=\"token punctuation\">,</span> xhr</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>list<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>list<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">push</span><span class=\"token punctuation\">(</span>xhr<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>list<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> <span class=\"token punctuation\">[</span>xhr<span class=\"token punctuation\">]</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function\">cancel</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">key</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>list<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span> <span class=\"token operator\">&amp;&amp;</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>list<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span>length<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">remove</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">,</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>list<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span><span class=\"token punctuation\">[</span><span class=\"token number\">0</span><span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n    <span class=\"token function\">remove</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">key<span class=\"token punctuation\">,</span> xhr</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>list<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        xhr<span class=\"token punctuation\">.</span><span class=\"token function\">abort</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span>list<span class=\"token punctuation\">[</span>key<span class=\"token punctuation\">]</span><span class=\"token punctuation\">.</span><span class=\"token function\">removeObject</span><span class=\"token punctuation\">(</span>xhr<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span></code></pre></div>\n<p>This is pretty straightfoward. XHRs are tracked by key (which ends up being a combination of the\nURL and HTTP method). Requests can be canceled, and when this happens, they are removed. When XHRs\nare removed, <code class=\"language-text\">xhr.abort()</code> is called, since that's the whole point of doing this, and then removed\nfrom the registry.</p>"},{"markdown":"\nOnce we have this registry in place, we need to find a place to actually capture the XHR. We can do\nthis in the `Adapter#ajaxOptions` method. The `ajaxOptions` object grants an opportunity to hook\ninto the XHR lifecycle.\n\n```js{12-14}\n// adapters/watchable.js\najaxOptions() {\n  const ajaxOptions = this._super(...arguments);\n  const key = this.xhrKey(...arguments);\n\n  const previousBeforeSend = ajaxOptions.beforeSend;\n  ajaxOptions.beforeSend = function(jqXHR) {\n    if (previousBeforeSend) {\n      previousBeforeSend(...arguments);\n    }\n    this.get('xhrs').track(key, jqXHR);\n    jqXHR.always(() => {\n      this.get('xhrs').remove(key, jqXHR);\n    });\n  };\n\n  return ajaxOptions;\n},\n```\n\nBefore we send the HTTP request, we track it, and if the request closes from natural causes, we\nstill want to remove it from the registry. `abort` is idempotent, so we can reuse that `remove`\nmethod.\n","html":"<p>Once we have this registry in place, we need to find a place to actually capture the XHR. We can do\nthis in the <code class=\"language-text\">Adapter#ajaxOptions</code> method. The <code class=\"language-text\">ajaxOptions</code> object grants an opportunity to hook\ninto the XHR lifecycle.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/watchable.js</span>\n<span class=\"token function\">ajaxOptions</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> ajaxOptions <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">_super</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> key <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">xhrKey</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">const</span> previousBeforeSend <span class=\"token operator\">=</span> ajaxOptions<span class=\"token punctuation\">.</span>beforeSend<span class=\"token punctuation\">;</span>\n  ajaxOptions<span class=\"token punctuation\">.</span><span class=\"token function-variable function\">beforeSend</span> <span class=\"token operator\">=</span> <span class=\"token keyword\">function</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">jqXHR</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>previousBeforeSend<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token function\">previousBeforeSend</span><span class=\"token punctuation\">(</span><span class=\"token operator\">...</span>arguments<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'xhrs'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">track</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">,</span> jqXHR<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"gatsby-highlight-code-line\">    jqXHR<span class=\"token punctuation\">.</span><span class=\"token function\">always</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">      <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'xhrs'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">remove</span><span class=\"token punctuation\">(</span>key<span class=\"token punctuation\">,</span> jqXHR<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span>  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">return</span> ajaxOptions<span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></code></pre></div>\n<p>Before we send the HTTP request, we track it, and if the request closes from natural causes, we\nstill want to remove it from the registry. <code class=\"language-text\">abort</code> is idempotent, so we can reuse that <code class=\"language-text\">remove</code>\nmethod.</p>"},{"markdown":"\nAlso in the adapter are these cancel methods that match the method signature of the `find` methods.\n\nThis allows us to control when we cancel requests from outside the adapter using parameters we know\nmust already be known outside of the adapter.\n\n```js{7,19,33}\n// adapters/watchable.js\ncancelFindRecord(modelName, id) {\n  if (!modelName || id == null) {\n    return;\n  }\n  const url = this.urlForFindRecord(id, modelName);\n  this.get('xhrs').cancel(`GET ${url}`);\n},\n\ncancelFindAll(modelName) {\n  if (!modelName) {\n    return;\n  }\n  let url = this.urlForFindAll(modelName);\n  const params = queryString.stringify(this.buildQuery());\n  if (params) {\n    url = `${url}?${params}`;\n  }\n  this.get('xhrs').cancel(`GET ${url}`);\n},\n\ncancelReloadRelationship(model, relationshipName) {\n  if (!model || !relationshipName) {\n    return;\n  }\n  const relationship = model.relationshipFor(relationshipName);\n  if (relationship.kind !== 'belongsTo' && relationship.kind !== 'hasMany') {\n    throw new Error(\n      `${relationship.key} must be a belongsTo or hasMany, instead it was ${relationship.kind}`\n    );\n  } else {\n    const url = model[relationship.kind](relationship.key).link();\n    this.get('xhrs').cancel(`GET ${url}`);\n  }\n},\n```\n","html":"<p>Also in the adapter are these cancel methods that match the method signature of the <code class=\"language-text\">find</code> methods.</p>\n<p>This allows us to control when we cancel requests from outside the adapter using parameters we know\nmust already be known outside of the adapter.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/watchable.js</span>\n<span class=\"token function\">cancelFindRecord</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">modelName<span class=\"token punctuation\">,</span> id</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>modelName <span class=\"token operator\">||</span> id <span class=\"token operator\">==</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">urlForFindRecord</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">,</span> modelName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"gatsby-highlight-code-line\">  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'xhrs'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">cancel</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">GET </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>url<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n<span class=\"token function\">cancelFindAll</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">modelName</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>modelName<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">let</span> url <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">urlForFindAll</span><span class=\"token punctuation\">(</span>modelName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">const</span> params <span class=\"token operator\">=</span> queryString<span class=\"token punctuation\">.</span><span class=\"token function\">stringify</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">buildQuery</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>params<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    url <span class=\"token operator\">=</span> <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>url<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\">?</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>params<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"gatsby-highlight-code-line\">  <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'xhrs'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">cancel</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">GET </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>url<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n\n<span class=\"token function\">cancelReloadRelationship</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">model<span class=\"token punctuation\">,</span> relationshipName</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>model <span class=\"token operator\">||</span> <span class=\"token operator\">!</span>relationshipName<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">return</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">const</span> relationship <span class=\"token operator\">=</span> model<span class=\"token punctuation\">.</span><span class=\"token function\">relationshipFor</span><span class=\"token punctuation\">(</span>relationshipName<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>relationship<span class=\"token punctuation\">.</span>kind <span class=\"token operator\">!==</span> <span class=\"token string\">'belongsTo'</span> <span class=\"token operator\">&amp;&amp;</span> relationship<span class=\"token punctuation\">.</span>kind <span class=\"token operator\">!==</span> <span class=\"token string\">'hasMany'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">throw</span> <span class=\"token keyword\">new</span> <span class=\"token class-name\">Error</span><span class=\"token punctuation\">(</span>\n      <span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>relationship<span class=\"token punctuation\">.</span>key<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token string\"> must be a belongsTo or hasMany, instead it was </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>relationship<span class=\"token punctuation\">.</span>kind<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span> <span class=\"token keyword\">else</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">const</span> url <span class=\"token operator\">=</span> model<span class=\"token punctuation\">[</span>relationship<span class=\"token punctuation\">.</span>kind<span class=\"token punctuation\">]</span><span class=\"token punctuation\">(</span>relationship<span class=\"token punctuation\">.</span>key<span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">link</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'xhrs'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">cancel</span><span class=\"token punctuation\">(</span><span class=\"token template-string\"><span class=\"token template-punctuation string\">`</span><span class=\"token string\">GET </span><span class=\"token interpolation\"><span class=\"token interpolation-punctuation punctuation\">${</span>url<span class=\"token interpolation-punctuation punctuation\">}</span></span><span class=\"token template-punctuation string\">`</span></span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span>  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></code></pre></div>"},{"markdown":"\nNow to call these cancel methods at the appropriate times. This would be the polling code, since it\nis the code that makes the requests in the first place.\n\nEmber Concurrency has cancelable tasks, which helps us a lot here. By adding a `finally` clause to\nthe various `watch` task macros, we can add instructions that execute whenever a task is canceled.\n\nTasks are canceled whenever the loop is no longer relevant, (e.g., on page transitions), so this is\nperfect.\n\n```js{17-19}\n// utils/properties/watch.js\nexport function watchRecord(modelName) {\n  return task(function*(id, throttle = 2000) {\n    if (typeof id === 'object') {\n      id = get(id, 'id');\n    }\n    while (!Ember.testing) {\n      try {\n        yield this.get('store').findRecord(modelName, id, {\n          reload: true,\n          adapterOptions: { watch: true },\n        });\n        yield timeout(throttle);\n      } catch (e) {\n        yield e;\n      } finally {\n        this.get('store')\n          .adapterFor(modelName)\n          .cancelFindRecord(modelName, id);\n      }\n    }\n  }).drop();\n}\n```\n","html":"<p>Now to call these cancel methods at the appropriate times. This would be the polling code, since it\nis the code that makes the requests in the first place.</p>\n<p>Ember Concurrency has cancelable tasks, which helps us a lot here. By adding a <code class=\"language-text\">finally</code> clause to\nthe various <code class=\"language-text\">watch</code> task macros, we can add instructions that execute whenever a task is canceled.</p>\n<p>Tasks are canceled whenever the loop is no longer relevant, (e.g., on page transitions), so this is\nperfect.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// utils/properties/watch.js</span>\n<span class=\"token keyword\">export</span> <span class=\"token keyword\">function</span> <span class=\"token function\">watchRecord</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">modelName</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">return</span> <span class=\"token function\">task</span><span class=\"token punctuation\">(</span><span class=\"token keyword\">function</span><span class=\"token operator\">*</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">,</span> throttle <span class=\"token operator\">=</span> <span class=\"token number\">2000</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token keyword\">typeof</span> id <span class=\"token operator\">===</span> <span class=\"token string\">'object'</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      id <span class=\"token operator\">=</span> <span class=\"token function\">get</span><span class=\"token punctuation\">(</span>id<span class=\"token punctuation\">,</span> <span class=\"token string\">'id'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n    <span class=\"token punctuation\">}</span>\n    <span class=\"token keyword\">while</span> <span class=\"token punctuation\">(</span><span class=\"token operator\">!</span>Ember<span class=\"token punctuation\">.</span>testing<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">yield</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'store'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">findRecord</span><span class=\"token punctuation\">(</span>modelName<span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n          reload<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">,</span>\n          adapterOptions<span class=\"token operator\">:</span> <span class=\"token punctuation\">{</span> watch<span class=\"token operator\">:</span> <span class=\"token boolean\">true</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n        <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n        <span class=\"token keyword\">yield</span> <span class=\"token function\">timeout</span><span class=\"token punctuation\">(</span>throttle<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        <span class=\"token keyword\">yield</span> e<span class=\"token punctuation\">;</span>\n      <span class=\"token punctuation\">}</span> <span class=\"token keyword\">finally</span> <span class=\"token punctuation\">{</span>\n<span class=\"gatsby-highlight-code-line\">        <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'store'</span><span class=\"token punctuation\">)</span></span><span class=\"gatsby-highlight-code-line\">          <span class=\"token punctuation\">.</span><span class=\"token function\">adapterFor</span><span class=\"token punctuation\">(</span>modelName<span class=\"token punctuation\">)</span></span><span class=\"gatsby-highlight-code-line\">          <span class=\"token punctuation\">.</span><span class=\"token function\">cancelFindRecord</span><span class=\"token punctuation\">(</span>modelName<span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span>      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">drop</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n<span class=\"token punctuation\">}</span></code></pre></div>"},{"markdown":"\nThis works great for canceling requests, but there an unforeseen consequence. When an XHR is aborted,\nan `AbortError` is thrown. This causes a promise to go into the `catch` handler which cascades into\na whole unwanted mess. We don't want an error thrown, and we don't want to be redirected to an error\npage. The abort is expected, so we have to handle this somewhow.\n\nThe place to do this is the `find` methods.\n","html":"<p>This works great for canceling requests, but there an unforeseen consequence. When an XHR is aborted,\nan <code class=\"language-text\">AbortError</code> is thrown. This causes a promise to go into the <code class=\"language-text\">catch</code> handler which cascades into\na whole unwanted mess. We don't want an error thrown, and we don't want to be redirected to an error\npage. The abort is expected, so we have to handle this somewhow.</p>\n<p>The place to do this is the <code class=\"language-text\">find</code> methods.</p>"},{"markdown":"\nThis isn't so bad to fix, but it's a bit of a bummer that we need to do it. It's just a consequence\nof promise-based control-flow.\n\nWe have to write our own `catch` handler to filter out `AbortErrors`. If the error isn't an\n`AbortError`, rethrowing puts the error handling back on track.\n\n```js{2,15-20}\n// adapters/watchable.js\nimport { AbortError } from 'ember-data/adapters/errors';\n// ...\nfindRecord(store, type, id, snapshot) {\n  const fullUrl = this.buildURL(type.modelName, id, snapshot, 'findRecord');\n  let [url, params] = fullUrl.split('?');\n  params = assign(queryString.parse(params) || {}, this.buildQuery());\n\n  if (get(snapshot || {}, 'adapterOptions.watch')) {\n    params.index = this.get('watchList').getIndexFor(url);\n  }\n\n  return this.ajax(url, 'GET', {\n    data: params,\n  }).catch(error => {\n    if (error instanceof AbortError) {\n      return;\n    }\n    throw error;\n  });\n},\n```\n","html":"<p>This isn't so bad to fix, but it's a bit of a bummer that we need to do it. It's just a consequence\nof promise-based control-flow.</p>\n<p>We have to write our own <code class=\"language-text\">catch</code> handler to filter out <code class=\"language-text\">AbortErrors</code>. If the error isn't an\n<code class=\"language-text\">AbortError</code>, rethrowing puts the error handling back on track.</p>\n<div class=\"gatsby-highlight has-highlighted-lines\" data-language=\"js\"><pre class=\"language-js\"><code class=\"language-js\"><span class=\"token comment\">// adapters/watchable.js</span>\n<span class=\"gatsby-highlight-code-line\"><span class=\"token keyword\">import</span> <span class=\"token punctuation\">{</span> AbortError <span class=\"token punctuation\">}</span> <span class=\"token keyword\">from</span> <span class=\"token string\">'ember-data/adapters/errors'</span><span class=\"token punctuation\">;</span></span><span class=\"token comment\">// ...</span>\n<span class=\"token function\">findRecord</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">store<span class=\"token punctuation\">,</span> type<span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">,</span> snapshot</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">const</span> fullUrl <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">buildURL</span><span class=\"token punctuation\">(</span>type<span class=\"token punctuation\">.</span>modelName<span class=\"token punctuation\">,</span> id<span class=\"token punctuation\">,</span> snapshot<span class=\"token punctuation\">,</span> <span class=\"token string\">'findRecord'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token keyword\">let</span> <span class=\"token punctuation\">[</span>url<span class=\"token punctuation\">,</span> params<span class=\"token punctuation\">]</span> <span class=\"token operator\">=</span> fullUrl<span class=\"token punctuation\">.</span><span class=\"token function\">split</span><span class=\"token punctuation\">(</span><span class=\"token string\">'?'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  params <span class=\"token operator\">=</span> <span class=\"token function\">assign</span><span class=\"token punctuation\">(</span>queryString<span class=\"token punctuation\">.</span><span class=\"token function\">parse</span><span class=\"token punctuation\">(</span>params<span class=\"token punctuation\">)</span> <span class=\"token operator\">||</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">buildQuery</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n\n  <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span>snapshot <span class=\"token operator\">||</span> <span class=\"token punctuation\">{</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> <span class=\"token string\">'adapterOptions.watch'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    params<span class=\"token punctuation\">.</span>index <span class=\"token operator\">=</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">get</span><span class=\"token punctuation\">(</span><span class=\"token string\">'watchList'</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getIndexFor</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span>\n  <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">return</span> <span class=\"token keyword\">this</span><span class=\"token punctuation\">.</span><span class=\"token function\">ajax</span><span class=\"token punctuation\">(</span>url<span class=\"token punctuation\">,</span> <span class=\"token string\">'GET'</span><span class=\"token punctuation\">,</span> <span class=\"token punctuation\">{</span>\n    data<span class=\"token operator\">:</span> params<span class=\"token punctuation\">,</span>\n<span class=\"gatsby-highlight-code-line\">  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">catch</span><span class=\"token punctuation\">(</span><span class=\"token parameter\">error</span> <span class=\"token operator\">=></span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>error <span class=\"token keyword\">instanceof</span> <span class=\"token class-name\">AbortError</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span></span><span class=\"gatsby-highlight-code-line\">      <span class=\"token keyword\">return</span><span class=\"token punctuation\">;</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token punctuation\">}</span></span><span class=\"gatsby-highlight-code-line\">    <span class=\"token keyword\">throw</span> error<span class=\"token punctuation\">;</span></span><span class=\"gatsby-highlight-code-line\">  <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">;</span></span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span></code></pre></div>"},{"markdown":"\nOkay! That's it! Let's celebrate!\n\nSome people celebrate by going out drinking or dancing. I like to sit alone and practice quiet\nreflection. I'm very fun at parties.\n","html":"<p>Okay! That's it! Let's celebrate!</p>\n<p>Some people celebrate by going out drinking or dancing. I like to sit alone and practice quiet\nreflection. I'm very fun at parties.</p>"},{"markdown":"\nLet's look back on what we built.\n\n1. We made a new service to manage indices\n2. Added new methods for reloading data and canceling requests\n3. Wrote new methods for removing data from the store\n4. Came up with polling code to continuously long-poll and stop on demand\n5. Introduced reusable patterns for adding realtime behaviors to a page\n\nThis is a lot of stuff! On one hand it makes sense that we wrote a lot of code, it's a big feature,\nbut we're using a framework. Shouldn't it be helping us?\n","html":"<p>Let's look back on what we built.</p>\n<ol>\n<li>We made a new service to manage indices</li>\n<li>Added new methods for reloading data and canceling requests</li>\n<li>Wrote new methods for removing data from the store</li>\n<li>Came up with polling code to continuously long-poll and stop on demand</li>\n<li>Introduced reusable patterns for adding realtime behaviors to a page</li>\n</ol>\n<p>This is a lot of stuff! On one hand it makes sense that we wrote a lot of code, it's a big feature,\nbut we're using a framework. Shouldn't it be helping us?</p>"},{"markdown":"\nIt should and it does! Consider all the code we didn't have to write.\n\n1. An architecture for maintaining an interface with a backend\n2. State management for records\n3. A away to make any data consistent from within the app\n4. A well-rounded modern approach to asynchronous control-flow\n5. An object and inheritance model that makes it easy to build abstractions\n\nNot to mention that this little box I labeled \"Views\" is actually the entirely of Ember core.\n","html":"<p>It should and it does! Consider all the code we didn't have to write.</p>\n<ol>\n<li>An architecture for maintaining an interface with a backend</li>\n<li>State management for records</li>\n<li>A away to make any data consistent from within the app</li>\n<li>A well-rounded modern approach to asynchronous control-flow</li>\n<li>An object and inheritance model that makes it easy to build abstractions</li>\n</ol>\n<p>Not to mention that this little box I labeled \"Views\" is actually the entirely of Ember core.</p>"},{"markdown":"\nThis includes\n\n1. Data-down, Actions-up\n2. Two-way data-binding\n3. Speedy re-renders\n4. Key-value observation\n\nAnd more.\n","html":"<p>This includes</p>\n<ol>\n<li>Data-down, Actions-up</li>\n<li>Two-way data-binding</li>\n<li>Speedy re-renders</li>\n<li>Key-value observation</li>\n</ol>\n<p>And more.</p>"},{"markdown":"\nIf we were to look at the distribution of code that gets run, the overwhelming majority of it was\nwritten by someone other than me. The Nomad UI is built on the shoulders of addons, Ember Data, and\nEmber.js.\n\n_(this is a totally unscientific pie chart, just trying to drive a point home)_\n","html":"<p>If we were to look at the distribution of code that gets run, the overwhelming majority of it was\nwritten by someone other than me. The Nomad UI is built on the shoulders of addons, Ember Data, and\nEmber.js.</p>\n<p><em>(this is a totally unscientific pie chart, just trying to drive a point home)</em></p>"},{"markdown":"\nSo what's the takeaway?\n\n- Products are all trying to be unique and differentiated\n- By definition, frameworks only solve common problems\n- We can use addons and build on top of Ember to create great new things\n- We can think like framework authors to build software that might withstand the test of time and\n  keep our coworkers happy\n","html":"<p>So what's the takeaway?</p>\n<ul>\n<li>Products are all trying to be unique and differentiated</li>\n<li>By definition, frameworks only solve common problems</li>\n<li>We can use addons and build on top of Ember to create great new things</li>\n<li>We can think like framework authors to build software that might withstand the test of time and\nkeep our coworkers happy</li>\n</ul>"},{"markdown":"\nAnd there's one more thing...\n","html":"<p>And there's one more thing...</p>"},{"markdown":"\nThis is all open source! Nomad and the Nomad UI are open source projects. [You can read through the\npull request that made the UI realtime](https://github.com/hashicorp/nomad/pull/3936).\n","html":"<p>This is all open source! Nomad and the Nomad UI are open source projects. <a href=\"https://github.com/hashicorp/nomad/pull/3936\">You can read through the\npull request that made the UI realtime</a>.</p>"},{"markdown":"\nNot only is Nomad open source and using Ember, but all our products are.\n\n- [Consul, and the Consul UI](https://github.com/hashicorp/consul)\n- [Vault, and the Vault UI](https://github.com/hashicorp/vault)\n- [Nomad, and the Nomad UI](https://github.com/hashicorp/nomad)\n- [Terraform Enterprise](https://www.hashicorp.com/products/terraform) (not open source, but it\n  still uses Ember!)\n","html":"<p>Not only is Nomad open source and using Ember, but all our products are.</p>\n<ul>\n<li><a href=\"https://github.com/hashicorp/consul\">Consul, and the Consul UI</a></li>\n<li><a href=\"https://github.com/hashicorp/vault\">Vault, and the Vault UI</a></li>\n<li><a href=\"https://github.com/hashicorp/nomad\">Nomad, and the Nomad UI</a></li>\n<li><a href=\"https://www.hashicorp.com/products/terraform\">Terraform Enterprise</a> (not open source, but it\nstill uses Ember!)</li>\n</ul>"},{"markdown":"\nThank you!\n\nMichael Lange, [@DingoEatingFuzz](https://twitter.com/DingoEatingFuzz)\n","html":"<p>Thank you!</p>\n<p>Michael Lange, <a href=\"https://twitter.com/DingoEatingFuzz\">@DingoEatingFuzz</a></p>"}]}}]},"allFile":{"edges":[{"node":{"relativePath":"going-realtime-with-ember/19.png","name":"19","childImageSharp":{"original":{"src":"/static/19-9f497ba8534f5ca448139afb0f7c7a17.png"},"fixed":{"width":450,"height":253,"src":"/static/9f497ba8534f5ca448139afb0f7c7a17/62b1f/19.png","srcSet":"/static/9f497ba8534f5ca448139afb0f7c7a17/62b1f/19.png 1x,\n/static/9f497ba8534f5ca448139afb0f7c7a17/e2e7f/19.png 1.5x,\n/static/9f497ba8534f5ca448139afb0f7c7a17/db955/19.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/37.png","name":"37","childImageSharp":{"original":{"src":"/static/37-8e7afc9707700a770351229d9fba681f.png"},"fixed":{"width":450,"height":253,"src":"/static/8e7afc9707700a770351229d9fba681f/62b1f/37.png","srcSet":"/static/8e7afc9707700a770351229d9fba681f/62b1f/37.png 1x,\n/static/8e7afc9707700a770351229d9fba681f/e2e7f/37.png 1.5x,\n/static/8e7afc9707700a770351229d9fba681f/db955/37.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/38.png","name":"38","childImageSharp":{"original":{"src":"/static/38-6653d69b46bc65965bd5777f284450f1.png"},"fixed":{"width":450,"height":253,"src":"/static/6653d69b46bc65965bd5777f284450f1/62b1f/38.png","srcSet":"/static/6653d69b46bc65965bd5777f284450f1/62b1f/38.png 1x,\n/static/6653d69b46bc65965bd5777f284450f1/e2e7f/38.png 1.5x,\n/static/6653d69b46bc65965bd5777f284450f1/db955/38.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/42.png","name":"42","childImageSharp":{"original":{"src":"/static/42-f4e7d1722f9f78d2817d6f56705a44ae.png"},"fixed":{"width":450,"height":253,"src":"/static/f4e7d1722f9f78d2817d6f56705a44ae/62b1f/42.png","srcSet":"/static/f4e7d1722f9f78d2817d6f56705a44ae/62b1f/42.png 1x,\n/static/f4e7d1722f9f78d2817d6f56705a44ae/e2e7f/42.png 1.5x,\n/static/f4e7d1722f9f78d2817d6f56705a44ae/db955/42.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/13.png","name":"13","childImageSharp":{"original":{"src":"/static/13-17a431dd7be08ab773900f69c167eb64.png"},"fixed":{"width":450,"height":253,"src":"/static/17a431dd7be08ab773900f69c167eb64/62b1f/13.png","srcSet":"/static/17a431dd7be08ab773900f69c167eb64/62b1f/13.png 1x,\n/static/17a431dd7be08ab773900f69c167eb64/e2e7f/13.png 1.5x,\n/static/17a431dd7be08ab773900f69c167eb64/db955/13.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/20.png","name":"20","childImageSharp":{"original":{"src":"/static/20-73ea1d384b5e0271c62bee01132d2cc5.png"},"fixed":{"width":450,"height":253,"src":"/static/73ea1d384b5e0271c62bee01132d2cc5/62b1f/20.png","srcSet":"/static/73ea1d384b5e0271c62bee01132d2cc5/62b1f/20.png 1x,\n/static/73ea1d384b5e0271c62bee01132d2cc5/e2e7f/20.png 1.5x,\n/static/73ea1d384b5e0271c62bee01132d2cc5/db955/20.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/21.png","name":"21","childImageSharp":{"original":{"src":"/static/21-65d6e6f41f6c27ab61580e3141616a6f.png"},"fixed":{"width":450,"height":253,"src":"/static/65d6e6f41f6c27ab61580e3141616a6f/62b1f/21.png","srcSet":"/static/65d6e6f41f6c27ab61580e3141616a6f/62b1f/21.png 1x,\n/static/65d6e6f41f6c27ab61580e3141616a6f/e2e7f/21.png 1.5x,\n/static/65d6e6f41f6c27ab61580e3141616a6f/db955/21.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/31.png","name":"31","childImageSharp":{"original":{"src":"/static/31-8e183b4aaae6503b11eb3825f4a23565.png"},"fixed":{"width":450,"height":253,"src":"/static/8e183b4aaae6503b11eb3825f4a23565/62b1f/31.png","srcSet":"/static/8e183b4aaae6503b11eb3825f4a23565/62b1f/31.png 1x,\n/static/8e183b4aaae6503b11eb3825f4a23565/e2e7f/31.png 1.5x,\n/static/8e183b4aaae6503b11eb3825f4a23565/db955/31.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/32.png","name":"32","childImageSharp":{"original":{"src":"/static/32-21084d66f683fd6f06125f1e73f86d60.png"},"fixed":{"width":450,"height":253,"src":"/static/21084d66f683fd6f06125f1e73f86d60/62b1f/32.png","srcSet":"/static/21084d66f683fd6f06125f1e73f86d60/62b1f/32.png 1x,\n/static/21084d66f683fd6f06125f1e73f86d60/e2e7f/32.png 1.5x,\n/static/21084d66f683fd6f06125f1e73f86d60/db955/32.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/4.png","name":"4","childImageSharp":{"original":{"src":"/static/4-c959756dd90c0e0a7dcf77ca4c7e4685.png"},"fixed":{"width":450,"height":253,"src":"/static/c959756dd90c0e0a7dcf77ca4c7e4685/62b1f/4.png","srcSet":"/static/c959756dd90c0e0a7dcf77ca4c7e4685/62b1f/4.png 1x,\n/static/c959756dd90c0e0a7dcf77ca4c7e4685/e2e7f/4.png 1.5x,\n/static/c959756dd90c0e0a7dcf77ca4c7e4685/db955/4.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/43.png","name":"43","childImageSharp":{"original":{"src":"/static/43-202d544661d415f22f24016d060deb25.png"},"fixed":{"width":450,"height":253,"src":"/static/202d544661d415f22f24016d060deb25/62b1f/43.png","srcSet":"/static/202d544661d415f22f24016d060deb25/62b1f/43.png 1x,\n/static/202d544661d415f22f24016d060deb25/e2e7f/43.png 1.5x,\n/static/202d544661d415f22f24016d060deb25/db955/43.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/44.png","name":"44","childImageSharp":{"original":{"src":"/static/44-8e183b4aaae6503b11eb3825f4a23565.png"},"fixed":{"width":450,"height":253,"src":"/static/8e183b4aaae6503b11eb3825f4a23565/62b1f/44.png","srcSet":"/static/8e183b4aaae6503b11eb3825f4a23565/62b1f/44.png 1x,\n/static/8e183b4aaae6503b11eb3825f4a23565/e2e7f/44.png 1.5x,\n/static/8e183b4aaae6503b11eb3825f4a23565/db955/44.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/45.png","name":"45","childImageSharp":{"original":{"src":"/static/45-efc3d438604170eab0f78d54371c1b32.png"},"fixed":{"width":450,"height":253,"src":"/static/efc3d438604170eab0f78d54371c1b32/62b1f/45.png","srcSet":"/static/efc3d438604170eab0f78d54371c1b32/62b1f/45.png 1x,\n/static/efc3d438604170eab0f78d54371c1b32/e2e7f/45.png 1.5x,\n/static/efc3d438604170eab0f78d54371c1b32/db955/45.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/5.png","name":"5","childImageSharp":{"original":{"src":"/static/5-0024d1694e1d68da6c5c8550b3795edf.png"},"fixed":{"width":450,"height":253,"src":"/static/0024d1694e1d68da6c5c8550b3795edf/62b1f/5.png","srcSet":"/static/0024d1694e1d68da6c5c8550b3795edf/62b1f/5.png 1x,\n/static/0024d1694e1d68da6c5c8550b3795edf/e2e7f/5.png 1.5x,\n/static/0024d1694e1d68da6c5c8550b3795edf/db955/5.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/74.png","name":"74","childImageSharp":{"original":{"src":"/static/74-58f980d1ab54bf923fc60ff35c88f07b.png"},"fixed":{"width":450,"height":253,"src":"/static/58f980d1ab54bf923fc60ff35c88f07b/62b1f/74.png","srcSet":"/static/58f980d1ab54bf923fc60ff35c88f07b/62b1f/74.png 1x,\n/static/58f980d1ab54bf923fc60ff35c88f07b/e2e7f/74.png 1.5x,\n/static/58f980d1ab54bf923fc60ff35c88f07b/db955/74.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/77.png","name":"77","childImageSharp":{"original":{"src":"/static/77-7683083b36acb8afb245f16106ec2f6e.png"},"fixed":{"width":450,"height":253,"src":"/static/7683083b36acb8afb245f16106ec2f6e/62b1f/77.png","srcSet":"/static/7683083b36acb8afb245f16106ec2f6e/62b1f/77.png 1x,\n/static/7683083b36acb8afb245f16106ec2f6e/e2e7f/77.png 1.5x,\n/static/7683083b36acb8afb245f16106ec2f6e/db955/77.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/112.png","name":"112","childImageSharp":{"original":{"src":"/static/112-240479dfcce42c2f332f89fc22efa519.png"},"fixed":{"width":450,"height":253,"src":"/static/240479dfcce42c2f332f89fc22efa519/62b1f/112.png","srcSet":"/static/240479dfcce42c2f332f89fc22efa519/62b1f/112.png 1x,\n/static/240479dfcce42c2f332f89fc22efa519/e2e7f/112.png 1.5x,\n/static/240479dfcce42c2f332f89fc22efa519/db955/112.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/17.png","name":"17","childImageSharp":{"original":{"src":"/static/17-29594a5711851c6201fa84eee2e9f14a.png"},"fixed":{"width":450,"height":253,"src":"/static/29594a5711851c6201fa84eee2e9f14a/62b1f/17.png","srcSet":"/static/29594a5711851c6201fa84eee2e9f14a/62b1f/17.png 1x,\n/static/29594a5711851c6201fa84eee2e9f14a/e2e7f/17.png 1.5x,\n/static/29594a5711851c6201fa84eee2e9f14a/db955/17.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/23.png","name":"23","childImageSharp":{"original":{"src":"/static/23-0f0ef3ee1d48bf356ce89531bb87a990.png"},"fixed":{"width":450,"height":253,"src":"/static/0f0ef3ee1d48bf356ce89531bb87a990/62b1f/23.png","srcSet":"/static/0f0ef3ee1d48bf356ce89531bb87a990/62b1f/23.png 1x,\n/static/0f0ef3ee1d48bf356ce89531bb87a990/e2e7f/23.png 1.5x,\n/static/0f0ef3ee1d48bf356ce89531bb87a990/db955/23.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/50.png","name":"50","childImageSharp":{"original":{"src":"/static/50-c86a5550f2839b2ac6646f2a0f080e10.png"},"fixed":{"width":450,"height":253,"src":"/static/c86a5550f2839b2ac6646f2a0f080e10/62b1f/50.png","srcSet":"/static/c86a5550f2839b2ac6646f2a0f080e10/62b1f/50.png 1x,\n/static/c86a5550f2839b2ac6646f2a0f080e10/e2e7f/50.png 1.5x,\n/static/c86a5550f2839b2ac6646f2a0f080e10/db955/50.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/51.png","name":"51","childImageSharp":{"original":{"src":"/static/51-85a5c40599c820cdb1a4ee650f879742.png"},"fixed":{"width":450,"height":253,"src":"/static/85a5c40599c820cdb1a4ee650f879742/62b1f/51.png","srcSet":"/static/85a5c40599c820cdb1a4ee650f879742/62b1f/51.png 1x,\n/static/85a5c40599c820cdb1a4ee650f879742/e2e7f/51.png 1.5x,\n/static/85a5c40599c820cdb1a4ee650f879742/db955/51.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/52.png","name":"52","childImageSharp":{"original":{"src":"/static/52-fbfa902587b3b75b0517a6e3103c6487.png"},"fixed":{"width":450,"height":253,"src":"/static/fbfa902587b3b75b0517a6e3103c6487/62b1f/52.png","srcSet":"/static/fbfa902587b3b75b0517a6e3103c6487/62b1f/52.png 1x,\n/static/fbfa902587b3b75b0517a6e3103c6487/e2e7f/52.png 1.5x,\n/static/fbfa902587b3b75b0517a6e3103c6487/db955/52.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/57.png","name":"57","childImageSharp":{"original":{"src":"/static/57-629028957f6f6c97cec22794c5e60ed7.png"},"fixed":{"width":450,"height":253,"src":"/static/629028957f6f6c97cec22794c5e60ed7/62b1f/57.png","srcSet":"/static/629028957f6f6c97cec22794c5e60ed7/62b1f/57.png 1x,\n/static/629028957f6f6c97cec22794c5e60ed7/e2e7f/57.png 1.5x,\n/static/629028957f6f6c97cec22794c5e60ed7/db955/57.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/60.png","name":"60","childImageSharp":{"original":{"src":"/static/60-7b628b893de569a2ae30b73f09f80caa.png"},"fixed":{"width":450,"height":253,"src":"/static/7b628b893de569a2ae30b73f09f80caa/62b1f/60.png","srcSet":"/static/7b628b893de569a2ae30b73f09f80caa/62b1f/60.png 1x,\n/static/7b628b893de569a2ae30b73f09f80caa/e2e7f/60.png 1.5x,\n/static/7b628b893de569a2ae30b73f09f80caa/db955/60.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/61.png","name":"61","childImageSharp":{"original":{"src":"/static/61-d70dd38568d3b4996aeff2608383009b.png"},"fixed":{"width":450,"height":253,"src":"/static/d70dd38568d3b4996aeff2608383009b/62b1f/61.png","srcSet":"/static/d70dd38568d3b4996aeff2608383009b/62b1f/61.png 1x,\n/static/d70dd38568d3b4996aeff2608383009b/e2e7f/61.png 1.5x,\n/static/d70dd38568d3b4996aeff2608383009b/db955/61.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/62.png","name":"62","childImageSharp":{"original":{"src":"/static/62-989c3b2568e4b76693a427eb3290ccca.png"},"fixed":{"width":450,"height":253,"src":"/static/989c3b2568e4b76693a427eb3290ccca/62b1f/62.png","srcSet":"/static/989c3b2568e4b76693a427eb3290ccca/62b1f/62.png 1x,\n/static/989c3b2568e4b76693a427eb3290ccca/e2e7f/62.png 1.5x,\n/static/989c3b2568e4b76693a427eb3290ccca/db955/62.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/67.png","name":"67","childImageSharp":{"original":{"src":"/static/67-3b7fc3ebd7afe58029ebec5d25e3fcaa.png"},"fixed":{"width":450,"height":253,"src":"/static/3b7fc3ebd7afe58029ebec5d25e3fcaa/62b1f/67.png","srcSet":"/static/3b7fc3ebd7afe58029ebec5d25e3fcaa/62b1f/67.png 1x,\n/static/3b7fc3ebd7afe58029ebec5d25e3fcaa/e2e7f/67.png 1.5x,\n/static/3b7fc3ebd7afe58029ebec5d25e3fcaa/db955/67.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/69.png","name":"69","childImageSharp":{"original":{"src":"/static/69-307cd91f5eb9e8f64998ece5decdfb6e.png"},"fixed":{"width":450,"height":253,"src":"/static/307cd91f5eb9e8f64998ece5decdfb6e/62b1f/69.png","srcSet":"/static/307cd91f5eb9e8f64998ece5decdfb6e/62b1f/69.png 1x,\n/static/307cd91f5eb9e8f64998ece5decdfb6e/e2e7f/69.png 1.5x,\n/static/307cd91f5eb9e8f64998ece5decdfb6e/db955/69.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/73.png","name":"73","childImageSharp":{"original":{"src":"/static/73-623a12c0980ceb7d05bb7536c7758796.png"},"fixed":{"width":450,"height":253,"src":"/static/623a12c0980ceb7d05bb7536c7758796/62b1f/73.png","srcSet":"/static/623a12c0980ceb7d05bb7536c7758796/62b1f/73.png 1x,\n/static/623a12c0980ceb7d05bb7536c7758796/e2e7f/73.png 1.5x,\n/static/623a12c0980ceb7d05bb7536c7758796/db955/73.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/71.png","name":"71","childImageSharp":{"original":{"src":"/static/71-a5f077c5431da12f692647753bf71557.png"},"fixed":{"width":450,"height":253,"src":"/static/a5f077c5431da12f692647753bf71557/62b1f/71.png","srcSet":"/static/a5f077c5431da12f692647753bf71557/62b1f/71.png 1x,\n/static/a5f077c5431da12f692647753bf71557/e2e7f/71.png 1.5x,\n/static/a5f077c5431da12f692647753bf71557/db955/71.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/96.png","name":"96","childImageSharp":{"original":{"src":"/static/96-4c51d2d1218554dd772ed473e2d52a02.png"},"fixed":{"width":450,"height":253,"src":"/static/4c51d2d1218554dd772ed473e2d52a02/62b1f/96.png","srcSet":"/static/4c51d2d1218554dd772ed473e2d52a02/62b1f/96.png 1x,\n/static/4c51d2d1218554dd772ed473e2d52a02/e2e7f/96.png 1.5x,\n/static/4c51d2d1218554dd772ed473e2d52a02/db955/96.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/84.png","name":"84","childImageSharp":{"original":{"src":"/static/84-0306338ce57e7668f58fd439df5ebd5e.png"},"fixed":{"width":450,"height":253,"src":"/static/0306338ce57e7668f58fd439df5ebd5e/62b1f/84.png","srcSet":"/static/0306338ce57e7668f58fd439df5ebd5e/62b1f/84.png 1x,\n/static/0306338ce57e7668f58fd439df5ebd5e/e2e7f/84.png 1.5x,\n/static/0306338ce57e7668f58fd439df5ebd5e/db955/84.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/101.png","name":"101","childImageSharp":{"original":{"src":"/static/101-e2c6920f09a682d72dae90a838ad5296.png"},"fixed":{"width":450,"height":253,"src":"/static/e2c6920f09a682d72dae90a838ad5296/62b1f/101.png","srcSet":"/static/e2c6920f09a682d72dae90a838ad5296/62b1f/101.png 1x,\n/static/e2c6920f09a682d72dae90a838ad5296/e2e7f/101.png 1.5x,\n/static/e2c6920f09a682d72dae90a838ad5296/db955/101.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/102.png","name":"102","childImageSharp":{"original":{"src":"/static/102-5886eb180efdd9b0471a152091eedae4.png"},"fixed":{"width":450,"height":253,"src":"/static/5886eb180efdd9b0471a152091eedae4/62b1f/102.png","srcSet":"/static/5886eb180efdd9b0471a152091eedae4/62b1f/102.png 1x,\n/static/5886eb180efdd9b0471a152091eedae4/e2e7f/102.png 1.5x,\n/static/5886eb180efdd9b0471a152091eedae4/db955/102.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/107.png","name":"107","childImageSharp":{"original":{"src":"/static/107-9bb86076cb962cbad9b866d643b246d2.png"},"fixed":{"width":450,"height":253,"src":"/static/9bb86076cb962cbad9b866d643b246d2/62b1f/107.png","srcSet":"/static/9bb86076cb962cbad9b866d643b246d2/62b1f/107.png 1x,\n/static/9bb86076cb962cbad9b866d643b246d2/e2e7f/107.png 1.5x,\n/static/9bb86076cb962cbad9b866d643b246d2/db955/107.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/113.png","name":"113","childImageSharp":{"original":{"src":"/static/113-477264da062bf53c0a6fc6cc0ccb4a7f.png"},"fixed":{"width":450,"height":253,"src":"/static/477264da062bf53c0a6fc6cc0ccb4a7f/62b1f/113.png","srcSet":"/static/477264da062bf53c0a6fc6cc0ccb4a7f/62b1f/113.png 1x,\n/static/477264da062bf53c0a6fc6cc0ccb4a7f/e2e7f/113.png 1.5x,\n/static/477264da062bf53c0a6fc6cc0ccb4a7f/db955/113.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/22.png","name":"22","childImageSharp":{"original":{"src":"/static/22-eb3679ae939c567087081df258bdf0c7.png"},"fixed":{"width":450,"height":253,"src":"/static/eb3679ae939c567087081df258bdf0c7/62b1f/22.png","srcSet":"/static/eb3679ae939c567087081df258bdf0c7/62b1f/22.png 1x,\n/static/eb3679ae939c567087081df258bdf0c7/e2e7f/22.png 1.5x,\n/static/eb3679ae939c567087081df258bdf0c7/db955/22.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/25.png","name":"25","childImageSharp":{"original":{"src":"/static/25-8d6dc1c37e409a66eb803e7d1d71ce56.png"},"fixed":{"width":450,"height":253,"src":"/static/8d6dc1c37e409a66eb803e7d1d71ce56/62b1f/25.png","srcSet":"/static/8d6dc1c37e409a66eb803e7d1d71ce56/62b1f/25.png 1x,\n/static/8d6dc1c37e409a66eb803e7d1d71ce56/e2e7f/25.png 1.5x,\n/static/8d6dc1c37e409a66eb803e7d1d71ce56/db955/25.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/36.png","name":"36","childImageSharp":{"original":{"src":"/static/36-3907f8342dbaa007079ccd0214e50a67.png"},"fixed":{"width":450,"height":253,"src":"/static/3907f8342dbaa007079ccd0214e50a67/62b1f/36.png","srcSet":"/static/3907f8342dbaa007079ccd0214e50a67/62b1f/36.png 1x,\n/static/3907f8342dbaa007079ccd0214e50a67/e2e7f/36.png 1.5x,\n/static/3907f8342dbaa007079ccd0214e50a67/db955/36.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/47.png","name":"47","childImageSharp":{"original":{"src":"/static/47-c77eca60c773a24904b342f81119567e.png"},"fixed":{"width":450,"height":253,"src":"/static/c77eca60c773a24904b342f81119567e/62b1f/47.png","srcSet":"/static/c77eca60c773a24904b342f81119567e/62b1f/47.png 1x,\n/static/c77eca60c773a24904b342f81119567e/e2e7f/47.png 1.5x,\n/static/c77eca60c773a24904b342f81119567e/db955/47.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/56.png","name":"56","childImageSharp":{"original":{"src":"/static/56-630e7b1dbe1d07eb374b35a396611b93.png"},"fixed":{"width":450,"height":253,"src":"/static/630e7b1dbe1d07eb374b35a396611b93/62b1f/56.png","srcSet":"/static/630e7b1dbe1d07eb374b35a396611b93/62b1f/56.png 1x,\n/static/630e7b1dbe1d07eb374b35a396611b93/e2e7f/56.png 1.5x,\n/static/630e7b1dbe1d07eb374b35a396611b93/db955/56.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/64.png","name":"64","childImageSharp":{"original":{"src":"/static/64-27cb78f8fe5122fb7413c211f3aa03cc.png"},"fixed":{"width":450,"height":253,"src":"/static/27cb78f8fe5122fb7413c211f3aa03cc/62b1f/64.png","srcSet":"/static/27cb78f8fe5122fb7413c211f3aa03cc/62b1f/64.png 1x,\n/static/27cb78f8fe5122fb7413c211f3aa03cc/e2e7f/64.png 1.5x,\n/static/27cb78f8fe5122fb7413c211f3aa03cc/db955/64.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/66.png","name":"66","childImageSharp":{"original":{"src":"/static/66-69372a5a1eec5599a6a48908d51efcec.png"},"fixed":{"width":450,"height":253,"src":"/static/69372a5a1eec5599a6a48908d51efcec/62b1f/66.png","srcSet":"/static/69372a5a1eec5599a6a48908d51efcec/62b1f/66.png 1x,\n/static/69372a5a1eec5599a6a48908d51efcec/e2e7f/66.png 1.5x,\n/static/69372a5a1eec5599a6a48908d51efcec/db955/66.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/7.png","name":"7","childImageSharp":{"original":{"src":"/static/7-60ed1f3ba972c57780aba800b9dce063.png"},"fixed":{"width":450,"height":253,"src":"/static/60ed1f3ba972c57780aba800b9dce063/62b1f/7.png","srcSet":"/static/60ed1f3ba972c57780aba800b9dce063/62b1f/7.png 1x,\n/static/60ed1f3ba972c57780aba800b9dce063/e2e7f/7.png 1.5x,\n/static/60ed1f3ba972c57780aba800b9dce063/db955/7.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/82.png","name":"82","childImageSharp":{"original":{"src":"/static/82-af38b0d5a56a1b356d6c2a9d72d9a1d5.png"},"fixed":{"width":450,"height":253,"src":"/static/af38b0d5a56a1b356d6c2a9d72d9a1d5/62b1f/82.png","srcSet":"/static/af38b0d5a56a1b356d6c2a9d72d9a1d5/62b1f/82.png 1x,\n/static/af38b0d5a56a1b356d6c2a9d72d9a1d5/e2e7f/82.png 1.5x,\n/static/af38b0d5a56a1b356d6c2a9d72d9a1d5/db955/82.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/9.png","name":"9","childImageSharp":{"original":{"src":"/static/9-a4ab86f19ed45e1f59ac50af50677a2a.png"},"fixed":{"width":450,"height":253,"src":"/static/a4ab86f19ed45e1f59ac50af50677a2a/62b1f/9.png","srcSet":"/static/a4ab86f19ed45e1f59ac50af50677a2a/62b1f/9.png 1x,\n/static/a4ab86f19ed45e1f59ac50af50677a2a/e2e7f/9.png 1.5x,\n/static/a4ab86f19ed45e1f59ac50af50677a2a/db955/9.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/91.png","name":"91","childImageSharp":{"original":{"src":"/static/91-ca152a731fb910b0533be56dcc37fb2e.png"},"fixed":{"width":450,"height":253,"src":"/static/ca152a731fb910b0533be56dcc37fb2e/62b1f/91.png","srcSet":"/static/ca152a731fb910b0533be56dcc37fb2e/62b1f/91.png 1x,\n/static/ca152a731fb910b0533be56dcc37fb2e/e2e7f/91.png 1.5x,\n/static/ca152a731fb910b0533be56dcc37fb2e/db955/91.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/98.png","name":"98","childImageSharp":{"original":{"src":"/static/98-9a8b5d23e63f65e793d08a1946f7142c.png"},"fixed":{"width":450,"height":253,"src":"/static/9a8b5d23e63f65e793d08a1946f7142c/62b1f/98.png","srcSet":"/static/9a8b5d23e63f65e793d08a1946f7142c/62b1f/98.png 1x,\n/static/9a8b5d23e63f65e793d08a1946f7142c/e2e7f/98.png 1.5x,\n/static/9a8b5d23e63f65e793d08a1946f7142c/db955/98.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/110.png","name":"110","childImageSharp":{"original":{"src":"/static/110-eb59a1ea35a7a83cf6d03a03953735cc.png"},"fixed":{"width":450,"height":253,"src":"/static/eb59a1ea35a7a83cf6d03a03953735cc/62b1f/110.png","srcSet":"/static/eb59a1ea35a7a83cf6d03a03953735cc/62b1f/110.png 1x,\n/static/eb59a1ea35a7a83cf6d03a03953735cc/e2e7f/110.png 1.5x,\n/static/eb59a1ea35a7a83cf6d03a03953735cc/db955/110.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/114.png","name":"114","childImageSharp":{"original":{"src":"/static/114-7aa3b09e34cfbf34875de5b0930c97ad.png"},"fixed":{"width":450,"height":253,"src":"/static/7aa3b09e34cfbf34875de5b0930c97ad/62b1f/114.png","srcSet":"/static/7aa3b09e34cfbf34875de5b0930c97ad/62b1f/114.png 1x,\n/static/7aa3b09e34cfbf34875de5b0930c97ad/e2e7f/114.png 1.5x,\n/static/7aa3b09e34cfbf34875de5b0930c97ad/db955/114.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/117.png","name":"117","childImageSharp":{"original":{"src":"/static/117-950529e3c0a65d6de6c80f7de99b691a.png"},"fixed":{"width":450,"height":253,"src":"/static/950529e3c0a65d6de6c80f7de99b691a/62b1f/117.png","srcSet":"/static/950529e3c0a65d6de6c80f7de99b691a/62b1f/117.png 1x,\n/static/950529e3c0a65d6de6c80f7de99b691a/e2e7f/117.png 1.5x,\n/static/950529e3c0a65d6de6c80f7de99b691a/db955/117.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/15.png","name":"15","childImageSharp":{"original":{"src":"/static/15-bca7ae413ae0324f368cf4b8d26d91aa.png"},"fixed":{"width":450,"height":253,"src":"/static/bca7ae413ae0324f368cf4b8d26d91aa/62b1f/15.png","srcSet":"/static/bca7ae413ae0324f368cf4b8d26d91aa/62b1f/15.png 1x,\n/static/bca7ae413ae0324f368cf4b8d26d91aa/e2e7f/15.png 1.5x,\n/static/bca7ae413ae0324f368cf4b8d26d91aa/db955/15.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/16.png","name":"16","childImageSharp":{"original":{"src":"/static/16-4a97ebc23704d9bc1510b0b52a8dfbfd.png"},"fixed":{"width":450,"height":253,"src":"/static/4a97ebc23704d9bc1510b0b52a8dfbfd/62b1f/16.png","srcSet":"/static/4a97ebc23704d9bc1510b0b52a8dfbfd/62b1f/16.png 1x,\n/static/4a97ebc23704d9bc1510b0b52a8dfbfd/e2e7f/16.png 1.5x,\n/static/4a97ebc23704d9bc1510b0b52a8dfbfd/db955/16.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/34.png","name":"34","childImageSharp":{"original":{"src":"/static/34-d9c52924e1b6779a1a33478ed95f3829.png"},"fixed":{"width":450,"height":253,"src":"/static/d9c52924e1b6779a1a33478ed95f3829/62b1f/34.png","srcSet":"/static/d9c52924e1b6779a1a33478ed95f3829/62b1f/34.png 1x,\n/static/d9c52924e1b6779a1a33478ed95f3829/e2e7f/34.png 1.5x,\n/static/d9c52924e1b6779a1a33478ed95f3829/db955/34.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/48.png","name":"48","childImageSharp":{"original":{"src":"/static/48-9f70fc88878b047d090a32f508b26834.png"},"fixed":{"width":450,"height":253,"src":"/static/9f70fc88878b047d090a32f508b26834/62b1f/48.png","srcSet":"/static/9f70fc88878b047d090a32f508b26834/62b1f/48.png 1x,\n/static/9f70fc88878b047d090a32f508b26834/e2e7f/48.png 1.5x,\n/static/9f70fc88878b047d090a32f508b26834/db955/48.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/49.png","name":"49","childImageSharp":{"original":{"src":"/static/49-7da87b53ba1ad6602af700da4c62547d.png"},"fixed":{"width":450,"height":253,"src":"/static/7da87b53ba1ad6602af700da4c62547d/62b1f/49.png","srcSet":"/static/7da87b53ba1ad6602af700da4c62547d/62b1f/49.png 1x,\n/static/7da87b53ba1ad6602af700da4c62547d/e2e7f/49.png 1.5x,\n/static/7da87b53ba1ad6602af700da4c62547d/db955/49.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/54.png","name":"54","childImageSharp":{"original":{"src":"/static/54-f21f4db88b1f9c14ac1a496ddc9135fd.png"},"fixed":{"width":450,"height":253,"src":"/static/f21f4db88b1f9c14ac1a496ddc9135fd/62b1f/54.png","srcSet":"/static/f21f4db88b1f9c14ac1a496ddc9135fd/62b1f/54.png 1x,\n/static/f21f4db88b1f9c14ac1a496ddc9135fd/e2e7f/54.png 1.5x,\n/static/f21f4db88b1f9c14ac1a496ddc9135fd/db955/54.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/68.png","name":"68","childImageSharp":{"original":{"src":"/static/68-d277ff3416110076a391b161e5c8f9fb.png"},"fixed":{"width":450,"height":253,"src":"/static/d277ff3416110076a391b161e5c8f9fb/62b1f/68.png","srcSet":"/static/d277ff3416110076a391b161e5c8f9fb/62b1f/68.png 1x,\n/static/d277ff3416110076a391b161e5c8f9fb/e2e7f/68.png 1.5x,\n/static/d277ff3416110076a391b161e5c8f9fb/db955/68.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/75.png","name":"75","childImageSharp":{"original":{"src":"/static/75-ae5d0ea3858601a07579a0ffd85ef949.png"},"fixed":{"width":450,"height":253,"src":"/static/ae5d0ea3858601a07579a0ffd85ef949/62b1f/75.png","srcSet":"/static/ae5d0ea3858601a07579a0ffd85ef949/62b1f/75.png 1x,\n/static/ae5d0ea3858601a07579a0ffd85ef949/e2e7f/75.png 1.5x,\n/static/ae5d0ea3858601a07579a0ffd85ef949/db955/75.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/78.png","name":"78","childImageSharp":{"original":{"src":"/static/78-ccdabe4bf53dc24045b61e22b1b1b858.png"},"fixed":{"width":450,"height":253,"src":"/static/ccdabe4bf53dc24045b61e22b1b1b858/62b1f/78.png","srcSet":"/static/ccdabe4bf53dc24045b61e22b1b1b858/62b1f/78.png 1x,\n/static/ccdabe4bf53dc24045b61e22b1b1b858/e2e7f/78.png 1.5x,\n/static/ccdabe4bf53dc24045b61e22b1b1b858/db955/78.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/79.png","name":"79","childImageSharp":{"original":{"src":"/static/79-6490b80ce1898d497002b58ca319878c.png"},"fixed":{"width":450,"height":253,"src":"/static/6490b80ce1898d497002b58ca319878c/62b1f/79.png","srcSet":"/static/6490b80ce1898d497002b58ca319878c/62b1f/79.png 1x,\n/static/6490b80ce1898d497002b58ca319878c/e2e7f/79.png 1.5x,\n/static/6490b80ce1898d497002b58ca319878c/db955/79.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/8.png","name":"8","childImageSharp":{"original":{"src":"/static/8-e3bb1b66729d1b6b2b8359247b6bf46b.png"},"fixed":{"width":450,"height":253,"src":"/static/e3bb1b66729d1b6b2b8359247b6bf46b/62b1f/8.png","srcSet":"/static/e3bb1b66729d1b6b2b8359247b6bf46b/62b1f/8.png 1x,\n/static/e3bb1b66729d1b6b2b8359247b6bf46b/e2e7f/8.png 1.5x,\n/static/e3bb1b66729d1b6b2b8359247b6bf46b/db955/8.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/80.png","name":"80","childImageSharp":{"original":{"src":"/static/80-ff7e4c209b7c70ed98cb822cde75e472.png"},"fixed":{"width":450,"height":253,"src":"/static/ff7e4c209b7c70ed98cb822cde75e472/62b1f/80.png","srcSet":"/static/ff7e4c209b7c70ed98cb822cde75e472/62b1f/80.png 1x,\n/static/ff7e4c209b7c70ed98cb822cde75e472/e2e7f/80.png 1.5x,\n/static/ff7e4c209b7c70ed98cb822cde75e472/db955/80.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/81.png","name":"81","childImageSharp":{"original":{"src":"/static/81-9354aba69e36201c1497fa4c960ebc39.png"},"fixed":{"width":450,"height":253,"src":"/static/9354aba69e36201c1497fa4c960ebc39/62b1f/81.png","srcSet":"/static/9354aba69e36201c1497fa4c960ebc39/62b1f/81.png 1x,\n/static/9354aba69e36201c1497fa4c960ebc39/e2e7f/81.png 1.5x,\n/static/9354aba69e36201c1497fa4c960ebc39/db955/81.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/89.png","name":"89","childImageSharp":{"original":{"src":"/static/89-bc16bc963fd07c7d62a9458e4f631c62.png"},"fixed":{"width":450,"height":253,"src":"/static/bc16bc963fd07c7d62a9458e4f631c62/62b1f/89.png","srcSet":"/static/bc16bc963fd07c7d62a9458e4f631c62/62b1f/89.png 1x,\n/static/bc16bc963fd07c7d62a9458e4f631c62/e2e7f/89.png 1.5x,\n/static/bc16bc963fd07c7d62a9458e4f631c62/db955/89.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/95.png","name":"95","childImageSharp":{"original":{"src":"/static/95-9f3055ab0d6ab6cc940b9676a05eb90e.png"},"fixed":{"width":450,"height":253,"src":"/static/9f3055ab0d6ab6cc940b9676a05eb90e/62b1f/95.png","srcSet":"/static/9f3055ab0d6ab6cc940b9676a05eb90e/62b1f/95.png 1x,\n/static/9f3055ab0d6ab6cc940b9676a05eb90e/e2e7f/95.png 1.5x,\n/static/9f3055ab0d6ab6cc940b9676a05eb90e/db955/95.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/97.png","name":"97","childImageSharp":{"original":{"src":"/static/97-13cfd6a2f89a6cfbcad135a7950c5c61.png"},"fixed":{"width":450,"height":253,"src":"/static/13cfd6a2f89a6cfbcad135a7950c5c61/62b1f/97.png","srcSet":"/static/13cfd6a2f89a6cfbcad135a7950c5c61/62b1f/97.png 1x,\n/static/13cfd6a2f89a6cfbcad135a7950c5c61/e2e7f/97.png 1.5x,\n/static/13cfd6a2f89a6cfbcad135a7950c5c61/db955/97.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/99.png","name":"99","childImageSharp":{"original":{"src":"/static/99-c3cef790d5e23685a4d90ea18c0ae269.png"},"fixed":{"width":450,"height":253,"src":"/static/c3cef790d5e23685a4d90ea18c0ae269/62b1f/99.png","srcSet":"/static/c3cef790d5e23685a4d90ea18c0ae269/62b1f/99.png 1x,\n/static/c3cef790d5e23685a4d90ea18c0ae269/e2e7f/99.png 1.5x,\n/static/c3cef790d5e23685a4d90ea18c0ae269/db955/99.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/103.png","name":"103","childImageSharp":{"original":{"src":"/static/103-6c84d3afb5139e8c70c1819609c3df71.png"},"fixed":{"width":450,"height":253,"src":"/static/6c84d3afb5139e8c70c1819609c3df71/62b1f/103.png","srcSet":"/static/6c84d3afb5139e8c70c1819609c3df71/62b1f/103.png 1x,\n/static/6c84d3afb5139e8c70c1819609c3df71/e2e7f/103.png 1.5x,\n/static/6c84d3afb5139e8c70c1819609c3df71/db955/103.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/111.png","name":"111","childImageSharp":{"original":{"src":"/static/111-c1f37f3d887bc56e8247cc13b3934429.png"},"fixed":{"width":450,"height":253,"src":"/static/c1f37f3d887bc56e8247cc13b3934429/62b1f/111.png","srcSet":"/static/c1f37f3d887bc56e8247cc13b3934429/62b1f/111.png 1x,\n/static/c1f37f3d887bc56e8247cc13b3934429/e2e7f/111.png 1.5x,\n/static/c1f37f3d887bc56e8247cc13b3934429/db955/111.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/115.png","name":"115","childImageSharp":{"original":{"src":"/static/115-18c22129f513e545c9c9e84d3db11153.png"},"fixed":{"width":450,"height":253,"src":"/static/18c22129f513e545c9c9e84d3db11153/62b1f/115.png","srcSet":"/static/18c22129f513e545c9c9e84d3db11153/62b1f/115.png 1x,\n/static/18c22129f513e545c9c9e84d3db11153/e2e7f/115.png 1.5x,\n/static/18c22129f513e545c9c9e84d3db11153/db955/115.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/27.png","name":"27","childImageSharp":{"original":{"src":"/static/27-3055b38f95720ac0356cac3a4ae78eb6.png"},"fixed":{"width":450,"height":253,"src":"/static/3055b38f95720ac0356cac3a4ae78eb6/62b1f/27.png","srcSet":"/static/3055b38f95720ac0356cac3a4ae78eb6/62b1f/27.png 1x,\n/static/3055b38f95720ac0356cac3a4ae78eb6/e2e7f/27.png 1.5x,\n/static/3055b38f95720ac0356cac3a4ae78eb6/db955/27.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/28.png","name":"28","childImageSharp":{"original":{"src":"/static/28-bfe59cafc9c84aead1210f853d2e7f14.png"},"fixed":{"width":450,"height":253,"src":"/static/bfe59cafc9c84aead1210f853d2e7f14/62b1f/28.png","srcSet":"/static/bfe59cafc9c84aead1210f853d2e7f14/62b1f/28.png 1x,\n/static/bfe59cafc9c84aead1210f853d2e7f14/e2e7f/28.png 1.5x,\n/static/bfe59cafc9c84aead1210f853d2e7f14/db955/28.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/29.png","name":"29","childImageSharp":{"original":{"src":"/static/29-27a195dc0c7ddb4a2b7c6869b11d018e.png"},"fixed":{"width":450,"height":253,"src":"/static/27a195dc0c7ddb4a2b7c6869b11d018e/62b1f/29.png","srcSet":"/static/27a195dc0c7ddb4a2b7c6869b11d018e/62b1f/29.png 1x,\n/static/27a195dc0c7ddb4a2b7c6869b11d018e/e2e7f/29.png 1.5x,\n/static/27a195dc0c7ddb4a2b7c6869b11d018e/db955/29.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/30.png","name":"30","childImageSharp":{"original":{"src":"/static/30-818bd911d3699d96387a1eb047522937.png"},"fixed":{"width":450,"height":253,"src":"/static/818bd911d3699d96387a1eb047522937/62b1f/30.png","srcSet":"/static/818bd911d3699d96387a1eb047522937/62b1f/30.png 1x,\n/static/818bd911d3699d96387a1eb047522937/e2e7f/30.png 1.5x,\n/static/818bd911d3699d96387a1eb047522937/db955/30.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/55.png","name":"55","childImageSharp":{"original":{"src":"/static/55-3dca2324f741f3197f0ebb3ba2690602.png"},"fixed":{"width":450,"height":253,"src":"/static/3dca2324f741f3197f0ebb3ba2690602/62b1f/55.png","srcSet":"/static/3dca2324f741f3197f0ebb3ba2690602/62b1f/55.png 1x,\n/static/3dca2324f741f3197f0ebb3ba2690602/e2e7f/55.png 1.5x,\n/static/3dca2324f741f3197f0ebb3ba2690602/db955/55.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/59.png","name":"59","childImageSharp":{"original":{"src":"/static/59-4b6977ce521eecfeef08369fb043ebc9.png"},"fixed":{"width":450,"height":253,"src":"/static/4b6977ce521eecfeef08369fb043ebc9/62b1f/59.png","srcSet":"/static/4b6977ce521eecfeef08369fb043ebc9/62b1f/59.png 1x,\n/static/4b6977ce521eecfeef08369fb043ebc9/e2e7f/59.png 1.5x,\n/static/4b6977ce521eecfeef08369fb043ebc9/db955/59.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/63.png","name":"63","childImageSharp":{"original":{"src":"/static/63-4630472467a1a8841f37fdb63224573e.png"},"fixed":{"width":450,"height":253,"src":"/static/4630472467a1a8841f37fdb63224573e/62b1f/63.png","srcSet":"/static/4630472467a1a8841f37fdb63224573e/62b1f/63.png 1x,\n/static/4630472467a1a8841f37fdb63224573e/e2e7f/63.png 1.5x,\n/static/4630472467a1a8841f37fdb63224573e/db955/63.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/86.png","name":"86","childImageSharp":{"original":{"src":"/static/86-d959e9396c92f5323732eb61dd500270.png"},"fixed":{"width":450,"height":253,"src":"/static/d959e9396c92f5323732eb61dd500270/62b1f/86.png","srcSet":"/static/d959e9396c92f5323732eb61dd500270/62b1f/86.png 1x,\n/static/d959e9396c92f5323732eb61dd500270/e2e7f/86.png 1.5x,\n/static/d959e9396c92f5323732eb61dd500270/db955/86.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/104.png","name":"104","childImageSharp":{"original":{"src":"/static/104-27a0cc69939179f4c05c96d7e575a26d.png"},"fixed":{"width":450,"height":253,"src":"/static/27a0cc69939179f4c05c96d7e575a26d/62b1f/104.png","srcSet":"/static/27a0cc69939179f4c05c96d7e575a26d/62b1f/104.png 1x,\n/static/27a0cc69939179f4c05c96d7e575a26d/e2e7f/104.png 1.5x,\n/static/27a0cc69939179f4c05c96d7e575a26d/db955/104.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/106.png","name":"106","childImageSharp":{"original":{"src":"/static/106-b98e2defb0249c6c2161112da0752218.png"},"fixed":{"width":450,"height":253,"src":"/static/b98e2defb0249c6c2161112da0752218/62b1f/106.png","srcSet":"/static/b98e2defb0249c6c2161112da0752218/62b1f/106.png 1x,\n/static/b98e2defb0249c6c2161112da0752218/e2e7f/106.png 1.5x,\n/static/b98e2defb0249c6c2161112da0752218/db955/106.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/108.png","name":"108","childImageSharp":{"original":{"src":"/static/108-7f791e0fa944d9e075ae3a0a5595f048.png"},"fixed":{"width":450,"height":253,"src":"/static/7f791e0fa944d9e075ae3a0a5595f048/62b1f/108.png","srcSet":"/static/7f791e0fa944d9e075ae3a0a5595f048/62b1f/108.png 1x,\n/static/7f791e0fa944d9e075ae3a0a5595f048/e2e7f/108.png 1.5x,\n/static/7f791e0fa944d9e075ae3a0a5595f048/db955/108.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/26.png","name":"26","childImageSharp":{"original":{"src":"/static/26-9b8a9fbc05e56f37b91a027a132403d9.png"},"fixed":{"width":450,"height":253,"src":"/static/9b8a9fbc05e56f37b91a027a132403d9/62b1f/26.png","srcSet":"/static/9b8a9fbc05e56f37b91a027a132403d9/62b1f/26.png 1x,\n/static/9b8a9fbc05e56f37b91a027a132403d9/e2e7f/26.png 1.5x,\n/static/9b8a9fbc05e56f37b91a027a132403d9/db955/26.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/53.png","name":"53","childImageSharp":{"original":{"src":"/static/53-d3426c00e2b3accbf3b2fffff63a9308.png"},"fixed":{"width":450,"height":253,"src":"/static/d3426c00e2b3accbf3b2fffff63a9308/62b1f/53.png","srcSet":"/static/d3426c00e2b3accbf3b2fffff63a9308/62b1f/53.png 1x,\n/static/d3426c00e2b3accbf3b2fffff63a9308/e2e7f/53.png 1.5x,\n/static/d3426c00e2b3accbf3b2fffff63a9308/db955/53.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/70.png","name":"70","childImageSharp":{"original":{"src":"/static/70-baed89cad307f624924ed34c493c49bc.png"},"fixed":{"width":450,"height":253,"src":"/static/baed89cad307f624924ed34c493c49bc/62b1f/70.png","srcSet":"/static/baed89cad307f624924ed34c493c49bc/62b1f/70.png 1x,\n/static/baed89cad307f624924ed34c493c49bc/e2e7f/70.png 1.5x,\n/static/baed89cad307f624924ed34c493c49bc/db955/70.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/72.png","name":"72","childImageSharp":{"original":{"src":"/static/72-469db607c12e34392b237f1394d19c85.png"},"fixed":{"width":450,"height":253,"src":"/static/469db607c12e34392b237f1394d19c85/62b1f/72.png","srcSet":"/static/469db607c12e34392b237f1394d19c85/62b1f/72.png 1x,\n/static/469db607c12e34392b237f1394d19c85/e2e7f/72.png 1.5x,\n/static/469db607c12e34392b237f1394d19c85/db955/72.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/83.png","name":"83","childImageSharp":{"original":{"src":"/static/83-5c80ec9367af42e4a319f14eed9a462e.png"},"fixed":{"width":450,"height":253,"src":"/static/5c80ec9367af42e4a319f14eed9a462e/62b1f/83.png","srcSet":"/static/5c80ec9367af42e4a319f14eed9a462e/62b1f/83.png 1x,\n/static/5c80ec9367af42e4a319f14eed9a462e/e2e7f/83.png 1.5x,\n/static/5c80ec9367af42e4a319f14eed9a462e/db955/83.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/105.png","name":"105","childImageSharp":{"original":{"src":"/static/105-8e25906a0f69adbc3b0b342df5c5c384.png"},"fixed":{"width":450,"height":253,"src":"/static/8e25906a0f69adbc3b0b342df5c5c384/62b1f/105.png","srcSet":"/static/8e25906a0f69adbc3b0b342df5c5c384/62b1f/105.png 1x,\n/static/8e25906a0f69adbc3b0b342df5c5c384/e2e7f/105.png 1.5x,\n/static/8e25906a0f69adbc3b0b342df5c5c384/db955/105.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/2.png","name":"2","childImageSharp":{"original":{"src":"/static/2-66d7c09a06c319110a51be9c1a701b4e.png"},"fixed":{"width":450,"height":253,"src":"/static/66d7c09a06c319110a51be9c1a701b4e/62b1f/2.png","srcSet":"/static/66d7c09a06c319110a51be9c1a701b4e/62b1f/2.png 1x,\n/static/66d7c09a06c319110a51be9c1a701b4e/e2e7f/2.png 1.5x,\n/static/66d7c09a06c319110a51be9c1a701b4e/db955/2.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/85.png","name":"85","childImageSharp":{"original":{"src":"/static/85-6743c9c51eff49e7fe3c5ffc79600df8.png"},"fixed":{"width":450,"height":253,"src":"/static/6743c9c51eff49e7fe3c5ffc79600df8/62b1f/85.png","srcSet":"/static/6743c9c51eff49e7fe3c5ffc79600df8/62b1f/85.png 1x,\n/static/6743c9c51eff49e7fe3c5ffc79600df8/e2e7f/85.png 1.5x,\n/static/6743c9c51eff49e7fe3c5ffc79600df8/db955/85.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/1.png","name":"1","childImageSharp":{"original":{"src":"/static/1-dc6c72a6725f8e2bed6cfd217b61421b.png"},"fixed":{"width":450,"height":253,"src":"/static/dc6c72a6725f8e2bed6cfd217b61421b/62b1f/1.png","srcSet":"/static/dc6c72a6725f8e2bed6cfd217b61421b/62b1f/1.png 1x,\n/static/dc6c72a6725f8e2bed6cfd217b61421b/e2e7f/1.png 1.5x,\n/static/dc6c72a6725f8e2bed6cfd217b61421b/db955/1.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/116.png","name":"116","childImageSharp":{"original":{"src":"/static/116-a39953b3532fc8c82027a1c0d5dc7571.png"},"fixed":{"width":450,"height":253,"src":"/static/a39953b3532fc8c82027a1c0d5dc7571/62b1f/116.png","srcSet":"/static/a39953b3532fc8c82027a1c0d5dc7571/62b1f/116.png 1x,\n/static/a39953b3532fc8c82027a1c0d5dc7571/e2e7f/116.png 1.5x,\n/static/a39953b3532fc8c82027a1c0d5dc7571/db955/116.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/14.png","name":"14","childImageSharp":{"original":{"src":"/static/14-0e8d3989dd1003b7ebf1bb0e2fb824d2.png"},"fixed":{"width":450,"height":253,"src":"/static/0e8d3989dd1003b7ebf1bb0e2fb824d2/62b1f/14.png","srcSet":"/static/0e8d3989dd1003b7ebf1bb0e2fb824d2/62b1f/14.png 1x,\n/static/0e8d3989dd1003b7ebf1bb0e2fb824d2/e2e7f/14.png 1.5x,\n/static/0e8d3989dd1003b7ebf1bb0e2fb824d2/db955/14.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/58.png","name":"58","childImageSharp":{"original":{"src":"/static/58-43f94c0f68dac5ed480d83f56654a0f0.png"},"fixed":{"width":450,"height":253,"src":"/static/43f94c0f68dac5ed480d83f56654a0f0/62b1f/58.png","srcSet":"/static/43f94c0f68dac5ed480d83f56654a0f0/62b1f/58.png 1x,\n/static/43f94c0f68dac5ed480d83f56654a0f0/e2e7f/58.png 1.5x,\n/static/43f94c0f68dac5ed480d83f56654a0f0/db955/58.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/6.png","name":"6","childImageSharp":{"original":{"src":"/static/6-66ae25ce820ecd1f446d756c8ca73a75.png"},"fixed":{"width":450,"height":253,"src":"/static/66ae25ce820ecd1f446d756c8ca73a75/62b1f/6.png","srcSet":"/static/66ae25ce820ecd1f446d756c8ca73a75/62b1f/6.png 1x,\n/static/66ae25ce820ecd1f446d756c8ca73a75/e2e7f/6.png 1.5x,\n/static/66ae25ce820ecd1f446d756c8ca73a75/db955/6.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/65.png","name":"65","childImageSharp":{"original":{"src":"/static/65-f9c299de3ca0030f13c89dfa4caacbb8.png"},"fixed":{"width":450,"height":253,"src":"/static/f9c299de3ca0030f13c89dfa4caacbb8/62b1f/65.png","srcSet":"/static/f9c299de3ca0030f13c89dfa4caacbb8/62b1f/65.png 1x,\n/static/f9c299de3ca0030f13c89dfa4caacbb8/e2e7f/65.png 1.5x,\n/static/f9c299de3ca0030f13c89dfa4caacbb8/db955/65.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/94.png","name":"94","childImageSharp":{"original":{"src":"/static/94-65ddc80d958c07b7f1c50ee11acb893d.png"},"fixed":{"width":450,"height":253,"src":"/static/65ddc80d958c07b7f1c50ee11acb893d/62b1f/94.png","srcSet":"/static/65ddc80d958c07b7f1c50ee11acb893d/62b1f/94.png 1x,\n/static/65ddc80d958c07b7f1c50ee11acb893d/e2e7f/94.png 1.5x,\n/static/65ddc80d958c07b7f1c50ee11acb893d/db955/94.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/100.png","name":"100","childImageSharp":{"original":{"src":"/static/100-5f9c41e28137f94236db12cb64be132e.png"},"fixed":{"width":450,"height":253,"src":"/static/5f9c41e28137f94236db12cb64be132e/62b1f/100.png","srcSet":"/static/5f9c41e28137f94236db12cb64be132e/62b1f/100.png 1x,\n/static/5f9c41e28137f94236db12cb64be132e/e2e7f/100.png 1.5x,\n/static/5f9c41e28137f94236db12cb64be132e/db955/100.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/10.png","name":"10","childImageSharp":{"original":{"src":"/static/10-12937d3c336a1c0a6ce5a2daa970863b.png"},"fixed":{"width":450,"height":253,"src":"/static/12937d3c336a1c0a6ce5a2daa970863b/62b1f/10.png","srcSet":"/static/12937d3c336a1c0a6ce5a2daa970863b/62b1f/10.png 1x,\n/static/12937d3c336a1c0a6ce5a2daa970863b/e2e7f/10.png 1.5x,\n/static/12937d3c336a1c0a6ce5a2daa970863b/db955/10.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/11.png","name":"11","childImageSharp":{"original":{"src":"/static/11-4f1502cbbafc7a6dce4908c02227006a.png"},"fixed":{"width":450,"height":253,"src":"/static/4f1502cbbafc7a6dce4908c02227006a/62b1f/11.png","srcSet":"/static/4f1502cbbafc7a6dce4908c02227006a/62b1f/11.png 1x,\n/static/4f1502cbbafc7a6dce4908c02227006a/e2e7f/11.png 1.5x,\n/static/4f1502cbbafc7a6dce4908c02227006a/db955/11.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/93.png","name":"93","childImageSharp":{"original":{"src":"/static/93-0f09cc7869dd7fe0aca4e67e7ea5ce31.png"},"fixed":{"width":450,"height":253,"src":"/static/0f09cc7869dd7fe0aca4e67e7ea5ce31/62b1f/93.png","srcSet":"/static/0f09cc7869dd7fe0aca4e67e7ea5ce31/62b1f/93.png 1x,\n/static/0f09cc7869dd7fe0aca4e67e7ea5ce31/e2e7f/93.png 1.5x,\n/static/0f09cc7869dd7fe0aca4e67e7ea5ce31/db955/93.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/46.png","name":"46","childImageSharp":{"original":{"src":"/static/46-8f55248e12be9d4b9075cf41711093e0.png"},"fixed":{"width":450,"height":253,"src":"/static/8f55248e12be9d4b9075cf41711093e0/62b1f/46.png","srcSet":"/static/8f55248e12be9d4b9075cf41711093e0/62b1f/46.png 1x,\n/static/8f55248e12be9d4b9075cf41711093e0/e2e7f/46.png 1.5x,\n/static/8f55248e12be9d4b9075cf41711093e0/db955/46.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/87.png","name":"87","childImageSharp":{"original":{"src":"/static/87-3bf5bc8ca748a8fbbf715bb5315b8fa1.png"},"fixed":{"width":450,"height":253,"src":"/static/3bf5bc8ca748a8fbbf715bb5315b8fa1/62b1f/87.png","srcSet":"/static/3bf5bc8ca748a8fbbf715bb5315b8fa1/62b1f/87.png 1x,\n/static/3bf5bc8ca748a8fbbf715bb5315b8fa1/e2e7f/87.png 1.5x,\n/static/3bf5bc8ca748a8fbbf715bb5315b8fa1/db955/87.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/90.png","name":"90","childImageSharp":{"original":{"src":"/static/90-0d025cfa09ecbac1626832cd3479cb10.png"},"fixed":{"width":450,"height":253,"src":"/static/0d025cfa09ecbac1626832cd3479cb10/62b1f/90.png","srcSet":"/static/0d025cfa09ecbac1626832cd3479cb10/62b1f/90.png 1x,\n/static/0d025cfa09ecbac1626832cd3479cb10/e2e7f/90.png 1.5x,\n/static/0d025cfa09ecbac1626832cd3479cb10/db955/90.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/92.png","name":"92","childImageSharp":{"original":{"src":"/static/92-719b45517c6345cb874c0b9970ab22a9.png"},"fixed":{"width":450,"height":253,"src":"/static/719b45517c6345cb874c0b9970ab22a9/62b1f/92.png","srcSet":"/static/719b45517c6345cb874c0b9970ab22a9/62b1f/92.png 1x,\n/static/719b45517c6345cb874c0b9970ab22a9/e2e7f/92.png 1.5x,\n/static/719b45517c6345cb874c0b9970ab22a9/db955/92.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/109.png","name":"109","childImageSharp":{"original":{"src":"/static/109-dcebb2db995efc9600f7484e2b94c63a.png"},"fixed":{"width":450,"height":253,"src":"/static/dcebb2db995efc9600f7484e2b94c63a/62b1f/109.png","srcSet":"/static/dcebb2db995efc9600f7484e2b94c63a/62b1f/109.png 1x,\n/static/dcebb2db995efc9600f7484e2b94c63a/e2e7f/109.png 1.5x,\n/static/dcebb2db995efc9600f7484e2b94c63a/db955/109.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/118.png","name":"118","childImageSharp":{"original":{"src":"/static/118-04556b4e4532a1ba91b1658fc8ce2ee3.png"},"fixed":{"width":450,"height":253,"src":"/static/04556b4e4532a1ba91b1658fc8ce2ee3/62b1f/118.png","srcSet":"/static/04556b4e4532a1ba91b1658fc8ce2ee3/62b1f/118.png 1x,\n/static/04556b4e4532a1ba91b1658fc8ce2ee3/e2e7f/118.png 1.5x,\n/static/04556b4e4532a1ba91b1658fc8ce2ee3/db955/118.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/18.png","name":"18","childImageSharp":{"original":{"src":"/static/18-c7c0ca1b3caa2cbedef87aa1c25b1e04.png"},"fixed":{"width":450,"height":253,"src":"/static/c7c0ca1b3caa2cbedef87aa1c25b1e04/62b1f/18.png","srcSet":"/static/c7c0ca1b3caa2cbedef87aa1c25b1e04/62b1f/18.png 1x,\n/static/c7c0ca1b3caa2cbedef87aa1c25b1e04/e2e7f/18.png 1.5x,\n/static/c7c0ca1b3caa2cbedef87aa1c25b1e04/db955/18.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/3.png","name":"3","childImageSharp":{"original":{"src":"/static/3-27f0fc58e1ea8e81aa698018a6f9326a.png"},"fixed":{"width":450,"height":253,"src":"/static/27f0fc58e1ea8e81aa698018a6f9326a/62b1f/3.png","srcSet":"/static/27f0fc58e1ea8e81aa698018a6f9326a/62b1f/3.png 1x,\n/static/27f0fc58e1ea8e81aa698018a6f9326a/e2e7f/3.png 1.5x,\n/static/27f0fc58e1ea8e81aa698018a6f9326a/db955/3.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/76.png","name":"76","childImageSharp":{"original":{"src":"/static/76-4b6e5872ef4cb4030e029f56e37d1b4d.png"},"fixed":{"width":450,"height":253,"src":"/static/4b6e5872ef4cb4030e029f56e37d1b4d/62b1f/76.png","srcSet":"/static/4b6e5872ef4cb4030e029f56e37d1b4d/62b1f/76.png 1x,\n/static/4b6e5872ef4cb4030e029f56e37d1b4d/e2e7f/76.png 1.5x,\n/static/4b6e5872ef4cb4030e029f56e37d1b4d/db955/76.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/88.png","name":"88","childImageSharp":{"original":{"src":"/static/88-a71a9db1bf22f42428e76f52a3aee5b0.png"},"fixed":{"width":450,"height":253,"src":"/static/a71a9db1bf22f42428e76f52a3aee5b0/62b1f/88.png","srcSet":"/static/a71a9db1bf22f42428e76f52a3aee5b0/62b1f/88.png 1x,\n/static/a71a9db1bf22f42428e76f52a3aee5b0/e2e7f/88.png 1.5x,\n/static/a71a9db1bf22f42428e76f52a3aee5b0/db955/88.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/41.png","name":"41","childImageSharp":{"original":{"src":"/static/41-ec0c90e8f149460dc5d679c0753bbe77.png"},"fixed":{"width":450,"height":253,"src":"/static/ec0c90e8f149460dc5d679c0753bbe77/62b1f/41.png","srcSet":"/static/ec0c90e8f149460dc5d679c0753bbe77/62b1f/41.png 1x,\n/static/ec0c90e8f149460dc5d679c0753bbe77/e2e7f/41.png 1.5x,\n/static/ec0c90e8f149460dc5d679c0753bbe77/db955/41.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/24.png","name":"24","childImageSharp":{"original":{"src":"/static/24-4747f0e3f90c62fc693081bceb95d94f.png"},"fixed":{"width":450,"height":253,"src":"/static/4747f0e3f90c62fc693081bceb95d94f/62b1f/24.png","srcSet":"/static/4747f0e3f90c62fc693081bceb95d94f/62b1f/24.png 1x,\n/static/4747f0e3f90c62fc693081bceb95d94f/e2e7f/24.png 1.5x,\n/static/4747f0e3f90c62fc693081bceb95d94f/db955/24.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/12.png","name":"12","childImageSharp":{"original":{"src":"/static/12-53ad1ad96f37376506c12a8b334b49c1.png"},"fixed":{"width":450,"height":253,"src":"/static/53ad1ad96f37376506c12a8b334b49c1/62b1f/12.png","srcSet":"/static/53ad1ad96f37376506c12a8b334b49c1/62b1f/12.png 1x,\n/static/53ad1ad96f37376506c12a8b334b49c1/e2e7f/12.png 1.5x,\n/static/53ad1ad96f37376506c12a8b334b49c1/db955/12.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/33.png","name":"33","childImageSharp":{"original":{"src":"/static/33-01234130053e776f49e9c338c193325d.png"},"fixed":{"width":450,"height":253,"src":"/static/01234130053e776f49e9c338c193325d/62b1f/33.png","srcSet":"/static/01234130053e776f49e9c338c193325d/62b1f/33.png 1x,\n/static/01234130053e776f49e9c338c193325d/e2e7f/33.png 1.5x,\n/static/01234130053e776f49e9c338c193325d/db955/33.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/40.png","name":"40","childImageSharp":{"original":{"src":"/static/40-d87cb8eda90348c1b98ca45ba7dba2d0.png"},"fixed":{"width":450,"height":253,"src":"/static/d87cb8eda90348c1b98ca45ba7dba2d0/62b1f/40.png","srcSet":"/static/d87cb8eda90348c1b98ca45ba7dba2d0/62b1f/40.png 1x,\n/static/d87cb8eda90348c1b98ca45ba7dba2d0/e2e7f/40.png 1.5x,\n/static/d87cb8eda90348c1b98ca45ba7dba2d0/db955/40.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/39.png","name":"39","childImageSharp":{"original":{"src":"/static/39-641ae311034f2879bc4c1ec303917422.png"},"fixed":{"width":450,"height":253,"src":"/static/641ae311034f2879bc4c1ec303917422/62b1f/39.png","srcSet":"/static/641ae311034f2879bc4c1ec303917422/62b1f/39.png 1x,\n/static/641ae311034f2879bc4c1ec303917422/e2e7f/39.png 1.5x,\n/static/641ae311034f2879bc4c1ec303917422/db955/39.png 2x"}}}},{"node":{"relativePath":"going-realtime-with-ember/35.png","name":"35","childImageSharp":{"original":{"src":"/static/35-a5bae660dcb691ff3bc29a05dba37642.png"},"fixed":{"width":450,"height":253,"src":"/static/a5bae660dcb691ff3bc29a05dba37642/62b1f/35.png","srcSet":"/static/a5bae660dcb691ff3bc29a05dba37642/62b1f/35.png 1x,\n/static/a5bae660dcb691ff3bc29a05dba37642/e2e7f/35.png 1.5x,\n/static/a5bae660dcb691ff3bc29a05dba37642/db955/35.png 2x"}}}}]}},"pageContext":{"slug":"/talks/going-realtime-with-ember/","slides":"/static/slides/going-realtime-with-ember$/"}},"staticQueryHashes":[]}